Compare commits
164 Commits
Author | SHA1 | Date | |
---|---|---|---|
d22148b418 | |||
5dc75c9cea | |||
200e82decb | |||
50359dbaf4 | |||
7165f21a62 | |||
8aab828c65 | |||
c9f8c12f5b | |||
a347f8fa49 | |||
2d76bdd0f8 | |||
c5cdd56edb | |||
6901ab39ed | |||
b851b7f431 | |||
ccaa99115c | |||
813de15e85 | |||
fa33181fa9 | |||
d4324dc0cb | |||
ccc27bcc14 | |||
014c1673c6 | |||
3a3319ff52 | |||
5b54ec8fb5 | |||
e690556286 | |||
660956917f | |||
3a5201747b | |||
b338e8a83f | |||
5584d56b6a | |||
c925498120 | |||
75615cf503 | |||
39f708b0fc | |||
ac32077221 | |||
a5902acacd | |||
c7c08b7511 | |||
7de915d47b | |||
9107547501 | |||
95dc76ca19 | |||
49c2a9b372 | |||
b378cabfc7 | |||
4263dbef31 | |||
32fc6ae2eb | |||
238cb0077f | |||
f5a06b6494 | |||
2c01329085 | |||
502de89ab1 | |||
128de6750c | |||
e59e2d9f0b | |||
2504b8391b | |||
330ea7d210 | |||
1edd173a29 | |||
98d873a7f9 | |||
09175b84df | |||
177e19632a | |||
8e6207f3e9 | |||
ff3a97f6cf | |||
b8e155ab40 | |||
b8e7df198d | |||
34311e3181 | |||
46115d3f04 | |||
c1d25d2394 | |||
880cea5a56 | |||
e7205d9cc2 | |||
f456feb3ff | |||
3f83beedb7 | |||
e6c9b1d9bd | |||
b46114f4fa | |||
8d77e2ba22 | |||
cb3900921f | |||
ae2021583d | |||
36cd88e6b7 | |||
517b0908da | |||
b23b3e4d21 | |||
883fc5dde0 | |||
9d044329f6 | |||
d1e9e74cb8 | |||
98a87ee75f | |||
331491077d | |||
913c3a6636 | |||
fbaf5fe355 | |||
804c932f60 | |||
cef6d1d1b6 | |||
e4e7ab1135 | |||
6ca30df8c4 | |||
a340d4ed8e | |||
ca7cb94358 | |||
54779b25f5 | |||
44d7652171 | |||
c9ed15b682 | |||
8faad646ae | |||
1d50bc3382 | |||
da4af041af | |||
e2ff408f2f | |||
50d1500dfc | |||
94441f93a5 | |||
5f712fbf3c | |||
1c757f10e0 | |||
0508d5f643 | |||
d9986b7a2f | |||
3d79e7a136 | |||
52fb1237ec | |||
8a7197726e | |||
b7f5458684 | |||
52710f3810 | |||
a54de07260 | |||
aa2c8d101e | |||
1441fd93b9 | |||
4a585e8920 | |||
8c4245a09d | |||
e4af16989a | |||
5dc0944fe8 | |||
b4d24f4377 | |||
67be47b8db | |||
e382982d32 | |||
b09b74b5da | |||
c628bdb7a6 | |||
2fcf6fb0fd | |||
4f3fc9ffd0 | |||
15839a7399 | |||
26b3a14a63 | |||
f2f0799df1 | |||
6c99c32100 | |||
93d25a2a34 | |||
88f5ec59d7 | |||
586d3c4db7 | |||
f45fb56e15 | |||
8fe153c7c1 | |||
36a8720fbb | |||
9cbfdc94d9 | |||
091923764d | |||
dc39caed1e | |||
bcd7d1f007 | |||
40d4dc0474 | |||
02ac30c0d0 | |||
518bc92673 | |||
a5b92e316c | |||
828c7b66a0 | |||
93474eaa06 | |||
237f366aa2 | |||
714bcf28d5 | |||
420eeb4d68 | |||
bc6daf4a2e | |||
6f7832c09b | |||
bef67fa275 | |||
05d7198667 | |||
df0bfc14e5 | |||
3f28f7451f | |||
dbb9199d6f | |||
72cb3b03af | |||
d0085f00ed | |||
43734f027b | |||
f799375635 | |||
65704bbf01 | |||
9cb3882efa | |||
a0833ca691 | |||
a4f197f608 | |||
bb903cab40 | |||
92f765bc47 | |||
742889a035 | |||
24453ebcc3 | |||
8b8ab1bf5c | |||
e9bc9b8675 | |||
eeaa27c7ca | |||
ccea1755fc | |||
c32a5d602b | |||
2a04f2ca4d | |||
37c80e8ef5 | |||
1dce62e42a |
129
CHANGELOG.md
129
CHANGELOG.md
@ -1,6 +1,135 @@
|
|||||||
ChangeLog
|
ChangeLog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
12.21.0 (2020/02/23)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* ノートのメニューに詳細ページへのリンクを追加
|
||||||
|
* UIの調整
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* チャットで自分の送信したURLが視認しにくい問題を修正
|
||||||
|
* ノートの内のインラインコードが横に突き抜ける問題を修正
|
||||||
|
* (新しいノートがあります)表示中にタイムラインを切り替えると、表示が消えなくなってしまう問題を修正
|
||||||
|
* 引用RNフォームを開いた時だけ、textareaにフォーカスが当たっていない問題を修正
|
||||||
|
|
||||||
|
12.20.0 (2020/02/22)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* UIの調整
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* 複数タブで開いてるときに動作がおかしい問題を修正
|
||||||
|
* メディア付きノートの詳細表示をした後TLに戻るとノートがバグる問題を修正
|
||||||
|
|
||||||
|
12.19.0 (2020/02/21)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* アンテナで除外キーワードを設定できるように
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* ハッシュタグをもっと見るできないのを修正
|
||||||
|
* 無効になっているタイムラインでも使用できるかのように表示される問題を修正
|
||||||
|
* バックグラウンドで受信したノートの画像が表示されない問題を修正
|
||||||
|
* サインインフォームが表示されない場所がある問題を修正
|
||||||
|
* ボリュームが0のときサウンドを鳴らさないように
|
||||||
|
|
||||||
|
12.18.1 (2020/02/20)
|
||||||
|
-------------------
|
||||||
|
### 🐛Fixes
|
||||||
|
* タイムラインのハイライトに自分のノートは含めないように
|
||||||
|
* ハッシュタグの集計に関する問題を修正
|
||||||
|
|
||||||
|
12.18.0 (2020/02/20)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* 効果音設定を強化
|
||||||
|
* UIの調整
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* ユーザープレビューが稀に画面上から消えなくなってしまう問題を修正
|
||||||
|
* ハッシュタグ検索が遅い問題を修正
|
||||||
|
|
||||||
|
12.17.0 (2020/02/20)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* 効果音を実装
|
||||||
|
* 切断時ダイアログを控えめに
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* 新しいノートの通知が崩れる問題を修正
|
||||||
|
* LegacyReaction変換にstarを追加
|
||||||
|
* ユーザープレビューが稀に画面上から消えなくなってしまう問題を修正
|
||||||
|
* media-listのgridの高さがsub-note-detailsのdetailsの中だと287pxになってしまっていたのを修正
|
||||||
|
|
||||||
|
12.16.0 (2020/02/19)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* 通知一覧をポップアップではなくページで表示できるように
|
||||||
|
* 返信、引用、メンションの通知を直接ノートとして表示するように
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* v12以前のリアクションが表示されない問題を修正
|
||||||
|
|
||||||
|
12.15.0 (2020/02/19)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* 固定投稿フォームを実装
|
||||||
|
* ページ遷移のトランジションを無しに
|
||||||
|
* スクロールしてるときに新しいノートが来たときにわかるように表示するように
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* ページのいいねボタンを修正
|
||||||
|
|
||||||
|
12.14.0 (2020/02/18)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* オブジェクトストレージの設定を実装
|
||||||
|
* サーバーログビューア実装
|
||||||
|
|
||||||
|
12.13.0 (2020/02/18)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* プロモーションノート機能を実装
|
||||||
|
* インスタンス管理者が、重要なお知らせやユーザーにやってもらいたいアンケートなどをタイムラインの途中に挿入する機能
|
||||||
|
* プロモーションされる期限を設定できる
|
||||||
|
* 複数のプロモーションがある場合はランダムに選択されて表示される
|
||||||
|
* ユーザーがプロモーションを個別に非表示にすることもできる
|
||||||
|
* ハイライトインジェクション機能を実装
|
||||||
|
* タイムラインの途中におすすめのノートを表示できる機能
|
||||||
|
* 設定で有効/無効を切り替えられる
|
||||||
|
* アクティビティウィジェットを実装
|
||||||
|
* フォトウィジェットを実装
|
||||||
|
* タイムラインの一番上までスクロールできるように
|
||||||
|
* 管理者はモデレーターに変更できないように
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* admin/show-users APIがadminかつmoderator設定されているとき使えない問題を修正
|
||||||
|
|
||||||
|
12.12.0 (2020/02/17)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* インスタンス情報ページを強化
|
||||||
|
* インスタンス設定ページを強化
|
||||||
|
* 設定ページをアカウント設定ページとクライアント設定ページに分離
|
||||||
|
* UIの調整
|
||||||
|
|
||||||
|
12.11.0 (2020/02/16)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* 投稿詳細ページで前後の投稿を見れるように
|
||||||
|
* 自分のfollowersノートはRenoteできるように
|
||||||
|
* 画像ダイアログを実装
|
||||||
|
* フォロー申請ページの調整
|
||||||
|
* 壁紙設定の強化
|
||||||
|
* 画面が狭い状態でMisskeyを起動した場合でも、画面幅が広がったときにウィジェットを表示するように
|
||||||
|
* 「もっと読み込む」したときの読み込み量を増量
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* 認証なしでグローバルTLにアクセスすると妙なエラーが返る問題を修正
|
||||||
|
* API docが見れない問題を修正
|
||||||
|
* 画面右上に当たり判定があるのを修正
|
||||||
|
|
||||||
12.10.0 (2020/02/15)
|
12.10.0 (2020/02/15)
|
||||||
-------------------
|
-------------------
|
||||||
### ✨Improvements
|
### ✨Improvements
|
||||||
|
22
README.md
22
README.md
@ -110,7 +110,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
|
||||||
<td><img src="https://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>
|
<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>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/3.png?token-time=2145916800&token-hash=Zq1TCK2tdY7xudEm_aV70bc_wxmol6pNj3ZWbpFUNbI%3D" alt="Nesakko" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/3.png?token-time=2145916800&token-hash=Zq1TCK2tdY7xudEm_aV70bc_wxmol6pNj3ZWbpFUNbI%3D" alt="Nesakko" width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
|
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/user?u=20832595">Roujo</a></td>
|
<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/user?u=27956229">Oliver Maximilian Seidel</a></td>
|
||||||
@ -119,9 +118,9 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||||||
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
|
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
|
<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
|
||||||
<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
|
<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
|
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<table><tr>
|
||||||
|
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y" width="100"></td>
|
||||||
@ -131,8 +130,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
|
||||||
<td><img src="https://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>
|
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
|
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
|
||||||
<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
|
<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td>
|
<td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y</a></td>
|
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y</a></td>
|
||||||
@ -142,55 +141,60 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||||||
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
|
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
|
||||||
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
|
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
|
||||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
|
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
|
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<table><tr>
|
||||||
|
<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>
|
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
|
||||||
<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/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/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/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/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/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>
|
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
|
<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>
|
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
|
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
|
||||||
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
|
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=26340354">totokoro</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=19356899">sheeta.s</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=5827393">motcha</a></td>
|
<td><a href="https://www.patreon.com/user?u=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/user?u=13737140">Satsuki Yanagi</a></td>
|
||||||
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
|
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
|
||||||
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
|
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<table><tr>
|
||||||
|
<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/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
|
||||||
<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/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>
|
<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://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://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
|
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
|
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
|
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
|
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
|
||||||
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
|
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
|
||||||
<td><a href="https://www.patreon.com/Corset">CG</a></td>
|
<td><a href="https://www.patreon.com/Corset">CG</a></td>
|
||||||
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
|
<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=24641572">uroco @99</a></td>
|
||||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<table><tr>
|
||||||
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
|
||||||
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
|
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
||||||
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
|
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
|
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
|
||||||
|
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||||
</tr></table>
|
</tr></table>
|
||||||
|
|
||||||
**Last updated:** Wed, 05 Feb 2020 00:42:12 UTC
|
**Last updated:** Fri, 21 Feb 2020 13:39:06 UTC
|
||||||
<!-- PATREON_END -->
|
<!-- PATREON_END -->
|
||||||
|
|
||||||
[backer-url]: #backers
|
[backer-url]: #backers
|
||||||
|
@ -1,2 +1,111 @@
|
|||||||
---
|
---
|
||||||
_lang_: "Deutsch"
|
_lang_: "Deutsch"
|
||||||
|
monthAndDay: "{day}/{month}"
|
||||||
|
search: "Suchen"
|
||||||
|
notifications: "Benachrichtigungen"
|
||||||
|
username: "Benutzername"
|
||||||
|
password: "Passwort"
|
||||||
|
fetchingAsApObject: "Aus Fediverse holen"
|
||||||
|
ok: "OK"
|
||||||
|
gotIt: "Verstanden!"
|
||||||
|
cancel: "Abbrechen"
|
||||||
|
enterUsername: "Benutzername eingeben"
|
||||||
|
renotedBy: "Renote von {user}"
|
||||||
|
noNotes: "Keine Notizen"
|
||||||
|
noNotifications: "Keine Benachrichtigungen"
|
||||||
|
instance: "Instanz"
|
||||||
|
settings: "Einstellungen"
|
||||||
|
profile: "Profil"
|
||||||
|
timeline: "Zeitleiste"
|
||||||
|
noAccountDescription: "Keine Selbsteinführung"
|
||||||
|
login: "Einloggen"
|
||||||
|
loggingIn: "Einloggen in bearbeitung"
|
||||||
|
logout: "Ausloggen"
|
||||||
|
signup: "Registrieren"
|
||||||
|
uploading: "Upload läuft"
|
||||||
|
save: "Speichern"
|
||||||
|
users: "Benutzer"
|
||||||
|
addUser: "Benutzer hinzufügen"
|
||||||
|
favorite: "Favoriten"
|
||||||
|
favorites: "Favoriten"
|
||||||
|
unfavorite: "Aus Favoriten entfernen"
|
||||||
|
pin: "Anheften"
|
||||||
|
unpin: "Lösen"
|
||||||
|
copyContent: "Inhalt kopieren"
|
||||||
|
copyLink: "Link kopieren"
|
||||||
|
delete: "Löschen"
|
||||||
|
addToList: "Zur Liste hinzufügen"
|
||||||
|
sendMessage: "Nachricht senden"
|
||||||
|
copyUsername: "Benutzernamen kopieren"
|
||||||
|
reply: "Antworten"
|
||||||
|
loadMore: "Zeige mehr"
|
||||||
|
youGotNewFollower: "Sie haben einen neuen Follower"
|
||||||
|
receiveFollowRequest: "Follow Request erhalten."
|
||||||
|
followRequestAccepted: "FollowRequestAkzeptiert"
|
||||||
|
mentions: "Erwähnungen"
|
||||||
|
directNotes: "Direktnachrichten"
|
||||||
|
importAndExport: "Importieren und Exportieren"
|
||||||
|
import: "Importieren"
|
||||||
|
export: "Exportieren"
|
||||||
|
files: "Dateien"
|
||||||
|
download: "Download"
|
||||||
|
lists: "Listen"
|
||||||
|
noLists: "Keine Liste!"
|
||||||
|
note: "Noten"
|
||||||
|
following: "Folgen"
|
||||||
|
followers: "Folgende"
|
||||||
|
manageLists: "Liste verwalten"
|
||||||
|
error: "Ein Problem ist aufgetreten"
|
||||||
|
retry: "Wiederholen"
|
||||||
|
privacy: "Privatsphäre"
|
||||||
|
defaultNoteVisibility: "Die Standardsichtbarkeit"
|
||||||
|
follow: "Folgen"
|
||||||
|
followRequest: "Follower-Anfragen"
|
||||||
|
followRequests: "Follower-Anfragen"
|
||||||
|
unfollow: "Nicht mehr folgen"
|
||||||
|
followRequestPending: "Ausstehend"
|
||||||
|
clickToShow: "Klicke zum den Inhalt anzusehen"
|
||||||
|
sensitive: "Dieser Inhalt ist NSFW"
|
||||||
|
add: "Hinzufügen"
|
||||||
|
reaction: "Reaktionen"
|
||||||
|
selectUser: "Benutzer wählen"
|
||||||
|
instances: "Instanz"
|
||||||
|
mutedUsers: "Stummgestellte Benutzer"
|
||||||
|
blockedUsers: "Blockierte Benutzer"
|
||||||
|
noUsers: "Keine Benutzer"
|
||||||
|
remove: "Löschen"
|
||||||
|
nsfw: "Dieser Inhalt ist NSFW"
|
||||||
|
userList: "Listen"
|
||||||
|
_sfx:
|
||||||
|
notification: "Benachrichtigungen"
|
||||||
|
_widgets:
|
||||||
|
notifications: "Benachrichtigungen"
|
||||||
|
timeline: "Zeitleiste"
|
||||||
|
_cw:
|
||||||
|
show: "Zeige mehr"
|
||||||
|
_visibility:
|
||||||
|
followers: "Folgende"
|
||||||
|
_profile:
|
||||||
|
username: "Benutzername"
|
||||||
|
_exportOrImport:
|
||||||
|
followingList: "Folgen"
|
||||||
|
userLists: "Listen"
|
||||||
|
_pages:
|
||||||
|
script:
|
||||||
|
categories:
|
||||||
|
list: "Listen"
|
||||||
|
blocks:
|
||||||
|
_join:
|
||||||
|
arg1: "Listen"
|
||||||
|
_randomPick:
|
||||||
|
arg1: "Listen"
|
||||||
|
_dailyRandomPick:
|
||||||
|
arg1: "Listen"
|
||||||
|
_seedRandomPick:
|
||||||
|
arg2: "Listen"
|
||||||
|
_pick:
|
||||||
|
arg1: "Listen"
|
||||||
|
_listLen:
|
||||||
|
arg1: "Listen"
|
||||||
|
types:
|
||||||
|
array: "Listen"
|
||||||
|
@ -239,6 +239,8 @@ avatar: "Avatar"
|
|||||||
banner: "Banner"
|
banner: "Banner"
|
||||||
nsfw: "NSFW"
|
nsfw: "NSFW"
|
||||||
disconnectedFromServer: "Connection to the server was inturrupted"
|
disconnectedFromServer: "Connection to the server was inturrupted"
|
||||||
|
reload: "Refresh"
|
||||||
|
doNothing: "Ignore"
|
||||||
reloadConfirm: "Would you like to retry?"
|
reloadConfirm: "Would you like to retry?"
|
||||||
watch: "Watch"
|
watch: "Watch"
|
||||||
unwatch: "Undo Watch"
|
unwatch: "Undo Watch"
|
||||||
@ -252,9 +254,9 @@ tosUrl: "Terms of Service URL"
|
|||||||
thisYear: "Year"
|
thisYear: "Year"
|
||||||
thisMonth: "Month"
|
thisMonth: "Month"
|
||||||
today: "Today"
|
today: "Today"
|
||||||
dayX: "{day} days"
|
dayX: "{day}"
|
||||||
monthX: "{month} months"
|
monthX: "{month}"
|
||||||
yearX: "{year} years"
|
yearX: "{year} /"
|
||||||
pages: "Pages"
|
pages: "Pages"
|
||||||
integration: "Integration"
|
integration: "Integration"
|
||||||
connectSerice: "Connect"
|
connectSerice: "Connect"
|
||||||
@ -283,7 +285,8 @@ antennas: "Antennas"
|
|||||||
manageAntennas: "Manage Antennas"
|
manageAntennas: "Manage Antennas"
|
||||||
name: "Name"
|
name: "Name"
|
||||||
antennaSource: "Antenna source"
|
antennaSource: "Antenna source"
|
||||||
antennaKeywords: "Antenna keywords"
|
antennaKeywords: "Keywords to receive"
|
||||||
|
antennaExcludeKeywords: "Keywords to exclude"
|
||||||
antennaKeywordsDescription: "Separate with spaces for AND condition. Separate with line breaks for OR."
|
antennaKeywordsDescription: "Separate with spaces for AND condition. Separate with line breaks for OR."
|
||||||
notifyAntenna: "Notify newer notes"
|
notifyAntenna: "Notify newer notes"
|
||||||
withFileAntenna: "Filter only notes with file attached"
|
withFileAntenna: "Filter only notes with file attached"
|
||||||
@ -400,6 +403,41 @@ docSource: "Source of this document"
|
|||||||
createAccount: "Create account"
|
createAccount: "Create account"
|
||||||
existingAcount: "Existing accounts"
|
existingAcount: "Existing accounts"
|
||||||
regenerate: "Regenerate"
|
regenerate: "Regenerate"
|
||||||
|
fontSize: "Font size"
|
||||||
|
noFollowRequests: "You don't have any pending follow requests"
|
||||||
|
openImageInNewTab: "Open image in new tab"
|
||||||
|
dashboard: "Dashboard"
|
||||||
|
local: "Local"
|
||||||
|
remote: "Remote"
|
||||||
|
total: "Total"
|
||||||
|
weekOverWeekChanges: "Weekly"
|
||||||
|
dayOverDayChanges: "Daily"
|
||||||
|
accessibility: "Accessibility"
|
||||||
|
clinetSettings: "Client Settings"
|
||||||
|
accountSettings: "Account Settings"
|
||||||
|
promotion: "Promoted"
|
||||||
|
promote: "Promote"
|
||||||
|
numberOfDays: "Amount of days"
|
||||||
|
hideThisNote: "Hide this note"
|
||||||
|
showFeaturedNotesInTimeline: "Show Featured notes in Timeline"
|
||||||
|
objectStorage: "Object Storage"
|
||||||
|
useObjectStorage: "Use object storage"
|
||||||
|
serverLogs: "Server logs"
|
||||||
|
deleteAll: "Delete all"
|
||||||
|
showFixedPostForm: "Display the posting form at the top of the timeline"
|
||||||
|
newNoteRecived: "You've got a new note"
|
||||||
|
useNotificationsPopup: "Display notification list in popup"
|
||||||
|
sounds: "Sounds"
|
||||||
|
listen: "Listen"
|
||||||
|
none: "None"
|
||||||
|
volume: "Volume"
|
||||||
|
_sfx:
|
||||||
|
note: "New note"
|
||||||
|
noteMy: "My note"
|
||||||
|
notification: "Notifications"
|
||||||
|
chat: "Messaging"
|
||||||
|
chatBg: "Messaging (Background)"
|
||||||
|
antenna: "Antenna Reception"
|
||||||
_ago:
|
_ago:
|
||||||
unknown: "Unknown"
|
unknown: "Unknown"
|
||||||
future: "Future"
|
future: "Future"
|
||||||
@ -426,7 +464,7 @@ _tutorial:
|
|||||||
step3_1: "Finished setting up your profile?"
|
step3_1: "Finished setting up your profile?"
|
||||||
step3_2: "The next step is to post a note. You can do this by pressing a pencil icon on the screen."
|
step3_2: "The next step is to post a note. You can do this by pressing a pencil icon on the screen."
|
||||||
step3_3: "Fill in the modal and press the button on the right top to post."
|
step3_3: "Fill in the modal and press the button on the right top to post."
|
||||||
step3_4: "Have nothing to say? Try \"I just started Misskey!\""
|
step3_4: "Have nothing to say? Try \"just setting up my msky\"!"
|
||||||
step4_1: "Finished posting your first note?"
|
step4_1: "Finished posting your first note?"
|
||||||
step4_2: "Hurray! Now your first note is displayed on your timeline."
|
step4_2: "Hurray! Now your first note is displayed on your timeline."
|
||||||
step5_1: "Now, let's try making your timeline more lively by following other people."
|
step5_1: "Now, let's try making your timeline more lively by following other people."
|
||||||
@ -500,6 +538,8 @@ _widgets:
|
|||||||
trends: "Trending"
|
trends: "Trending"
|
||||||
clock: "Clock"
|
clock: "Clock"
|
||||||
rss: "RSS reader"
|
rss: "RSS reader"
|
||||||
|
activity: "Activity"
|
||||||
|
photos: "Photos"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Hide"
|
hide: "Hide"
|
||||||
show: "Load more"
|
show: "Load more"
|
||||||
|
@ -239,6 +239,8 @@ avatar: "Avatar"
|
|||||||
banner: "Banner"
|
banner: "Banner"
|
||||||
nsfw: "Marcado como sensible"
|
nsfw: "Marcado como sensible"
|
||||||
disconnectedFromServer: "Desconectado del servidor"
|
disconnectedFromServer: "Desconectado del servidor"
|
||||||
|
reload: "Recargar"
|
||||||
|
doNothing: "No hacer nada"
|
||||||
reloadConfirm: "¿Desea recargar?"
|
reloadConfirm: "¿Desea recargar?"
|
||||||
watch: "Ver"
|
watch: "Ver"
|
||||||
unwatch: "Dejar de ver"
|
unwatch: "Dejar de ver"
|
||||||
@ -283,7 +285,8 @@ antennas: "Antenas"
|
|||||||
manageAntennas: "Administrar antenas"
|
manageAntennas: "Administrar antenas"
|
||||||
name: "Nombre"
|
name: "Nombre"
|
||||||
antennaSource: "Origen de la antena"
|
antennaSource: "Origen de la antena"
|
||||||
antennaKeywords: "Palabras clave de la antena"
|
antennaKeywords: "Palabras clave para recibir"
|
||||||
|
antennaExcludeKeywords: "Palabras clave para excluir"
|
||||||
antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar con una linea nueva es una declaración OR"
|
antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar con una linea nueva es una declaración OR"
|
||||||
notifyAntenna: "Notificar nueva nota"
|
notifyAntenna: "Notificar nueva nota"
|
||||||
withFileAntenna: "Sólo notas con archivos adjuntados"
|
withFileAntenna: "Sólo notas con archivos adjuntados"
|
||||||
@ -400,6 +403,41 @@ docSource: "Fuente de este documento"
|
|||||||
createAccount: "Crear cuenta"
|
createAccount: "Crear cuenta"
|
||||||
existingAcount: "Cuentas existentes"
|
existingAcount: "Cuentas existentes"
|
||||||
regenerate: "Regenerar"
|
regenerate: "Regenerar"
|
||||||
|
fontSize: "Tamaño de la letra"
|
||||||
|
noFollowRequests: "No hay solicitudes de seguimiento"
|
||||||
|
openImageInNewTab: "Abrir imagen en nueva pestaña"
|
||||||
|
dashboard: "Panel de control"
|
||||||
|
local: "Local"
|
||||||
|
remote: "Remoto"
|
||||||
|
total: "Total"
|
||||||
|
weekOverWeekChanges: "Dif semanal"
|
||||||
|
dayOverDayChanges: "Dif diaria"
|
||||||
|
accessibility: "Accesibilidad"
|
||||||
|
clinetSettings: "Ajustes del cliente"
|
||||||
|
accountSettings: "Ajustes de cuenta"
|
||||||
|
promotion: "Promovido"
|
||||||
|
promote: "Promover"
|
||||||
|
numberOfDays: "Cantidad de dias"
|
||||||
|
hideThisNote: "Ocultar esta nota"
|
||||||
|
showFeaturedNotesInTimeline: "Mostrar notas destacadas en la línea de tiempo"
|
||||||
|
objectStorage: "Almacenamiento de objetos"
|
||||||
|
useObjectStorage: "Usar almacenamiento de objetos"
|
||||||
|
serverLogs: "Registros del servidor"
|
||||||
|
deleteAll: "Eliminar todos"
|
||||||
|
showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo"
|
||||||
|
newNoteRecived: "Tienes una nota nuevo"
|
||||||
|
useNotificationsPopup: "Mostrar lista de notificaciones en ventana emergente"
|
||||||
|
sounds: "Sonidos"
|
||||||
|
listen: "Escuchar"
|
||||||
|
none: "Ninguna"
|
||||||
|
volume: "Volumen"
|
||||||
|
_sfx:
|
||||||
|
note: "Notas"
|
||||||
|
noteMy: "Nota (a mí mismo)"
|
||||||
|
notification: "Notificaciones"
|
||||||
|
chat: "Chat"
|
||||||
|
chatBg: "Chat (Fondo)"
|
||||||
|
antenna: "Antena receptora"
|
||||||
_ago:
|
_ago:
|
||||||
unknown: "Desconocido"
|
unknown: "Desconocido"
|
||||||
future: "Futuro"
|
future: "Futuro"
|
||||||
@ -500,6 +538,8 @@ _widgets:
|
|||||||
trends: "Tendencias"
|
trends: "Tendencias"
|
||||||
clock: "Reloj"
|
clock: "Reloj"
|
||||||
rss: "Lector RSS"
|
rss: "Lector RSS"
|
||||||
|
activity: "Actividad"
|
||||||
|
photos: "Fotos"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Ocultar"
|
hide: "Ocultar"
|
||||||
show: "Ver más"
|
show: "Ver más"
|
||||||
|
@ -239,6 +239,8 @@ avatar: "Avatar"
|
|||||||
banner: "Bannière"
|
banner: "Bannière"
|
||||||
nsfw: "Contenu sensible"
|
nsfw: "Contenu sensible"
|
||||||
disconnectedFromServer: "Déconnecté du serveur"
|
disconnectedFromServer: "Déconnecté du serveur"
|
||||||
|
reload: "Rafraîchir"
|
||||||
|
doNothing: "Ignorer"
|
||||||
reloadConfirm: "Voulez-vous recharger?"
|
reloadConfirm: "Voulez-vous recharger?"
|
||||||
watch: "Surveiller"
|
watch: "Surveiller"
|
||||||
unwatch: "Ne plus surveiller"
|
unwatch: "Ne plus surveiller"
|
||||||
@ -283,7 +285,8 @@ antennas: "Antenne"
|
|||||||
manageAntennas: "Gestion d'antenne"
|
manageAntennas: "Gestion d'antenne"
|
||||||
name: "Nom"
|
name: "Nom"
|
||||||
antennaSource: "Recevoir la source"
|
antennaSource: "Recevoir la source"
|
||||||
antennaKeywords: "Mots clés entrants"
|
antennaKeywords: "Mots clés à recevoir"
|
||||||
|
antennaExcludeKeywords: "Mots clés à exclure"
|
||||||
antennaKeywordsDescription: "Lorsqu'il est séparé par un espace, il devient une spécification ET, et lorsqu'il est séparé par un saut de ligne, il devient une spécification OU."
|
antennaKeywordsDescription: "Lorsqu'il est séparé par un espace, il devient une spécification ET, et lorsqu'il est séparé par un saut de ligne, il devient une spécification OU."
|
||||||
notifyAntenna: "Notifier les nouvelles notes"
|
notifyAntenna: "Notifier les nouvelles notes"
|
||||||
withFileAntenna: "Notes uniquement avec fichiers joints"
|
withFileAntenna: "Notes uniquement avec fichiers joints"
|
||||||
@ -400,6 +403,41 @@ docSource: "Source de ce document"
|
|||||||
createAccount: "Créer compte"
|
createAccount: "Créer compte"
|
||||||
existingAcount: "Comptes existants"
|
existingAcount: "Comptes existants"
|
||||||
regenerate: "Régénérer"
|
regenerate: "Régénérer"
|
||||||
|
fontSize: "Taille de la police"
|
||||||
|
noFollowRequests: "Vous n'avez aucune demandes d'abonnement en attente"
|
||||||
|
openImageInNewTab: "Ouvrir l'image dans un nouvel onglet"
|
||||||
|
dashboard: "Tableau de bord"
|
||||||
|
local: "Local"
|
||||||
|
remote: "Distant"
|
||||||
|
total: "Total"
|
||||||
|
weekOverWeekChanges: "Diff hebdo"
|
||||||
|
dayOverDayChanges: "Diff quotidien"
|
||||||
|
accessibility: "Accessibilité"
|
||||||
|
clinetSettings: "Paramètres du client"
|
||||||
|
accountSettings: "Paramètres du compte"
|
||||||
|
promotion: "Promu"
|
||||||
|
promote: "Promouvoir"
|
||||||
|
numberOfDays: "Nombre de jours"
|
||||||
|
hideThisNote: "Masquer cette note"
|
||||||
|
showFeaturedNotesInTimeline: "Afficher les notes en vedette dans Fil d'actualité"
|
||||||
|
objectStorage: "Stockage d'objets"
|
||||||
|
useObjectStorage: "Utiliser le stockage d'objets"
|
||||||
|
serverLogs: "Journaux serveur"
|
||||||
|
deleteAll: "Supprimer tout"
|
||||||
|
showFixedPostForm: "Afficher le formulaire en haut du fil d'actualité"
|
||||||
|
newNoteRecived: "Vous avez un nouveau note"
|
||||||
|
useNotificationsPopup: "Afficher la liste des notifications dans une fenêtre contextuelle"
|
||||||
|
sounds: "Sons"
|
||||||
|
listen: "Écouter"
|
||||||
|
none: "Rien"
|
||||||
|
volume: "Volume"
|
||||||
|
_sfx:
|
||||||
|
note: "Nouvelle note"
|
||||||
|
noteMy: "Ma note"
|
||||||
|
notification: "Notifications"
|
||||||
|
chat: "Discuter"
|
||||||
|
chatBg: "Discuter (De fond)"
|
||||||
|
antenna: "Réception d'antenne"
|
||||||
_ago:
|
_ago:
|
||||||
unknown: "Inconnu"
|
unknown: "Inconnu"
|
||||||
future: "Futur"
|
future: "Futur"
|
||||||
@ -419,6 +457,16 @@ _time:
|
|||||||
_tutorial:
|
_tutorial:
|
||||||
title: "Comment utiliser Misskey"
|
title: "Comment utiliser Misskey"
|
||||||
step1_1: "Bienvenue,"
|
step1_1: "Bienvenue,"
|
||||||
|
step1_2: "Cette page est appelée \"timeline\". Elle montre les \"notes\" des personnes que vous \"suivez\" dans l'ordre chronologique."
|
||||||
|
step1_3: "Vous n'avez pas encore posté de notes ou ne suivez personne, vous ne devriez donc rien voir dans la chronologie."
|
||||||
|
step2_1: "Finissons de créer votre profil avant d'écrire une note ou de suivre quelqu'un."
|
||||||
|
step2_2: "En fournissant quelques informations sur vous, il sera plus facile pour les autres de vous suivre."
|
||||||
|
step3_1: "Vous avez fini de créer votre profil ?"
|
||||||
|
step3_2: "L’étape suivante consiste à créer une note. Vous pouvez commencer en cliquant sur l’icône crayon sur l’écran."
|
||||||
|
step3_3: "Remplissez le cadran et cliquez sur le bouton en haut à droite pour envoyer."
|
||||||
|
step3_4: "Vous n'avez rien à dire ? Essayez de dire \"J'ai commencé à utiliser Misskey\"."
|
||||||
|
step4_1: "Avez-vous posté votre première notes ?"
|
||||||
|
step4_2: "Votre première note est maintenant affichée sur votre timeline."
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "Cette étape à déjà été complétée"
|
alreadyRegistered: "Cette étape à déjà été complétée"
|
||||||
registerDevice: "S’inscrire l'appareil"
|
registerDevice: "S’inscrire l'appareil"
|
||||||
@ -480,6 +528,8 @@ _widgets:
|
|||||||
trends: "Tendances"
|
trends: "Tendances"
|
||||||
clock: "Horloge"
|
clock: "Horloge"
|
||||||
rss: "Lecteur de flux RSS"
|
rss: "Lecteur de flux RSS"
|
||||||
|
activity: "Activités"
|
||||||
|
photos: "Photos"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Masquer"
|
hide: "Masquer"
|
||||||
show: "Voir plus"
|
show: "Voir plus"
|
||||||
|
@ -239,6 +239,8 @@ avatar: "アイコン"
|
|||||||
banner: "バナー"
|
banner: "バナー"
|
||||||
nsfw: "閲覧注意"
|
nsfw: "閲覧注意"
|
||||||
disconnectedFromServer: "サーバーから切断されました"
|
disconnectedFromServer: "サーバーから切断されました"
|
||||||
|
reload: "リロード"
|
||||||
|
doNothing: "なにもしない"
|
||||||
reloadConfirm: "リロードしますか?"
|
reloadConfirm: "リロードしますか?"
|
||||||
watch: "ウォッチ"
|
watch: "ウォッチ"
|
||||||
unwatch: "ウォッチ解除"
|
unwatch: "ウォッチ解除"
|
||||||
@ -284,6 +286,7 @@ manageAntennas: "アンテナの管理"
|
|||||||
name: "名前"
|
name: "名前"
|
||||||
antennaSource: "受信ソース"
|
antennaSource: "受信ソース"
|
||||||
antennaKeywords: "受信キーワード"
|
antennaKeywords: "受信キーワード"
|
||||||
|
antennaExcludeKeywords: "除外キーワード"
|
||||||
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
||||||
notifyAntenna: "新しいノートを通知する"
|
notifyAntenna: "新しいノートを通知する"
|
||||||
withFileAntenna: "ファイルが添付されたノートのみ"
|
withFileAntenna: "ファイルが添付されたノートのみ"
|
||||||
@ -401,6 +404,42 @@ createAccount: "アカウントを作成"
|
|||||||
existingAcount: "既存のアカウント"
|
existingAcount: "既存のアカウント"
|
||||||
regenerate: "再生成"
|
regenerate: "再生成"
|
||||||
fontSize: "フォントサイズ"
|
fontSize: "フォントサイズ"
|
||||||
|
noFollowRequests: "フォロー申請はありません"
|
||||||
|
openImageInNewTab: "画像を新しいタブで開く"
|
||||||
|
dashboard: "ダッシュボード"
|
||||||
|
local: "ローカル"
|
||||||
|
remote: "リモート"
|
||||||
|
total: "合計"
|
||||||
|
weekOverWeekChanges: "前週比"
|
||||||
|
dayOverDayChanges: "前日比"
|
||||||
|
accessibility: "アクセシビリティ"
|
||||||
|
clinetSettings: "クライアント設定"
|
||||||
|
accountSettings: "アカウント設定"
|
||||||
|
promotion: "プロモーション"
|
||||||
|
promote: "プロモート"
|
||||||
|
numberOfDays: "日数"
|
||||||
|
hideThisNote: "このノートを非表示"
|
||||||
|
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示する"
|
||||||
|
objectStorage: "オブジェクトストレージ"
|
||||||
|
useObjectStorage: "オブジェクトストレージを使用"
|
||||||
|
serverLogs: "サーバーログ"
|
||||||
|
deleteAll: "全て削除"
|
||||||
|
showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
|
||||||
|
newNoteRecived: "新しいノートがあります"
|
||||||
|
useNotificationsPopup: "通知一覧をポップアップで表示"
|
||||||
|
sounds: "サウンド"
|
||||||
|
listen: "聴く"
|
||||||
|
none: "なし"
|
||||||
|
volume: "音量"
|
||||||
|
details: "詳細"
|
||||||
|
|
||||||
|
_sfx:
|
||||||
|
note: "ノート"
|
||||||
|
noteMy: "ノート(自分)"
|
||||||
|
notification: "通知"
|
||||||
|
chat: "チャット"
|
||||||
|
chatBg: "チャット(バックグラウンド)"
|
||||||
|
antenna: "アンテナ受信"
|
||||||
|
|
||||||
_ago:
|
_ago:
|
||||||
unknown: "謎"
|
unknown: "謎"
|
||||||
@ -510,6 +549,8 @@ _widgets:
|
|||||||
trends: "トレンド"
|
trends: "トレンド"
|
||||||
clock: "時計"
|
clock: "時計"
|
||||||
rss: "RSSリーダー"
|
rss: "RSSリーダー"
|
||||||
|
activity: "アクティビティ"
|
||||||
|
photos: "フォト"
|
||||||
|
|
||||||
_cw:
|
_cw:
|
||||||
hide: "隠す"
|
hide: "隠す"
|
||||||
|
@ -109,6 +109,8 @@ aboutMisskey: "Misskeyってなんや?"
|
|||||||
notFoundDescription: "指定されたURLに該当するページはあらへんやった。"
|
notFoundDescription: "指定されたURLに該当するページはあらへんやった。"
|
||||||
close: "さいなら"
|
close: "さいなら"
|
||||||
joinedGroups: "参加しとるグループ"
|
joinedGroups: "参加しとるグループ"
|
||||||
|
_sfx:
|
||||||
|
notification: "通知"
|
||||||
_ago:
|
_ago:
|
||||||
unknown: "謎"
|
unknown: "謎"
|
||||||
future: "未来"
|
future: "未来"
|
||||||
|
@ -1,2 +1,65 @@
|
|||||||
---
|
---
|
||||||
_lang_: "ಕನ್ನಡ"
|
_lang_: "ಕನ್ನಡ"
|
||||||
|
introMisskey: "ಸ್ವಾಗತ! Misskey ಓಪನ್ ಸೋರ್ಸ್ ಒಕ್ಕೂಟ ಮೈಕ್ರೋಬ್ಲಾಗಿಂಗ್ ಸೇವೆಯಾಗಿದೆ.\n ಏನಾಗುತ್ತಿದೆ ಎಂಬುದನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಅಥವಾ ನಿಮ್ಮ ಬಗ್ಗೆ ಎಲ್ಲರಿಗೂ ಹೇಳಲು \"ಟಿಪ್ಪಣಿ\"ಗಳನ್ನು ರಚಿಸಿ📡\n \"ಸ್ಪಂದನೆ\" ಕ್ರಿಯೆಯೊಂದಿಗೆ, ನೀವು ಎಲ್ಲರ ಟಿಪ್ಪಣಿಗಳಿಗೆ ತ್ವರಿತವಾಗಿ ಸ್ಪಂದನೆಗಳನ್ನು ಕೂಡ ಸೇರಿಸಬಹುದು.👍\n ಹೊಸ ಜಗತ್ತನ್ನು ಅನ್ವೇಷಿಸಿ🚀"
|
||||||
|
monthAndDay: "{month}ನೇ ತಿಂಗಳ {day}ನೇ ದಿನ"
|
||||||
|
search: "ಹುಡುಕು"
|
||||||
|
notifications: "ಅಧಿಸೂಚನೆಗಳು"
|
||||||
|
username: "ಬಳಕೆಹೆಸರು"
|
||||||
|
password: "ಗುಪ್ತಪದ"
|
||||||
|
fetchingAsApObject: "ಒಕ್ಕೂಟದಿಂದ ಪಡೆಯಲಾಗುತ್ತಿದೆ..."
|
||||||
|
ok: "ಸರಿ"
|
||||||
|
gotIt: "ಅರ್ಥವಾಯಿತು!"
|
||||||
|
cancel: "ರದ್ದು"
|
||||||
|
enterUsername: "ಬಳಕೆಹೆಸರನ್ನು ಭರ್ತಿ ಮಾಡಿ"
|
||||||
|
renotedBy: "{user} ಪುನರಾವರ್ತಿಸಿದರು"
|
||||||
|
noNotes: "ಟಿಪ್ಪಣಿಗಳಿಲ್ಲ"
|
||||||
|
noNotifications: "ಅಧಿಸೂಚನೆಗಳಿಲ್ಲ"
|
||||||
|
instance: "ನಿದರ್ಶನ"
|
||||||
|
settings: "ಸಿದ್ಧತೆಗಳು"
|
||||||
|
profile: "ಪ್ರೊಫೈಲು"
|
||||||
|
timeline: "ಸಮಯಸಾಲು"
|
||||||
|
noAccountDescription: "ಇವರು ಸ್ವಯಂ ಪರಿಚಯ ರಚಿಸಿಲ್ಲ"
|
||||||
|
login: "ಪ್ರವೇಶ"
|
||||||
|
loggingIn: "ಪ್ರವೇಶಿಸುತ್ತಾ..."
|
||||||
|
logout: "ಆಚೆಗೆ"
|
||||||
|
signup: "ನೋಂದಣಿ"
|
||||||
|
uploading: "ಅಪ್ಲೋಡಾಗುತ್ತಿದೆ"
|
||||||
|
save: "ಉಳಿಸಿ"
|
||||||
|
users: "ಬಳಕೆದಾರ"
|
||||||
|
addUser: "ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ"
|
||||||
|
favorite: "ಮೆಚ್ಚಿನ"
|
||||||
|
favorites: "ಮೆಚ್ಚಿನವುಗಳು"
|
||||||
|
unfavorite: "ಮೆಚ್ಚುಗೆ ಅಳಿಸು"
|
||||||
|
pin: "ಪ್ರೊಫ಼ೈಲಿಗೆ ಅಂಟಿಸು"
|
||||||
|
unpin: "ಪ್ರೊಫ಼ೈಲಿಂದ ಅಂಟುತೆಗೆ"
|
||||||
|
copyContent: "ವಿಷಯವನ್ನು ನಕಲಿಸು"
|
||||||
|
copyLink: "ಲಿಂಕನ್ನು ನಕಲಿಸು"
|
||||||
|
delete: "ಅಳಿಸು"
|
||||||
|
addToList: "ಪಟ್ಟಿಗೆ ಸೇರಿಸು"
|
||||||
|
sendMessage: "ಸಂದೇಶ ಕಳುಹಿಸು"
|
||||||
|
copyUsername: "ಬಳಕೆಹೆಸರು ನಕಲಿಸು"
|
||||||
|
reply: "ಉತ್ತರಿಸು"
|
||||||
|
loadMore: "ಇನ್ನಷ್ಟು ನೋಡು"
|
||||||
|
youGotNewFollower: "ಹಿಂಬಾಲಿಸಿದರು"
|
||||||
|
receiveFollowRequest: "ಹಿಂಬಾಲನೆ ವಿನಂತಿ ಬಂದಿದೆ"
|
||||||
|
followRequestAccepted: "ಹಿಂಬಾಲನೆ ವಿನಂತಿ ಸ್ವೀಕರಿಸಲಾಯಿತು"
|
||||||
|
mentions: "ಹೆಸರಿಸಿದ"
|
||||||
|
directNotes: "ನೇರ ಟಿಪ್ಪಣಿಗಳು"
|
||||||
|
importAndExport: "ಆಮದು/ರಫ್ತು"
|
||||||
|
import: "ಆಮದು"
|
||||||
|
export: "ರಫ್ತು"
|
||||||
|
files: "ಕಡತಗಳು"
|
||||||
|
download: "ಜಾಲದಿಂದಿಳಿಸು"
|
||||||
|
driveFileDeleteConfirm: "\"{name}\" ಕಡತವನ್ನು ಅಳಿಸಲು ನೀವು ಬಯಸುವಿರಾ? ಈ ನೋಡಿರಿ ಲಗತ್ತಿಸಲಾದ ಟಿಪ್ಪಣಿ ಸಹ ಕಣ್ಮರೆಯಾಗುತ್ತದೆ."
|
||||||
|
unfollowConfirm: "{name}ಅನ್ನು ಹಿಂಬಾಲಿಸದಿರುವುದೇ?"
|
||||||
|
instances: "ನಿದರ್ಶನ"
|
||||||
|
remove: "ಅಳಿಸು"
|
||||||
|
_sfx:
|
||||||
|
notification: "ಅಧಿಸೂಚನೆಗಳು"
|
||||||
|
_widgets:
|
||||||
|
notifications: "ಅಧಿಸೂಚನೆಗಳು"
|
||||||
|
timeline: "ಸಮಯಸಾಲು"
|
||||||
|
_cw:
|
||||||
|
show: "ಇನ್ನಷ್ಟು ನೋಡು"
|
||||||
|
_profile:
|
||||||
|
username: "ಬಳಕೆಹೆಸರು"
|
||||||
|
@ -106,8 +106,8 @@ customEmojis: "커스텀 이모지"
|
|||||||
emojiName: "이모지 이름"
|
emojiName: "이모지 이름"
|
||||||
emojiUrl: "이모지 URL"
|
emojiUrl: "이모지 URL"
|
||||||
addEmoji: "이모지 추가"
|
addEmoji: "이모지 추가"
|
||||||
cacheRemoteFiles: "원격 파일을 캐시"
|
cacheRemoteFiles: "리모트 파일을 캐시"
|
||||||
cacheRemoteFilesDescription: "이 설정을 해지하면 원격 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다."
|
cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다."
|
||||||
flagAsBot: "나는 봇입니다"
|
flagAsBot: "나는 봇입니다"
|
||||||
flagAsCat: "나는 고양이다냥"
|
flagAsCat: "나는 고양이다냥"
|
||||||
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
|
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
|
||||||
@ -154,7 +154,7 @@ clearQueue: "대기열 비우기"
|
|||||||
clearQueueConfirmTitle: "대기열을 비우시겠습니까?"
|
clearQueueConfirmTitle: "대기열을 비우시겠습니까?"
|
||||||
clearQueueConfirmText: "대기열에 남아 있는 노트는 더이상 연합되지 않습니다. 보통의 경우 이 작업은 필요하지 않습니다."
|
clearQueueConfirmText: "대기열에 남아 있는 노트는 더이상 연합되지 않습니다. 보통의 경우 이 작업은 필요하지 않습니다."
|
||||||
clearCachedFiles: "캐시 비우기"
|
clearCachedFiles: "캐시 비우기"
|
||||||
clearCachedFilesConfirm: "캐시된 원격 파일을 모두 삭제하시겠습니까?"
|
clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?"
|
||||||
blockedInstances: "차단된 인스턴스"
|
blockedInstances: "차단된 인스턴스"
|
||||||
blockedInstancesDescription: "차단하려는 인스턴스의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다."
|
blockedInstancesDescription: "차단하려는 인스턴스의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다."
|
||||||
muteAndBlock: "뮤트 및 차단"
|
muteAndBlock: "뮤트 및 차단"
|
||||||
@ -239,6 +239,8 @@ avatar: "아바타"
|
|||||||
banner: "배너"
|
banner: "배너"
|
||||||
nsfw: "열람주의"
|
nsfw: "열람주의"
|
||||||
disconnectedFromServer: "서버와의 연결이 끊어졌습니다"
|
disconnectedFromServer: "서버와의 연결이 끊어졌습니다"
|
||||||
|
reload: "새로고침"
|
||||||
|
doNothing: "무시하기"
|
||||||
reloadConfirm: "새로고침 하시겠습니까?"
|
reloadConfirm: "새로고침 하시겠습니까?"
|
||||||
watch: "지켜보기"
|
watch: "지켜보기"
|
||||||
unwatch: "지켜보기 해제"
|
unwatch: "지켜보기 해제"
|
||||||
@ -253,7 +255,7 @@ thisYear: "올해"
|
|||||||
thisMonth: "이번 달"
|
thisMonth: "이번 달"
|
||||||
today: "오늘"
|
today: "오늘"
|
||||||
dayX: "{day}일"
|
dayX: "{day}일"
|
||||||
monthX: "{month}개월"
|
monthX: "{month}월"
|
||||||
yearX: "{year}년"
|
yearX: "{year}년"
|
||||||
pages: "페이지"
|
pages: "페이지"
|
||||||
integration: "연동"
|
integration: "연동"
|
||||||
@ -265,8 +267,8 @@ disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리
|
|||||||
registration: "등록"
|
registration: "등록"
|
||||||
enableRegistration: "신규 회원가입을 활성화"
|
enableRegistration: "신규 회원가입을 활성화"
|
||||||
invite: "초대"
|
invite: "초대"
|
||||||
proxyRemoteFiles: "원격 파일 프록시"
|
proxyRemoteFiles: "리모트 파일 프록시"
|
||||||
proxyRemoteFilesDescription: "이 설정을 활성화할 경우, 저장되지 않았거나 저장용량 초과로 삭제된 원격 파일을 로컬에서 프록시하여 썸네일을 생성하게 됩니다. 서버의 스토리지에는 영향을 주지 않습니다."
|
proxyRemoteFilesDescription: "이 설정을 활성화할 경우, 저장되지 않았거나 저장용량 초과로 삭제된 리모트 파일을 로컬에서 프록시하여 썸네일을 생성하게 됩니다. 서버의 스토리지에는 영향을 주지 않습니다."
|
||||||
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
|
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
|
||||||
driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량"
|
driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량"
|
||||||
inMb: "메가바이트 단위"
|
inMb: "메가바이트 단위"
|
||||||
@ -284,6 +286,7 @@ manageAntennas: "안테나 관리"
|
|||||||
name: "이름"
|
name: "이름"
|
||||||
antennaSource: "받을 소스"
|
antennaSource: "받을 소스"
|
||||||
antennaKeywords: "받을 키워드"
|
antennaKeywords: "받을 키워드"
|
||||||
|
antennaExcludeKeywords: "제외할 키워드"
|
||||||
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
|
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
|
||||||
notifyAntenna: "새로운 노트를 알림"
|
notifyAntenna: "새로운 노트를 알림"
|
||||||
withFileAntenna: "파일이 첨부된 노트만"
|
withFileAntenna: "파일이 첨부된 노트만"
|
||||||
@ -400,6 +403,41 @@ docSource: "이 문서의 소스"
|
|||||||
createAccount: "계정 만들기"
|
createAccount: "계정 만들기"
|
||||||
existingAcount: "기존 계정"
|
existingAcount: "기존 계정"
|
||||||
regenerate: "다시 생성"
|
regenerate: "다시 생성"
|
||||||
|
fontSize: "글자 크기"
|
||||||
|
noFollowRequests: "처리되지 않은 팔로우 요청이 없습니다"
|
||||||
|
openImageInNewTab: "새 탭에서 이미지 열기"
|
||||||
|
dashboard: "대시보드"
|
||||||
|
local: "로컬"
|
||||||
|
remote: "리모트"
|
||||||
|
total: "합계"
|
||||||
|
weekOverWeekChanges: "지난주보다"
|
||||||
|
dayOverDayChanges: "어제보다"
|
||||||
|
accessibility: "접근성"
|
||||||
|
clinetSettings: "클라이언트 설정"
|
||||||
|
accountSettings: "계정 설정"
|
||||||
|
promotion: "프로모션"
|
||||||
|
promote: "프로모션하기"
|
||||||
|
numberOfDays: "며칠동안"
|
||||||
|
hideThisNote: "이 노트를 숨기기"
|
||||||
|
showFeaturedNotesInTimeline: "타임라인에 추천 노트를 표시"
|
||||||
|
objectStorage: "오브젝트 스토리지"
|
||||||
|
useObjectStorage: "오브젝트 스토리지를 사용"
|
||||||
|
serverLogs: "서버 로그"
|
||||||
|
deleteAll: "모두 삭제"
|
||||||
|
showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
|
||||||
|
newNoteRecived: "새 노트가 있습니다"
|
||||||
|
useNotificationsPopup: "알림 목록을 팝업으로 표시"
|
||||||
|
sounds: "소리"
|
||||||
|
listen: "듣기"
|
||||||
|
none: "없음"
|
||||||
|
volume: "음량"
|
||||||
|
_sfx:
|
||||||
|
note: "새 노트"
|
||||||
|
noteMy: "내 노트"
|
||||||
|
notification: "알림"
|
||||||
|
chat: "대화"
|
||||||
|
chatBg: "대화 (백그라운드)"
|
||||||
|
antenna: "안테나 수신"
|
||||||
_ago:
|
_ago:
|
||||||
unknown: "알 수 없음"
|
unknown: "알 수 없음"
|
||||||
future: "미래"
|
future: "미래"
|
||||||
@ -500,6 +538,8 @@ _widgets:
|
|||||||
trends: "트렌드"
|
trends: "트렌드"
|
||||||
clock: "시계"
|
clock: "시계"
|
||||||
rss: "RSS 리더"
|
rss: "RSS 리더"
|
||||||
|
activity: "활동"
|
||||||
|
photos: "사진"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "숨기기"
|
hide: "숨기기"
|
||||||
show: "더 보기"
|
show: "더 보기"
|
||||||
|
@ -1,2 +1,36 @@
|
|||||||
---
|
---
|
||||||
_lang_: "Русский язык"
|
_lang_: "Русский язык"
|
||||||
|
search: "Поиск"
|
||||||
|
notifications: "Уведомления"
|
||||||
|
password: "Пароль"
|
||||||
|
ok: "Окей"
|
||||||
|
cancel: "Отмена"
|
||||||
|
instance: "Экземпляр"
|
||||||
|
settings: "Настройки"
|
||||||
|
profile: "Профиль"
|
||||||
|
timeline: "Лента"
|
||||||
|
login: "Войти"
|
||||||
|
logout: "Выйти"
|
||||||
|
signup: "Регистрация"
|
||||||
|
save: "Сохранить"
|
||||||
|
favorite: "Избранное"
|
||||||
|
favorites: "Избранное"
|
||||||
|
unfavorite: "Удалить из избранных"
|
||||||
|
pin: "Закрепить"
|
||||||
|
unpin: "Открепить"
|
||||||
|
copyLink: "Скопировать ссылку"
|
||||||
|
delete: "Удалить"
|
||||||
|
addToList: "Добавить в список"
|
||||||
|
reply: "Ответить"
|
||||||
|
loadMore: "Показать еще"
|
||||||
|
importAndExport: "Импорт / Экспорт"
|
||||||
|
files: "Файл"
|
||||||
|
instances: "Экземпляр"
|
||||||
|
remove: "Удалить"
|
||||||
|
_sfx:
|
||||||
|
notification: "Уведомления"
|
||||||
|
_widgets:
|
||||||
|
notifications: "Уведомления"
|
||||||
|
timeline: "Лента"
|
||||||
|
_cw:
|
||||||
|
show: "Показать еще"
|
||||||
|
@ -121,18 +121,23 @@ searchWith: "搜索:{q}"
|
|||||||
youHaveNoLists: "列表为空"
|
youHaveNoLists: "列表为空"
|
||||||
followConfirm: "你确定要关注{name}吗?"
|
followConfirm: "你确定要关注{name}吗?"
|
||||||
proxyAccount: "代理账户"
|
proxyAccount: "代理账户"
|
||||||
|
proxyAccountDescription: "代理帐户是在某些情况下充当用户的远程关注者的帐户。 例如,当一个用户列出一个远程用户时,如果没有人跟随该列出的用户,则该活动将不会传递到该实例,因此将代之以代理帐户。"
|
||||||
host: "主机名"
|
host: "主机名"
|
||||||
selectUser: "选择用户"
|
selectUser: "选择用户"
|
||||||
recipient: "收件人"
|
recipient: "收件人"
|
||||||
annotation: "注解"
|
annotation: "注解"
|
||||||
federation: "联合"
|
federation: "联合"
|
||||||
instances: "实例"
|
instances: "实例"
|
||||||
|
registeredAt: "初次观察"
|
||||||
latestRequestSentAt: "上次发送的请求"
|
latestRequestSentAt: "上次发送的请求"
|
||||||
latestRequestReceivedAt: "上次收到的请求"
|
latestRequestReceivedAt: "上次收到的请求"
|
||||||
|
latestStatus: "最后状态"
|
||||||
storageUsage: "已用存储"
|
storageUsage: "已用存储"
|
||||||
charts: "图表"
|
charts: "图表"
|
||||||
perHour: "每小时"
|
perHour: "每小时"
|
||||||
perDay: "每天"
|
perDay: "每天"
|
||||||
|
stopActivityDelivery: "停止发送活动"
|
||||||
|
blockThisInstance: "阻止此实例"
|
||||||
operations: "操作"
|
operations: "操作"
|
||||||
software: "软件"
|
software: "软件"
|
||||||
version: "版本"
|
version: "版本"
|
||||||
@ -147,6 +152,7 @@ instanceInfo: "实例情报"
|
|||||||
statistics: "统计"
|
statistics: "统计"
|
||||||
clearQueue: "清除队列"
|
clearQueue: "清除队列"
|
||||||
clearQueueConfirmTitle: "确定清除队列?"
|
clearQueueConfirmTitle: "确定清除队列?"
|
||||||
|
clearQueueConfirmText: "未送达的帖子将不会送达。 通常,您不需要这样做。"
|
||||||
clearCachedFiles: "清除缓存"
|
clearCachedFiles: "清除缓存"
|
||||||
clearCachedFilesConfirm: "确定要清除缓存文件?"
|
clearCachedFilesConfirm: "确定要清除缓存文件?"
|
||||||
blockedInstances: "被阻拦的实例"
|
blockedInstances: "被阻拦的实例"
|
||||||
@ -273,6 +279,7 @@ recaptcha: "reCAPTCHA"
|
|||||||
enableRecaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
|
enableRecaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
|
||||||
recaptchaSiteKey: "网站密钥"
|
recaptchaSiteKey: "网站密钥"
|
||||||
recaptchaSecretKey: "reCAPTCHA 密钥"
|
recaptchaSecretKey: "reCAPTCHA 密钥"
|
||||||
|
antennas: "天线"
|
||||||
name: "名称"
|
name: "名称"
|
||||||
antennaKeywordsDescription: "使用空格分隔会产生AND规范,并且使用换行符分隔会产生OR规范"
|
antennaKeywordsDescription: "使用空格分隔会产生AND规范,并且使用换行符分隔会产生OR规范"
|
||||||
serviceworker: "ServiceWorker"
|
serviceworker: "ServiceWorker"
|
||||||
@ -297,6 +304,7 @@ aboutMisskey: "关于 Misskey"
|
|||||||
aboutMisskeyText: "Misskey是由syuilo于2014年开发的开放源代码软件。"
|
aboutMisskeyText: "Misskey是由syuilo于2014年开发的开放源代码软件。"
|
||||||
misskeyMembers: "现在由以下成员进行开发和维护:"
|
misskeyMembers: "现在由以下成员进行开发和维护:"
|
||||||
misskeySource: "源代码在这里公开:"
|
misskeySource: "源代码在这里公开:"
|
||||||
|
misskeyTranslation: "与我们一同进行Misskey的翻译工作:"
|
||||||
misskeyDonate: "可以向 Misskey 进行捐款以支持开发:"
|
misskeyDonate: "可以向 Misskey 进行捐款以支持开发:"
|
||||||
morePatrons: "还有很多其他的人也在支持我们,非常感谢🥰"
|
morePatrons: "还有很多其他的人也在支持我们,非常感谢🥰"
|
||||||
patrons: "支持者"
|
patrons: "支持者"
|
||||||
@ -307,8 +315,10 @@ moderator: "版主"
|
|||||||
nUsersMentioned: "{n} 被提到"
|
nUsersMentioned: "{n} 被提到"
|
||||||
securityKey: "安全密钥"
|
securityKey: "安全密钥"
|
||||||
securityKeyName: "密钥名称"
|
securityKeyName: "密钥名称"
|
||||||
|
registerSecurityKey: "注册安全密钥"
|
||||||
lastUsed: "最后使用:"
|
lastUsed: "最后使用:"
|
||||||
unregister: "删除账户"
|
unregister: "删除账户"
|
||||||
|
passwordLessLogin: "无密码登录"
|
||||||
resetPassword: "重置密码"
|
resetPassword: "重置密码"
|
||||||
newPasswordIs: "新的密码是「{password}」"
|
newPasswordIs: "新的密码是「{password}」"
|
||||||
post: "投稿"
|
post: "投稿"
|
||||||
@ -319,10 +329,12 @@ autoNoteWatchDescription: "让您能够收到关于「反应」和回复其他
|
|||||||
reduceUiAnimation: "减少UI动画"
|
reduceUiAnimation: "减少UI动画"
|
||||||
share: "分享"
|
share: "分享"
|
||||||
notFound: "未找到"
|
notFound: "未找到"
|
||||||
|
notFoundDescription: "没有与指定URL对应的页面。"
|
||||||
uploadFolder: "默认上传文件夹"
|
uploadFolder: "默认上传文件夹"
|
||||||
cacheClear: "清空缓存"
|
cacheClear: "清空缓存"
|
||||||
markAsReadAllNotifications: "将所有通知标为已读"
|
markAsReadAllNotifications: "将所有通知标为已读"
|
||||||
markAsReadAllUnreadNotes: "将所有帖子标记为已读"
|
markAsReadAllUnreadNotes: "将所有帖子标记为已读"
|
||||||
|
markAsReadAllTalkMessages: "将所有聊天标记为已读"
|
||||||
help: "帮助"
|
help: "帮助"
|
||||||
inputMessageHere: "在此键入信息"
|
inputMessageHere: "在此键入信息"
|
||||||
close: "关闭"
|
close: "关闭"
|
||||||
@ -335,6 +347,8 @@ invites: "邀请"
|
|||||||
groupName: "群组名"
|
groupName: "群组名"
|
||||||
members: "成员"
|
members: "成员"
|
||||||
transfer: "转让"
|
transfer: "转让"
|
||||||
|
messagingWithUser: "与用户聊天"
|
||||||
|
messagingWithGroup: "与群组聊天"
|
||||||
title: "标题"
|
title: "标题"
|
||||||
text: "文本"
|
text: "文本"
|
||||||
enable: "启用"
|
enable: "启用"
|
||||||
@ -343,6 +357,69 @@ retype: "重新输入"
|
|||||||
noteOf: "{user}的帖子"
|
noteOf: "{user}的帖子"
|
||||||
inviteToGroup: "群组邀请"
|
inviteToGroup: "群组邀请"
|
||||||
maxNoteTextLength: "帖子的字数限制"
|
maxNoteTextLength: "帖子的字数限制"
|
||||||
|
quoteAttached: "已引用"
|
||||||
|
quoteQuestion: "是否将其作为引用附上?"
|
||||||
|
noMessagesYet: "现在没有新的聊天"
|
||||||
|
newMessageExists: "新信息"
|
||||||
|
onlyOneFileCanBeAttached: "只能添加一个附件"
|
||||||
|
signinRequired: "请先登录"
|
||||||
|
invitationCode: "邀请码"
|
||||||
|
checking: "正在确认"
|
||||||
|
available: "可用"
|
||||||
|
unavailable: "不可用"
|
||||||
|
usernameInvalidFormat: "可使用大小写英文字母、数字和下划线。"
|
||||||
|
tooShort: "过短"
|
||||||
|
tooLong: "过长"
|
||||||
|
weakPassword: "密码强度:弱"
|
||||||
|
normalPassword: "密码强度:中等"
|
||||||
|
strongPassword: "密码强度:强"
|
||||||
|
passwordMatched: "密码一致"
|
||||||
|
passwordNotMatched: "密码不一致"
|
||||||
|
signinWith: "以{x}登录"
|
||||||
|
tapSecurityKey: "点击安全密钥"
|
||||||
|
or: "或者"
|
||||||
|
uiLanguage: "显示语言"
|
||||||
|
groupInvited: "群组招待"
|
||||||
|
aboutX: "关于 {x}"
|
||||||
|
useOsNativeEmojis: "使用OS原生Emoji"
|
||||||
|
noGroups: "没有组"
|
||||||
|
joinOrCreateGroup: "加入或者创建群组"
|
||||||
|
noHistory: "没有历史记录"
|
||||||
|
disableAnimatedMfm: "禁用MFM动画"
|
||||||
|
doing: "正在进行"
|
||||||
|
category: "类别"
|
||||||
|
tags: "标签"
|
||||||
|
docSource: "文件来源"
|
||||||
|
createAccount: "注册账户"
|
||||||
|
existingAcount: "现有的帐户"
|
||||||
|
regenerate: "重新生成"
|
||||||
|
fontSize: "字体大小"
|
||||||
|
noFollowRequests: "没有关注申请"
|
||||||
|
openImageInNewTab: "在新标签页中打开图片"
|
||||||
|
dashboard: "Dashboard"
|
||||||
|
local: "本地"
|
||||||
|
remote: "远程"
|
||||||
|
total: "总计"
|
||||||
|
weekOverWeekChanges: "与前一周相比"
|
||||||
|
dayOverDayChanges: "与前一日相比"
|
||||||
|
accessibility: "辅助功能"
|
||||||
|
clinetSettings: "客户端设置"
|
||||||
|
accountSettings: "账户设置"
|
||||||
|
numberOfDays: "天数"
|
||||||
|
hideThisNote: "隐藏这条帖子"
|
||||||
|
showFeaturedNotesInTimeline: "在时间轴上显示热门推荐"
|
||||||
|
objectStorage: "对象存储"
|
||||||
|
useObjectStorage: "使用对象存储"
|
||||||
|
serverLogs: "服务器日志"
|
||||||
|
deleteAll: "删除全部"
|
||||||
|
showFixedPostForm: "在时间线顶部显示帖子表单"
|
||||||
|
newNoteRecived: "有新的帖子"
|
||||||
|
useNotificationsPopup: "在弹出窗口中显示通知列表"
|
||||||
|
none: "空"
|
||||||
|
_sfx:
|
||||||
|
note: "帖子"
|
||||||
|
notification: "通知"
|
||||||
|
chat: "聊天"
|
||||||
_ago:
|
_ago:
|
||||||
unknown: "未知"
|
unknown: "未知"
|
||||||
future: "未来"
|
future: "未来"
|
||||||
@ -362,6 +439,17 @@ _time:
|
|||||||
_tutorial:
|
_tutorial:
|
||||||
title: "Misskey的使用方法"
|
title: "Misskey的使用方法"
|
||||||
step1_1: "欢迎!"
|
step1_1: "欢迎!"
|
||||||
|
step1_2: "这个页面叫做「时间线」,它会按照时间顺序显示所有你「关注」的人所发的「帖子」。"
|
||||||
|
step1_3: "如果你并没有发布任何帖子,也没有关注其他的人,你的时间线页面应当什么都没有显示。"
|
||||||
|
step2_1: "在你想发布一些帖子之前,让我们先进行一下个人资料设置。"
|
||||||
|
step2_2: "如果别人能够更加的了解你,关注你的概率也会得到提升。"
|
||||||
|
step3_1: "已经设置完个人资料了吗?"
|
||||||
|
step3_2: "那么接下来,试着写一些什么东西来发布吧。你可以通过点击屏幕上的铅笔图标来打开投稿页面。"
|
||||||
|
step3_3: "写完内容后,点击窗口右上方的按钮就可以投稿。"
|
||||||
|
step3_4: "不知道说些什么好吗?那就写下「Misskey我来啦!」这样的话吧。"
|
||||||
|
step4_1: "将你的话语发布出去了吗?"
|
||||||
|
step4_2: "太棒了!现在你可以在你的时间线中看到你刚刚发布的帖子了。"
|
||||||
|
step7_3: "接下来,享受Misskey带来的乐趣吧🚀"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "此设备已被注册"
|
alreadyRegistered: "此设备已被注册"
|
||||||
registerDevice: "注册设备"
|
registerDevice: "注册设备"
|
||||||
@ -392,6 +480,9 @@ _permissions:
|
|||||||
"write:user-groups": "操作用户组"
|
"write:user-groups": "操作用户组"
|
||||||
_auth:
|
_auth:
|
||||||
permissionAsk: "这个应用程序需要以下权限"
|
permissionAsk: "这个应用程序需要以下权限"
|
||||||
|
_antennaSources:
|
||||||
|
all: "所有帖子"
|
||||||
|
homeTimeline: "已关注用户的帖子"
|
||||||
_weekday:
|
_weekday:
|
||||||
sunday: "星期日"
|
sunday: "星期日"
|
||||||
monday: "星期一"
|
monday: "星期一"
|
||||||
@ -408,6 +499,8 @@ _widgets:
|
|||||||
trends: "趋势"
|
trends: "趋势"
|
||||||
clock: "时钟"
|
clock: "时钟"
|
||||||
rss: "RSS阅读器"
|
rss: "RSS阅读器"
|
||||||
|
activity: "活动"
|
||||||
|
photos: "照片"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "隐藏"
|
hide: "隐藏"
|
||||||
show: "查看更多"
|
show: "查看更多"
|
||||||
@ -439,13 +532,27 @@ _poll:
|
|||||||
_visibility:
|
_visibility:
|
||||||
public: "公开"
|
public: "公开"
|
||||||
home: "首页"
|
home: "首页"
|
||||||
|
homeDescription: "仅发送至首页的时间线"
|
||||||
followers: "关注者"
|
followers: "关注者"
|
||||||
|
followersDescription: "仅发送至关注者"
|
||||||
specified: "指定用户"
|
specified: "指定用户"
|
||||||
|
specifiedDescription: "仅发送至指定用户"
|
||||||
localOnly: "仅限本地"
|
localOnly: "仅限本地"
|
||||||
|
_postForm:
|
||||||
|
replyPlaceholder: "回复这个帖子..."
|
||||||
|
quotePlaceholder: "引用这个帖子..."
|
||||||
|
_placeholders:
|
||||||
|
a: "现在如何?"
|
||||||
|
b: "发生了什么?"
|
||||||
|
c: "你有什么想法?"
|
||||||
|
d: "你想要发布些什么吗?"
|
||||||
|
e: "请写下来吧"
|
||||||
|
f: "等待您的发布..."
|
||||||
_profile:
|
_profile:
|
||||||
name: "名称"
|
name: "名称"
|
||||||
username: "用户名"
|
username: "用户名"
|
||||||
description: "个人简介"
|
description: "个人简介"
|
||||||
|
youCanIncludeHashtags: "您可以包含一个哈希标签。"
|
||||||
metadata: "额外信息"
|
metadata: "额外信息"
|
||||||
metadataLabel: "标签"
|
metadataLabel: "标签"
|
||||||
metadataContent: "内容"
|
metadataContent: "内容"
|
||||||
@ -464,9 +571,11 @@ _charts:
|
|||||||
notesIncDec: "帖子:增加/减少"
|
notesIncDec: "帖子:增加/减少"
|
||||||
notesTotal: "帖子总数"
|
notesTotal: "帖子总数"
|
||||||
_instanceCharts:
|
_instanceCharts:
|
||||||
|
requests: "请求"
|
||||||
users: "用户数量:增加/减少"
|
users: "用户数量:增加/减少"
|
||||||
usersTotal: "用户总数"
|
usersTotal: "用户总数"
|
||||||
notes: "帖子:增加/减少"
|
notes: "帖子:增加/减少"
|
||||||
|
notesTotal: "帖子:总数"
|
||||||
ff: "关注/被关注:数量变化"
|
ff: "关注/被关注:数量变化"
|
||||||
ffTotal: "关注/被关注:总数"
|
ffTotal: "关注/被关注:总数"
|
||||||
cacheSize: "缓存大小:增加/减少"
|
cacheSize: "缓存大小:增加/减少"
|
||||||
|
28
migration/1581979837262-promo.ts
Normal file
28
migration/1581979837262-promo.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class promo1581979837262 implements MigrationInterface {
|
||||||
|
name = 'promo1581979837262'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`CREATE TABLE "promo_note" ("noteId" character varying(32) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, CONSTRAINT "REL_e263909ca4fe5d57f8d4230dd5" UNIQUE ("noteId"), CONSTRAINT "PK_e263909ca4fe5d57f8d4230dd5c" PRIMARY KEY ("noteId"))`, undefined);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_83f0862e9bae44af52ced7099e" ON "promo_note" ("userId") `, undefined);
|
||||||
|
await queryRunner.query(`CREATE TABLE "promo_read" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, CONSTRAINT "PK_61917c1541002422b703318b7c9" PRIMARY KEY ("id"))`, undefined);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_9657d55550c3d37bfafaf7d4b0" ON "promo_read" ("userId") `, undefined);
|
||||||
|
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2882b8a1a07c7d281a98b6db16" ON "promo_read" ("userId", "noteId") `, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "promo_read" ADD CONSTRAINT "FK_9657d55550c3d37bfafaf7d4b05" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "promo_read" ADD CONSTRAINT "FK_a46a1a603ecee695d7db26da5f4" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "promo_read" DROP CONSTRAINT "FK_a46a1a603ecee695d7db26da5f4"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "promo_read" DROP CONSTRAINT "FK_9657d55550c3d37bfafaf7d4b05"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`, undefined);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_2882b8a1a07c7d281a98b6db16"`, undefined);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_9657d55550c3d37bfafaf7d4b0"`, undefined);
|
||||||
|
await queryRunner.query(`DROP TABLE "promo_read"`, undefined);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_83f0862e9bae44af52ced7099e"`, undefined);
|
||||||
|
await queryRunner.query(`DROP TABLE "promo_note"`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
migration/1582019042083-featured-injecttion.ts
Normal file
14
migration/1582019042083-featured-injecttion.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class featuredInjecttion1582019042083 implements MigrationInterface {
|
||||||
|
name = 'featuredInjecttion1582019042083'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ADD "injectFeaturedNote" boolean NOT NULL DEFAULT true`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "injectFeaturedNote"`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
migration/1582210532752-antenna-exclude.ts
Normal file
14
migration/1582210532752-antenna-exclude.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class antennaExclude1582210532752 implements MigrationInterface {
|
||||||
|
name = 'antennaExclude1582210532752'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "antenna" ADD "excludeKeywords" jsonb NOT NULL DEFAULT '[]'`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "excludeKeywords"`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||||
"version": "12.10.0",
|
"version": "12.21.0",
|
||||||
"codename": "indigo",
|
"codename": "indigo",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -250,7 +250,6 @@
|
|||||||
"vue-meta": "2.3.2",
|
"vue-meta": "2.3.2",
|
||||||
"vue-prism-component": "1.1.1",
|
"vue-prism-component": "1.1.1",
|
||||||
"vue-router": "3.1.5",
|
"vue-router": "3.1.5",
|
||||||
"vue-sequential-entrance": "1.1.3",
|
|
||||||
"vue-style-loader": "4.1.2",
|
"vue-style-loader": "4.1.2",
|
||||||
"vue-svg-inline-loader": "1.4.5",
|
"vue-svg-inline-loader": "1.4.5",
|
||||||
"vue-template-compiler": "2.6.11",
|
"vue-template-compiler": "2.6.11",
|
||||||
|
@ -44,27 +44,33 @@
|
|||||||
<mk-avatar :user="$store.state.i" class="avatar"/><mk-acct class="text" :user="$store.state.i"/>
|
<mk-avatar :user="$store.state.i" class="avatar"/><mk-acct class="text" :user="$store.state.i"/>
|
||||||
</button>
|
</button>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<router-link class="item index" active-class="active" to="/" exact v-if="$store.getters.isSignedIn">
|
<button class="item _button index active" @click="top()" v-if="$route.name === 'index'">
|
||||||
<fa :icon="faHome" fixed-width/><span class="text">{{ $t('timeline') }}</span>
|
<fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
|
||||||
</router-link>
|
|
||||||
<router-link class="item index" active-class="active" to="/" exact v-else>
|
|
||||||
<fa :icon="faHome" fixed-width/><span class="text">{{ $t('home') }}</span>
|
|
||||||
</router-link>
|
|
||||||
<button class="item _button notifications" @click="notificationsOpen = !notificationsOpen" ref="notificationButton" v-if="$store.getters.isSignedIn">
|
|
||||||
<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
|
|
||||||
<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
|
|
||||||
</button>
|
</button>
|
||||||
<router-link class="item" active-class="active" to="/my/messaging" v-if="$store.getters.isSignedIn">
|
<router-link class="item index" active-class="active" to="/" exact v-else>
|
||||||
<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
|
<fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
|
||||||
<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
|
|
||||||
</router-link>
|
|
||||||
<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.getters.isSignedIn && $store.state.i.isLocked">
|
|
||||||
<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
|
|
||||||
<i v-if="$store.state.i.pendingReceivedFollowRequestsCount"><fa :icon="faCircle"/></i>
|
|
||||||
</router-link>
|
|
||||||
<router-link class="item" active-class="active" to="/my/drive" v-if="$store.getters.isSignedIn">
|
|
||||||
<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
|
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<template v-if="$store.getters.isSignedIn">
|
||||||
|
<button class="item _button notifications" @click="notificationsOpen = !notificationsOpen" ref="notificationButton" v-if="$store.state.device.useNotificationsPopup">
|
||||||
|
<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
|
||||||
|
<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
|
||||||
|
</button>
|
||||||
|
<router-link class="item notifications" active-class="active" to="/my/notifications" ref="notificationButton" v-else>
|
||||||
|
<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
|
||||||
|
<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
|
||||||
|
</router-link>
|
||||||
|
<router-link class="item" active-class="active" to="/my/messaging">
|
||||||
|
<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
|
||||||
|
<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
|
||||||
|
</router-link>
|
||||||
|
<router-link class="item" active-class="active" to="/my/drive">
|
||||||
|
<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.state.i.isLocked">
|
||||||
|
<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
|
||||||
|
<i v-if="$store.state.i.hasPendingReceivedFollowRequest"><fa :icon="faCircle"/></i>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<router-link class="item" active-class="active" to="/featured">
|
<router-link class="item" active-class="active" to="/featured">
|
||||||
<fa :icon="faFireAlt" fixed-width/><span class="text">{{ $t('featured') }}</span>
|
<fa :icon="faFireAlt" fixed-width/><span class="text">{{ $t('featured') }}</span>
|
||||||
@ -87,11 +93,14 @@
|
|||||||
<fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span>
|
<fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span>
|
||||||
<i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadMentions || $store.state.i.hasUnreadSpecifiedNotes)"><fa :icon="faCircle"/></i>
|
<i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadMentions || $store.state.i.hasUnreadSpecifiedNotes)"><fa :icon="faCircle"/></i>
|
||||||
</button>
|
</button>
|
||||||
|
<router-link class="item" active-class="active" to="/preferences">
|
||||||
|
<fa :icon="faCog" fixed-width/><span class="text">{{ $t('settings') }}</span>
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<div class="contents" ref="contents">
|
<div class="contents" ref="contents" :class="{ wallpaper }">
|
||||||
<main ref="main">
|
<main ref="main">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<transition :name="$store.state.device.animation ? 'page' : ''" mode="out-in" @enter="onTransition">
|
<transition :name="$store.state.device.animation ? 'page' : ''" mode="out-in" @enter="onTransition">
|
||||||
@ -137,9 +146,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button v-if="$store.getters.isSignedIn" class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.state.i.hasUnreadSpecifiedNotes || $store.state.i.pendingReceivedFollowRequestsCount || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement"><fa :icon="faCircle"/></i></button>
|
<button class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadSpecifiedNotes || $store.state.i.hasPendingReceivedFollowRequest || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement)"><fa :icon="faCircle"/></i></button>
|
||||||
<button v-if="$store.getters.isSignedIn" class="button home _button" :disabled="$route.path === '/'" @click="$router.push('/')"><fa :icon="faHome"/></button>
|
<button v-if="$route.name === 'index'" class="button home _button" @click="top()"><fa :icon="faHome"/></button>
|
||||||
<button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton2"><fa :icon="notificationsOpen ? faTimes : faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
|
<button v-else class="button home _button" @click="$router.push('/')"><fa :icon="faHome"/></button>
|
||||||
|
<button v-if="$store.getters.isSignedIn && $store.state.device.useNotificationsPopup" class="button notifications _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton2"><fa :icon="notificationsOpen ? faTimes : faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
|
||||||
|
<button v-if="$store.getters.isSignedIn && !$store.state.device.useNotificationsPopup" class="button notifications _button" @click="$router.push('/my/notifications')" ref="notificationButton2"><fa :icon="faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
|
||||||
<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
|
<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -148,6 +159,8 @@
|
|||||||
<transition name="zoom-in-top">
|
<transition name="zoom-in-top">
|
||||||
<x-notifications v-if="notificationsOpen" class="notifications" ref="notifications"/>
|
<x-notifications v-if="notificationsOpen" class="notifications" ref="notifications"/>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
|
<stream-indicator v-if="$store.getters.isSignedIn"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -163,6 +176,8 @@ import { search } from './scripts/search';
|
|||||||
import contains from './scripts/contains';
|
import contains from './scripts/contains';
|
||||||
import MkToast from './components/toast.vue';
|
import MkToast from './components/toast.vue';
|
||||||
|
|
||||||
|
const DESKTOP_THRESHOLD = 1100;
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
|
|
||||||
@ -186,9 +201,9 @@ export default Vue.extend({
|
|||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
searchWait: false,
|
searchWait: false,
|
||||||
widgetsEditMode: false,
|
widgetsEditMode: false,
|
||||||
isDesktop: window.innerWidth >= 1100,
|
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
|
||||||
canBack: false,
|
canBack: false,
|
||||||
disconnectedDialog: null as Promise<void> | null,
|
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
|
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
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -226,6 +241,10 @@ export default Vue.extend({
|
|||||||
el.removeEventListener('mousedown', this.onMousedown);
|
el.removeEventListener('mousedown', this.onMousedown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isDesktop() {
|
||||||
|
if (this.isDesktop) this.adjustWidgetsWidth();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -247,44 +266,10 @@ export default Vue.extend({
|
|||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$root.stream.on('_disconnected_', () => {
|
|
||||||
if (this.disconnectedDialog) return;
|
|
||||||
if (this.$store.state.device.autoReload) {
|
|
||||||
location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.$root.stream.state !== 'reconnecting') return;
|
|
||||||
|
|
||||||
this.disconnectedDialog = this.$root.dialog({
|
|
||||||
type: 'warning',
|
|
||||||
showCancelButton: true,
|
|
||||||
title: this.$t('disconnectedFromServer'),
|
|
||||||
text: this.$t('reloadConfirm'),
|
|
||||||
}).then(({ canceled }) => {
|
|
||||||
if (!canceled) {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
this.disconnectedDialog = null;
|
|
||||||
});
|
|
||||||
}, 150)
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
// https://stackoverflow.com/questions/33891709/when-flexbox-items-wrap-in-column-mode-container-does-not-grow-its-width
|
if (this.isDesktop) this.adjustWidgetsWidth();
|
||||||
if (this.isDesktop) {
|
|
||||||
const adjustWidgetsWidth = () => {
|
|
||||||
const lastChild = this.$refs.widgets.children[this.$refs.widgets.children.length - 1];
|
|
||||||
if (lastChild == null) return;
|
|
||||||
|
|
||||||
const width = lastChild.offsetLeft + 300 + 16;
|
|
||||||
this.$refs.widgets.style.width = width + 'px';
|
|
||||||
};
|
|
||||||
setInterval(adjustWidgetsWidth, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const adjustTitlePosition = () => {
|
const adjustTitlePosition = () => {
|
||||||
this.$refs.title.style.left = (this.$refs.main.getBoundingClientRect().left - this.$refs.nav.offsetWidth) + 'px';
|
this.$refs.title.style.left = (this.$refs.main.getBoundingClientRect().left - this.$refs.nav.offsetWidth) + 'px';
|
||||||
@ -298,10 +283,33 @@ export default Vue.extend({
|
|||||||
|
|
||||||
ro.observe(this.$refs.contents);
|
ro.observe(this.$refs.contents);
|
||||||
|
|
||||||
window.addEventListener('resize', adjustTitlePosition);
|
window.addEventListener('resize', adjustTitlePosition, { passive: true });
|
||||||
|
|
||||||
|
if (!this.isDesktop) {
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true;
|
||||||
|
}, { passive: true });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
adjustWidgetsWidth() {
|
||||||
|
// https://stackoverflow.com/questions/33891709/when-flexbox-items-wrap-in-column-mode-container-does-not-grow-its-width
|
||||||
|
const adjust = () => {
|
||||||
|
const lastChild = this.$refs.widgets.children[this.$refs.widgets.children.length - 1];
|
||||||
|
if (lastChild == null) return;
|
||||||
|
|
||||||
|
const width = lastChild.offsetLeft + 300 + 16;
|
||||||
|
this.$refs.widgets.style.width = width + 'px';
|
||||||
|
};
|
||||||
|
setInterval(adjust, 1000);
|
||||||
|
setTimeout(adjust, 100);
|
||||||
|
},
|
||||||
|
|
||||||
|
top() {
|
||||||
|
window.scroll({ top: 0, behavior: 'smooth' });
|
||||||
|
},
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
this.$router.push('/docs/keyboard-shortcut');
|
this.$router.push('/docs/keyboard-shortcut');
|
||||||
},
|
},
|
||||||
@ -361,21 +369,18 @@ export default Vue.extend({
|
|||||||
avatar: this.$store.state.i,
|
avatar: this.$store.state.i,
|
||||||
}, {
|
}, {
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: this.$t('settings'),
|
text: this.$t('accountSettings'),
|
||||||
to: '/my/settings',
|
to: '/my/settings',
|
||||||
icon: faCog,
|
icon: faCog,
|
||||||
}, null, ...accountItems, {
|
}, null, ...accountItems, {
|
||||||
type: 'item',
|
|
||||||
icon: faPlus,
|
icon: faPlus,
|
||||||
text: this.$t('addAcount'),
|
text: this.$t('addAcount'),
|
||||||
action: () => {
|
action: () => {
|
||||||
this.$root.menu({
|
this.$root.menu({
|
||||||
items: [{
|
items: [{
|
||||||
type: 'item',
|
|
||||||
text: this.$t('existingAcount'),
|
text: this.$t('existingAcount'),
|
||||||
action: () => { this.addAcount(); },
|
action: () => { this.addAcount(); },
|
||||||
}, {
|
}, {
|
||||||
type: 'item',
|
|
||||||
text: this.$t('createAccount'),
|
text: this.$t('createAccount'),
|
||||||
action: () => { this.createAccount(); },
|
action: () => { this.createAccount(); },
|
||||||
}],
|
}],
|
||||||
@ -397,9 +402,14 @@ export default Vue.extend({
|
|||||||
this.$root.menu({
|
this.$root.menu({
|
||||||
items: [{
|
items: [{
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: this.$t('statistics'),
|
text: this.$t('dashboard'),
|
||||||
to: '/instance/stats',
|
to: '/instance',
|
||||||
icon: faChartBar,
|
icon: faTachometerAlt,
|
||||||
|
}, null, {
|
||||||
|
type: 'link',
|
||||||
|
text: this.$t('settings'),
|
||||||
|
to: '/instance/settings',
|
||||||
|
icon: faCog,
|
||||||
}, {
|
}, {
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: this.$t('customEmojis'),
|
text: this.$t('customEmojis'),
|
||||||
@ -415,11 +425,6 @@ export default Vue.extend({
|
|||||||
text: this.$t('files'),
|
text: this.$t('files'),
|
||||||
to: '/instance/files',
|
to: '/instance/files',
|
||||||
icon: faCloud,
|
icon: faCloud,
|
||||||
}, {
|
|
||||||
type: 'link',
|
|
||||||
text: this.$t('monitor'),
|
|
||||||
to: '/instance/monitor',
|
|
||||||
icon: faTachometerAlt,
|
|
||||||
}, {
|
}, {
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: this.$t('jobQueue'),
|
text: this.$t('jobQueue'),
|
||||||
@ -435,11 +440,6 @@ export default Vue.extend({
|
|||||||
text: this.$t('announcements'),
|
text: this.$t('announcements'),
|
||||||
to: '/instance/announcements',
|
to: '/instance/announcements',
|
||||||
icon: faBroadcastTower,
|
icon: faBroadcastTower,
|
||||||
}, null, {
|
|
||||||
type: 'link',
|
|
||||||
text: this.$t('general'),
|
|
||||||
to: '/instance',
|
|
||||||
icon: faCog,
|
|
||||||
}],
|
}],
|
||||||
align: 'left',
|
align: 'left',
|
||||||
fixed: true,
|
fixed: true,
|
||||||
@ -555,13 +555,17 @@ export default Vue.extend({
|
|||||||
|
|
||||||
onNotification(notification) {
|
onNotification(notification) {
|
||||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||||
this.$root.stream.send('readNotification', {
|
if (true) {
|
||||||
id: notification.id
|
this.$root.stream.send('readNotification', {
|
||||||
});
|
id: notification.id
|
||||||
|
});
|
||||||
|
|
||||||
this.$root.new(MkToast, {
|
this.$root.new(MkToast, {
|
||||||
notification
|
notification
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$root.sound('notification');
|
||||||
},
|
},
|
||||||
|
|
||||||
onMousedown(e) {
|
onMousedown(e) {
|
||||||
@ -590,7 +594,9 @@ export default Vue.extend({
|
|||||||
'calendar',
|
'calendar',
|
||||||
'rss',
|
'rss',
|
||||||
'trends',
|
'trends',
|
||||||
'clock'
|
'clock',
|
||||||
|
'activity',
|
||||||
|
'photos',
|
||||||
];
|
];
|
||||||
|
|
||||||
this.$root.menu({
|
this.$root.menu({
|
||||||
@ -620,30 +626,6 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.header-enter-active, .header-leave-active {
|
|
||||||
transition: opacity 0.5s, transform 0.5s !important;
|
|
||||||
}
|
|
||||||
.header-enter {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
.header-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-enter-active, .page-leave-active {
|
|
||||||
transition: opacity 0.5s, transform 0.5s !important;
|
|
||||||
}
|
|
||||||
.page-enter {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-32px);
|
|
||||||
}
|
|
||||||
.page-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(32px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-enter-active,
|
.nav-enter-active,
|
||||||
.nav-leave-active {
|
.nav-leave-active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -870,6 +852,7 @@ export default Vue.extend({
|
|||||||
width: $nav-width;
|
width: $nav-width;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
|
padding-bottom: calc(3.7rem + 24px);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: var(--navBg);
|
background: var(--navBg);
|
||||||
@ -883,6 +866,7 @@ export default Vue.extend({
|
|||||||
@media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) {
|
@media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) {
|
||||||
width: $nav-icon-only-width;
|
width: $nav-icon-only-width;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
|
padding-bottom: calc(3.7rem + 24px);
|
||||||
|
|
||||||
> .divider {
|
> .divider {
|
||||||
margin: 8px auto;
|
margin: 8px auto;
|
||||||
@ -930,12 +914,24 @@ export default Vue.extend({
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
color: var(--navHoverFg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: var(--navActive);
|
color: var(--navActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: inherit;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
background: var(--navBg);
|
||||||
|
border-top: solid 1px var(--divider);
|
||||||
|
border-right: solid 1px var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) {
|
@media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -972,6 +968,10 @@ export default Vue.extend({
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
|
&.wallpaper {
|
||||||
|
background: var(--wallpaperOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
> main {
|
> main {
|
||||||
width: $main-width;
|
width: $main-width;
|
||||||
min-width: $main-width;
|
min-width: $main-width;
|
||||||
@ -1168,7 +1168,7 @@ export default Vue.extend({
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
color: var(--accent);
|
color: var(--indicator);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
animation: blink 1s infinite;
|
animation: blink 1s infinite;
|
||||||
}
|
}
|
||||||
@ -1182,15 +1182,17 @@ export default Vue.extend({
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 8px 8px 0 8px;
|
||||||
z-index: 10001;
|
z-index: 10001;
|
||||||
width: 350px;
|
width: 350px;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
|
box-sizing: border-box;
|
||||||
background: var(--vocsgcxy);
|
background: var(--vocsgcxy);
|
||||||
-webkit-backdrop-filter: blur(12px);
|
-webkit-backdrop-filter: blur(12px);
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15);
|
box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15);
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
|
BIN
src/client/assets/fedi.jpg
Normal file
BIN
src/client/assets/fedi.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
24
src/client/assets/redoc.html
Normal file
24
src/client/assets/redoc.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Misskey API</title>
|
||||||
|
<!-- needed for adaptive design -->
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ReDoc doesn't change outer page styles
|
||||||
|
-->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<redoc spec-url='/api.json'></redoc>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
src/client/assets/sounds/aisha/1.mp3
Normal file
BIN
src/client/assets/sounds/aisha/1.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/aisha/2.mp3
Normal file
BIN
src/client/assets/sounds/aisha/2.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/aisha/3.mp3
Normal file
BIN
src/client/assets/sounds/aisha/3.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/noizenecio/kick_gaba.mp3
Normal file
BIN
src/client/assets/sounds/noizenecio/kick_gaba.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/down.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/down.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/poi1.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/poi1.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/poi2.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/poi2.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/pope1.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/pope1.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/pope2.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/pope2.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/popo.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/popo.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/triple.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/triple.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/up.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/up.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/waon.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/waon.mp3
Normal file
Binary file not shown.
@ -55,13 +55,15 @@ export default Vue.extend({
|
|||||||
handsTailLength: 0.7,
|
handsTailLength: 0.7,
|
||||||
hHandLengthRatio: 0.75,
|
hHandLengthRatio: 0.75,
|
||||||
mHandLengthRatio: 1,
|
mHandLengthRatio: 1,
|
||||||
sHandLengthRatio: 1
|
sHandLengthRatio: 1,
|
||||||
|
|
||||||
|
computedStyle: getComputedStyle(document.documentElement)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
dark(): boolean {
|
dark(): boolean {
|
||||||
return tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--bg')).isDark();
|
return tinycolor(this.computedStyle.getPropertyValue('--bg')).isDark();
|
||||||
},
|
},
|
||||||
|
|
||||||
majorGraduationColor(): string {
|
majorGraduationColor(): string {
|
||||||
@ -75,10 +77,10 @@ export default Vue.extend({
|
|||||||
return this.dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
|
return this.dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
|
||||||
},
|
},
|
||||||
mHandColor(): string {
|
mHandColor(): string {
|
||||||
return tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--fg')).toHexString();
|
return tinycolor(this.computedStyle.getPropertyValue('--fg')).toHexString();
|
||||||
},
|
},
|
||||||
hHandColor(): string {
|
hHandColor(): string {
|
||||||
return tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent')).toHexString();
|
return tinycolor(this.computedStyle.getPropertyValue('--accent')).toHexString();
|
||||||
},
|
},
|
||||||
|
|
||||||
ms(): number {
|
ms(): number {
|
||||||
@ -123,6 +125,16 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
update();
|
update();
|
||||||
|
|
||||||
|
this.$store.subscribe((mutation, state) => {
|
||||||
|
if (mutation.type !== 'device/set') return;
|
||||||
|
|
||||||
|
if (mutation?.payload?.key !== 'theme') return;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.computedStyle = getComputedStyle(document.documentElement);
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="swhvrteh" @contextmenu.prevent="() => {}">
|
<div class="swhvrteh" @contextmenu.prevent="() => {}">
|
||||||
<ol class="users" ref="suggests" v-if="type === 'user'">
|
<ol class="users" ref="suggests" v-if="type === 'user'">
|
||||||
<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1" class="user">
|
<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1" class="user">
|
||||||
<img class="avatar" :src="user.avatarUrl" alt=""/>
|
<img class="avatar" :src="user.avatarUrl"/>
|
||||||
<span class="name">
|
<span class="name">
|
||||||
<mk-user-name :user="user" :key="user.id"/>
|
<mk-user-name :user="user" :key="user.id"/>
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<sequential-entrance class="sqadhkmv" ref="list" :direction="direction" :reversed="reversed">
|
<component :is="$store.state.device.animation ? 'transition-group' : 'div'" class="sqadhkmv" name="list" tag="div" appear :data-direction="direction" :data-reversed="reversed ? 'true' : 'false'">
|
||||||
<template v-for="(item, i) in items">
|
<template v-for="(item, i) in items">
|
||||||
<slot :item="item" :i="i"></slot>
|
<slot :item="item" :i="i"></slot>
|
||||||
<div class="separator" :key="item.id + '_date'" v-if="i != items.length - 1 && new Date(item.createdAt).getDate() != new Date(items[i + 1].createdAt).getDate()">
|
<div class="separator" :key="item.id + '_date'" v-if="showDate(i, item)">
|
||||||
<p class="date">
|
<p class="date">
|
||||||
<span><fa class="icon" :icon="faAngleUp"/>{{ getDateText(item.createdAt) }}</span>
|
<span><fa class="icon" :icon="faAngleUp"/>{{ getDateText(item.createdAt) }}</span>
|
||||||
<span>{{ getDateText(items[i + 1].createdAt) }}<fa class="icon" :icon="faAngleDown"/></span>
|
<span>{{ getDateText(items[i + 1].createdAt) }}<fa class="icon" :icon="faAngleDown"/></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</sequential-entrance>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@ -27,7 +27,8 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
direction: {
|
direction: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false
|
required: false,
|
||||||
|
default: 'down'
|
||||||
},
|
},
|
||||||
reversed: {
|
reversed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -52,13 +53,49 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showDate(i, item) {
|
||||||
|
return (
|
||||||
|
i != this.items.length - 1 &&
|
||||||
|
new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() &&
|
||||||
|
!item._prId_ &&
|
||||||
|
!this.items[i + 1]._prId_ &&
|
||||||
|
!item._featuredId_ &&
|
||||||
|
!this.items[i + 1]._featuredId_);
|
||||||
|
},
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.$refs.list.focus();
|
this.$slots.default[0].elm.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.sqadhkmv {
|
||||||
|
> .list-move {
|
||||||
|
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .list-enter-active {
|
||||||
|
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-direction="up"] {
|
||||||
|
> .list-enter {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(64px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-direction="down"] {
|
||||||
|
> .list-enter {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-64px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.sqadhkmv {
|
.sqadhkmv {
|
||||||
> .separator {
|
> .separator {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-dialog" :class="{ iconOnly }">
|
<div class="mk-dialog" :class="{ iconOnly }">
|
||||||
<transition name="bg-fade" appear>
|
<transition :name="$store.state.device.animation ? 'bg-fade' : ''" appear>
|
||||||
<div class="bg" ref="bg" @click="onBgClick" v-if="show"></div>
|
<div class="bg" ref="bg" @click="onBgClick" v-if="show"></div>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="dialog" appear @after-leave="() => { destroyDom(); }">
|
<transition :name="$store.state.device.animation ? 'dialog' : ''" appear @after-leave="() => { destroyDom(); }">
|
||||||
<div class="main" ref="main" v-if="show">
|
<div class="main" ref="main" v-if="show">
|
||||||
<template v-if="type == 'signin'">
|
<template v-if="type == 'signin'">
|
||||||
<mk-signin/>
|
<mk-signin/>
|
||||||
@ -55,6 +55,7 @@ import { faTimesCircle, faQuestionCircle } from '@fortawesome/free-regular-svg-i
|
|||||||
import MkButton from './ui/button.vue';
|
import MkButton from './ui/button.vue';
|
||||||
import MkInput from './ui/input.vue';
|
import MkInput from './ui/input.vue';
|
||||||
import MkSelect from './ui/select.vue';
|
import MkSelect from './ui/select.vue';
|
||||||
|
import MkSignin from './signin.vue';
|
||||||
import parseAcct from '../../misc/acct/parse';
|
import parseAcct from '../../misc/acct/parse';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ export default Vue.extend({
|
|||||||
MkButton,
|
MkButton,
|
||||||
MkInput,
|
MkInput,
|
||||||
MkSelect,
|
MkSelect,
|
||||||
|
MkSignin,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
preload="metadata"
|
preload="metadata"
|
||||||
controls
|
controls
|
||||||
v-else-if="detail && is === 'video'"/>
|
v-else-if="detail && is === 'video'"/>
|
||||||
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded" :style="`object-fit: ${ fit }`" v-else-if="isThumbnailAvailable"/>
|
<img :src="file.thumbnailUrl" @load="onThumbnailLoaded" :style="`object-fit: ${ fit }`" v-else-if="isThumbnailAvailable"/>
|
||||||
<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/>
|
<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/>
|
||||||
<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/>
|
<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/>
|
||||||
|
|
||||||
|
@ -83,17 +83,14 @@ export default Vue.extend({
|
|||||||
} else {
|
} else {
|
||||||
this.$root.menu({
|
this.$root.menu({
|
||||||
items: [{
|
items: [{
|
||||||
type: 'item',
|
|
||||||
text: this.$t('rename'),
|
text: this.$t('rename'),
|
||||||
icon: faICursor,
|
icon: faICursor,
|
||||||
action: this.rename
|
action: this.rename
|
||||||
}, {
|
}, {
|
||||||
type: 'item',
|
|
||||||
text: this.file.isSensitive ? this.$t('unmarkAsSensitive') : this.$t('markAsSensitive'),
|
text: this.file.isSensitive ? this.$t('unmarkAsSensitive') : this.$t('markAsSensitive'),
|
||||||
icon: this.file.isSensitive ? faEye : faEyeSlash,
|
icon: this.file.isSensitive ? faEye : faEyeSlash,
|
||||||
action: this.toggleSensitive
|
action: this.toggleSensitive
|
||||||
}, null, {
|
}, null, {
|
||||||
type: 'item',
|
|
||||||
text: this.$t('copyUrl'),
|
text: this.$t('copyUrl'),
|
||||||
icon: faLink,
|
icon: faLink,
|
||||||
action: this.copyUrl
|
action: this.copyUrl
|
||||||
@ -105,22 +102,9 @@ export default Vue.extend({
|
|||||||
icon: faDownload,
|
icon: faDownload,
|
||||||
download: this.file.name
|
download: this.file.name
|
||||||
}, null, {
|
}, null, {
|
||||||
type: 'item',
|
|
||||||
text: this.$t('delete'),
|
text: this.$t('delete'),
|
||||||
icon: faTrashAlt,
|
icon: faTrashAlt,
|
||||||
action: this.deleteFile
|
action: this.deleteFile
|
||||||
}, null, {
|
|
||||||
type: 'nest',
|
|
||||||
text: this.$t('contextmenu.else-files'),
|
|
||||||
menu: [{
|
|
||||||
type: 'item',
|
|
||||||
text: this.$t('contextmenu.set-as-avatar'),
|
|
||||||
action: this.setAsAvatar
|
|
||||||
}, {
|
|
||||||
type: 'item',
|
|
||||||
text: this.$t('contextmenu.set-as-banner'),
|
|
||||||
action: this.setAsBanner
|
|
||||||
}]
|
|
||||||
}],
|
}],
|
||||||
source: ev.currentTarget || ev.target,
|
source: ev.currentTarget || ev.target,
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mjndxjcg _panel">
|
<div class="mjndxjcg _panel">
|
||||||
<img src="https://xn--931a.moe/assets/error.png" alt="" class="_ghost"/>
|
<img src="https://xn--931a.moe/assets/error.png" class="_ghost"/>
|
||||||
<p><fa :icon="faExclamationTriangle"/> {{ $t('error') }}</p>
|
<p><fa :icon="faExclamationTriangle"/> {{ $t('error') }}</p>
|
||||||
<mk-button @click="() => $emit('retry')" class="button">{{ $t('retry') }}</mk-button>
|
<mk-button @click="() => $emit('retry')" class="button">{{ $t('retry') }}</mk-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<span class="hhnn">{{ hh }}<span :style="{ visibility: now.getSeconds() % 2 == 0 ? 'visible' : 'hidden' }">:</span>{{ nn }}</span>
|
<span class="hhnn">{{ hh }}<span :style="{ visibility: now.getSeconds() % 2 == 0 ? 'visible' : 'hidden' }">:</span>{{ nn }}</span>
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
<div class="content _panel">
|
<div class="content _panel _ghost">
|
||||||
<mk-clock/>
|
<mk-clock/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -66,8 +66,10 @@ export default Vue.extend({
|
|||||||
|
|
||||||
> .header {
|
> .header {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
|
padding-top: 4px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
font-family: Lucida Console, Courier, monospace;
|
||||||
|
|
||||||
&:hover + .content {
|
&:hover + .content {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -90,7 +92,6 @@ export default Vue.extend({
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: auto;
|
top: auto;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 3;
|
|
||||||
margin: 16px 0 0 0;
|
margin: 16px 0 0 0;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
width: 230px;
|
width: 230px;
|
||||||
|
54
src/client/components/image-viewer.vue
Normal file
54
src/client/components/image-viewer.vue
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<x-modal ref="modal" @closed="() => { $emit('closed'); destroyDom(); }">
|
||||||
|
<img class="xubzgfga" ref="img" :src="image.url" :alt="image.name" :title="image.name" @click="close" tabindex="-1"/>
|
||||||
|
</x-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
import XModal from './modal.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n,
|
||||||
|
|
||||||
|
components: {
|
||||||
|
XModal,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
image: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.img.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$refs.modal.close();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.xubzgfga {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 2;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
margin: auto;
|
||||||
|
cursor: zoom-out;
|
||||||
|
image-orientation: from-image;
|
||||||
|
}
|
||||||
|
</style>
|
@ -11,6 +11,7 @@ import url from './url.vue';
|
|||||||
import loading from './loading.vue';
|
import loading from './loading.vue';
|
||||||
import SequentialEntrance from './sequential-entrance.vue';
|
import SequentialEntrance from './sequential-entrance.vue';
|
||||||
import error from './error.vue';
|
import error from './error.vue';
|
||||||
|
import streamIndicator from './stream-indicator.vue';
|
||||||
|
|
||||||
Vue.component('mfm', mfm);
|
Vue.component('mfm', mfm);
|
||||||
Vue.component('mk-acct', acct);
|
Vue.component('mk-acct', acct);
|
||||||
@ -23,3 +24,4 @@ Vue.component('mk-url', url);
|
|||||||
Vue.component('mk-loading', loading);
|
Vue.component('mk-loading', loading);
|
||||||
Vue.component('mk-error', error);
|
Vue.component('mk-error', error);
|
||||||
Vue.component('sequential-entrance', SequentialEntrance);
|
Vue.component('sequential-entrance', SequentialEntrance);
|
||||||
|
Vue.component('stream-indicator', streamIndicator);
|
||||||
|
@ -1,8 +1,91 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-instance-stats">
|
<div class="zbcjwnqg">
|
||||||
|
<div class="stats" v-if="info">
|
||||||
|
<div class="_panel">
|
||||||
|
<div>
|
||||||
|
<b><fa :icon="faUser"/>{{ $t('users') }}</b>
|
||||||
|
<small>{{ $t('local') }}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl class="total">
|
||||||
|
<dt>{{ $t('total') }}</dt>
|
||||||
|
<dd>{{ info.originalUsersCount | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="diff" :class="{ inc: usersLocalDoD > 0 }">
|
||||||
|
<dt>{{ $t('dayOverDayChanges') }}</dt>
|
||||||
|
<dd>{{ usersLocalDoD | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="diff" :class="{ inc: usersLocalWoW > 0 }">
|
||||||
|
<dt>{{ $t('weekOverWeekChanges') }}</dt>
|
||||||
|
<dd>{{ usersLocalWoW | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="_panel">
|
||||||
|
<div>
|
||||||
|
<b><fa :icon="faUser"/>{{ $t('users') }}</b>
|
||||||
|
<small>{{ $t('remote') }}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl class="total">
|
||||||
|
<dt>{{ $t('total') }}</dt>
|
||||||
|
<dd>{{ (info.usersCount - info.originalUsersCount) | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="diff" :class="{ inc: usersRemoteDoD > 0 }">
|
||||||
|
<dt>{{ $t('dayOverDayChanges') }}</dt>
|
||||||
|
<dd>{{ usersRemoteDoD | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="diff" :class="{ inc: usersRemoteWoW > 0 }">
|
||||||
|
<dt>{{ $t('weekOverWeekChanges') }}</dt>
|
||||||
|
<dd>{{ usersRemoteWoW | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="_panel">
|
||||||
|
<div>
|
||||||
|
<b><fa :icon="faPencilAlt"/>{{ $t('notes') }}</b>
|
||||||
|
<small>{{ $t('local') }}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl class="total">
|
||||||
|
<dt>{{ $t('total') }}</dt>
|
||||||
|
<dd>{{ info.originalNotesCount | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="diff" :class="{ inc: notesLocalDoD > 0 }">
|
||||||
|
<dt>{{ $t('dayOverDayChanges') }}</dt>
|
||||||
|
<dd>{{ notesLocalDoD | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="diff" :class="{ inc: notesLocalWoW > 0 }">
|
||||||
|
<dt>{{ $t('weekOverWeekChanges') }}</dt>
|
||||||
|
<dd>{{ notesLocalWoW | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="_panel">
|
||||||
|
<div>
|
||||||
|
<b><fa :icon="faPencilAlt"/>{{ $t('notes') }}</b>
|
||||||
|
<small>{{ $t('remote') }}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dl class="total">
|
||||||
|
<dt>{{ $t('total') }}</dt>
|
||||||
|
<dd>{{ (info.notesCount - info.originalNotesCount) | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="diff" :class="{ inc: notesRemoteDoD > 0 }">
|
||||||
|
<dt>{{ $t('dayOverDayChanges') }}</dt>
|
||||||
|
<dd>{{ notesRemoteDoD | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="diff" :class="{ inc: notesRemoteWoW > 0 }">
|
||||||
|
<dt>{{ $t('weekOverWeekChanges') }}</dt>
|
||||||
|
<dd>{{ notesRemoteWoW | number }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section class="_card">
|
<section class="_card">
|
||||||
<div class="_title"><fa :icon="faChartBar"/> {{ $t('statistics') }}</div>
|
<div class="_title"><fa :icon="faChartBar"/> {{ $t('statistics') }}</div>
|
||||||
<div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
|
<div class="_content" style="margin-top: -8px;">
|
||||||
<div class="selects" style="display: flex;">
|
<div class="selects" style="display: flex;">
|
||||||
<mk-select v-model="chartSrc" style="margin: 0; flex: 1;">
|
<mk-select v-model="chartSrc" style="margin: 0; flex: 1;">
|
||||||
<optgroup :label="$t('federation')">
|
<optgroup :label="$t('federation')">
|
||||||
@ -40,10 +123,10 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { faChartBar } from '@fortawesome/free-solid-svg-icons';
|
import { faChartBar, faUser, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import Chart from 'chart.js';
|
import Chart from 'chart.js';
|
||||||
import i18n from '../../i18n';
|
import i18n from '../i18n';
|
||||||
import MkSelect from '../../components/ui/select.vue';
|
import MkSelect from './ui/select.vue';
|
||||||
|
|
||||||
const chartLimit = 90;
|
const chartLimit = 90;
|
||||||
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
|
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
|
||||||
@ -59,24 +142,27 @@ const alpha = (hex, a) => {
|
|||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
|
|
||||||
metaInfo() {
|
|
||||||
return {
|
|
||||||
title: `${this.$t('statistics')} | ${this.$t('instance')}`
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
MkSelect
|
MkSelect
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
info: null,
|
||||||
|
notesLocalWoW: 0,
|
||||||
|
notesLocalDoD: 0,
|
||||||
|
notesRemoteWoW: 0,
|
||||||
|
notesRemoteDoD: 0,
|
||||||
|
usersLocalWoW: 0,
|
||||||
|
usersLocalDoD: 0,
|
||||||
|
usersRemoteWoW: 0,
|
||||||
|
usersRemoteDoD: 0,
|
||||||
now: null,
|
now: null,
|
||||||
chart: null,
|
chart: null,
|
||||||
chartInstance: null,
|
chartInstance: null,
|
||||||
chartSrc: 'notes',
|
chartSrc: 'notes',
|
||||||
chartSpan: 'hour',
|
chartSpan: 'hour',
|
||||||
faChartBar
|
faChartBar, faUser, faPencilAlt
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -121,6 +207,8 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
|
this.info = await this.$root.api('stats');
|
||||||
|
|
||||||
this.now = new Date();
|
this.now = new Date();
|
||||||
|
|
||||||
const [perHour, perDay] = await Promise.all([Promise.all([
|
const [perHour, perDay] = await Promise.all([Promise.all([
|
||||||
@ -154,6 +242,15 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.notesLocalWoW = this.info.originalNotesCount - chart.perDay.notes.local.total[7];
|
||||||
|
this.notesLocalDoD = this.info.originalNotesCount - chart.perDay.notes.local.total[1];
|
||||||
|
this.notesRemoteWoW = (this.info.notesCount - this.info.originalNotesCount) - chart.perDay.notes.remote.total[7];
|
||||||
|
this.notesRemoteDoD = (this.info.notesCount - this.info.originalNotesCount) - chart.perDay.notes.remote.total[1];
|
||||||
|
this.usersLocalWoW = this.info.originalUsersCount - chart.perDay.users.local.total[7];
|
||||||
|
this.usersLocalDoD = this.info.originalUsersCount - chart.perDay.users.local.total[1];
|
||||||
|
this.usersRemoteWoW = (this.info.usersCount - this.info.originalUsersCount) - chart.perDay.users.remote.total[7];
|
||||||
|
this.usersRemoteDoD = (this.info.usersCount - this.info.originalUsersCount) - chart.perDay.users.remote.total[1];
|
||||||
|
|
||||||
this.chart = chart;
|
this.chart = chart;
|
||||||
|
|
||||||
this.renderChart();
|
this.renderChart();
|
||||||
@ -489,3 +586,80 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.zbcjwnqg {
|
||||||
|
> .stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: calc(0px - var(--margin) / 2);
|
||||||
|
margin-bottom: calc(var(--margin) / 2);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 213px;
|
||||||
|
margin: calc(var(--margin) / 2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px 20px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
> b {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
> [data-icon] {
|
||||||
|
width: 16px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> small {
|
||||||
|
margin-left: 16px + 8px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
> dl {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5em;
|
||||||
|
|
||||||
|
> dt,
|
||||||
|
> dd {
|
||||||
|
width: 50%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> dt {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.total {
|
||||||
|
> dt,
|
||||||
|
> dd {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.diff.inc {
|
||||||
|
> dd {
|
||||||
|
color: #82c11c;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "+";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -20,6 +20,7 @@ import Vue from 'vue';
|
|||||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
import { getStaticImageUrl } from '../scripts/get-static-image-url';
|
import { getStaticImageUrl } from '../scripts/get-static-image-url';
|
||||||
|
import ImageViewer from './image-viewer.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
@ -60,7 +61,16 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClick() {
|
onClick() {
|
||||||
window.open(this.image.url, '_blank');
|
if (this.$store.state.device.imageNewTab) {
|
||||||
|
window.open(this.image.url, '_blank');
|
||||||
|
} else {
|
||||||
|
const viewer = this.$root.new(ImageViewer, {
|
||||||
|
image: this.image
|
||||||
|
});
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
viewer.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
<template v-for="media in mediaList.filter(media => !previewable(media))">
|
<template v-for="media in mediaList.filter(media => !previewable(media))">
|
||||||
<x-banner :media="media" :key="media.id"/>
|
<x-banner :media="media" :key="media.id"/>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container">
|
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container" ref="gridOuter">
|
||||||
<div :data-count="mediaList.filter(media => previewable(media)).length" ref="grid">
|
<div :data-count="mediaList.filter(media => previewable(media)).length" :style="gridInnerStyle">
|
||||||
<template v-for="media in mediaList">
|
<template v-for="media in mediaList">
|
||||||
<x-video :video="media" :key="media.id" v-if="media.type.startsWith('video')"/>
|
<x-video :video="media" :key="media.id" v-if="media.type.startsWith('video')"/>
|
||||||
<x-image :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/>
|
<x-image :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/>
|
||||||
@ -32,19 +32,56 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
raw: {
|
raw: {
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
// specify the parent element
|
||||||
|
parentElement: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
gridInnerStyle: {},
|
||||||
|
sizeWaiting: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
//#region for Safari bug
|
this.size();
|
||||||
if (this.$refs.grid) {
|
window.addEventListener('resize', this.size);
|
||||||
this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px`
|
},
|
||||||
: '287px';
|
beforeDestroy() {
|
||||||
}
|
window.removeEventListener('resize', this.size);
|
||||||
//#endregion
|
},
|
||||||
|
activated() {
|
||||||
|
this.size();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
previewable(file) {
|
previewable(file) {
|
||||||
return file.type.startsWith('video') || file.type.startsWith('image');
|
return file.type.startsWith('video') || file.type.startsWith('image');
|
||||||
|
},
|
||||||
|
size() {
|
||||||
|
// for Safari bug
|
||||||
|
if (this.sizeWaiting) return;
|
||||||
|
|
||||||
|
this.sizeWaiting = true;
|
||||||
|
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
this.sizeWaiting = false;
|
||||||
|
|
||||||
|
if (this.$refs.gridOuter) {
|
||||||
|
let height = 287;
|
||||||
|
const parent = this.$props.parentElement || this.$parent.$el;
|
||||||
|
|
||||||
|
if (this.$refs.gridOuter.clientHeight) {
|
||||||
|
height = this.$refs.gridOuter.clientHeight;
|
||||||
|
} else if (parent) {
|
||||||
|
height = parent.getBoundingClientRect().width * 9 / 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gridInnerStyle = { height: `${height}px` };
|
||||||
|
} else {
|
||||||
|
this.gridInnerStyle = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -176,7 +176,7 @@ export default Vue.extend({
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
left: 13px;
|
left: 13px;
|
||||||
color: var(--accent);
|
color: var(--indicator);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
animation: blink 1s infinite;
|
animation: blink 1s infinite;
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,10 @@ export default Vue.extend({
|
|||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep > code {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
::v-deep .title {
|
::v-deep .title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: solid 1px var(--divider);
|
border-bottom: solid 1px var(--divider);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-modal">
|
<div class="mk-modal" v-hotkey.global="keymap">
|
||||||
<transition :name="$store.state.device.animation ? 'bg-fade' : ''" appear>
|
<transition :name="$store.state.device.animation ? 'bg-fade' : ''" appear>
|
||||||
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
|
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
|
||||||
</transition>
|
</transition>
|
||||||
@ -20,6 +20,13 @@ export default Vue.extend({
|
|||||||
show: true,
|
show: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
keymap(): any {
|
||||||
|
return {
|
||||||
|
'esc': this.close,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
this.show = false;
|
this.show = false;
|
||||||
|
@ -77,23 +77,19 @@ export default Vue.extend({
|
|||||||
> .admin,
|
> .admin,
|
||||||
> .moderator {
|
> .moderator {
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
|
color: var(--badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .username {
|
> .username {
|
||||||
margin: 0 .5em 0 0;
|
margin: 0 .5em 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
color: var(--noteHeaderAcct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .info {
|
> .info {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
> * {
|
|
||||||
color: var(--noteHeaderInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .mobile {
|
> .mobile {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
>
|
>
|
||||||
<x-sub v-for="note in conversation" :key="note.id" :note="note"/>
|
<x-sub v-for="note in conversation" :key="note.id" :note="note"/>
|
||||||
<x-sub :note="appearNote.reply" class="reply-to" v-if="appearNote.reply"/>
|
<x-sub :note="appearNote.reply" class="reply-to" v-if="appearNote.reply"/>
|
||||||
<div class="pinned" v-if="pinned"><fa :icon="faThumbtack"/> {{ $t('pinnedNote') }}</div>
|
<div class="info" v-if="pinned"><fa :icon="faThumbtack"/> {{ $t('pinnedNote') }}</div>
|
||||||
|
<div class="info" v-if="appearNote._prId_"><fa :icon="faBullhorn"/> {{ $t('promotion') }}<button class="_textButton hide" @click="readPromo()">{{ $t('hideThisNote') }} <fa :icon="faTimes"/></button></div>
|
||||||
|
<div class="info" v-if="appearNote._featuredId_"><fa :icon="faBolt"/> {{ $t('featured') }}</div>
|
||||||
<div class="renote" v-if="isRenote">
|
<div class="renote" v-if="isRenote">
|
||||||
<mk-avatar class="avatar" :user="note.user"/>
|
<mk-avatar class="avatar" :user="note.user"/>
|
||||||
<fa :icon="faRetweet"/>
|
<fa :icon="faRetweet"/>
|
||||||
@ -58,7 +60,7 @@
|
|||||||
<template v-else><fa :icon="faReply"/></template>
|
<template v-else><fa :icon="faReply"/></template>
|
||||||
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="['public', 'home'].includes(appearNote.visibility)" @click="renote()" class="button _button" ref="renoteButton">
|
<button v-if="canRenote" @click="renote()" class="button _button" ref="renoteButton">
|
||||||
<fa :icon="faRetweet"/><p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
<fa :icon="faRetweet"/><p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
||||||
</button>
|
</button>
|
||||||
<button v-else class="button _button">
|
<button v-else class="button _button">
|
||||||
@ -83,7 +85,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight } from '@fortawesome/free-solid-svg-icons';
|
import { faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faCopy, faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
|
import { faCopy, faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
|
||||||
import { parse } from '../../mfm/parse';
|
import { parse } from '../../mfm/parse';
|
||||||
import { sum, unique } from '../../prelude/array';
|
import { sum, unique } from '../../prelude/array';
|
||||||
@ -140,7 +142,7 @@ export default Vue.extend({
|
|||||||
replies: [],
|
replies: [],
|
||||||
showContent: false,
|
showContent: false,
|
||||||
hideThisNote: false,
|
hideThisNote: false,
|
||||||
faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan
|
faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -190,16 +192,16 @@ export default Vue.extend({
|
|||||||
return this.$store.getters.isSignedIn && (this.$store.state.i.id === this.appearNote.userId);
|
return this.$store.getters.isSignedIn && (this.$store.state.i.id === this.appearNote.userId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
canRenote(): boolean {
|
||||||
|
return ['public', 'home'].includes(this.appearNote.visibility) || this.isMyNote;
|
||||||
|
},
|
||||||
|
|
||||||
reactionsCount(): number {
|
reactionsCount(): number {
|
||||||
return this.appearNote.reactions
|
return this.appearNote.reactions
|
||||||
? sum(Object.values(this.appearNote.reactions))
|
? sum(Object.values(this.appearNote.reactions))
|
||||||
: 0;
|
: 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
title(): string {
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
|
|
||||||
urls(): string[] {
|
urls(): string[] {
|
||||||
if (this.appearNote.text) {
|
if (this.appearNote.text) {
|
||||||
const ast = parse(this.appearNote.text);
|
const ast = parse(this.appearNote.text);
|
||||||
@ -263,6 +265,13 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
readPromo() {
|
||||||
|
(this as any).$root.api('promo/read', {
|
||||||
|
noteId: this.appearNote.id
|
||||||
|
});
|
||||||
|
this.hideThisNote = true;
|
||||||
|
},
|
||||||
|
|
||||||
capture(withHandler = false) {
|
capture(withHandler = false) {
|
||||||
if (this.$store.getters.isSignedIn) {
|
if (this.$store.getters.isSignedIn) {
|
||||||
this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
|
this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
|
||||||
@ -383,7 +392,7 @@ export default Vue.extend({
|
|||||||
}]
|
}]
|
||||||
source: this.$refs.renoteButton,
|
source: this.$refs.renoteButton,
|
||||||
viaKeyboard
|
viaKeyboard
|
||||||
}).then(this.focus);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renoteDirectly() {
|
renoteDirectly() {
|
||||||
@ -480,6 +489,11 @@ export default Vue.extend({
|
|||||||
noteId: this.appearNote.id
|
noteId: this.appearNote.id
|
||||||
});
|
});
|
||||||
menu = [{
|
menu = [{
|
||||||
|
type: 'link',
|
||||||
|
icon: faInfoCircle,
|
||||||
|
text: this.$t('details'),
|
||||||
|
to: '/notes/' + this.appearNote.id
|
||||||
|
}, null, {
|
||||||
icon: faCopy,
|
icon: faCopy,
|
||||||
text: this.$t('copyContent'),
|
text: this.$t('copyContent'),
|
||||||
action: this.copyContent
|
action: this.copyContent
|
||||||
@ -522,6 +536,15 @@ export default Vue.extend({
|
|||||||
text: this.$t('pin'),
|
text: this.$t('pin'),
|
||||||
action: () => this.togglePin(true)
|
action: () => this.togglePin(true)
|
||||||
} : undefined,
|
} : undefined,
|
||||||
|
...(this.$store.state.i.isModerator || this.$store.state.i.isAdmin ? [
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
icon: faBullhorn,
|
||||||
|
text: this.$t('promote'),
|
||||||
|
action: this.promote
|
||||||
|
}]
|
||||||
|
: []
|
||||||
|
),
|
||||||
...(this.appearNote.userId == this.$store.state.i.id ? [
|
...(this.appearNote.userId == this.$store.state.i.id ? [
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
@ -614,6 +637,30 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async promote() {
|
||||||
|
const { canceled, result: days } = await this.$root.dialog({
|
||||||
|
title: this.$t('numberOfDays'),
|
||||||
|
input: { type: 'number' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
this.$root.api('admin/promo/create', {
|
||||||
|
noteId: this.appearNote.id,
|
||||||
|
expiresAt: Date.now() + (86400000 * days)
|
||||||
|
}).then(() => {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'success',
|
||||||
|
iconOnly: true, autoClose: true
|
||||||
|
});
|
||||||
|
}).catch(e => {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: e
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.$el.focus();
|
this.$el.focus();
|
||||||
},
|
},
|
||||||
@ -710,7 +757,9 @@ export default Vue.extend({
|
|||||||
border-radius: 0 0 var(--radius) var(--radius);
|
border-radius: 0 0 var(--radius) var(--radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .pinned {
|
> .info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
padding: 16px 32px 8px 32px;
|
padding: 16px 32px 8px 32px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
@ -724,9 +773,14 @@ export default Vue.extend({
|
|||||||
> [data-icon] {
|
> [data-icon] {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .hide {
|
||||||
|
margin-left: auto;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .pinned + .article {
|
> .info + .article {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,22 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-notes" v-size="[{ max: 500 }]">
|
<div class="mk-notes" v-size="[{ max: 500 }]">
|
||||||
<div class="empty" v-if="empty">
|
<div class="empty" v-if="empty">
|
||||||
<img src="https://xn--931a.moe/assets/info.png" alt="" class="_ghost"/>
|
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
|
||||||
<div>{{ $t('noNotes') }}</div>
|
<div>{{ $t('noNotes') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mk-error v-if="error" @retry="init()"/>
|
<mk-error v-if="error" @retry="init()"/>
|
||||||
|
|
||||||
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }">
|
<div class="more" v-if="more && reversed" style="margin-bottom: var(--margin);">
|
||||||
<x-note :note="note" :detail="detail" :key="note.id"/>
|
|
||||||
</x-list>
|
|
||||||
|
|
||||||
<footer class="more" v-if="more">
|
|
||||||
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
|
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
|
||||||
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||||
<template v-if="moreFetching"><mk-loading inline/></template>
|
<template v-if="moreFetching"><mk-loading inline/></template>
|
||||||
</mk-button>
|
</mk-button>
|
||||||
</footer>
|
</div>
|
||||||
|
|
||||||
|
<x-list ref="notes" class="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-list>
|
||||||
|
|
||||||
|
<div class="more" v-if="more && !reversed" style="margin-top: var(--margin);">
|
||||||
|
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
|
||||||
|
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||||
|
<template v-if="moreFetching"><mk-loading inline/></template>
|
||||||
|
</mk-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -67,6 +74,10 @@ export default Vue.extend({
|
|||||||
notes(): any[] {
|
notes(): any[] {
|
||||||
return this.extract ? this.extract(this.items) : this.items;
|
return this.extract ? this.extract(this.items) : this.items;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
reversed(): boolean {
|
||||||
|
return this.pagination.reversed;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@ -92,14 +103,14 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .notes {
|
> .notes {
|
||||||
> ::v-deep * {
|
> ::v-deep *:not(:last-child) {
|
||||||
margin-bottom: var(--marginFull);
|
margin-bottom: var(--marginFull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.max-width_500px {
|
&.max-width_500px {
|
||||||
> .notes {
|
> .notes {
|
||||||
> ::v-deep * {
|
> ::v-deep *:not(:last-child) {
|
||||||
margin-bottom: var(--marginHalf);
|
margin-bottom: var(--marginHalf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-notification" :class="notification.type">
|
<div class="mk-notification" :class="notification.type" v-size="[{ max: 500 }, { max: 600 }]">
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<mk-avatar class="avatar" :user="notification.user"/>
|
<mk-avatar class="avatar" :user="notification.user"/>
|
||||||
<div class="icon" :class="notification.type">
|
<div class="icon" :class="notification.type">
|
||||||
@ -113,12 +113,17 @@ export default Vue.extend({
|
|||||||
.mk-notification {
|
.mk-notification {
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 16px;
|
padding: 24px 32px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
&.max-width_600px {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.max-width_500px {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
@ -169,7 +174,7 @@ export default Vue.extend({
|
|||||||
background: #36aed2;
|
background: #36aed2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.retweet {
|
&.renote {
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
background: #36d298;
|
background: #36d298;
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-notifications">
|
<div class="mk-notifications" :class="{ page }">
|
||||||
<div class="contents">
|
<x-list class="notifications" :items="items" v-slot="{ item: notification }">
|
||||||
<x-list class="notifications" :items="items" v-slot="{ item: notification, i }">
|
<x-note v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" :key="notification.id"/>
|
||||||
<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id"/>
|
<x-notification v-else :notification="notification" :with-time="true" :full="true" class="notification" :class="{ _panel: page }" :key="notification.id"/>
|
||||||
</x-list>
|
</x-list>
|
||||||
|
|
||||||
<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
||||||
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||||
<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
|
<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p class="empty" v-if="empty">{{ $t('noNotifications') }}</p>
|
<p class="empty" v-if="empty">{{ $t('noNotifications') }}</p>
|
||||||
|
|
||||||
<mk-error v-if="error" @retry="init()"/>
|
<mk-error v-if="error" @retry="init()"/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -24,6 +23,7 @@ import i18n from '../i18n';
|
|||||||
import paging from '../scripts/paging';
|
import paging from '../scripts/paging';
|
||||||
import XNotification from './notification.vue';
|
import XNotification from './notification.vue';
|
||||||
import XList from './date-separated-list.vue';
|
import XList from './date-separated-list.vue';
|
||||||
|
import XNote from './note.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
@ -31,6 +31,7 @@ export default Vue.extend({
|
|||||||
components: {
|
components: {
|
||||||
XNotification,
|
XNotification,
|
||||||
XList,
|
XList,
|
||||||
|
XNote,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [
|
mixins: [
|
||||||
@ -42,7 +43,7 @@ export default Vue.extend({
|
|||||||
type: String,
|
type: String,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
wide: {
|
page: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
@ -93,11 +94,15 @@ export default Vue.extend({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mk-notifications {
|
.mk-notifications {
|
||||||
> .contents {
|
&.page {
|
||||||
overflow: auto;
|
> .notifications {
|
||||||
height: 100%;
|
> ::v-deep * {
|
||||||
padding: 8px 8px 0 8px;
|
margin-bottom: var(--margin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.page) {
|
||||||
> .notifications {
|
> .notifications {
|
||||||
> ::v-deep * {
|
> ::v-deep * {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@ -109,28 +114,28 @@ export default Vue.extend({
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .more {
|
> .more {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
||||||
> [data-icon] {
|
> [data-icon] {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .empty {
|
> .empty {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .placeholder {
|
> .placeholder {
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="lzyxtsnt">
|
<div class="lzyxtsnt">
|
||||||
<img v-if="image" :src="image.url" alt=""/>
|
<img v-if="image" :src="image.url"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
108
src/client/components/particle.vue
Normal file
108
src/client/components/particle.vue
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<div class="vswabwbm" :style="{ top: `${y - 64}px`, left: `${x - 64}px` }" :class="{ active }">
|
||||||
|
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle fill="none" cx="64" cy="64">
|
||||||
|
<animate attributeName="r"
|
||||||
|
begin="0s" dur="0.5s"
|
||||||
|
values="4; 32"
|
||||||
|
calcMode="spline"
|
||||||
|
keyTimes="0; 1"
|
||||||
|
keySplines="0.165, 0.84, 0.44, 1"
|
||||||
|
repeatCount="1" />
|
||||||
|
<animate attributeName="stroke-width"
|
||||||
|
begin="0s" dur="0.5s"
|
||||||
|
values="16; 0"
|
||||||
|
calcMode="spline"
|
||||||
|
keyTimes="0; 1"
|
||||||
|
keySplines="0.3, 0.61, 0.355, 1"
|
||||||
|
repeatCount="1" />
|
||||||
|
</circle>
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<circle v-for="(particle, i) in particles" :key="i" :fill="particle.color">
|
||||||
|
<animate attributeName="r"
|
||||||
|
begin="0s" dur="0.8s"
|
||||||
|
:values="`${particle.size}; 0`"
|
||||||
|
calcMode="spline"
|
||||||
|
keyTimes="0; 1"
|
||||||
|
keySplines="0.165, 0.84, 0.44, 1"
|
||||||
|
repeatCount="1" />
|
||||||
|
<animate attributeName="cx"
|
||||||
|
begin="0s" dur="0.8s"
|
||||||
|
:values="`${particle.xA}; ${particle.xB}`"
|
||||||
|
calcMode="spline"
|
||||||
|
keyTimes="0; 1"
|
||||||
|
keySplines="0.3, 0.61, 0.355, 1"
|
||||||
|
repeatCount="1" />
|
||||||
|
<animate attributeName="cy"
|
||||||
|
begin="0s" dur="0.8s"
|
||||||
|
:values="`${particle.yA}; ${particle.yB}`"
|
||||||
|
calcMode="spline"
|
||||||
|
keyTimes="0; 1"
|
||||||
|
keySplines="0.3, 0.61, 0.355, 1"
|
||||||
|
repeatCount="1" />
|
||||||
|
</circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
x: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
const particles = [];
|
||||||
|
const origin = 64;
|
||||||
|
const colors = ['#FF1493', '#00FFFF', '#FFE202'];
|
||||||
|
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
const angle = Math.random() * (Math.PI * 2);
|
||||||
|
const pos = Math.random() * 16;
|
||||||
|
const velocity = 16 + (Math.random() * 48);
|
||||||
|
particles.push({
|
||||||
|
size: 4 + (Math.random() * 8),
|
||||||
|
xA: origin + (Math.sin(angle) * pos),
|
||||||
|
yA: origin + (Math.cos(angle) * pos),
|
||||||
|
xB: origin + (Math.sin(angle) * (pos + velocity)),
|
||||||
|
yB: origin + (Math.cos(angle) * (pos + velocity)),
|
||||||
|
color: colors[Math.floor(Math.random() * colors.length)]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
particles
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.destroyDom();
|
||||||
|
}, 1100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.vswabwbm {
|
||||||
|
pointer-events: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000000;
|
||||||
|
width: 128px;
|
||||||
|
height: 128px;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
> circle {
|
||||||
|
stroke: var(--accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -112,8 +112,7 @@ export default Vue.extend({
|
|||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: var(--pollChoiceText);
|
border: solid 1px var(--divider);
|
||||||
border: solid 1px var(--pollChoiceBorder);
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
|
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
|
||||||
<transition :name="$store.state.device.animation ? 'form-fade' : ''" appear>
|
<transition :name="$store.state.device.animation ? 'form-fade' : ''" appear @after-leave="$emit('closed');">
|
||||||
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
|
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
|
||||||
</transition>
|
</transition>
|
||||||
<div class="main" ref="main" @click.self="close()" @keydown="onKeydown">
|
<div class="main" ref="main" @click.self="close()" @keydown="onKeydown">
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
@drop.stop="onDrop"
|
@drop.stop="onDrop"
|
||||||
>
|
>
|
||||||
<header>
|
<header>
|
||||||
<button class="cancel _button" @click="cancel"><fa :icon="faTimes"/></button>
|
<button v-if="!fixed" class="cancel _button" @click="cancel"><fa :icon="faTimes"/></button>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-count" :class="{ over: trimmedLength(text) > max }">{{ max - trimmedLength(text) }}</span>
|
<span class="text-count" :class="{ over: trimmedLength(text) > max }">{{ max - trimmedLength(text) }}</span>
|
||||||
<button class="_button visibility" @click="setVisibility" ref="visibilityButton">
|
<button class="_button visibility" @click="setVisibility" ref="visibilityButton">
|
||||||
@ -18,7 +18,7 @@
|
|||||||
<button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<fa :icon="reply ? faReply : renote ? faQuoteRight : faPaperPlane"/></button>
|
<button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<fa :icon="reply ? faReply : renote ? faQuoteRight : faPaperPlane"/></button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="form">
|
<div class="form" :class="{ fixed }">
|
||||||
<x-note-preview class="preview" v-if="reply" :note="reply"/>
|
<x-note-preview class="preview" v-if="reply" :note="reply"/>
|
||||||
<x-note-preview class="preview" v-if="renote" :note="renote"/>
|
<x-note-preview class="preview" v-if="renote" :note="renote"/>
|
||||||
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('quoteAttached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
|
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('quoteAttached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
|
||||||
@ -108,6 +108,11 @@ export default Vue.extend({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
fixed: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -582,7 +587,6 @@ export default Vue.extend({
|
|||||||
.gafaadew {
|
.gafaadew {
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
box-shadow: 0 0 2px rgba(#000, 0.1);
|
|
||||||
|
|
||||||
> header {
|
> header {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@ -651,6 +655,10 @@ export default Vue.extend({
|
|||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
|
&.fixed {
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
> .preview {
|
> .preview {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
|
<x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
|
||||||
<div class="rdfaahpb">
|
<div class="rdfaahpb">
|
||||||
<transition-group
|
<div class="buttons" ref="buttons" :class="{ showFocus }">
|
||||||
name="reaction-fade"
|
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="reaction" v-particle><x-reaction-icon :reaction="reaction"/></button>
|
||||||
tag="div"
|
</div>
|
||||||
class="buttons"
|
|
||||||
ref="buttons"
|
|
||||||
:class="{ showFocus }"
|
|
||||||
:css="false"
|
|
||||||
@before-enter="beforeEnter"
|
|
||||||
@enter="enter"
|
|
||||||
mode="out-in"
|
|
||||||
appear
|
|
||||||
>
|
|
||||||
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="reaction"><x-reaction-icon :reaction="reaction"/></button>
|
|
||||||
</transition-group>
|
|
||||||
<input class="text" v-model="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
|
<input class="text" v-model="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
|
||||||
</div>
|
</div>
|
||||||
</x-popup>
|
</x-popup>
|
||||||
@ -84,7 +73,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
focus(i) {
|
focus(i) {
|
||||||
this.$refs.buttons.children[i].elm.focus();
|
this.$refs.buttons.children[i].focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -129,21 +118,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
choose() {
|
choose() {
|
||||||
this.$refs.buttons.children[this.focus].elm.click();
|
this.$refs.buttons.children[this.focus].click();
|
||||||
},
|
|
||||||
|
|
||||||
beforeEnter(el) {
|
|
||||||
el.style.opacity = 0;
|
|
||||||
el.style.transform = 'scale(0.7)';
|
|
||||||
},
|
|
||||||
|
|
||||||
enter(el, done) {
|
|
||||||
el.style.transition = [getComputedStyle(el).transition, 'transform 1s cubic-bezier(0.23, 1, 0.32, 1)', 'opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1)'].filter(x => x != '').join(',');
|
|
||||||
setTimeout(() => {
|
|
||||||
el.style.opacity = 1;
|
|
||||||
el.style.transform = 'scale(1)';
|
|
||||||
setTimeout(done, 1000);
|
|
||||||
}, 0 * el.dataset.index)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<button
|
||||||
class="reaction _button"
|
class="hkzvhatu _button"
|
||||||
:class="{ reacted: note.myReaction == reaction }"
|
:class="{ reacted: note.myReaction == reaction }"
|
||||||
@click="toggleReaction(reaction)"
|
@click="toggleReaction(reaction)"
|
||||||
v-if="count > 0"
|
v-if="count > 0"
|
||||||
@mouseover="onMouseover"
|
@mouseover="onMouseover"
|
||||||
@mouseleave="onMouseleave"
|
@mouseleave="onMouseleave"
|
||||||
ref="reaction"
|
ref="reaction"
|
||||||
|
v-particle
|
||||||
>
|
>
|
||||||
<x-reaction-icon :reaction="reaction" ref="icon"/>
|
<x-reaction-icon :reaction="reaction" ref="icon"/>
|
||||||
<span>{{ count }}</span>
|
<span>{{ count }}</span>
|
||||||
</span>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@ -136,7 +137,7 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.reaction {
|
.hkzvhatu {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-reactions-viewer" :class="{ isMe }">
|
<div class="tdflqwzn" :class="{ isMe }">
|
||||||
<x-reaction v-for="(count, reaction) in note.reactions" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note" :key="reaction"/>
|
<x-reaction v-for="(count, reaction) in note.reactions" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note" :key="reaction"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -32,7 +32,7 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mk-reactions-viewer {
|
.tdflqwzn {
|
||||||
margin: 4px -2px 0 -2px;
|
margin: 4px -2px 0 -2px;
|
||||||
|
|
||||||
&:empty {
|
&:empty {
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition-group v-if="$store.state.device.animation"
|
<transition-group v-if="$store.state.device.animation"
|
||||||
name="staggered-fade"
|
class="uupnnhew"
|
||||||
|
name="staggered"
|
||||||
tag="div"
|
tag="div"
|
||||||
:css="false"
|
|
||||||
@before-enter="beforeEnter"
|
|
||||||
@enter="enter"
|
|
||||||
@leave="leave"
|
|
||||||
mode="out-in"
|
|
||||||
appear
|
appear
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@ -20,65 +16,25 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: {
|
|
||||||
delay: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
default: 40
|
|
||||||
},
|
|
||||||
direction: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: 'down'
|
|
||||||
},
|
|
||||||
reversed: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
i: 0,
|
|
||||||
methods: {
|
methods: {
|
||||||
beforeEnter(el) {
|
|
||||||
if (document.hidden) return;
|
|
||||||
|
|
||||||
el.style.opacity = 0;
|
|
||||||
el.style.transform = this.direction === 'down' ? 'translateY(-64px)' : 'translateY(64px)';
|
|
||||||
const delay = this.delay * this.$options.i;
|
|
||||||
el.style.transition = [getComputedStyle(el).transition, `transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`, `opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`].filter(x => x != '').join(',');
|
|
||||||
this.$options.i++;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
el.style.transition = null;
|
|
||||||
el.style.transform = null;
|
|
||||||
el.style.opacity = null;
|
|
||||||
this.$options.i--;
|
|
||||||
}, delay + 710);
|
|
||||||
},
|
|
||||||
enter(el) {
|
|
||||||
if (document.hidden) {
|
|
||||||
el.style.opacity = 1;
|
|
||||||
el.style.transform = 'translateY(0px)';
|
|
||||||
} else {
|
|
||||||
setTimeout(() => { // 必要
|
|
||||||
el.style.opacity = 1;
|
|
||||||
el.style.transform = 'translateY(0px)';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
leave(el) {
|
|
||||||
el.style.opacity = 0;
|
|
||||||
el.style.transform = this.direction === 'down' ? 'translateY(64px)' : 'translateY(-64px)';
|
|
||||||
},
|
|
||||||
focus() {
|
focus() {
|
||||||
this.$slots.default[0].elm.focus();
|
this.$slots.default[0].elm.focus();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.staggered-fade-move {
|
.uupnnhew {
|
||||||
transition: transform 0.7s !important;
|
> .staggered-enter {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-64px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through 30 {
|
||||||
|
> .staggered-enter-active:nth-child(#{$i}) {
|
||||||
|
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1)), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<x-window ref="window" @closed="() => { $emit('closed'); destroyDom(); }">
|
<x-window ref="window" @closed="() => { $emit('closed'); destroyDom(); }">
|
||||||
<template #header>{{ $t('login') }}</template>
|
<template #header>{{ $t('login') }}</template>
|
||||||
<x-signin :auto-set="autoSet" @login="onLogin"/>
|
<mk-signin :auto-set="autoSet" @login="onLogin"/>
|
||||||
</x-window>
|
</x-window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -9,13 +9,13 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
import XWindow from './window.vue';
|
import XWindow from './window.vue';
|
||||||
import XSignin from './signin.vue';
|
import MkSignin from './signin.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
XSignin,
|
MkSignin,
|
||||||
XWindow,
|
XWindow,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
80
src/client/components/stream-indicator.vue
Normal file
80
src/client/components/stream-indicator.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="nsbbhtug" v-if="hasDisconnected" @click="resetDisconnected">
|
||||||
|
<div>{{ $t('disconnectedFromServer') }}</div>
|
||||||
|
<div class="command">
|
||||||
|
<button class="_textButton" @click="reload">{{ $t('reload') }}</button>
|
||||||
|
<button class="_textButton">{{ $t('doNothing') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hasDisconnected: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
stream() {
|
||||||
|
return this.$root.stream;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$root.stream.on('_connected_', this.onConnected);
|
||||||
|
this.$root.stream.on('_disconnected_', this.onDisconnected);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$root.stream.off('_connected_', this.onConnected);
|
||||||
|
this.$root.stream.off('_disconnected_', this.onDisconnected);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onConnected() {
|
||||||
|
if (this.hasDisconnected) {
|
||||||
|
if (this.$store.state.device.autoReload) {
|
||||||
|
this.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDisconnected() {
|
||||||
|
this.hasDisconnected = true;
|
||||||
|
},
|
||||||
|
resetDisconnected() {
|
||||||
|
this.hasDisconnected = false;
|
||||||
|
},
|
||||||
|
reload() {
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.nsbbhtug {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 16385;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 8px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #fff;
|
||||||
|
background: #000;
|
||||||
|
opacity: 0.8;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-width: 320px;
|
||||||
|
|
||||||
|
> .command {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
padding: 0.7em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<x-notes ref="tl" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/>
|
<x-notes ref="tl" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)" @queue="$emit('queue', $event)"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@ -21,6 +21,11 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
antenna: {
|
antenna: {
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
sound: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -46,6 +51,10 @@ export default Vue.extend({
|
|||||||
|
|
||||||
const prepend = note => {
|
const prepend = note => {
|
||||||
(this.$refs.tl as any).prepend(note);
|
(this.$refs.tl as any).prepend(note);
|
||||||
|
|
||||||
|
if (this.sound) {
|
||||||
|
this.$root.sound(note.userId === this.$store.state.i.id ? 'noteMy' : 'note');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUserAdded = () => {
|
const onUserAdded = () => {
|
||||||
|
@ -243,6 +243,10 @@ export default Vue.extend({
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.inline):last-child {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
> .icon {
|
> .icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -56,7 +56,7 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
filled(): boolean {
|
filled(): boolean {
|
||||||
return this.v != '' && this.v != null;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -81,6 +81,10 @@ export default Vue.extend({
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.inline):last-child {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
> .icon {
|
> .icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -96,6 +100,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
> .input {
|
> .input {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: '';
|
content: '';
|
||||||
@ -147,7 +152,7 @@ export default Vue.extend({
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
background: var(--panel);
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -129,7 +129,6 @@ export default Vue.extend({
|
|||||||
> .label {
|
> .label {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: inherit;
|
transition: inherit;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
|
@ -51,6 +51,7 @@ export default Vue.extend({
|
|||||||
target: self ? null : '_blank',
|
target: self ? null : '_blank',
|
||||||
showTimer: null,
|
showTimer: null,
|
||||||
hideTimer: null,
|
hideTimer: null,
|
||||||
|
checkTimer: null,
|
||||||
preview: null,
|
preview: null,
|
||||||
faExternalLinkSquareAlt
|
faExternalLinkSquareAlt
|
||||||
};
|
};
|
||||||
@ -78,9 +79,14 @@ export default Vue.extend({
|
|||||||
}).$mount();
|
}).$mount();
|
||||||
|
|
||||||
document.body.appendChild(this.preview.$el);
|
document.body.appendChild(this.preview.$el);
|
||||||
|
|
||||||
|
this.checkTimer = setInterval(() => {
|
||||||
|
if (!document.body.contains(this.$el)) this.closePreview();
|
||||||
|
}, 1000);
|
||||||
},
|
},
|
||||||
closePreview() {
|
closePreview() {
|
||||||
if (this.preview) {
|
if (this.preview) {
|
||||||
|
clearInterval(this.checkTimer);
|
||||||
this.preview.destroyDom();
|
this.preview.destroyDom();
|
||||||
this.preview = null;
|
this.preview = null;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<template #header><mk-user-name :user="user"/></template>
|
<template #header><mk-user-name :user="user"/></template>
|
||||||
<div class="vrcsvlkm">
|
<div class="vrcsvlkm">
|
||||||
<mk-button @click="resetPassword()" primary>{{ $t('resetPassword') }}</mk-button>
|
<mk-button @click="resetPassword()" primary>{{ $t('resetPassword') }}</mk-button>
|
||||||
<mk-switch v-if="$store.state.i.isAdmin" @change="toggleModerator()" v-model="moderator">{{ $t('moderator') }}</mk-switch>
|
<mk-switch v-if="$store.state.i.isAdmin && (this.moderator || !user.isAdmin)" @change="toggleModerator()" v-model="moderator">{{ $t('moderator') }}</mk-switch>
|
||||||
<mk-switch @change="toggleSilence()" v-model="silenced">{{ $t('silence') }}</mk-switch>
|
<mk-switch @change="toggleSilence()" v-model="silenced">{{ $t('silence') }}</mk-switch>
|
||||||
<mk-switch @change="toggleSuspend()" v-model="suspended">{{ $t('suspend') }}</mk-switch>
|
<mk-switch @change="toggleSuspend()" v-model="suspended">{{ $t('suspend') }}</mk-switch>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,6 +53,7 @@ export default Vue.extend({
|
|||||||
return {
|
return {
|
||||||
u: null,
|
u: null,
|
||||||
show: false,
|
show: false,
|
||||||
|
closed: false,
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
};
|
};
|
||||||
@ -68,6 +69,7 @@ export default Vue.extend({
|
|||||||
{ userId: this.user };
|
{ userId: this.user };
|
||||||
|
|
||||||
this.$root.api('users/show', query).then(user => {
|
this.$root.api('users/show', query).then(user => {
|
||||||
|
if (this.closed) return;
|
||||||
this.u = user;
|
this.u = user;
|
||||||
this.show = true;
|
this.show = true;
|
||||||
});
|
});
|
||||||
@ -83,6 +85,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
|
this.closed = true;
|
||||||
this.show = false;
|
this.show = false;
|
||||||
if (this.$refs.content) (this.$refs.content as any).style.pointerEvents = 'none';
|
if (this.$refs.content) (this.$refs.content as any).style.pointerEvents = 'none';
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,10 @@ import Vue from 'vue';
|
|||||||
import userPreview from './user-preview';
|
import userPreview from './user-preview';
|
||||||
import autocomplete from './autocomplete';
|
import autocomplete from './autocomplete';
|
||||||
import size from './size';
|
import size from './size';
|
||||||
|
import particle from './particle';
|
||||||
|
|
||||||
Vue.directive('autocomplete', autocomplete);
|
Vue.directive('autocomplete', autocomplete);
|
||||||
Vue.directive('userPreview', userPreview);
|
Vue.directive('userPreview', userPreview);
|
||||||
Vue.directive('user-preview', userPreview);
|
Vue.directive('user-preview', userPreview);
|
||||||
Vue.directive('size', size);
|
Vue.directive('size', size);
|
||||||
|
Vue.directive('particle', particle);
|
||||||
|
22
src/client/directives/particle.ts
Normal file
22
src/client/directives/particle.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Particle from '../components/particle.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
bind(el, binding, vn) {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
|
||||||
|
const x = rect.left + (el.clientWidth / 2);
|
||||||
|
const y = rect.top + (el.clientHeight / 2);
|
||||||
|
|
||||||
|
const particle = new Particle({
|
||||||
|
parent: vn.context,
|
||||||
|
propsData: {
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
}
|
||||||
|
}).$mount();
|
||||||
|
|
||||||
|
document.body.appendChild(particle.$el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -54,6 +54,8 @@ export default {
|
|||||||
|
|
||||||
calc();
|
calc();
|
||||||
|
|
||||||
|
vn.context.$on('hook:activated', calc);
|
||||||
|
|
||||||
const ro = new ResizeObserver((entries, observer) => {
|
const ro = new ResizeObserver((entries, observer) => {
|
||||||
calc();
|
calc();
|
||||||
});
|
});
|
||||||
|
@ -8,9 +8,11 @@ export default {
|
|||||||
self.tag = null;
|
self.tag = null;
|
||||||
self.showTimer = null;
|
self.showTimer = null;
|
||||||
self.hideTimer = null;
|
self.hideTimer = null;
|
||||||
|
self.checkTimer = null;
|
||||||
|
|
||||||
self.close = () => {
|
self.close = () => {
|
||||||
if (self.tag) {
|
if (self.tag) {
|
||||||
|
clearInterval(self.checkTimer);
|
||||||
self.tag.close();
|
self.tag.close();
|
||||||
self.tag = null;
|
self.tag = null;
|
||||||
}
|
}
|
||||||
@ -38,6 +40,14 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.body.appendChild(self.tag.$el);
|
document.body.appendChild(self.tag.$el);
|
||||||
|
|
||||||
|
self.checkTimer = setInterval(() => {
|
||||||
|
if (!document.body.contains(el)) {
|
||||||
|
clearTimeout(self.showTimer);
|
||||||
|
clearTimeout(self.hideTimer);
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
el.addEventListener('mouseover', () => {
|
el.addEventListener('mouseover', () => {
|
||||||
@ -60,8 +70,6 @@ export default {
|
|||||||
|
|
||||||
unbind(el, binding, vn) {
|
unbind(el, binding, vn) {
|
||||||
const self = el._userPreviewDirective_;
|
const self = el._userPreviewDirective_;
|
||||||
clearTimeout(self.showTimer);
|
clearInterval(self.checkTimer);
|
||||||
clearTimeout(self.hideTimer);
|
|
||||||
self.close();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -136,7 +136,13 @@ document.body.innerHTML = '<div id="app"></div>';
|
|||||||
const os = new MiOS();
|
const os = new MiOS();
|
||||||
|
|
||||||
os.init(async () => {
|
os.init(async () => {
|
||||||
if (os.store.state.settings.wallpaper) document.documentElement.style.backgroundImage = `url(${os.store.state.settings.wallpaper})`;
|
window.addEventListener('storage', e => {
|
||||||
|
if (e.key === 'vuex') {
|
||||||
|
os.store.replaceState(JSON.parse(localStorage['vuex']));
|
||||||
|
} else if (e.key === 'i') {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
|
|
||||||
if ('Notification' in window && os.store.getters.isSignedIn) {
|
if ('Notification' in window && os.store.getters.isSignedIn) {
|
||||||
// 許可を得ていなかったらリクエスト
|
// 許可を得ていなかったらリクエスト
|
||||||
@ -191,6 +197,14 @@ os.init(async () => {
|
|||||||
if (cb) vm.$once('closed', cb);
|
if (cb) vm.$once('closed', cb);
|
||||||
(vm as any).focus();
|
(vm as any).focus();
|
||||||
},
|
},
|
||||||
|
sound(type: string) {
|
||||||
|
if (this.$store.state.device.sfxVolume === 0) return;
|
||||||
|
const sound = this.$store.state.device['sfx' + type.substr(0, 1).toUpperCase() + type.substr(1)];
|
||||||
|
if (sound == null) return;
|
||||||
|
const audio = new Audio(`/assets/sounds/${sound}.mp3`);
|
||||||
|
audio.volume = this.$store.state.device.sfxVolume;
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
router: router,
|
router: router,
|
||||||
render: createEl => createEl(App)
|
render: createEl => createEl(App)
|
||||||
@ -200,4 +214,96 @@ os.init(async () => {
|
|||||||
|
|
||||||
// マウント
|
// マウント
|
||||||
app.$mount('#app');
|
app.$mount('#app');
|
||||||
|
|
||||||
|
if (app.$store.getters.isSignedIn) {
|
||||||
|
const main = os.stream.useSharedConnection('main');
|
||||||
|
|
||||||
|
// 自分の情報が更新されたとき
|
||||||
|
main.on('meUpdated', i => {
|
||||||
|
app.$store.dispatch('mergeMe', i);
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllNotifications', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadNotification: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadNotification', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadNotification: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadMention', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMentions: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllUnreadMentions', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMentions: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadSpecifiedNote', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadSpecifiedNotes: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllUnreadSpecifiedNotes', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadSpecifiedNotes: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllMessagingMessages', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMessagingMessage: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadMessagingMessage', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMessagingMessage: true
|
||||||
|
});
|
||||||
|
|
||||||
|
app.sound('chatBg');
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllAntennas', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadAntenna: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadAntenna', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadAntenna: true
|
||||||
|
});
|
||||||
|
|
||||||
|
app.sound('antenna');
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllAnnouncements', () => {
|
||||||
|
app.$store.dispatch('mergeMe', {
|
||||||
|
hasUnreadAnnouncement: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('clientSettingUpdated', x => {
|
||||||
|
app.$store.commit('settings/set', {
|
||||||
|
key: x.key,
|
||||||
|
value: x.value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// トークンが再生成されたとき
|
||||||
|
// このままではMisskeyが利用できないので強制的にサインアウトさせる
|
||||||
|
main.on('myTokenRegenerated', () => {
|
||||||
|
os.signout();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ import Vue from 'vue';
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
|
||||||
import initStore from './store';
|
import initStore from './store';
|
||||||
import { apiUrl, version, locale } from './config';
|
import { apiUrl, version } from './config';
|
||||||
import Progress from './scripts/loading';
|
import Progress from './scripts/loading';
|
||||||
|
|
||||||
import Stream from './scripts/stream';
|
import Stream from './scripts/stream';
|
||||||
@ -142,95 +142,6 @@ export default class MiOS extends EventEmitter {
|
|||||||
@autobind
|
@autobind
|
||||||
private initStream() {
|
private initStream() {
|
||||||
this.stream = new Stream(this);
|
this.stream = new Stream(this);
|
||||||
|
|
||||||
if (this.store.getters.isSignedIn) {
|
|
||||||
const main = this.stream.useSharedConnection('main');
|
|
||||||
|
|
||||||
// 自分の情報が更新されたとき
|
|
||||||
main.on('meUpdated', i => {
|
|
||||||
this.store.dispatch('mergeMe', i);
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllNotifications', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadNotification: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadNotification', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadNotification: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadMention', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadMentions: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllUnreadMentions', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadMentions: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadSpecifiedNote', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadSpecifiedNotes: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllUnreadSpecifiedNotes', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadSpecifiedNotes: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllMessagingMessages', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadMessagingMessage: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadMessagingMessage', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadMessagingMessage: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllAntennas', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadAntenna: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadAntenna', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadAntenna: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllAnnouncements', () => {
|
|
||||||
this.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadAnnouncement: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('clientSettingUpdated', x => {
|
|
||||||
this.store.commit('settings/set', {
|
|
||||||
key: x.key,
|
|
||||||
value: x.value
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// トークンが再生成されたとき
|
|
||||||
// このままではMisskeyが利用できないので強制的にサインアウトさせる
|
|
||||||
main.on('myTokenRegenerated', () => {
|
|
||||||
alert(locale['common']['my-token-regenerated']);
|
|
||||||
this.signout();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,14 +12,12 @@
|
|||||||
<div><b>{{ $t('administrator') }}</b><span>{{ meta.maintainerName }}</span></div>
|
<div><b>{{ $t('administrator') }}</b><span>{{ meta.maintainerName }}</span></div>
|
||||||
<div><b></b><span>{{ meta.maintainerEmail }}</span></div>
|
<div><b></b><span>{{ meta.maintainerEmail }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="_content table" v-if="stats">
|
|
||||||
<div><b>{{ $t('users') }}</b><span>{{ stats.originalUsersCount | number }}</span></div>
|
|
||||||
<div><b>{{ $t('notes') }}</b><span>{{ stats.originalNotesCount | number }}</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="_content table">
|
<div class="_content table">
|
||||||
<div><b>Misskey</b><span>v{{ version }}</span></div>
|
<div><b>Misskey</b><span>v{{ version }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<mk-instance-stats style="margin-top: var(--margin);"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -28,6 +26,7 @@ import Vue from 'vue';
|
|||||||
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { version } from '../config';
|
import { version } from '../config';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
|
import MkInstanceStats from '../components/instance-stats.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
@ -38,10 +37,13 @@ export default Vue.extend({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
MkInstanceStats
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
stats: null,
|
|
||||||
serverInfo: null,
|
serverInfo: null,
|
||||||
faInfoCircle
|
faInfoCircle
|
||||||
}
|
}
|
||||||
@ -52,12 +54,6 @@ export default Vue.extend({
|
|||||||
return this.$store.state.instance.meta;
|
return this.$store.state.instance.meta;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
|
||||||
this.$root.api('stats').then(res => {
|
|
||||||
this.stats = res;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="_title"><span v-if="$store.getters.isSignedIn && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
|
<div class="_title"><span v-if="$store.getters.isSignedIn && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<mfm :text="announcement.text"/>
|
<mfm :text="announcement.text"/>
|
||||||
<img v-if="announcement.imageUrl" :src="announcement.imageUrl" alt=""/>
|
<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="_footer" v-if="$store.getters.isSignedIn && !announcement.isRead">
|
<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(announcement)" primary><fa :icon="faCheck"/> {{ $t('gotIt') }}</mk-button>
|
||||||
|
@ -34,11 +34,13 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
import XForm from './auth.form.vue';
|
import XForm from './auth.form.vue';
|
||||||
|
import MkSignin from '../components/signin.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
components: {
|
components: {
|
||||||
XForm
|
XForm,
|
||||||
|
MkSignin,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -1,27 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<mk-pagination :pagination="pagination" #default="{items}" class="mk-follow-requests" ref="list">
|
<div>
|
||||||
<div class="user _panel" v-for="(req, i) in items" :key="req.id">
|
<portal to="icon"><fa :icon="faUserClock"/></portal>
|
||||||
<mk-avatar class="avatar" :user="req.follower"/>
|
<portal to="title">{{ $t('followRequests') }}</portal>
|
||||||
<div class="body">
|
|
||||||
<div class="name">
|
<mk-pagination :pagination="pagination" class="mk-follow-requests" ref="list">
|
||||||
<router-link class="name" :to="req.follower | userPage" v-user-preview="req.follower.id"><mk-user-name :user="req.follower"/></router-link>
|
<template #empty>
|
||||||
<p class="acct">@{{ req.follower | acct }}</p>
|
<div class="tkdrhpxr">
|
||||||
|
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
|
||||||
|
<div>{{ $t('noFollowRequests') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="description" v-if="req.follower.description" :title="req.follower.description">
|
</template>
|
||||||
<mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$store.state.i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/>
|
<template #default="{items}">
|
||||||
|
<div class="user _panel" v-for="req in items" :key="req.id">
|
||||||
|
<mk-avatar class="avatar" :user="req.follower"/>
|
||||||
|
<div class="body">
|
||||||
|
<div class="name">
|
||||||
|
<router-link class="name" :to="req.follower | userPage" v-user-preview="req.follower.id"><mk-user-name :user="req.follower"/></router-link>
|
||||||
|
<p class="acct">@{{ req.follower | acct }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="description" v-if="req.follower.description" :title="req.follower.description">
|
||||||
|
<mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$store.state.i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="_button" @click="accept(req.follower)"><fa :icon="faCheck"/></button>
|
||||||
|
<button class="_button" @click="reject(req.follower)"><fa :icon="faTimes"/></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
</template>
|
||||||
<button class="_button" @click="accept(req.follower)"><fa :icon="faCheck"/></button>
|
</mk-pagination>
|
||||||
<button class="_button" @click="reject(req.follower)"><fa :icon="faTimes"/></button>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mk-pagination>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { faCheck, faTimes } from '@fortawesome/free-solid-svg-icons';
|
import { faUserClock, faCheck, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||||
import MkPagination from '../components/ui/pagination.vue';
|
import MkPagination from '../components/ui/pagination.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
@ -41,7 +54,7 @@ export default Vue.extend({
|
|||||||
endpoint: 'following/requests/list',
|
endpoint: 'following/requests/list',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
},
|
},
|
||||||
faCheck, faTimes
|
faCheck, faTimes, faUserClock
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -62,6 +75,18 @@ export default Vue.extend({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mk-follow-requests {
|
.mk-follow-requests {
|
||||||
|
.tkdrhpxr {
|
||||||
|
padding: 32px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
vertical-align: bottom;
|
||||||
|
height: 128px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .user {
|
> .user {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
@ -14,9 +14,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</portal>
|
</portal>
|
||||||
|
|
||||||
|
<div class="new" v-if="queue > 0" :style="{ width: width + 'px' }"><button class="_buttonPrimary" @click="top()">{{ $t('newNoteRecived') }}</button></div>
|
||||||
|
|
||||||
<x-tutorial class="tutorial" v-if="$store.state.settings.tutorial != -1"/>
|
<x-tutorial class="tutorial" v-if="$store.state.settings.tutorial != -1"/>
|
||||||
|
|
||||||
<x-timeline ref="tl" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src" :src="src" :list="list" :antenna="antenna" @before="before()" @after="after()"/>
|
<x-post-form class="post-form _panel" fixed v-if="$store.state.device.showFixedPostForm"/>
|
||||||
|
<x-timeline ref="tl" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src" :src="src" :list="list" :antenna="antenna" :sound="true" @before="before()" @after="after()" @queue="queueUpdated"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -27,6 +30,7 @@ import { faComments } from '@fortawesome/free-regular-svg-icons';
|
|||||||
import Progress from '../scripts/loading';
|
import Progress from '../scripts/loading';
|
||||||
import XTimeline from '../components/timeline.vue';
|
import XTimeline from '../components/timeline.vue';
|
||||||
import XTutorial from './index.home.tutorial.vue';
|
import XTutorial from './index.home.tutorial.vue';
|
||||||
|
import XPostForm from '../components/post-form.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
metaInfo() {
|
metaInfo() {
|
||||||
@ -38,6 +42,7 @@ export default Vue.extend({
|
|||||||
components: {
|
components: {
|
||||||
XTimeline,
|
XTimeline,
|
||||||
XTutorial,
|
XTutorial,
|
||||||
|
XPostForm,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@ -53,6 +58,8 @@ export default Vue.extend({
|
|||||||
list: null,
|
list: null,
|
||||||
antenna: null,
|
antenna: null,
|
||||||
menuOpened: false,
|
menuOpened: false,
|
||||||
|
queue: 0,
|
||||||
|
width: 0,
|
||||||
faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faComments, faListUl, faSatellite, faCircle
|
faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faComments, faListUl, faSatellite, faCircle
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -63,6 +70,10 @@ export default Vue.extend({
|
|||||||
't': this.focus
|
't': this.focus
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
meta() {
|
||||||
|
return this.$store.state.instance.meta;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@ -91,6 +102,10 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.width = this.$el.offsetWidth;
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
before() {
|
before() {
|
||||||
Progress.start();
|
Progress.start();
|
||||||
@ -100,7 +115,17 @@ export default Vue.extend({
|
|||||||
Progress.done();
|
Progress.done();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
queueUpdated(q) {
|
||||||
|
if (this.$el.offsetWidth !== 0) this.width = this.$el.offsetWidth;
|
||||||
|
this.queue = q;
|
||||||
|
},
|
||||||
|
|
||||||
|
top() {
|
||||||
|
window.scroll({ top: 0, behavior: 'instant' });
|
||||||
|
},
|
||||||
|
|
||||||
async choose(ev) {
|
async choose(ev) {
|
||||||
|
if (this.meta == null) return;
|
||||||
this.menuOpened = true;
|
this.menuOpened = true;
|
||||||
const [antennas, lists] = await Promise.all([
|
const [antennas, lists] = await Promise.all([
|
||||||
this.$root.api('antennas/list'),
|
this.$root.api('antennas/list'),
|
||||||
@ -128,15 +153,15 @@ export default Vue.extend({
|
|||||||
text: this.$t('_timelines.home'),
|
text: this.$t('_timelines.home'),
|
||||||
icon: faHome,
|
icon: faHome,
|
||||||
action: () => { this.setSrc('home') }
|
action: () => { this.setSrc('home') }
|
||||||
}, {
|
}, this.meta.disableLocalTimeline && !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin ? undefined : {
|
||||||
text: this.$t('_timelines.local'),
|
text: this.$t('_timelines.local'),
|
||||||
icon: faComments,
|
icon: faComments,
|
||||||
action: () => { this.setSrc('local') }
|
action: () => { this.setSrc('local') }
|
||||||
}, {
|
}, this.meta.disableLocalTimeline && !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin ? undefined : {
|
||||||
text: this.$t('_timelines.social'),
|
text: this.$t('_timelines.social'),
|
||||||
icon: faShareAlt,
|
icon: faShareAlt,
|
||||||
action: () => { this.setSrc('social') }
|
action: () => { this.setSrc('social') }
|
||||||
}, {
|
}, this.meta.disableGlobalTimeline && !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin ? undefined : {
|
||||||
text: this.$t('_timelines.global'),
|
text: this.$t('_timelines.global'),
|
||||||
icon: faGlobe,
|
icon: faGlobe,
|
||||||
action: () => { this.setSrc('global') }
|
action: () => { this.setSrc('global') }
|
||||||
@ -169,9 +194,26 @@ export default Vue.extend({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mk-home {
|
.mk-home {
|
||||||
|
> .new {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .tutorial {
|
> .tutorial {
|
||||||
margin-bottom: var(--margin);
|
margin-bottom: var(--margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .post-form {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: var(--margin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
._kjvfvyph_ {
|
._kjvfvyph_ {
|
||||||
@ -184,7 +226,7 @@ export default Vue.extend({
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 16px;
|
top: 16px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
color: var(--accent);
|
color: var(--indicator);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
animation: blink 1s infinite;
|
animation: blink 1s infinite;
|
||||||
}
|
}
|
||||||
|
@ -1,165 +1,90 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="meta" class="mk-instance-page">
|
<div v-if="meta" class="xhexznfu">
|
||||||
<portal to="icon"><fa :icon="faServer"/></portal>
|
<portal to="icon"><fa :icon="faServer"/></portal>
|
||||||
<portal to="title">{{ $t('instance') }}</portal>
|
<portal to="title">{{ $t('instance') }}</portal>
|
||||||
|
|
||||||
<section class="_card info">
|
<mk-instance-stats style="margin-bottom: var(--margin);"/>
|
||||||
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div>
|
|
||||||
|
<section class="_card logs">
|
||||||
|
<div class="_title"><fa :icon="faStream"/> {{ $t('serverLogs') }}</div>
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<mk-input v-model="name">{{ $t('instanceName') }}</mk-input>
|
<div class="_inputs">
|
||||||
<mk-textarea v-model="description">{{ $t('instanceDescription') }}</mk-textarea>
|
<mk-input v-model="logDomain" :debounce="true">
|
||||||
<mk-input v-model="iconUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('iconUrl') }}</mk-input>
|
<span>{{ $t('domain') }}</span>
|
||||||
<mk-input v-model="bannerUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('bannerUrl') }}</mk-input>
|
</mk-input>
|
||||||
<mk-input v-model="tosUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('tosUrl') }}</mk-input>
|
<mk-select v-model="logLevel">
|
||||||
<mk-input v-model="maintainerName">{{ $t('maintainerName') }}</mk-input>
|
<template #label>{{ $t('level') }}</template>
|
||||||
<mk-input v-model="maintainerEmail" type="email"><template #icon><fa :icon="faEnvelope"/></template>{{ $t('maintainerEmail') }}</mk-input>
|
<option value="all">{{ $t('levels.all') }}</option>
|
||||||
|
<option value="info">{{ $t('levels.info') }}</option>
|
||||||
|
<option value="success">{{ $t('levels.success') }}</option>
|
||||||
|
<option value="warning">{{ $t('levels.warning') }}</option>
|
||||||
|
<option value="error">{{ $t('levels.error') }}</option>
|
||||||
|
<option value="debug">{{ $t('levels.debug') }}</option>
|
||||||
|
</mk-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="logs">
|
||||||
|
<code v-for="log in logs" :key="log.id" :class="log.level">
|
||||||
|
<details>
|
||||||
|
<summary><mk-time :time="log.createdAt"/> [{{ log.domain.join('.') }}] {{ log.message }}</summary>
|
||||||
|
<vue-json-pretty v-if="log.data" :data="log.data"></vue-json-pretty>
|
||||||
|
</details>
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="_footer">
|
<div class="_footer">
|
||||||
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
<mk-button @click="deleteAllLogs()" primary><fa :icon="faTrashAlt"/> {{ $t('deleteAll') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card chart">
|
||||||
|
<div class="_title"><fa :icon="faMicrochip"/> {{ $t('cpuAndMemory') }}</div>
|
||||||
|
<div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
|
||||||
|
<canvas ref="cpumem"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="_content" v-if="serverInfo">
|
||||||
|
<div class="table">
|
||||||
|
<div class="row">
|
||||||
|
<div class="cell"><div class="label">CPU</div>{{ serverInfo.cpu.model }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="cell"><div class="label">MEM total</div>{{ serverInfo.mem.total | bytes }}</div>
|
||||||
|
<div class="cell"><div class="label">MEM used</div>{{ memUsage | bytes }} ({{ (memUsage / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
|
||||||
|
<div class="cell"><div class="label">MEM free</div>{{ serverInfo.mem.total - memUsage | bytes }} ({{ ((serverInfo.mem.total - memUsage) / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="_card chart">
|
||||||
|
<div class="_title"><fa :icon="faHdd"/> {{ $t('disk') }}</div>
|
||||||
|
<div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
|
||||||
|
<canvas ref="disk"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="_content" v-if="serverInfo">
|
||||||
|
<div class="table">
|
||||||
|
<div class="row">
|
||||||
|
<div class="cell"><div class="label">Disk total</div>{{ serverInfo.fs.total | bytes }}</div>
|
||||||
|
<div class="cell"><div class="label">Disk used</div>{{ serverInfo.fs.used | bytes }} ({{ (serverInfo.fs.used / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
|
||||||
|
<div class="cell"><div class="label">Disk free</div>{{ serverInfo.fs.total - serverInfo.fs.used | bytes }} ({{ ((serverInfo.fs.total - serverInfo.fs.used) / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="_card chart">
|
||||||
|
<div class="_title"><fa :icon="faExchangeAlt"/> {{ $t('network') }}</div>
|
||||||
|
<div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
|
||||||
|
<canvas ref="net"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="_content" v-if="serverInfo">
|
||||||
|
<div class="table">
|
||||||
|
<div class="row">
|
||||||
|
<div class="cell"><div class="label">Interface</div>{{ serverInfo.net.interface }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="_card info">
|
<section class="_card info">
|
||||||
<div class="_content">
|
|
||||||
<mk-input v-model="maxNoteTextLength" type="number" :save="() => save()" style="margin:0;"><template #icon><fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</mk-input>
|
|
||||||
</div>
|
|
||||||
<div class="_content">
|
|
||||||
<mk-switch v-model="enableLocalTimeline" @change="save()">{{ $t('enableLocalTimeline') }}</mk-switch>
|
|
||||||
<mk-switch v-model="enableGlobalTimeline" @change="save()">{{ $t('enableGlobalTimeline') }}</mk-switch>
|
|
||||||
<mk-info>{{ $t('disablingTimelinesInfo') }}</mk-info>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="_card info">
|
|
||||||
<div class="_title"><fa :icon="faUser"/> {{ $t('registration') }}</div>
|
|
||||||
<div class="_content">
|
|
||||||
<mk-switch v-model="enableRegistration" @change="save()">{{ $t('enableRegistration') }}</mk-switch>
|
|
||||||
<mk-button v-if="!enableRegistration" @click="invite">{{ $t('invite') }}</mk-button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faShieldAlt"/> {{ $t('recaptcha') }}</div>
|
|
||||||
<div class="_content">
|
|
||||||
<mk-switch v-model="enableRecaptcha">{{ $t('enableRecaptcha') }}</mk-switch>
|
|
||||||
<template v-if="enableRecaptcha">
|
|
||||||
<mk-info>{{ $t('recaptchaInfo') }}</mk-info>
|
|
||||||
<mk-info warn>{{ $t('recaptchaInfo2') }}</mk-info>
|
|
||||||
<mk-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSiteKey') }}</mk-input>
|
|
||||||
<mk-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSecretKey') }}</mk-input>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="_content" v-if="enableRecaptcha && recaptchaSiteKey">
|
|
||||||
<header>{{ $t('preview') }}</header>
|
|
||||||
<div ref="recaptcha" style="margin: 16px 0 0 0;" :key="recaptchaSiteKey"></div>
|
|
||||||
</div>
|
|
||||||
<div class="_footer">
|
|
||||||
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faBolt"/> {{ $t('serviceworker') }}</div>
|
|
||||||
<div class="_content">
|
|
||||||
<mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></mk-switch>
|
|
||||||
<template v-if="enableServiceWorker">
|
|
||||||
<mk-info>{{ $t('vapidInfo') }}<br><code>npx web-push generate-vapid-keys</code></mk-info>
|
|
||||||
<mk-horizon-group inputs class="fit-bottom">
|
|
||||||
<mk-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Public key</mk-input>
|
|
||||||
<mk-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Private key</mk-input>
|
|
||||||
</mk-horizon-group>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="_footer">
|
|
||||||
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div>
|
|
||||||
<div class="_content">
|
|
||||||
<mk-textarea v-model="pinnedUsers">
|
|
||||||
<template #desc>{{ $t('pinnedUsersDescription') }} <button class="_textButton" @click="addPinUser">{{ $t('addUser') }}</button></template>
|
|
||||||
</mk-textarea>
|
|
||||||
</div>
|
|
||||||
<div class="_footer">
|
|
||||||
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faCloud"/> {{ $t('files') }}</div>
|
|
||||||
<div class="_content">
|
|
||||||
<mk-switch v-model="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></mk-switch>
|
|
||||||
<mk-switch v-model="proxyRemoteFiles">{{ $t('proxyRemoteFiles') }}<template #desc>{{ $t('proxyRemoteFilesDescription') }}</template></mk-switch>
|
|
||||||
<mk-input v-model="localDriveCapacityMb" type="number">{{ $t('driveCapacityPerLocalAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></mk-input>
|
|
||||||
<mk-input v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" style="margin-bottom: 0;">{{ $t('driveCapacityPerRemoteAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></mk-input>
|
|
||||||
</div>
|
|
||||||
<div class="_footer">
|
|
||||||
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div>
|
|
||||||
<div class="_content">
|
|
||||||
<mk-input :value="proxyAccount ? proxyAccount.username : null" style="margin: 0;" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></mk-input>
|
|
||||||
<mk-button primary @click="chooseProxyAccount">{{ $t('chooseProxyAccount') }}</mk-button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faBan"/> {{ $t('blockedInstances') }}</div>
|
|
||||||
<div class="_content">
|
|
||||||
<mk-textarea v-model="blockedHosts">
|
|
||||||
<template #desc>{{ $t('blockedInstancesDescription') }}</template>
|
|
||||||
</mk-textarea>
|
|
||||||
</div>
|
|
||||||
<div class="_footer">
|
|
||||||
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faShareAlt"/> {{ $t('integration') }}</div>
|
|
||||||
<div class="_content">
|
|
||||||
<header><fa :icon="faTwitter"/> Twitter</header>
|
|
||||||
<mk-switch v-model="enableTwitterIntegration">{{ $t('enable') }}</mk-switch>
|
|
||||||
<template v-if="enableTwitterIntegration">
|
|
||||||
<mk-info>Callback URL: {{ `${url}/api/tw/cb` }}</mk-info>
|
|
||||||
<mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Key</mk-input>
|
|
||||||
<mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Secret</mk-input>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="_content">
|
|
||||||
<header><fa :icon="faGithub"/> GitHub</header>
|
|
||||||
<mk-switch v-model="enableGithubIntegration">{{ $t('enable') }}</mk-switch>
|
|
||||||
<template v-if="enableGithubIntegration">
|
|
||||||
<mk-info>Callback URL: {{ `${url}/api/gh/cb` }}</mk-info>
|
|
||||||
<mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
|
|
||||||
<mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="_content">
|
|
||||||
<header><fa :icon="faDiscord"/> Discord</header>
|
|
||||||
<mk-switch v-model="enableDiscordIntegration">{{ $t('enable') }}</mk-switch>
|
|
||||||
<template v-if="enableDiscordIntegration">
|
|
||||||
<mk-info>Callback URL: {{ `${url}/api/dc/cb` }}</mk-info>
|
|
||||||
<mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
|
|
||||||
<mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="_footer">
|
|
||||||
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="_card info">
|
|
||||||
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('instanceInfo') }}</div>
|
|
||||||
<div class="_content table" v-if="stats">
|
|
||||||
<div><b>{{ $t('users') }}</b><span>{{ stats.originalUsersCount | number }}</span></div>
|
|
||||||
<div><b>{{ $t('notes') }}</b><span>{{ stats.originalNotesCount | number }}</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="_content table">
|
<div class="_content table">
|
||||||
<div><b>Misskey</b><span>v{{ version }}</span></div>
|
<div><b>Misskey</b><span>v{{ version }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
@ -174,18 +99,23 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { faPencilAlt, faShareAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faThumbtack, faUser, faShieldAlt, faKey, faBolt } from '@fortawesome/free-solid-svg-icons';
|
import { faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faTrashAlt, faEnvelope } from '@fortawesome/free-regular-svg-icons';
|
import Chart from 'chart.js';
|
||||||
import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons';
|
import VueJsonPretty from 'vue-json-pretty';
|
||||||
|
import MkInstanceStats from '../../components/instance-stats.vue';
|
||||||
import MkButton from '../../components/ui/button.vue';
|
import MkButton from '../../components/ui/button.vue';
|
||||||
|
import MkSelect from '../../components/ui/select.vue';
|
||||||
import MkInput from '../../components/ui/input.vue';
|
import MkInput from '../../components/ui/input.vue';
|
||||||
import MkTextarea from '../../components/ui/textarea.vue';
|
|
||||||
import MkSwitch from '../../components/ui/switch.vue';
|
|
||||||
import MkInfo from '../../components/ui/info.vue';
|
|
||||||
import MkUserSelect from '../../components/user-select.vue';
|
|
||||||
import { version, url } from '../../config';
|
import { version, url } from '../../config';
|
||||||
import i18n from '../../i18n';
|
import i18n from '../../i18n';
|
||||||
import getAcct from '../../../misc/acct/render';
|
|
||||||
|
const alpha = (hex, a) => {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
|
||||||
|
const r = parseInt(result[1], 16);
|
||||||
|
const g = parseInt(result[2], 16);
|
||||||
|
const b = parseInt(result[3], 16);
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||||
|
};
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
@ -197,11 +127,11 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
MkInstanceStats,
|
||||||
MkButton,
|
MkButton,
|
||||||
|
MkSelect,
|
||||||
MkInput,
|
MkInput,
|
||||||
MkTextarea,
|
VueJsonPretty
|
||||||
MkSwitch,
|
|
||||||
MkInfo,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@ -210,41 +140,14 @@ export default Vue.extend({
|
|||||||
url,
|
url,
|
||||||
stats: null,
|
stats: null,
|
||||||
serverInfo: null,
|
serverInfo: null,
|
||||||
proxyAccount: null,
|
connection: null,
|
||||||
proxyAccountId: null,
|
memUsage: 0,
|
||||||
cacheRemoteFiles: false,
|
chartCpuMem: null,
|
||||||
proxyRemoteFiles: false,
|
chartNet: null,
|
||||||
localDriveCapacityMb: 0,
|
logs: [],
|
||||||
remoteDriveCapacityMb: 0,
|
logLevel: 'all',
|
||||||
blockedHosts: '',
|
logDomain: '',
|
||||||
pinnedUsers: '',
|
faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt
|
||||||
maintainerName: null,
|
|
||||||
maintainerEmail: null,
|
|
||||||
name: null,
|
|
||||||
description: null,
|
|
||||||
tosUrl: null,
|
|
||||||
bannerUrl: null,
|
|
||||||
iconUrl: null,
|
|
||||||
maxNoteTextLength: 0,
|
|
||||||
enableRegistration: false,
|
|
||||||
enableLocalTimeline: false,
|
|
||||||
enableGlobalTimeline: false,
|
|
||||||
enableRecaptcha: false,
|
|
||||||
recaptchaSiteKey: null,
|
|
||||||
recaptchaSecretKey: null,
|
|
||||||
enableServiceWorker: false,
|
|
||||||
swPublicKey: null,
|
|
||||||
swPrivateKey: null,
|
|
||||||
enableTwitterIntegration: false,
|
|
||||||
twitterConsumerKey: null,
|
|
||||||
twitterConsumerSecret: null,
|
|
||||||
enableGithubIntegration: false,
|
|
||||||
githubClientId: null,
|
|
||||||
githubClientSecret: null,
|
|
||||||
enableDiscordIntegration: false,
|
|
||||||
discordClientId: null,
|
|
||||||
discordClientSecret: null,
|
|
||||||
faPencilAlt, faTwitter, faDiscord, faGithub, faShareAlt, faTrashAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faEnvelope, faThumbtack, faUser, faShieldAlt, faKey, faBolt
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -254,153 +157,376 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
watch: {
|
||||||
this.name = this.meta.name;
|
logLevel() {
|
||||||
this.description = this.meta.description;
|
this.logs = [];
|
||||||
this.tosUrl = this.meta.tosUrl;
|
this.fetchLogs();
|
||||||
this.bannerUrl = this.meta.bannerUrl;
|
},
|
||||||
this.iconUrl = this.meta.iconUrl;
|
logDomain() {
|
||||||
this.maintainerName = this.meta.maintainerName;
|
this.logs = [];
|
||||||
this.maintainerEmail = this.meta.maintainerEmail;
|
this.fetchLogs();
|
||||||
this.maxNoteTextLength = this.meta.maxNoteTextLength;
|
|
||||||
this.enableRegistration = !this.meta.disableRegistration;
|
|
||||||
this.enableLocalTimeline = !this.meta.disableLocalTimeline;
|
|
||||||
this.enableGlobalTimeline = !this.meta.disableGlobalTimeline;
|
|
||||||
this.enableRecaptcha = this.meta.enableRecaptcha;
|
|
||||||
this.recaptchaSiteKey = this.meta.recaptchaSiteKey;
|
|
||||||
this.recaptchaSecretKey = this.meta.recaptchaSecretKey;
|
|
||||||
this.proxyAccountId = this.meta.proxyAccountId;
|
|
||||||
this.cacheRemoteFiles = this.meta.cacheRemoteFiles;
|
|
||||||
this.proxyRemoteFiles = this.meta.proxyRemoteFiles;
|
|
||||||
this.localDriveCapacityMb = this.meta.driveCapacityPerLocalUserMb;
|
|
||||||
this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb;
|
|
||||||
this.blockedHosts = this.meta.blockedHosts.join('\n');
|
|
||||||
this.pinnedUsers = this.meta.pinnedUsers.join('\n');
|
|
||||||
this.enableServiceWorker = this.meta.enableServiceWorker;
|
|
||||||
this.swPublicKey = this.meta.swPublickey;
|
|
||||||
this.swPrivateKey = this.meta.swPrivateKey;
|
|
||||||
this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
|
|
||||||
this.twitterConsumerKey = this.meta.twitterConsumerKey;
|
|
||||||
this.twitterConsumerSecret = this.meta.twitterConsumerSecret;
|
|
||||||
this.enableGithubIntegration = this.meta.enableGithubIntegration;
|
|
||||||
this.githubClientId = this.meta.githubClientId;
|
|
||||||
this.githubClientSecret = this.meta.githubClientSecret;
|
|
||||||
this.enableDiscordIntegration = this.meta.enableDiscordIntegration;
|
|
||||||
this.discordClientId = this.meta.discordClientId;
|
|
||||||
this.discordClientSecret = this.meta.discordClientSecret;
|
|
||||||
|
|
||||||
if (this.proxyAccountId) {
|
|
||||||
this.$root.api('users/show', { userId: this.proxyAccountId }).then(proxyAccount => {
|
|
||||||
this.proxyAccount = proxyAccount;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$root.api('admin/server-info').then(res => {
|
|
||||||
this.serverInfo = res;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$root.api('stats').then(res => {
|
|
||||||
this.stats = res;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
const renderRecaptchaPreview = () => {
|
this.fetchLogs();
|
||||||
if (!(window as any).grecaptcha) return;
|
|
||||||
if (!this.$refs.recaptcha) return;
|
Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
|
||||||
if (!this.recaptchaSiteKey) return;
|
|
||||||
(window as any).grecaptcha.render(this.$refs.recaptcha, {
|
this.chartCpuMem = new Chart(this.$refs.cpumem, {
|
||||||
sitekey: this.recaptchaSiteKey
|
type: 'line',
|
||||||
});
|
data: {
|
||||||
};
|
labels: [],
|
||||||
window.onRecaotchaLoad = () => {
|
datasets: [{
|
||||||
renderRecaptchaPreview();
|
label: 'CPU',
|
||||||
};
|
pointRadius: 0,
|
||||||
const head = document.getElementsByTagName('head')[0];
|
lineTension: 0,
|
||||||
const script = document.createElement('script');
|
borderWidth: 2,
|
||||||
script.setAttribute('src', 'https://www.google.com/recaptcha/api.js?onload=onRecaotchaLoad');
|
borderColor: '#86b300',
|
||||||
head.appendChild(script);
|
backgroundColor: alpha('#86b300', 0.1),
|
||||||
this.$watch('enableRecaptcha', () => {
|
data: []
|
||||||
renderRecaptchaPreview();
|
}, {
|
||||||
|
label: 'MEM (active)',
|
||||||
|
pointRadius: 0,
|
||||||
|
lineTension: 0,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#935dbf',
|
||||||
|
backgroundColor: alpha('#935dbf', 0.02),
|
||||||
|
data: []
|
||||||
|
}, {
|
||||||
|
label: 'MEM (used)',
|
||||||
|
pointRadius: 0,
|
||||||
|
lineTension: 0,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#935dbf',
|
||||||
|
borderDash: [5, 5],
|
||||||
|
fill: false,
|
||||||
|
data: []
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
aspectRatio: 3,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 8,
|
||||||
|
bottom: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
boxWidth: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
gridLines: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
yAxes: [{
|
||||||
|
position: 'right',
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
intersect: false,
|
||||||
|
mode: 'index',
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.$watch('recaptchaSiteKey', () => {
|
|
||||||
renderRecaptchaPreview();
|
this.chartNet = new Chart(this.$refs.net, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'In',
|
||||||
|
pointRadius: 0,
|
||||||
|
lineTension: 0,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#94a029',
|
||||||
|
backgroundColor: alpha('#94a029', 0.1),
|
||||||
|
data: []
|
||||||
|
}, {
|
||||||
|
label: 'Out',
|
||||||
|
pointRadius: 0,
|
||||||
|
lineTension: 0,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#ff9156',
|
||||||
|
backgroundColor: alpha('#ff9156', 0.1),
|
||||||
|
data: []
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
aspectRatio: 3,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 8,
|
||||||
|
bottom: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
boxWidth: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
gridLines: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
yAxes: [{
|
||||||
|
position: 'right',
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
intersect: false,
|
||||||
|
mode: 'index',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.chartDisk = new Chart(this.$refs.disk, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Read',
|
||||||
|
pointRadius: 0,
|
||||||
|
lineTension: 0,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#94a029',
|
||||||
|
backgroundColor: alpha('#94a029', 0.1),
|
||||||
|
data: []
|
||||||
|
}, {
|
||||||
|
label: 'Write',
|
||||||
|
pointRadius: 0,
|
||||||
|
lineTension: 0,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#ff9156',
|
||||||
|
backgroundColor: alpha('#ff9156', 0.1),
|
||||||
|
data: []
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
aspectRatio: 3,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 8,
|
||||||
|
bottom: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
boxWidth: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
gridLines: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
yAxes: [{
|
||||||
|
position: 'right',
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
intersect: false,
|
||||||
|
mode: 'index',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$root.api('admin/server-info', {}).then(res => {
|
||||||
|
this.serverInfo = res;
|
||||||
|
|
||||||
|
this.connection = this.$root.stream.useSharedConnection('serverStats');
|
||||||
|
this.connection.on('stats', this.onStats);
|
||||||
|
this.connection.on('statsLog', this.onStatsLog);
|
||||||
|
this.connection.send('requestLog', {
|
||||||
|
id: Math.random().toString().substr(2, 8),
|
||||||
|
length: 150
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.connection.off('stats', this.onStats);
|
||||||
|
this.connection.off('statsLog', this.onStatsLog);
|
||||||
|
this.connection.dispose();
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
addPinUser() {
|
fetchLogs() {
|
||||||
this.$root.new(MkUserSelect, {}).$once('selected', user => {
|
this.$root.api('admin/logs', {
|
||||||
this.pinnedUsers = this.pinnedUsers.trim();
|
level: this.logLevel === 'all' ? null : this.logLevel,
|
||||||
this.pinnedUsers += '\n@' + getAcct(user);
|
domain: this.logDomain === '' ? null : this.logDomain,
|
||||||
this.pinnedUsers = this.pinnedUsers.trim();
|
limit: 30
|
||||||
|
}).then(logs => {
|
||||||
|
this.logs = logs.reverse();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
chooseProxyAccount() {
|
deleteAllLogs() {
|
||||||
this.$root.new(MkUserSelect, {}).$once('selected', user => {
|
this.$root.api('admin/delete-logs').then(() => {
|
||||||
this.proxyAccount = user;
|
|
||||||
this.proxyAccountId = user.id;
|
|
||||||
this.save(true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
save(withDialog = false) {
|
|
||||||
this.$root.api('admin/update-meta', {
|
|
||||||
name: this.name,
|
|
||||||
description: this.description,
|
|
||||||
tosUrl: this.tosUrl,
|
|
||||||
bannerUrl: this.bannerUrl,
|
|
||||||
iconUrl: this.iconUrl,
|
|
||||||
maintainerName: this.maintainerName,
|
|
||||||
maintainerEmail: this.maintainerEmail,
|
|
||||||
maxNoteTextLength: this.maxNoteTextLength,
|
|
||||||
disableRegistration: !this.enableRegistration,
|
|
||||||
disableLocalTimeline: !this.enableLocalTimeline,
|
|
||||||
disableGlobalTimeline: !this.enableGlobalTimeline,
|
|
||||||
enableRecaptcha: this.enableRecaptcha,
|
|
||||||
recaptchaSiteKey: this.recaptchaSiteKey,
|
|
||||||
recaptchaSecretKey: this.recaptchaSecretKey,
|
|
||||||
proxyAccountId: this.proxyAccountId,
|
|
||||||
cacheRemoteFiles: this.cacheRemoteFiles,
|
|
||||||
proxyRemoteFiles: this.proxyRemoteFiles,
|
|
||||||
localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
|
|
||||||
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
|
|
||||||
blockedHosts: this.blockedHosts.split('\n') || [],
|
|
||||||
pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [],
|
|
||||||
enableServiceWorker: this.enableServiceWorker,
|
|
||||||
swPublicKey: this.swPublicKey,
|
|
||||||
swPrivateKey: this.swPrivateKey,
|
|
||||||
enableTwitterIntegration: this.enableTwitterIntegration,
|
|
||||||
twitterConsumerKey: this.twitterConsumerKey,
|
|
||||||
twitterConsumerSecret: this.twitterConsumerSecret,
|
|
||||||
enableGithubIntegration: this.enableGithubIntegration,
|
|
||||||
githubClientId: this.githubClientId,
|
|
||||||
githubClientSecret: this.githubClientSecret,
|
|
||||||
enableDiscordIntegration: this.enableDiscordIntegration,
|
|
||||||
discordClientId: this.discordClientId,
|
|
||||||
discordClientSecret: this.discordClientSecret,
|
|
||||||
}).then(() => {
|
|
||||||
this.$store.dispatch('instance/fetch');
|
|
||||||
if (withDialog) {
|
|
||||||
this.$root.dialog({
|
|
||||||
type: 'success',
|
|
||||||
iconOnly: true, autoClose: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
this.$root.dialog({
|
this.$root.dialog({
|
||||||
type: 'error',
|
type: 'success',
|
||||||
text: e
|
iconOnly: true, autoClose: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onStats(stats) {
|
||||||
|
const cpu = (stats.cpu * 100).toFixed(0);
|
||||||
|
const memActive = (stats.mem.active / this.serverInfo.mem.total * 100).toFixed(0);
|
||||||
|
const memUsed = (stats.mem.used / this.serverInfo.mem.total * 100).toFixed(0);
|
||||||
|
this.memUsage = stats.mem.active;
|
||||||
|
|
||||||
|
this.chartCpuMem.data.labels.push('');
|
||||||
|
this.chartCpuMem.data.datasets[0].data.push(cpu);
|
||||||
|
this.chartCpuMem.data.datasets[1].data.push(memActive);
|
||||||
|
this.chartCpuMem.data.datasets[2].data.push(memUsed);
|
||||||
|
this.chartNet.data.labels.push('');
|
||||||
|
this.chartNet.data.datasets[0].data.push(stats.net.rx);
|
||||||
|
this.chartNet.data.datasets[1].data.push(stats.net.tx);
|
||||||
|
this.chartDisk.data.labels.push('');
|
||||||
|
this.chartDisk.data.datasets[0].data.push(stats.fs.r);
|
||||||
|
this.chartDisk.data.datasets[1].data.push(stats.fs.w);
|
||||||
|
if (this.chartCpuMem.data.datasets[0].data.length > 150) {
|
||||||
|
this.chartCpuMem.data.labels.shift();
|
||||||
|
this.chartCpuMem.data.datasets[0].data.shift();
|
||||||
|
this.chartCpuMem.data.datasets[1].data.shift();
|
||||||
|
this.chartCpuMem.data.datasets[2].data.shift();
|
||||||
|
this.chartNet.data.labels.shift();
|
||||||
|
this.chartNet.data.datasets[0].data.shift();
|
||||||
|
this.chartNet.data.datasets[1].data.shift();
|
||||||
|
this.chartDisk.data.labels.shift();
|
||||||
|
this.chartDisk.data.datasets[0].data.shift();
|
||||||
|
this.chartDisk.data.datasets[1].data.shift();
|
||||||
|
}
|
||||||
|
this.chartCpuMem.update();
|
||||||
|
this.chartNet.update();
|
||||||
|
this.chartDisk.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
onStatsLog(statsLog) {
|
||||||
|
for (const stats of statsLog.reverse()) {
|
||||||
|
this.onStats(stats);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mk-instance-page {
|
.xhexznfu {
|
||||||
|
> .stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: calc(0px - var(--margin) / 2);
|
||||||
|
margin-bottom: calc(var(--margin) / 2);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex: 1 0 213px;
|
||||||
|
margin: calc(var(--margin) / 2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .logs {
|
||||||
|
> ._content {
|
||||||
|
> .logs {
|
||||||
|
padding: 8px;
|
||||||
|
background: #000;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
> code {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
color: #f00;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
color: #ff0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
color: #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.debug {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .chart {
|
||||||
|
> ._content {
|
||||||
|
> .table {
|
||||||
|
> .row {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .cell {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
> .label {
|
||||||
|
font-size: 80%;
|
||||||
|
opacity: 0.7;
|
||||||
|
|
||||||
|
> .icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .info {
|
> .info {
|
||||||
> .table {
|
> .table {
|
||||||
> div {
|
> div {
|
||||||
|
@ -1,381 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mk-instance-monitor">
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faMicrochip"/> {{ $t('cpuAndMemory') }}</div>
|
|
||||||
<div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
|
|
||||||
<canvas ref="cpumem"></canvas>
|
|
||||||
</div>
|
|
||||||
<div class="_content" v-if="serverInfo">
|
|
||||||
<div class="table">
|
|
||||||
<div class="row">
|
|
||||||
<div class="cell"><div class="label">CPU</div>{{ serverInfo.cpu.model }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="cell"><div class="label">MEM total</div>{{ serverInfo.mem.total | bytes }}</div>
|
|
||||||
<div class="cell"><div class="label">MEM used</div>{{ memUsage | bytes }} ({{ (memUsage / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
|
|
||||||
<div class="cell"><div class="label">MEM free</div>{{ serverInfo.mem.total - memUsage | bytes }} ({{ ((serverInfo.mem.total - memUsage) / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faHdd"/> {{ $t('disk') }}</div>
|
|
||||||
<div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
|
|
||||||
<canvas ref="disk"></canvas>
|
|
||||||
</div>
|
|
||||||
<div class="_content" v-if="serverInfo">
|
|
||||||
<div class="table">
|
|
||||||
<div class="row">
|
|
||||||
<div class="cell"><div class="label">Disk total</div>{{ serverInfo.fs.total | bytes }}</div>
|
|
||||||
<div class="cell"><div class="label">Disk used</div>{{ serverInfo.fs.used | bytes }} ({{ (serverInfo.fs.used / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
|
|
||||||
<div class="cell"><div class="label">Disk free</div>{{ serverInfo.fs.total - serverInfo.fs.used | bytes }} ({{ ((serverInfo.fs.total - serverInfo.fs.used) / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="_card">
|
|
||||||
<div class="_title"><fa :icon="faExchangeAlt"/> {{ $t('network') }}</div>
|
|
||||||
<div class="_content" style="margin-top: -8px; margin-bottom: -12px;">
|
|
||||||
<canvas ref="net"></canvas>
|
|
||||||
</div>
|
|
||||||
<div class="_content" v-if="serverInfo">
|
|
||||||
<div class="table">
|
|
||||||
<div class="row">
|
|
||||||
<div class="cell"><div class="label">Interface</div>{{ serverInfo.net.interface }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import { faTachometerAlt, faExchangeAlt, faMicrochip, faHdd } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import Chart from 'chart.js';
|
|
||||||
import i18n from '../../i18n';
|
|
||||||
|
|
||||||
const alpha = (hex, a) => {
|
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
|
|
||||||
const r = parseInt(result[1], 16);
|
|
||||||
const g = parseInt(result[2], 16);
|
|
||||||
const b = parseInt(result[3], 16);
|
|
||||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
i18n,
|
|
||||||
|
|
||||||
metaInfo() {
|
|
||||||
return {
|
|
||||||
title: `${this.$t('monitor')} | ${this.$t('instance')}`
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
components: {
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
connection: null,
|
|
||||||
serverInfo: null,
|
|
||||||
memUsage: 0,
|
|
||||||
chartCpuMem: null,
|
|
||||||
chartNet: null,
|
|
||||||
faTachometerAlt, faExchangeAlt, faMicrochip, faHdd
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
|
|
||||||
|
|
||||||
this.chartCpuMem = new Chart(this.$refs.cpumem, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: [{
|
|
||||||
label: 'CPU',
|
|
||||||
pointRadius: 0,
|
|
||||||
lineTension: 0,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#86b300',
|
|
||||||
backgroundColor: alpha('#86b300', 0.1),
|
|
||||||
data: []
|
|
||||||
}, {
|
|
||||||
label: 'MEM (active)',
|
|
||||||
pointRadius: 0,
|
|
||||||
lineTension: 0,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#935dbf',
|
|
||||||
backgroundColor: alpha('#935dbf', 0.02),
|
|
||||||
data: []
|
|
||||||
}, {
|
|
||||||
label: 'MEM (used)',
|
|
||||||
pointRadius: 0,
|
|
||||||
lineTension: 0,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#935dbf',
|
|
||||||
borderDash: [5, 5],
|
|
||||||
fill: false,
|
|
||||||
data: []
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
aspectRatio: 3,
|
|
||||||
layout: {
|
|
||||||
padding: {
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 8,
|
|
||||||
bottom: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
position: 'bottom',
|
|
||||||
labels: {
|
|
||||||
boxWidth: 16,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
xAxes: [{
|
|
||||||
gridLines: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
display: false
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
yAxes: [{
|
|
||||||
position: 'right',
|
|
||||||
ticks: {
|
|
||||||
display: false,
|
|
||||||
max: 100
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
intersect: false,
|
|
||||||
mode: 'index',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chartNet = new Chart(this.$refs.net, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: [{
|
|
||||||
label: 'In',
|
|
||||||
pointRadius: 0,
|
|
||||||
lineTension: 0,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#94a029',
|
|
||||||
backgroundColor: alpha('#94a029', 0.1),
|
|
||||||
data: []
|
|
||||||
}, {
|
|
||||||
label: 'Out',
|
|
||||||
pointRadius: 0,
|
|
||||||
lineTension: 0,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#ff9156',
|
|
||||||
backgroundColor: alpha('#ff9156', 0.1),
|
|
||||||
data: []
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
aspectRatio: 3,
|
|
||||||
layout: {
|
|
||||||
padding: {
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 8,
|
|
||||||
bottom: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
position: 'bottom',
|
|
||||||
labels: {
|
|
||||||
boxWidth: 16,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
xAxes: [{
|
|
||||||
gridLines: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
display: false
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
yAxes: [{
|
|
||||||
position: 'right',
|
|
||||||
ticks: {
|
|
||||||
display: false,
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
intersect: false,
|
|
||||||
mode: 'index',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chartDisk = new Chart(this.$refs.disk, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: [{
|
|
||||||
label: 'Read',
|
|
||||||
pointRadius: 0,
|
|
||||||
lineTension: 0,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#94a029',
|
|
||||||
backgroundColor: alpha('#94a029', 0.1),
|
|
||||||
data: []
|
|
||||||
}, {
|
|
||||||
label: 'Write',
|
|
||||||
pointRadius: 0,
|
|
||||||
lineTension: 0,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#ff9156',
|
|
||||||
backgroundColor: alpha('#ff9156', 0.1),
|
|
||||||
data: []
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
aspectRatio: 3,
|
|
||||||
layout: {
|
|
||||||
padding: {
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 8,
|
|
||||||
bottom: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
position: 'bottom',
|
|
||||||
labels: {
|
|
||||||
boxWidth: 16,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
xAxes: [{
|
|
||||||
gridLines: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
display: false
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
yAxes: [{
|
|
||||||
position: 'right',
|
|
||||||
ticks: {
|
|
||||||
display: false,
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
intersect: false,
|
|
||||||
mode: 'index',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$root.api('admin/server-info', {}).then(res => {
|
|
||||||
this.serverInfo = res;
|
|
||||||
|
|
||||||
this.connection = this.$root.stream.useSharedConnection('serverStats');
|
|
||||||
this.connection.on('stats', this.onStats);
|
|
||||||
this.connection.on('statsLog', this.onStatsLog);
|
|
||||||
this.connection.send('requestLog', {
|
|
||||||
id: Math.random().toString().substr(2, 8),
|
|
||||||
length: 150
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.connection.off('stats', this.onStats);
|
|
||||||
this.connection.off('statsLog', this.onStatsLog);
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
onStats(stats) {
|
|
||||||
const cpu = (stats.cpu * 100).toFixed(0);
|
|
||||||
const memActive = (stats.mem.active / this.serverInfo.mem.total * 100).toFixed(0);
|
|
||||||
const memUsed = (stats.mem.used / this.serverInfo.mem.total * 100).toFixed(0);
|
|
||||||
this.memUsage = stats.mem.active;
|
|
||||||
|
|
||||||
this.chartCpuMem.data.labels.push('');
|
|
||||||
this.chartCpuMem.data.datasets[0].data.push(cpu);
|
|
||||||
this.chartCpuMem.data.datasets[1].data.push(memActive);
|
|
||||||
this.chartCpuMem.data.datasets[2].data.push(memUsed);
|
|
||||||
this.chartNet.data.labels.push('');
|
|
||||||
this.chartNet.data.datasets[0].data.push(stats.net.rx);
|
|
||||||
this.chartNet.data.datasets[1].data.push(stats.net.tx);
|
|
||||||
this.chartDisk.data.labels.push('');
|
|
||||||
this.chartDisk.data.datasets[0].data.push(stats.fs.r);
|
|
||||||
this.chartDisk.data.datasets[1].data.push(stats.fs.w);
|
|
||||||
if (this.chartCpuMem.data.datasets[0].data.length > 150) {
|
|
||||||
this.chartCpuMem.data.labels.shift();
|
|
||||||
this.chartCpuMem.data.datasets[0].data.shift();
|
|
||||||
this.chartCpuMem.data.datasets[1].data.shift();
|
|
||||||
this.chartCpuMem.data.datasets[2].data.shift();
|
|
||||||
this.chartNet.data.labels.shift();
|
|
||||||
this.chartNet.data.datasets[0].data.shift();
|
|
||||||
this.chartNet.data.datasets[1].data.shift();
|
|
||||||
this.chartDisk.data.labels.shift();
|
|
||||||
this.chartDisk.data.datasets[0].data.shift();
|
|
||||||
this.chartDisk.data.datasets[1].data.shift();
|
|
||||||
}
|
|
||||||
this.chartCpuMem.update();
|
|
||||||
this.chartNet.update();
|
|
||||||
this.chartDisk.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
onStatsLog(statsLog) {
|
|
||||||
for (const stats of statsLog.reverse()) {
|
|
||||||
this.onStats(stats);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.mk-instance-monitor {
|
|
||||||
> section {
|
|
||||||
> ._content {
|
|
||||||
> .table {
|
|
||||||
> .row {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .cell {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
> .label {
|
|
||||||
font-size: 80%;
|
|
||||||
opacity: 0.7;
|
|
||||||
|
|
||||||
> .icon {
|
|
||||||
margin-right: 4px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
427
src/client/pages/instance/settings.vue
Normal file
427
src/client/pages/instance/settings.vue
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="meta">
|
||||||
|
<portal to="icon"><fa :icon="faCog"/></portal>
|
||||||
|
<portal to="title">{{ $t('settings') }}</portal>
|
||||||
|
|
||||||
|
<section class="_card info">
|
||||||
|
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-input v-model="name">{{ $t('instanceName') }}</mk-input>
|
||||||
|
<mk-textarea v-model="description">{{ $t('instanceDescription') }}</mk-textarea>
|
||||||
|
<mk-input v-model="iconUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('iconUrl') }}</mk-input>
|
||||||
|
<mk-input v-model="bannerUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('bannerUrl') }}</mk-input>
|
||||||
|
<mk-input v-model="tosUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('tosUrl') }}</mk-input>
|
||||||
|
<mk-input v-model="maintainerName">{{ $t('maintainerName') }}</mk-input>
|
||||||
|
<mk-input v-model="maintainerEmail" type="email"><template #icon><fa :icon="faEnvelope"/></template>{{ $t('maintainerEmail') }}</mk-input>
|
||||||
|
</div>
|
||||||
|
<div class="_footer">
|
||||||
|
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card info">
|
||||||
|
<div class="_content">
|
||||||
|
<mk-input v-model="maxNoteTextLength" type="number" :save="() => save()" style="margin:0;"><template #icon><fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</mk-input>
|
||||||
|
</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-switch v-model="enableLocalTimeline" @change="save()">{{ $t('enableLocalTimeline') }}</mk-switch>
|
||||||
|
<mk-switch v-model="enableGlobalTimeline" @change="save()">{{ $t('enableGlobalTimeline') }}</mk-switch>
|
||||||
|
<mk-info>{{ $t('disablingTimelinesInfo') }}</mk-info>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card info">
|
||||||
|
<div class="_title"><fa :icon="faUser"/> {{ $t('registration') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-switch v-model="enableRegistration" @change="save()">{{ $t('enableRegistration') }}</mk-switch>
|
||||||
|
<mk-button v-if="!enableRegistration" @click="invite">{{ $t('invite') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card">
|
||||||
|
<div class="_title"><fa :icon="faShieldAlt"/> {{ $t('recaptcha') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-switch v-model="enableRecaptcha">{{ $t('enableRecaptcha') }}</mk-switch>
|
||||||
|
<template v-if="enableRecaptcha">
|
||||||
|
<mk-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSiteKey') }}</mk-input>
|
||||||
|
<mk-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSecretKey') }}</mk-input>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="_content" v-if="enableRecaptcha && recaptchaSiteKey">
|
||||||
|
<header>{{ $t('preview') }}</header>
|
||||||
|
<div ref="recaptcha" style="margin: 16px 0 0 0;" :key="recaptchaSiteKey"></div>
|
||||||
|
</div>
|
||||||
|
<div class="_footer">
|
||||||
|
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card">
|
||||||
|
<div class="_title"><fa :icon="faBolt"/> {{ $t('serviceworker') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></mk-switch>
|
||||||
|
<template v-if="enableServiceWorker">
|
||||||
|
<div class="_inputs">
|
||||||
|
<mk-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Public key</mk-input>
|
||||||
|
<mk-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Private key</mk-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="_footer">
|
||||||
|
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card">
|
||||||
|
<div class="_title"><fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-textarea v-model="pinnedUsers">
|
||||||
|
<template #desc>{{ $t('pinnedUsersDescription') }} <button class="_textButton" @click="addPinUser">{{ $t('addUser') }}</button></template>
|
||||||
|
</mk-textarea>
|
||||||
|
</div>
|
||||||
|
<div class="_footer">
|
||||||
|
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card">
|
||||||
|
<div class="_title"><fa :icon="faCloud"/> {{ $t('files') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-switch v-model="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></mk-switch>
|
||||||
|
<mk-switch v-model="proxyRemoteFiles">{{ $t('proxyRemoteFiles') }}<template #desc>{{ $t('proxyRemoteFilesDescription') }}</template></mk-switch>
|
||||||
|
<mk-input v-model="localDriveCapacityMb" type="number">{{ $t('driveCapacityPerLocalAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></mk-input>
|
||||||
|
<mk-input v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" style="margin-bottom: 0;">{{ $t('driveCapacityPerRemoteAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></mk-input>
|
||||||
|
</div>
|
||||||
|
<div class="_footer">
|
||||||
|
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card">
|
||||||
|
<div class="_title"><fa :icon="faCloud"/> {{ $t('objectStorage') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-switch v-model="useObjectStorage">{{ $t('useObjectStorage') }}</mk-switch>
|
||||||
|
<template v-if="useObjectStorage">
|
||||||
|
<mk-input v-model="objectStorageBaseUrl" :disabled="!useObjectStorage">URL</mk-input>
|
||||||
|
<div class="_inputs">
|
||||||
|
<mk-input v-model="objectStorageBucket" :disabled="!useObjectStorage">Bucket</mk-input>
|
||||||
|
<mk-input v-model="objectStoragePrefix" :disabled="!useObjectStorage">Prefix</mk-input>
|
||||||
|
</div>
|
||||||
|
<mk-input v-model="objectStorageEndpoint" :disabled="!useObjectStorage">Endpoint</mk-input>
|
||||||
|
<div class="_inputs">
|
||||||
|
<mk-input v-model="objectStorageRegion" :disabled="!useObjectStorage">Region</mk-input>
|
||||||
|
<mk-input v-model="objectStoragePort" type="number" :disabled="!useObjectStorage">Port</mk-input>
|
||||||
|
</div>
|
||||||
|
<div class="_inputs">
|
||||||
|
<mk-input v-model="objectStorageAccessKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Access key</mk-input>
|
||||||
|
<mk-input v-model="objectStorageSecretKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Secret key</mk-input>
|
||||||
|
</div>
|
||||||
|
<mk-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">SSL</mk-switch>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="_footer">
|
||||||
|
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card">
|
||||||
|
<div class="_title"><fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-input :value="proxyAccount ? proxyAccount.username : null" style="margin: 0;" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></mk-input>
|
||||||
|
<mk-button primary @click="chooseProxyAccount">{{ $t('chooseProxyAccount') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card">
|
||||||
|
<div class="_title"><fa :icon="faBan"/> {{ $t('blockedInstances') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-textarea v-model="blockedHosts">
|
||||||
|
<template #desc>{{ $t('blockedInstancesDescription') }}</template>
|
||||||
|
</mk-textarea>
|
||||||
|
</div>
|
||||||
|
<div class="_footer">
|
||||||
|
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="_card">
|
||||||
|
<div class="_title"><fa :icon="faShareAlt"/> {{ $t('integration') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<header><fa :icon="faTwitter"/> Twitter</header>
|
||||||
|
<mk-switch v-model="enableTwitterIntegration">{{ $t('enable') }}</mk-switch>
|
||||||
|
<template v-if="enableTwitterIntegration">
|
||||||
|
<mk-info>Callback URL: {{ `${url}/api/tw/cb` }}</mk-info>
|
||||||
|
<mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Key</mk-input>
|
||||||
|
<mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Secret</mk-input>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="_content">
|
||||||
|
<header><fa :icon="faGithub"/> GitHub</header>
|
||||||
|
<mk-switch v-model="enableGithubIntegration">{{ $t('enable') }}</mk-switch>
|
||||||
|
<template v-if="enableGithubIntegration">
|
||||||
|
<mk-info>Callback URL: {{ `${url}/api/gh/cb` }}</mk-info>
|
||||||
|
<mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
|
||||||
|
<mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="_content">
|
||||||
|
<header><fa :icon="faDiscord"/> Discord</header>
|
||||||
|
<mk-switch v-model="enableDiscordIntegration">{{ $t('enable') }}</mk-switch>
|
||||||
|
<template v-if="enableDiscordIntegration">
|
||||||
|
<mk-info>Callback URL: {{ `${url}/api/dc/cb` }}</mk-info>
|
||||||
|
<mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
|
||||||
|
<mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="_footer">
|
||||||
|
<mk-button primary @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { faPencilAlt, faShareAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faThumbtack, faUser, faShieldAlt, faKey, faBolt } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { faTrashAlt, faEnvelope } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons';
|
||||||
|
import MkButton from '../../components/ui/button.vue';
|
||||||
|
import MkInput from '../../components/ui/input.vue';
|
||||||
|
import MkTextarea from '../../components/ui/textarea.vue';
|
||||||
|
import MkSwitch from '../../components/ui/switch.vue';
|
||||||
|
import MkInfo from '../../components/ui/info.vue';
|
||||||
|
import MkUserSelect from '../../components/user-select.vue';
|
||||||
|
import { url } from '../../config';
|
||||||
|
import i18n from '../../i18n';
|
||||||
|
import getAcct from '../../../misc/acct/render';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n,
|
||||||
|
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
title: this.$t('instance') as string
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
MkButton,
|
||||||
|
MkInput,
|
||||||
|
MkTextarea,
|
||||||
|
MkSwitch,
|
||||||
|
MkInfo,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
proxyAccount: null,
|
||||||
|
proxyAccountId: null,
|
||||||
|
cacheRemoteFiles: false,
|
||||||
|
proxyRemoteFiles: false,
|
||||||
|
localDriveCapacityMb: 0,
|
||||||
|
remoteDriveCapacityMb: 0,
|
||||||
|
blockedHosts: '',
|
||||||
|
pinnedUsers: '',
|
||||||
|
maintainerName: null,
|
||||||
|
maintainerEmail: null,
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
tosUrl: null,
|
||||||
|
bannerUrl: null,
|
||||||
|
iconUrl: null,
|
||||||
|
maxNoteTextLength: 0,
|
||||||
|
enableRegistration: false,
|
||||||
|
enableLocalTimeline: false,
|
||||||
|
enableGlobalTimeline: false,
|
||||||
|
enableRecaptcha: false,
|
||||||
|
recaptchaSiteKey: null,
|
||||||
|
recaptchaSecretKey: null,
|
||||||
|
enableServiceWorker: false,
|
||||||
|
swPublicKey: null,
|
||||||
|
swPrivateKey: null,
|
||||||
|
useObjectStorage: false,
|
||||||
|
objectStorageBaseUrl: null,
|
||||||
|
objectStorageBucket: null,
|
||||||
|
objectStoragePrefix: null,
|
||||||
|
objectStorageEndpoint: null,
|
||||||
|
objectStorageRegion: null,
|
||||||
|
objectStoragePort: null,
|
||||||
|
objectStorageAccessKey: null,
|
||||||
|
objectStorageSecretKey: null,
|
||||||
|
objectStorageUseSSL: false,
|
||||||
|
enableTwitterIntegration: false,
|
||||||
|
twitterConsumerKey: null,
|
||||||
|
twitterConsumerSecret: null,
|
||||||
|
enableGithubIntegration: false,
|
||||||
|
githubClientId: null,
|
||||||
|
githubClientSecret: null,
|
||||||
|
enableDiscordIntegration: false,
|
||||||
|
discordClientId: null,
|
||||||
|
discordClientSecret: null,
|
||||||
|
faPencilAlt, faTwitter, faDiscord, faGithub, faShareAlt, faTrashAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faEnvelope, faThumbtack, faUser, faShieldAlt, faKey, faBolt
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
meta() {
|
||||||
|
return this.$store.state.instance.meta;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.name = this.meta.name;
|
||||||
|
this.description = this.meta.description;
|
||||||
|
this.tosUrl = this.meta.tosUrl;
|
||||||
|
this.bannerUrl = this.meta.bannerUrl;
|
||||||
|
this.iconUrl = this.meta.iconUrl;
|
||||||
|
this.maintainerName = this.meta.maintainerName;
|
||||||
|
this.maintainerEmail = this.meta.maintainerEmail;
|
||||||
|
this.maxNoteTextLength = this.meta.maxNoteTextLength;
|
||||||
|
this.enableRegistration = !this.meta.disableRegistration;
|
||||||
|
this.enableLocalTimeline = !this.meta.disableLocalTimeline;
|
||||||
|
this.enableGlobalTimeline = !this.meta.disableGlobalTimeline;
|
||||||
|
this.enableRecaptcha = this.meta.enableRecaptcha;
|
||||||
|
this.recaptchaSiteKey = this.meta.recaptchaSiteKey;
|
||||||
|
this.recaptchaSecretKey = this.meta.recaptchaSecretKey;
|
||||||
|
this.proxyAccountId = this.meta.proxyAccountId;
|
||||||
|
this.cacheRemoteFiles = this.meta.cacheRemoteFiles;
|
||||||
|
this.proxyRemoteFiles = this.meta.proxyRemoteFiles;
|
||||||
|
this.localDriveCapacityMb = this.meta.driveCapacityPerLocalUserMb;
|
||||||
|
this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb;
|
||||||
|
this.blockedHosts = this.meta.blockedHosts.join('\n');
|
||||||
|
this.pinnedUsers = this.meta.pinnedUsers.join('\n');
|
||||||
|
this.enableServiceWorker = this.meta.enableServiceWorker;
|
||||||
|
this.swPublicKey = this.meta.swPublickey;
|
||||||
|
this.swPrivateKey = this.meta.swPrivateKey;
|
||||||
|
this.useObjectStorage = this.meta.useObjectStorage;
|
||||||
|
this.objectStorageBaseUrl = this.meta.objectStorageBaseUrl;
|
||||||
|
this.objectStorageBucket = this.meta.objectStorageBucket;
|
||||||
|
this.objectStoragePrefix = this.meta.objectStoragePrefix;
|
||||||
|
this.objectStorageEndpoint = this.meta.objectStorageEndpoint;
|
||||||
|
this.objectStorageRegion = this.meta.objectStorageRegion;
|
||||||
|
this.objectStoragePort = this.meta.objectStoragePort;
|
||||||
|
this.objectStorageAccessKey = this.meta.objectStorageAccessKey;
|
||||||
|
this.objectStorageSecretKey = this.meta.objectStorageSecretKey;
|
||||||
|
this.objectStorageUseSSL = this.meta.objectStorageUseSSL;
|
||||||
|
this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
|
||||||
|
this.twitterConsumerKey = this.meta.twitterConsumerKey;
|
||||||
|
this.twitterConsumerSecret = this.meta.twitterConsumerSecret;
|
||||||
|
this.enableGithubIntegration = this.meta.enableGithubIntegration;
|
||||||
|
this.githubClientId = this.meta.githubClientId;
|
||||||
|
this.githubClientSecret = this.meta.githubClientSecret;
|
||||||
|
this.enableDiscordIntegration = this.meta.enableDiscordIntegration;
|
||||||
|
this.discordClientId = this.meta.discordClientId;
|
||||||
|
this.discordClientSecret = this.meta.discordClientSecret;
|
||||||
|
|
||||||
|
if (this.proxyAccountId) {
|
||||||
|
this.$root.api('users/show', { userId: this.proxyAccountId }).then(proxyAccount => {
|
||||||
|
this.proxyAccount = proxyAccount;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
const renderRecaptchaPreview = () => {
|
||||||
|
if (!(window as any).grecaptcha) return;
|
||||||
|
if (!this.$refs.recaptcha) return;
|
||||||
|
if (!this.recaptchaSiteKey) return;
|
||||||
|
(window as any).grecaptcha.render(this.$refs.recaptcha, {
|
||||||
|
sitekey: this.recaptchaSiteKey
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.onRecaotchaLoad = () => {
|
||||||
|
renderRecaptchaPreview();
|
||||||
|
};
|
||||||
|
const head = document.getElementsByTagName('head')[0];
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.setAttribute('src', 'https://www.google.com/recaptcha/api.js?onload=onRecaotchaLoad');
|
||||||
|
head.appendChild(script);
|
||||||
|
this.$watch('enableRecaptcha', () => {
|
||||||
|
renderRecaptchaPreview();
|
||||||
|
});
|
||||||
|
this.$watch('recaptchaSiteKey', () => {
|
||||||
|
renderRecaptchaPreview();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
addPinUser() {
|
||||||
|
this.$root.new(MkUserSelect, {}).$once('selected', user => {
|
||||||
|
this.pinnedUsers = this.pinnedUsers.trim();
|
||||||
|
this.pinnedUsers += '\n@' + getAcct(user);
|
||||||
|
this.pinnedUsers = this.pinnedUsers.trim();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
chooseProxyAccount() {
|
||||||
|
this.$root.new(MkUserSelect, {}).$once('selected', user => {
|
||||||
|
this.proxyAccount = user;
|
||||||
|
this.proxyAccountId = user.id;
|
||||||
|
this.save(true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
save(withDialog = false) {
|
||||||
|
this.$root.api('admin/update-meta', {
|
||||||
|
name: this.name,
|
||||||
|
description: this.description,
|
||||||
|
tosUrl: this.tosUrl,
|
||||||
|
bannerUrl: this.bannerUrl,
|
||||||
|
iconUrl: this.iconUrl,
|
||||||
|
maintainerName: this.maintainerName,
|
||||||
|
maintainerEmail: this.maintainerEmail,
|
||||||
|
maxNoteTextLength: this.maxNoteTextLength,
|
||||||
|
disableRegistration: !this.enableRegistration,
|
||||||
|
disableLocalTimeline: !this.enableLocalTimeline,
|
||||||
|
disableGlobalTimeline: !this.enableGlobalTimeline,
|
||||||
|
enableRecaptcha: this.enableRecaptcha,
|
||||||
|
recaptchaSiteKey: this.recaptchaSiteKey,
|
||||||
|
recaptchaSecretKey: this.recaptchaSecretKey,
|
||||||
|
proxyAccountId: this.proxyAccountId,
|
||||||
|
cacheRemoteFiles: this.cacheRemoteFiles,
|
||||||
|
proxyRemoteFiles: this.proxyRemoteFiles,
|
||||||
|
localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
|
||||||
|
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
|
||||||
|
blockedHosts: this.blockedHosts.split('\n') || [],
|
||||||
|
pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [],
|
||||||
|
enableServiceWorker: this.enableServiceWorker,
|
||||||
|
swPublicKey: this.swPublicKey,
|
||||||
|
swPrivateKey: this.swPrivateKey,
|
||||||
|
useObjectStorage: this.useObjectStorage,
|
||||||
|
objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null,
|
||||||
|
objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null,
|
||||||
|
objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null,
|
||||||
|
objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null,
|
||||||
|
objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null,
|
||||||
|
objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null,
|
||||||
|
objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null,
|
||||||
|
objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null,
|
||||||
|
objectStorageUseSSL: this.objectStorageUseSSL,
|
||||||
|
enableTwitterIntegration: this.enableTwitterIntegration,
|
||||||
|
twitterConsumerKey: this.twitterConsumerKey,
|
||||||
|
twitterConsumerSecret: this.twitterConsumerSecret,
|
||||||
|
enableGithubIntegration: this.enableGithubIntegration,
|
||||||
|
githubClientId: this.githubClientId,
|
||||||
|
githubClientSecret: this.githubClientSecret,
|
||||||
|
enableDiscordIntegration: this.enableDiscordIntegration,
|
||||||
|
discordClientId: this.discordClientId,
|
||||||
|
discordClientSecret: this.discordClientSecret,
|
||||||
|
}).then(() => {
|
||||||
|
this.$store.dispatch('instance/fetch');
|
||||||
|
if (withDialog) {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'success',
|
||||||
|
iconOnly: true, autoClose: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: e
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -310,7 +310,7 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .text {
|
> .text {
|
||||||
&, * {
|
&, ::v-deep * {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,12 +184,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onMessage(message) {
|
onMessage(message) {
|
||||||
// サウンドを再生する
|
this.$root.sound('chat');
|
||||||
if (this.$store.state.device.enableSounds) {
|
|
||||||
const sound = new Audio(`${url}/assets/message.mp3`);
|
|
||||||
sound.volume = this.$store.state.device.soundVolume;
|
|
||||||
sound.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
const isBottom = this.isBottom();
|
const isBottom = this.isBottom();
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</sequential-entrance>
|
</sequential-entrance>
|
||||||
<div class="no-history" v-if="!fetching && messages.length == 0">
|
<div class="no-history" v-if="!fetching && messages.length == 0">
|
||||||
<img src="https://xn--931a.moe/assets/info.png" alt="" class="_ghost"/>
|
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
|
||||||
<div>{{ $t('noHistory') }}</div>
|
<div>{{ $t('noHistory') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<mk-loading v-if="fetching"/>
|
<mk-loading v-if="fetching"/>
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
<span>{{ $t('antennaKeywords') }}</span>
|
<span>{{ $t('antennaKeywords') }}</span>
|
||||||
<template #desc>{{ $t('antennaKeywordsDescription') }}</template>
|
<template #desc>{{ $t('antennaKeywordsDescription') }}</template>
|
||||||
</mk-textarea>
|
</mk-textarea>
|
||||||
|
<mk-textarea v-model="excludeKeywords">
|
||||||
|
<span>{{ $t('antennaExcludeKeywords') }}</span>
|
||||||
|
<template #desc>{{ $t('antennaKeywordsDescription') }}</template>
|
||||||
|
</mk-textarea>
|
||||||
<mk-switch v-model="caseSensitive">{{ $t('caseSensitive') }}</mk-switch>
|
<mk-switch v-model="caseSensitive">{{ $t('caseSensitive') }}</mk-switch>
|
||||||
<mk-switch v-model="withFile">{{ $t('withFileAntenna') }}</mk-switch>
|
<mk-switch v-model="withFile">{{ $t('withFileAntenna') }}</mk-switch>
|
||||||
<mk-switch v-model="notify">{{ $t('notifyAntenna') }}</mk-switch>
|
<mk-switch v-model="notify">{{ $t('notifyAntenna') }}</mk-switch>
|
||||||
@ -75,6 +79,7 @@ export default Vue.extend({
|
|||||||
userGroupId: null,
|
userGroupId: null,
|
||||||
users: '',
|
users: '',
|
||||||
keywords: '',
|
keywords: '',
|
||||||
|
excludeKeywords: '',
|
||||||
caseSensitive: false,
|
caseSensitive: false,
|
||||||
withReplies: false,
|
withReplies: false,
|
||||||
withFile: false,
|
withFile: false,
|
||||||
@ -107,6 +112,7 @@ export default Vue.extend({
|
|||||||
this.userGroupId = this.antenna.userGroupId;
|
this.userGroupId = this.antenna.userGroupId;
|
||||||
this.users = this.antenna.users.join('\n');
|
this.users = this.antenna.users.join('\n');
|
||||||
this.keywords = this.antenna.keywords.map(x => x.join(' ')).join('\n');
|
this.keywords = this.antenna.keywords.map(x => x.join(' ')).join('\n');
|
||||||
|
this.excludeKeywords = this.antenna.excludeKeywords.map(x => x.join(' ')).join('\n');
|
||||||
this.caseSensitive = this.antenna.caseSensitive;
|
this.caseSensitive = this.antenna.caseSensitive;
|
||||||
this.withReplies = this.antenna.withReplies;
|
this.withReplies = this.antenna.withReplies;
|
||||||
this.withFile = this.antenna.withFile;
|
this.withFile = this.antenna.withFile;
|
||||||
@ -126,7 +132,8 @@ export default Vue.extend({
|
|||||||
notify: this.notify,
|
notify: this.notify,
|
||||||
caseSensitive: this.caseSensitive,
|
caseSensitive: this.caseSensitive,
|
||||||
users: this.users.trim().split('\n').map(x => x.trim()),
|
users: this.users.trim().split('\n').map(x => x.trim()),
|
||||||
keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' '))
|
keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')),
|
||||||
|
excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
|
||||||
});
|
});
|
||||||
this.$emit('created');
|
this.$emit('created');
|
||||||
} else {
|
} else {
|
||||||
@ -141,7 +148,8 @@ export default Vue.extend({
|
|||||||
notify: this.notify,
|
notify: this.notify,
|
||||||
caseSensitive: this.caseSensitive,
|
caseSensitive: this.caseSensitive,
|
||||||
users: this.users.trim().split('\n').map(x => x.trim()),
|
users: this.users.trim().split('\n').map(x => x.trim()),
|
||||||
keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' '))
|
keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')),
|
||||||
|
excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ export default Vue.extend({
|
|||||||
userGroupId: null,
|
userGroupId: null,
|
||||||
users: [],
|
users: [],
|
||||||
keywords: [],
|
keywords: [],
|
||||||
|
excludeKeywords: [],
|
||||||
withReplies: false,
|
withReplies: false,
|
||||||
caseSensitive: false,
|
caseSensitive: false,
|
||||||
withFile: false,
|
withFile: false,
|
||||||
|
109
src/client/pages/my-settings/index.vue
Normal file
109
src/client/pages/my-settings/index.vue
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<portal to="icon"><fa :icon="faCog"/></portal>
|
||||||
|
<portal to="title">{{ $t('accountSettings') }}</portal>
|
||||||
|
|
||||||
|
<x-profile-setting/>
|
||||||
|
<x-privacy-setting/>
|
||||||
|
<x-reaction-setting/>
|
||||||
|
|
||||||
|
<section class="_card">
|
||||||
|
<div class="_title"><fa :icon="faCog"/> {{ $t('general') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-switch v-model="$store.state.i.autoWatch" @change="onChangeAutoWatch">
|
||||||
|
{{ $t('autoNoteWatch') }}<template #desc>{{ $t('autoNoteWatchDescription') }}</template>
|
||||||
|
</mk-switch>
|
||||||
|
<mk-switch v-model="$store.state.i.injectFeaturedNote" @change="onChangeInjectFeaturedNote">
|
||||||
|
{{ $t('showFeaturedNotesInTimeline') }}
|
||||||
|
</mk-switch>
|
||||||
|
</div>
|
||||||
|
<div class="_content">
|
||||||
|
<mk-button @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</mk-button>
|
||||||
|
<mk-button @click="readAllUnreadNotes">{{ $t('markAsReadAllUnreadNotes') }}</mk-button>
|
||||||
|
<mk-button @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<x-import-export/>
|
||||||
|
<x-drive/>
|
||||||
|
<x-mute-block/>
|
||||||
|
<x-security/>
|
||||||
|
<x-2fa/>
|
||||||
|
<x-integration/>
|
||||||
|
<x-api/>
|
||||||
|
|
||||||
|
<mk-button @click="$root.signout()" primary style="margin: var(--margin) auto;">{{ $t('logout') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { faCog } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import XProfileSetting from './profile.vue';
|
||||||
|
import XPrivacySetting from './privacy.vue';
|
||||||
|
import XImportExport from './import-export.vue';
|
||||||
|
import XDrive from './drive.vue';
|
||||||
|
import XReactionSetting from './reaction.vue';
|
||||||
|
import XMuteBlock from './mute-block.vue';
|
||||||
|
import XSecurity from './security.vue';
|
||||||
|
import X2fa from './2fa.vue';
|
||||||
|
import XIntegration from './integration.vue';
|
||||||
|
import XApi from './api.vue';
|
||||||
|
import MkButton from '../../components/ui/button.vue';
|
||||||
|
import MkSwitch from '../../components/ui/switch.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
title: this.$t('settings') as string
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
XProfileSetting,
|
||||||
|
XPrivacySetting,
|
||||||
|
XImportExport,
|
||||||
|
XDrive,
|
||||||
|
XReactionSetting,
|
||||||
|
XMuteBlock,
|
||||||
|
XSecurity,
|
||||||
|
X2fa,
|
||||||
|
XIntegration,
|
||||||
|
XApi,
|
||||||
|
MkButton,
|
||||||
|
MkSwitch,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
faCog
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onChangeAutoWatch(v) {
|
||||||
|
this.$root.api('i/update', {
|
||||||
|
autoWatch: v
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeInjectFeaturedNote(v) {
|
||||||
|
this.$root.api('i/update', {
|
||||||
|
injectFeaturedNote: v
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
readAllUnreadNotes() {
|
||||||
|
this.$root.api('i/read_all_unread_notes');
|
||||||
|
},
|
||||||
|
|
||||||
|
readAllMessagingMessages() {
|
||||||
|
this.$root.api('i/read_all_messaging_messages');
|
||||||
|
},
|
||||||
|
|
||||||
|
readAllNotifications() {
|
||||||
|
this.$root.api('notifications/mark_all_as_read');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user