Compare commits
166 Commits
Author | SHA1 | Date | |
---|---|---|---|
6fb7721798 | |||
0018fd469e | |||
019f7480e8 | |||
8e0b088deb | |||
452005381f | |||
9722ed99a3 | |||
ee6311e83d | |||
1471e52307 | |||
f1fc12d9cc | |||
ebbc42bebc | |||
4785ee8c32 | |||
ab40756c1a | |||
c88e737a84 | |||
def5ea7978 | |||
e69ab45044 | |||
25d0b4bbf1 | |||
f86f5ac6cc | |||
07ce365bfd | |||
f31c94e2ea | |||
933638d035 | |||
b0151afa9a | |||
5bbd4ae703 | |||
f2f7f532a0 | |||
80eedf7449 | |||
1b48e0d6e0 | |||
0420c548da | |||
6e98b75d13 | |||
e772cb00d1 | |||
b5d5275e9b | |||
a2d3d22b6e | |||
1ad8603cc2 | |||
aeaf535ea2 | |||
917726fecc | |||
49a5b4eb14 | |||
8946f3ea18 | |||
1947835c51 | |||
c7c537c8b8 | |||
4e2f954683 | |||
abb0184329 | |||
ee483ecfd3 | |||
99384b4c22 | |||
65503bc68d | |||
ec6aadb5ce | |||
5f642886d9 | |||
4c26e3c54d | |||
3ca3712bae | |||
a471e4b783 | |||
20ac7e62e9 | |||
3e61aa0835 | |||
c18f6fde80 | |||
8b9397a0ce | |||
678ff17d0f | |||
ea2016c208 | |||
b5bdf266d3 | |||
2309680c38 | |||
e99bf569c5 | |||
d3fd0f810a | |||
484dc9b08a | |||
3bd827d7da | |||
d425c72134 | |||
969cd16638 | |||
569be15705 | |||
03f54c5b02 | |||
06ddc8ec50 | |||
1528935008 | |||
7121bdef6b | |||
f6c376f20d | |||
241769d6fc | |||
7727651871 | |||
ce331826ac | |||
ae92c52d61 | |||
54aef5fe6f | |||
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 | |||
f799375635 | |||
65704bbf01 | |||
9cb3882efa | |||
a0833ca691 | |||
a4f197f608 |
@ -88,7 +88,9 @@ redis:
|
||||
#elasticsearch:
|
||||
# host: localhost
|
||||
# port: 9200
|
||||
# pass: null
|
||||
# ssl: false
|
||||
# user:
|
||||
# pass:
|
||||
|
||||
# ┌───────────────┐
|
||||
#───┘ ID generation └───────────────────────────────────────────
|
||||
|
11
.github/workflows/nodejs.yml
vendored
11
.github/workflows/nodejs.yml
vendored
@ -21,6 +21,7 @@ jobs:
|
||||
- 5432:5432
|
||||
env:
|
||||
POSTGRES_DB: test-misskey
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
redis:
|
||||
image: redis:alpine
|
||||
ports:
|
||||
@ -40,3 +41,13 @@ jobs:
|
||||
run: yarn build
|
||||
- name: Test
|
||||
run: yarn test
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- run: yarn install
|
||||
- run: yarn lint
|
||||
|
4
.mocharc.json
Normal file
4
.mocharc.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"timeout": 30000,
|
||||
"slow": 1000
|
||||
}
|
87
CHANGELOG.md
87
CHANGELOG.md
@ -1,6 +1,93 @@
|
||||
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
|
||||
|
28
README.md
28
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/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://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
|
||||
</tr><tr>
|
||||
<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>
|
||||
@ -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=24430516">Eduardo Quiros</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>
|
||||
<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://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>
|
||||
@ -130,9 +129,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/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/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
|
||||
</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/user?u=557245">mkatze</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y</a></td>
|
||||
@ -141,56 +139,64 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
|
||||
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
|
||||
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
|
||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
|
||||
</tr></table>
|
||||
<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/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/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
|
||||
</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=17866454">sikyosyounin</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=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=20494440">axtuki1</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
|
||||
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
|
||||
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
|
||||
</tr></table>
|
||||
<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/28295158/cd2451bfb94a449dbf705ef4718cd355/2.jpeg?token-time=2145916800&token-hash=MRv3BxufHPuCyiBSxU5UYmLGvD6YZlhtSFRfMWg2k4U%3D" alt="012" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic" width="100"></td>
|
||||
<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>
|
||||
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=28295158">012</a></td>
|
||||
<td><a href="https://www.patreon.com/nijimiss">nafuchoco</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
|
||||
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
|
||||
<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/user?u=24641572">uroco @99</a></td>
|
||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
|
||||
<td><a href="https://www.patreon.com/efertone">Efertone</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Wed, 05 Feb 2020 00:42:12 UTC
|
||||
**Last updated:** Tue, 17 Mar 2020 18:57:08 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
[backer-url]: #backers
|
||||
|
@ -26,14 +26,86 @@ 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"
|
||||
nsfw: "NSFW"
|
||||
disconnectedFromServer: "Connection to the server was inturrupted"
|
||||
reload: "Refresh"
|
||||
doNothing: "Ignore"
|
||||
reloadConfirm: "Would you like to retry?"
|
||||
watch: "Watch"
|
||||
unwatch: "Undo Watch"
|
||||
@ -283,7 +285,8 @@ antennas: "Antennas"
|
||||
manageAntennas: "Manage Antennas"
|
||||
name: "Name"
|
||||
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."
|
||||
notifyAntenna: "Notify newer notes"
|
||||
withFileAntenna: "Filter only notes with file attached"
|
||||
@ -412,6 +415,29 @@ 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:
|
||||
unknown: "Unknown"
|
||||
future: "Future"
|
||||
@ -513,6 +539,7 @@ _widgets:
|
||||
clock: "Clock"
|
||||
rss: "RSS reader"
|
||||
activity: "Activity"
|
||||
photos: "Photos"
|
||||
_cw:
|
||||
hide: "Hide"
|
||||
show: "Load more"
|
||||
|
@ -239,6 +239,8 @@ avatar: "Avatar"
|
||||
banner: "Banner"
|
||||
nsfw: "Marcado como sensible"
|
||||
disconnectedFromServer: "Desconectado del servidor"
|
||||
reload: "Recargar"
|
||||
doNothing: "No hacer nada"
|
||||
reloadConfirm: "¿Desea recargar?"
|
||||
watch: "Ver"
|
||||
unwatch: "Dejar de ver"
|
||||
@ -283,7 +285,8 @@ antennas: "Antenas"
|
||||
manageAntennas: "Administrar antenas"
|
||||
name: "Nombre"
|
||||
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"
|
||||
notifyAntenna: "Notificar nueva nota"
|
||||
withFileAntenna: "Sólo notas con archivos adjuntados"
|
||||
@ -412,6 +415,29 @@ 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:
|
||||
unknown: "Desconocido"
|
||||
future: "Futuro"
|
||||
@ -513,6 +539,7 @@ _widgets:
|
||||
clock: "Reloj"
|
||||
rss: "Lector RSS"
|
||||
activity: "Actividad"
|
||||
photos: "Fotos"
|
||||
_cw:
|
||||
hide: "Ocultar"
|
||||
show: "Ver más"
|
||||
|
@ -239,6 +239,8 @@ avatar: "Avatar"
|
||||
banner: "Bannière"
|
||||
nsfw: "Contenu sensible"
|
||||
disconnectedFromServer: "Déconnecté du serveur"
|
||||
reload: "Rafraîchir"
|
||||
doNothing: "Ignorer"
|
||||
reloadConfirm: "Voulez-vous recharger?"
|
||||
watch: "Surveiller"
|
||||
unwatch: "Ne plus surveiller"
|
||||
@ -283,7 +285,8 @@ antennas: "Antenne"
|
||||
manageAntennas: "Gestion d'antenne"
|
||||
name: "Nom"
|
||||
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."
|
||||
notifyAntenna: "Notifier les nouvelles notes"
|
||||
withFileAntenna: "Notes uniquement avec fichiers joints"
|
||||
@ -412,6 +415,29 @@ 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:
|
||||
unknown: "Inconnu"
|
||||
future: "Futur"
|
||||
@ -431,6 +457,16 @@ _time:
|
||||
_tutorial:
|
||||
title: "Comment utiliser Misskey"
|
||||
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:
|
||||
alreadyRegistered: "Cette étape à déjà été complétée"
|
||||
registerDevice: "S’inscrire l'appareil"
|
||||
@ -493,6 +529,7 @@ _widgets:
|
||||
clock: "Horloge"
|
||||
rss: "Lecteur de flux RSS"
|
||||
activity: "Activités"
|
||||
photos: "Photos"
|
||||
_cw:
|
||||
hide: "Masquer"
|
||||
show: "Voir plus"
|
||||
|
@ -84,7 +84,7 @@ clickToShow: "クリックして表示"
|
||||
sensitive: "閲覧注意"
|
||||
add: "追加"
|
||||
reaction: "リアクション"
|
||||
reactionSettingDescription: "リアクションピッカーに表示するリアクションを改行で区切って設定します。"
|
||||
reactionSettingDescription: "リアクションピッカーに表示するリアクションを設定します。"
|
||||
rememberNoteVisibility: "公開範囲を記憶する"
|
||||
renameFile: "ファイル名を変更"
|
||||
attachCancel: "添付取り消し"
|
||||
@ -168,6 +168,7 @@ intro: "Misskeyのインストールが完了しました!管理者アカウ
|
||||
done: "完了"
|
||||
processing: "処理中"
|
||||
preview: "プレビュー"
|
||||
default: "デフォルト"
|
||||
noCustomEmojis: "絵文字はありません"
|
||||
customEmojisOfRemote: "リモートの絵文字"
|
||||
noJobs: "ジョブはありません"
|
||||
@ -239,6 +240,8 @@ avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
nsfw: "閲覧注意"
|
||||
disconnectedFromServer: "サーバーから切断されました"
|
||||
reload: "リロード"
|
||||
doNothing: "なにもしない"
|
||||
reloadConfirm: "リロードしますか?"
|
||||
watch: "ウォッチ"
|
||||
unwatch: "ウォッチ解除"
|
||||
@ -284,6 +287,7 @@ manageAntennas: "アンテナの管理"
|
||||
name: "名前"
|
||||
antennaSource: "受信ソース"
|
||||
antennaKeywords: "受信キーワード"
|
||||
antennaExcludeKeywords: "除外キーワード"
|
||||
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
||||
notifyAntenna: "新しいノートを通知する"
|
||||
withFileAntenna: "ファイルが添付されたノートのみ"
|
||||
@ -417,6 +421,38 @@ promote: "プロモート"
|
||||
numberOfDays: "日数"
|
||||
hideThisNote: "このノートを非表示"
|
||||
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示する"
|
||||
objectStorage: "オブジェクトストレージ"
|
||||
useObjectStorage: "オブジェクトストレージを使用"
|
||||
objectStorageBaseUrl: "Base URL"
|
||||
objectStorageBaseUrlDesc: "参照に使用するURL。CDNやProxyを使用している場合はそのURL、S3: 'https://<bucket>.s3.amazonaws.com'、GCS等: 'https://storage.googleapis.com/<bucket>'。"
|
||||
objectStorageBucket: "Bucket"
|
||||
objectStorageBucketDesc: "使用サービスのbucket名を指定してください。"
|
||||
objectStoragePrefix: "Prefix"
|
||||
objectStoragePrefixDesc: "このprefixのディレクトリ下に格納されます。"
|
||||
objectStorageEndpoint: "Endpoint"
|
||||
objectStorageEndpointDesc: "S3の場合は空、それ以外の場合は各サービスのendpointを指定してください。'<host>'または'<host>:<port>'のように指定します。"
|
||||
objectStorageRegion: "Region"
|
||||
objectStorageRegionDesc: "'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は、空または'us-east-1'にしてください。"
|
||||
objectStorageUseSSL: "SSLを使用する"
|
||||
objectStorageUseSSLDesc: "API接続にhttpsを使用しない場合はオフにしてください"
|
||||
serverLogs: "サーバーログ"
|
||||
deleteAll: "全て削除"
|
||||
showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
|
||||
newNoteRecived: "新しいノートがあります"
|
||||
sounds: "サウンド"
|
||||
listen: "聴く"
|
||||
none: "なし"
|
||||
volume: "音量"
|
||||
details: "詳細"
|
||||
chooseEmoji: "絵文字を選択"
|
||||
|
||||
_sfx:
|
||||
note: "ノート"
|
||||
noteMy: "ノート(自分)"
|
||||
notification: "通知"
|
||||
chat: "チャット"
|
||||
chatBg: "チャット(バックグラウンド)"
|
||||
antenna: "アンテナ受信"
|
||||
|
||||
_ago:
|
||||
unknown: "謎"
|
||||
|
@ -109,6 +109,8 @@ aboutMisskey: "Misskeyってなんや?"
|
||||
notFoundDescription: "指定されたURLに該当するページはあらへんやった。"
|
||||
close: "さいなら"
|
||||
joinedGroups: "参加しとるグループ"
|
||||
_sfx:
|
||||
notification: "通知"
|
||||
_ago:
|
||||
unknown: "謎"
|
||||
future: "未来"
|
||||
|
@ -23,9 +23,43 @@ 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: "ಬಳಕೆಹೆಸರು"
|
||||
|
@ -239,6 +239,8 @@ avatar: "아바타"
|
||||
banner: "배너"
|
||||
nsfw: "열람주의"
|
||||
disconnectedFromServer: "서버와의 연결이 끊어졌습니다"
|
||||
reload: "새로고침"
|
||||
doNothing: "무시하기"
|
||||
reloadConfirm: "새로고침 하시겠습니까?"
|
||||
watch: "지켜보기"
|
||||
unwatch: "지켜보기 해제"
|
||||
@ -284,6 +286,7 @@ manageAntennas: "안테나 관리"
|
||||
name: "이름"
|
||||
antennaSource: "받을 소스"
|
||||
antennaKeywords: "받을 키워드"
|
||||
antennaExcludeKeywords: "제외할 키워드"
|
||||
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
|
||||
notifyAntenna: "새로운 노트를 알림"
|
||||
withFileAntenna: "파일이 첨부된 노트만"
|
||||
@ -412,6 +415,29 @@ 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:
|
||||
unknown: "알 수 없음"
|
||||
future: "미래"
|
||||
@ -513,6 +539,7 @@ _widgets:
|
||||
clock: "시계"
|
||||
rss: "RSS 리더"
|
||||
activity: "활동"
|
||||
photos: "사진"
|
||||
_cw:
|
||||
hide: "숨기기"
|
||||
show: "더 보기"
|
||||
|
@ -1,2 +1,36 @@
|
||||
---
|
||||
_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: "Показать еще"
|
||||
|
@ -315,8 +315,10 @@ moderator: "版主"
|
||||
nUsersMentioned: "{n} 被提到"
|
||||
securityKey: "安全密钥"
|
||||
securityKeyName: "密钥名称"
|
||||
registerSecurityKey: "注册安全密钥"
|
||||
lastUsed: "最后使用:"
|
||||
unregister: "删除账户"
|
||||
passwordLessLogin: "无密码登录"
|
||||
resetPassword: "重置密码"
|
||||
newPasswordIs: "新的密码是「{password}」"
|
||||
post: "投稿"
|
||||
@ -327,10 +329,12 @@ autoNoteWatchDescription: "让您能够收到关于「反应」和回复其他
|
||||
reduceUiAnimation: "减少UI动画"
|
||||
share: "分享"
|
||||
notFound: "未找到"
|
||||
notFoundDescription: "没有与指定URL对应的页面。"
|
||||
uploadFolder: "默认上传文件夹"
|
||||
cacheClear: "清空缓存"
|
||||
markAsReadAllNotifications: "将所有通知标为已读"
|
||||
markAsReadAllUnreadNotes: "将所有帖子标记为已读"
|
||||
markAsReadAllTalkMessages: "将所有聊天标记为已读"
|
||||
help: "帮助"
|
||||
inputMessageHere: "在此键入信息"
|
||||
close: "关闭"
|
||||
@ -343,6 +347,8 @@ invites: "邀请"
|
||||
groupName: "群组名"
|
||||
members: "成员"
|
||||
transfer: "转让"
|
||||
messagingWithUser: "与用户聊天"
|
||||
messagingWithGroup: "与群组聊天"
|
||||
title: "标题"
|
||||
text: "文本"
|
||||
enable: "启用"
|
||||
@ -353,6 +359,7 @@ inviteToGroup: "群组邀请"
|
||||
maxNoteTextLength: "帖子的字数限制"
|
||||
quoteAttached: "已引用"
|
||||
quoteQuestion: "是否将其作为引用附上?"
|
||||
noMessagesYet: "现在没有新的聊天"
|
||||
newMessageExists: "新信息"
|
||||
onlyOneFileCanBeAttached: "只能添加一个附件"
|
||||
signinRequired: "请先登录"
|
||||
@ -368,6 +375,8 @@ normalPassword: "密码强度:中等"
|
||||
strongPassword: "密码强度:强"
|
||||
passwordMatched: "密码一致"
|
||||
passwordNotMatched: "密码不一致"
|
||||
signinWith: "以{x}登录"
|
||||
tapSecurityKey: "点击安全密钥"
|
||||
or: "或者"
|
||||
uiLanguage: "显示语言"
|
||||
groupInvited: "群组招待"
|
||||
@ -380,6 +389,7 @@ disableAnimatedMfm: "禁用MFM动画"
|
||||
doing: "正在进行"
|
||||
category: "类别"
|
||||
tags: "标签"
|
||||
docSource: "文件来源"
|
||||
createAccount: "注册账户"
|
||||
existingAcount: "现有的帐户"
|
||||
regenerate: "重新生成"
|
||||
@ -395,6 +405,21 @@ dayOverDayChanges: "与前一日相比"
|
||||
accessibility: "辅助功能"
|
||||
clinetSettings: "客户端设置"
|
||||
accountSettings: "账户设置"
|
||||
numberOfDays: "天数"
|
||||
hideThisNote: "隐藏这条帖子"
|
||||
showFeaturedNotesInTimeline: "在时间轴上显示热门推荐"
|
||||
objectStorage: "对象存储"
|
||||
useObjectStorage: "使用对象存储"
|
||||
serverLogs: "服务器日志"
|
||||
deleteAll: "删除全部"
|
||||
showFixedPostForm: "在时间线顶部显示帖子表单"
|
||||
newNoteRecived: "有新的帖子"
|
||||
useNotificationsPopup: "在弹出窗口中显示通知列表"
|
||||
none: "空"
|
||||
_sfx:
|
||||
note: "帖子"
|
||||
notification: "通知"
|
||||
chat: "聊天"
|
||||
_ago:
|
||||
unknown: "未知"
|
||||
future: "未来"
|
||||
@ -414,6 +439,16 @@ _time:
|
||||
_tutorial:
|
||||
title: "Misskey的使用方法"
|
||||
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:
|
||||
alreadyRegistered: "此设备已被注册"
|
||||
@ -447,6 +482,7 @@ _auth:
|
||||
permissionAsk: "这个应用程序需要以下权限"
|
||||
_antennaSources:
|
||||
all: "所有帖子"
|
||||
homeTimeline: "已关注用户的帖子"
|
||||
_weekday:
|
||||
sunday: "星期日"
|
||||
monday: "星期一"
|
||||
@ -464,6 +500,7 @@ _widgets:
|
||||
clock: "时钟"
|
||||
rss: "RSS阅读器"
|
||||
activity: "活动"
|
||||
photos: "照片"
|
||||
_cw:
|
||||
hide: "隐藏"
|
||||
show: "查看更多"
|
||||
@ -534,6 +571,7 @@ _charts:
|
||||
notesIncDec: "帖子:增加/减少"
|
||||
notesTotal: "帖子总数"
|
||||
_instanceCharts:
|
||||
requests: "请求"
|
||||
users: "用户数量:增加/减少"
|
||||
usersTotal: "用户总数"
|
||||
notes: "帖子:增加/减少"
|
||||
|
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);
|
||||
}
|
||||
|
||||
}
|
14
migration/1582875306439-note-reaction-length.ts
Normal file
14
migration/1582875306439-note-reaction-length.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class noteReactionLength1582875306439 implements MigrationInterface {
|
||||
name = 'noteReactionLength1582875306439'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(130)`, undefined);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(128)`, undefined);
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||
"version": "12.13.0",
|
||||
"version": "12.22.0",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -21,7 +21,7 @@
|
||||
"gulp": "gulp build",
|
||||
"clean": "gulp clean",
|
||||
"cleanall": "gulp cleanall",
|
||||
"lint": "gulp lint",
|
||||
"lint": "tslint 'src/**/*.ts'",
|
||||
"test": "cross-env TS_NODE_FILES=true gulp test",
|
||||
"format": "gulp format"
|
||||
},
|
||||
@ -250,7 +250,6 @@
|
||||
"vue-meta": "2.3.2",
|
||||
"vue-prism-component": "1.1.1",
|
||||
"vue-router": "3.1.5",
|
||||
"vue-sequential-entrance": "1.1.3",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-svg-inline-loader": "1.4.5",
|
||||
"vue-template-compiler": "2.6.11",
|
||||
|
9
src/@types/jsrsasign.d.ts
vendored
9
src/@types/jsrsasign.d.ts
vendored
@ -171,6 +171,7 @@ declare module 'jsrsasign' {
|
||||
|
||||
public static getTLVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): ASN1TLV;
|
||||
|
||||
// tslint:disable-next-line:bool-param-default
|
||||
public static getVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string, removeUnusedbits?: boolean): ASN1V;
|
||||
|
||||
public static hextooidstr(hex: ASN1OIDV): OID;
|
||||
@ -620,9 +621,7 @@ declare module 'jsrsasign' {
|
||||
|
||||
public encrypt(text: string): HexString | null;
|
||||
|
||||
public encryptOAEP(text: string, hash?: string, hashLen?: number): HexString | null;
|
||||
|
||||
public encryptOAEP(text: string, hash?: (s: string) => string, hashLen?: number): HexString | null;
|
||||
public encryptOAEP(text: string, hash?: string | ((s: string) => string), hashLen?: number): HexString | null;
|
||||
|
||||
//// RSA PRIVATE
|
||||
|
||||
@ -638,9 +637,7 @@ declare module 'jsrsasign' {
|
||||
|
||||
public decrypt(ctext: HexString): string;
|
||||
|
||||
public decryptOAEP(ctext: HexString, hash?: string, hashLen?: number): string | null;
|
||||
|
||||
public encryptOAEP(ctext: HexString, hash?: (s: string) => string, hashLen?: number): string | null;
|
||||
public decryptOAEP(ctext: HexString, hash?: string | ((s: string) => string), hashLen?: number): string | null;
|
||||
|
||||
//// RSA PEM
|
||||
|
||||
|
@ -50,21 +50,23 @@
|
||||
<router-link class="item index" active-class="active" to="/" exact v-else>
|
||||
<fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $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>
|
||||
<router-link class="item" active-class="active" to="/my/messaging" v-if="$store.getters.isSignedIn">
|
||||
<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" v-if="$store.getters.isSignedIn">
|
||||
<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.getters.isSignedIn && $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 v-if="$store.getters.isSignedIn">
|
||||
<router-link class="item notifications" active-class="active" to="/my/notifications" ref="notificationButton">
|
||||
<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>
|
||||
<router-link class="item" active-class="active" to="/featured">
|
||||
<fa :icon="faFireAlt" fixed-width/><span class="text">{{ $t('featured') }}</span>
|
||||
@ -87,7 +89,7 @@
|
||||
<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>
|
||||
</button>
|
||||
<router-link class="item" active-class="active" to="/settings">
|
||||
<router-link class="item" active-class="active" to="/preferences">
|
||||
<fa :icon="faCog" fixed-width/><span class="text">{{ $t('settings') }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
@ -143,15 +145,13 @@
|
||||
<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="$route.name === 'index'" class="button home _button" @click="top()"><fa :icon="faHome"/></button>
|
||||
<button v-else class="button home _button" @click="$router.push('/')"><fa :icon="faHome"/></button>
|
||||
<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-if="$store.getters.isSignedIn" 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>
|
||||
</div>
|
||||
|
||||
<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
|
||||
|
||||
<transition name="zoom-in-top">
|
||||
<x-notifications v-if="notificationsOpen" class="notifications" ref="notifications"/>
|
||||
</transition>
|
||||
<stream-indicator v-if="$store.getters.isSignedIn"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -164,7 +164,6 @@ import { v4 as uuid } from 'uuid';
|
||||
import i18n from './i18n';
|
||||
import { host, instanceName } from './config';
|
||||
import { search } from './scripts/search';
|
||||
import contains from './scripts/contains';
|
||||
import MkToast from './components/toast.vue';
|
||||
|
||||
const DESKTOP_THRESHOLD = 1100;
|
||||
@ -174,7 +173,6 @@ export default Vue.extend({
|
||||
|
||||
components: {
|
||||
XClock: () => import('./components/header-clock.vue').then(m => m.default),
|
||||
XNotifications: () => import('./components/notifications.vue').then(m => m.default),
|
||||
MkButton: () => import('./components/ui/button.vue').then(m => m.default),
|
||||
XDraggable: () => import('vuedraggable'),
|
||||
},
|
||||
@ -185,7 +183,6 @@ export default Vue.extend({
|
||||
pageKey: 0,
|
||||
showNav: false,
|
||||
searching: false,
|
||||
notificationsOpen: false,
|
||||
accounts: [],
|
||||
lists: [],
|
||||
connection: null,
|
||||
@ -194,7 +191,6 @@ export default Vue.extend({
|
||||
widgetsEditMode: false,
|
||||
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
|
||||
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
|
||||
};
|
||||
@ -218,23 +214,10 @@ export default Vue.extend({
|
||||
watch:{
|
||||
$route(to, from) {
|
||||
this.pageKey++;
|
||||
this.notificationsOpen = false;
|
||||
this.showNav = false;
|
||||
this.canBack = (window.history.length > 0 && !['index'].includes(to.name));
|
||||
},
|
||||
|
||||
notificationsOpen(open) {
|
||||
if (open) {
|
||||
for (const el of Array.from(document.querySelectorAll('*'))) {
|
||||
el.addEventListener('mousedown', this.onMousedown);
|
||||
}
|
||||
} else {
|
||||
for (const el of Array.from(document.querySelectorAll('*'))) {
|
||||
el.removeEventListener('mousedown', this.onMousedown);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
isDesktop() {
|
||||
if (this.isDesktop) this.adjustWidgetsWidth();
|
||||
}
|
||||
@ -258,30 +241,6 @@ 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() {
|
||||
@ -571,22 +530,17 @@ export default Vue.extend({
|
||||
|
||||
onNotification(notification) {
|
||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||
this.$root.stream.send('readNotification', {
|
||||
id: notification.id
|
||||
});
|
||||
if (true) {
|
||||
this.$root.stream.send('readNotification', {
|
||||
id: notification.id
|
||||
});
|
||||
|
||||
this.$root.new(MkToast, {
|
||||
notification
|
||||
});
|
||||
},
|
||||
this.$root.new(MkToast, {
|
||||
notification
|
||||
});
|
||||
}
|
||||
|
||||
onMousedown(e) {
|
||||
e.preventDefault();
|
||||
if (!contains(this.$refs.notifications.$el, e.target) &&
|
||||
!contains(this.$refs.notificationButton, e.target) &&
|
||||
!contains(this.$refs.notificationButton2, e.target)
|
||||
) this.notificationsOpen = false;
|
||||
return false;
|
||||
this.$root.sound('notification');
|
||||
},
|
||||
|
||||
widgetFunc(id) {
|
||||
@ -638,30 +592,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<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-leave-active {
|
||||
opacity: 1;
|
||||
@ -688,7 +618,7 @@ export default Vue.extend({
|
||||
$header-height: 60px;
|
||||
$nav-width: 250px;
|
||||
$nav-icon-only-width: 74px;
|
||||
$main-width: 700px;
|
||||
$main-width: 650px;
|
||||
$ui-font-size: 1em;
|
||||
$nav-icon-only-threshold: 1300px;
|
||||
$nav-hide-threshold: 700px;
|
||||
@ -1011,17 +941,21 @@ export default Vue.extend({
|
||||
> main {
|
||||
width: $main-width;
|
||||
min-width: $main-width;
|
||||
box-shadow: 1px 0 0 0 var(--divider), -1px 0 0 0 var(--divider);
|
||||
|
||||
@media (max-width: $side-hide-threshold) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
> .content {
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
> * {
|
||||
&:not(.full) {
|
||||
padding: var(--margin) 0;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
padding: 8px;
|
||||
&:not(.naked) {
|
||||
background: var(--pageBg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1059,6 +993,7 @@ export default Vue.extend({
|
||||
|
||||
> .widgets {
|
||||
box-sizing: border-box;
|
||||
margin-left: var(--margin);
|
||||
|
||||
@media (max-width: $side-hide-threshold) {
|
||||
display: none;
|
||||
@ -1211,32 +1146,5 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .notifications {
|
||||
position: fixed;
|
||||
top: 32px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
z-index: 10001;
|
||||
width: 350px;
|
||||
height: 400px;
|
||||
background: var(--vocsgcxy);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15);
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
width: 320px;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
width: 290px;
|
||||
height: 310px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
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 |
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.
@ -1,5 +1,5 @@
|
||||
<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" :data-direction="direction" :data-reversed="reversed ? 'true' : 'false'">
|
||||
<template v-for="(item, i) in items">
|
||||
<slot :item="item" :i="i"></slot>
|
||||
<div class="separator" :key="item.id + '_date'" v-if="showDate(i, item)">
|
||||
@ -9,7 +9,7 @@
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</sequential-entrance>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -27,7 +27,8 @@ export default Vue.extend({
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
required: false
|
||||
required: false,
|
||||
default: 'down'
|
||||
},
|
||||
reversed: {
|
||||
type: Boolean,
|
||||
@ -63,12 +64,38 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$refs.list.focus();
|
||||
this.$slots.default[0].elm.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
</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>
|
||||
.sqadhkmv {
|
||||
> .separator {
|
||||
@ -82,8 +109,6 @@ export default Vue.extend({
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
border-radius: 64px;
|
||||
background: var(--dateLabelBg);
|
||||
color: var(--dateLabelFg);
|
||||
|
||||
> span {
|
||||
|
@ -55,6 +55,7 @@ import { faTimesCircle, faQuestionCircle } from '@fortawesome/free-regular-svg-i
|
||||
import MkButton from './ui/button.vue';
|
||||
import MkInput from './ui/input.vue';
|
||||
import MkSelect from './ui/select.vue';
|
||||
import MkSignin from './signin.vue';
|
||||
import parseAcct from '../../misc/acct/parse';
|
||||
import i18n from '../i18n';
|
||||
|
||||
@ -65,6 +66,7 @@ export default Vue.extend({
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkSelect,
|
||||
MkSignin,
|
||||
},
|
||||
|
||||
props: {
|
||||
|
@ -105,16 +105,6 @@ export default Vue.extend({
|
||||
text: this.$t('delete'),
|
||||
icon: faTrashAlt,
|
||||
action: this.deleteFile
|
||||
}, null, {
|
||||
type: 'nest',
|
||||
text: this.$t('contextmenu.else-files'),
|
||||
menu: [{
|
||||
text: this.$t('contextmenu.set-as-avatar'),
|
||||
action: this.setAsAvatar
|
||||
}, {
|
||||
text: this.$t('contextmenu.set-as-banner'),
|
||||
action: this.setAsBanner
|
||||
}]
|
||||
}],
|
||||
source: ev.currentTarget || ev.target,
|
||||
});
|
||||
|
@ -27,8 +27,6 @@ export default Vue.extend({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mjndxjcg {
|
||||
max-width: 350px;
|
||||
margin: 0 auto;
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div class="mk-google">
|
||||
<input type="search" v-model="query" :placeholder="q">
|
||||
<button @click="search"><fa icon="search"/> {{ $t('search') }}</button>
|
||||
<button @click="search"><fa :icon="faSearch"/> {{ $t('search') }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -14,7 +15,8 @@ export default Vue.extend({
|
||||
props: ['q'],
|
||||
data() {
|
||||
return {
|
||||
query: null
|
||||
query: null,
|
||||
faSearch
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -42,27 +44,17 @@ export default Vue.extend({
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
color: var(--googleSearchFg);
|
||||
background: var(--googleSearchBg);
|
||||
border: solid 1px var(--googleSearchBorder);
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--googleSearchHoverBorder);
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
flex-shrink: 0;
|
||||
padding: 0 16px;
|
||||
border: solid 1px var(--googleSearchBorder);
|
||||
border: solid 1px var(--divider);
|
||||
border-left: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--googleSearchHoverButton);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 2px 4px rgba(#000, 0.15) inset;
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ import ellipsis from './ellipsis.vue';
|
||||
import time from './time.vue';
|
||||
import url from './url.vue';
|
||||
import loading from './loading.vue';
|
||||
import SequentialEntrance from './sequential-entrance.vue';
|
||||
import error from './error.vue';
|
||||
import streamIndicator from './stream-indicator.vue';
|
||||
|
||||
Vue.component('mfm', mfm);
|
||||
Vue.component('mk-acct', acct);
|
||||
@ -22,4 +22,4 @@ Vue.component('mk-time', time);
|
||||
Vue.component('mk-url', url);
|
||||
Vue.component('mk-loading', loading);
|
||||
Vue.component('mk-error', error);
|
||||
Vue.component('sequential-entrance', SequentialEntrance);
|
||||
Vue.component('stream-indicator', streamIndicator);
|
||||
|
@ -90,7 +90,7 @@ export default Vue.extend({
|
||||
> div {
|
||||
background-color: var(--fg);
|
||||
border-radius: 6px;
|
||||
color: var(--secondary);
|
||||
color: var(--accentLighten);
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
|
@ -3,8 +3,8 @@
|
||||
<template v-for="media in mediaList.filter(media => !previewable(media))">
|
||||
<x-banner :media="media" :key="media.id"/>
|
||||
</template>
|
||||
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container">
|
||||
<div :data-count="mediaList.filter(media => previewable(media)).length" ref="grid">
|
||||
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container" ref="gridOuter">
|
||||
<div :data-count="mediaList.filter(media => previewable(media)).length" :style="gridInnerStyle">
|
||||
<template v-for="media in mediaList">
|
||||
<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"/>
|
||||
@ -32,19 +32,56 @@ export default Vue.extend({
|
||||
},
|
||||
raw: {
|
||||
default: false
|
||||
},
|
||||
// specify the parent element
|
||||
parentElement: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
gridInnerStyle: {},
|
||||
sizeWaiting: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//#region for Safari bug
|
||||
if (this.$refs.grid) {
|
||||
this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px`
|
||||
: '287px';
|
||||
}
|
||||
//#endregion
|
||||
this.size();
|
||||
window.addEventListener('resize', this.size);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.size);
|
||||
},
|
||||
activated() {
|
||||
this.size();
|
||||
},
|
||||
methods: {
|
||||
previewable(file) {
|
||||
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 = {};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<x-popup :source="source" :no-center="noCenter" :fixed="fixed" :width="width" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
|
||||
<sequential-entrance class="rrevdjwt" :class="{ left: align === 'left' }" :delay="15" :direction="direction" ref="items">
|
||||
<div class="rrevdjwt" :class="{ left: align === 'left' }" ref="items">
|
||||
<template v-for="(item, i) in items.filter(item => item !== undefined)">
|
||||
<div v-if="item === null" class="divider" :key="i"></div>
|
||||
<span v-else-if="item.type === 'label'" class="label item" :key="i">
|
||||
@ -28,7 +28,7 @@
|
||||
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
|
||||
</button>
|
||||
</template>
|
||||
</sequential-entrance>
|
||||
</div>
|
||||
</x-popup>
|
||||
</template>
|
||||
|
||||
@ -91,7 +91,7 @@ export default Vue.extend({
|
||||
mounted() {
|
||||
if (this.viaKeyboard) {
|
||||
this.$nextTick(() => {
|
||||
focusNext(this.$refs.items.$slots.default[0].elm, true);
|
||||
focusNext(this.$refs.items.children[0], true);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -37,6 +37,10 @@ export default Vue.extend({
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
::v-deep > code {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
::v-deep .title {
|
||||
text-align: center;
|
||||
border-bottom: solid 1px var(--divider);
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="zlrxdaqttccpwhpaagdmkawtzklsccam">
|
||||
<div class="wrpstxzv" v-size="[{ max: 450 }]">
|
||||
<mk-avatar class="avatar" :user="note.user"/>
|
||||
<div class="main">
|
||||
<x-note-header class="header" :note="note" :mini="true"/>
|
||||
@ -56,13 +56,12 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zlrxdaqttccpwhpaagdmkawtzklsccam {
|
||||
.wrpstxzv {
|
||||
display: flex;
|
||||
padding: 16px 32px;
|
||||
font-size: 0.9em;
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
|
||||
@media (max-width: 450px) {
|
||||
&.max-width_450px {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
|
@ -79,13 +79,13 @@
|
||||
<div class="deleted" v-if="appearNote.deletedAt != null">{{ $t('deleted') }}</div>
|
||||
</div>
|
||||
</article>
|
||||
<x-sub v-for="note in replies" :key="note.id" :note="note"/>
|
||||
<x-sub v-for="note in replies" :key="note.id" :note="note" class="reply"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faBolt, faTimes, faBullhorn, 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 { parse } from '../../mfm/parse';
|
||||
import { sum, unique } from '../../prelude/array';
|
||||
@ -392,7 +392,7 @@ export default Vue.extend({
|
||||
}]
|
||||
source: this.$refs.renoteButton,
|
||||
viaKeyboard
|
||||
}).then(this.focus);
|
||||
});
|
||||
},
|
||||
|
||||
renoteDirectly() {
|
||||
@ -489,6 +489,11 @@ export default Vue.extend({
|
||||
noteId: this.appearNote.id
|
||||
});
|
||||
menu = [{
|
||||
type: 'link',
|
||||
icon: faInfoCircle,
|
||||
text: this.$t('details'),
|
||||
to: '/notes/' + this.appearNote.id
|
||||
}, null, {
|
||||
icon: faCopy,
|
||||
text: this.$t('copyContent'),
|
||||
action: this.copyContent
|
||||
@ -679,6 +684,7 @@ export default Vue.extend({
|
||||
.note {
|
||||
position: relative;
|
||||
transition: box-shadow 0.1s ease;
|
||||
overflow: hidden;
|
||||
|
||||
&.max-width_500px {
|
||||
font-size: 0.9em;
|
||||
@ -744,14 +750,6 @@ export default Vue.extend({
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
> *:first-child {
|
||||
border-radius: var(--radius) var(--radius) 0 0;
|
||||
}
|
||||
|
||||
> *:last-child {
|
||||
border-radius: 0 0 var(--radius) var(--radius);
|
||||
}
|
||||
|
||||
> .info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -779,6 +777,11 @@ export default Vue.extend({
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
> .reply-to {
|
||||
opacity: 0.7;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
> .renote {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -932,5 +935,9 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .reply {
|
||||
border-top: solid 1px var(--divider);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -7,22 +7,22 @@
|
||||
|
||||
<mk-error v-if="error" @retry="init()"/>
|
||||
|
||||
<div class="more" v-if="more && reversed" style="margin-bottom: var(--margin);">
|
||||
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
|
||||
<div v-if="more && reversed" style="margin-bottom: var(--margin);">
|
||||
<button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()">
|
||||
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||
<template v-if="moreFetching"><mk-loading inline/></template>
|
||||
</mk-button>
|
||||
</button>
|
||||
</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>
|
||||
<div v-if="more && !reversed" style="margin-top: var(--margin);">
|
||||
<button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()">
|
||||
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||
<template v-if="moreFetching"><mk-loading inline/></template>
|
||||
</mk-button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -111,16 +111,10 @@ export default Vue.extend({
|
||||
&.max-width_500px {
|
||||
> .notes {
|
||||
> ::v-deep *:not(:last-child) {
|
||||
margin-bottom: var(--marginHalf);
|
||||
//margin-bottom: var(--marginHalf);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .more > .button {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mk-notification" :class="notification.type">
|
||||
<div class="mk-notification" :class="notification.type" v-size="[{ max: 500 }, { max: 600 }]">
|
||||
<div class="head">
|
||||
<mk-avatar class="avatar" :user="notification.user"/>
|
||||
<div class="icon" :class="notification.type">
|
||||
@ -113,12 +113,17 @@ export default Vue.extend({
|
||||
.mk-notification {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 24px 32px;
|
||||
font-size: 0.9em;
|
||||
overflow-wrap: break-word;
|
||||
display: flex;
|
||||
|
||||
@media (max-width: 500px) {
|
||||
&.max-width_600px {
|
||||
padding: 16px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
&.max-width_500px {
|
||||
padding: 12px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
@ -1,29 +1,28 @@
|
||||
<template>
|
||||
<div class="mk-notifications">
|
||||
<div class="contents">
|
||||
<x-list class="notifications" :items="items" v-slot="{ item: notification, i }">
|
||||
<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id"/>
|
||||
</x-list>
|
||||
<x-list class="notifications" :items="items" v-slot="{ item: notification }">
|
||||
<x-note v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" :key="notification.id"/>
|
||||
<x-notification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/>
|
||||
</x-list>
|
||||
|
||||
<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
||||
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||
<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
|
||||
</button>
|
||||
<button class="_panel _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
||||
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||
<template v-if="moreFetching"><mk-loading inline/></template>
|
||||
</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()"/>
|
||||
</div>
|
||||
<mk-error v-if="error" @retry="init()"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../i18n';
|
||||
import paging from '../scripts/paging';
|
||||
import XNotification from './notification.vue';
|
||||
import XList from './date-separated-list.vue';
|
||||
import XNote from './note.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
@ -31,6 +30,7 @@ export default Vue.extend({
|
||||
components: {
|
||||
XNotification,
|
||||
XList,
|
||||
XNote,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
@ -42,11 +42,6 @@ export default Vue.extend({
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
wide: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -59,7 +54,6 @@ export default Vue.extend({
|
||||
includeTypes: this.type ? [this.type] : undefined
|
||||
})
|
||||
},
|
||||
faSpinner
|
||||
};
|
||||
},
|
||||
|
||||
@ -93,44 +87,23 @@ export default Vue.extend({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-notifications {
|
||||
> .contents {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
padding: 8px 8px 0 8px;
|
||||
|
||||
> .notifications {
|
||||
> ::v-deep * {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
> .notification {
|
||||
background: var(--panel);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
> .notifications {
|
||||
> ::v-deep * {
|
||||
//margin-bottom: var(--margin);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .more {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
> .empty {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
> [data-icon] {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
> .empty {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
> .placeholder {
|
||||
padding: 32px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
> .placeholder {
|
||||
padding: 32px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
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>
|
@ -53,7 +53,7 @@ import Vue from 'vue';
|
||||
import { faExclamationTriangle, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../i18n';
|
||||
import { erase } from '../../prelude/array';
|
||||
import { addTimespan } from '../../prelude/time';
|
||||
import { addTime } from '../../prelude/time';
|
||||
import { formatDateTimeString } from '../../misc/format-time-string';
|
||||
import MkInput from './ui/input.vue';
|
||||
import MkSelect from './ui/select.vue';
|
||||
@ -73,7 +73,7 @@ export default Vue.extend({
|
||||
choices: ['', ''],
|
||||
multiple: false,
|
||||
expiration: 'infinite',
|
||||
atDate: formatDateTimeString(addTimespan(new Date(), 1, 'days'), 'yyyy-MM-dd'),
|
||||
atDate: formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'),
|
||||
atTime: '00:00',
|
||||
after: 0,
|
||||
unit: 'second',
|
||||
|
@ -112,8 +112,7 @@ export default Vue.extend({
|
||||
margin: 4px 0;
|
||||
padding: 4px 8px;
|
||||
width: 100%;
|
||||
color: var(--pollChoiceText);
|
||||
border: solid 1px var(--pollChoiceBorder);
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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>
|
||||
</transition>
|
||||
<div class="main" ref="main" @click.self="close()" @keydown="onKeydown">
|
||||
@ -17,7 +17,8 @@
|
||||
:initial-note="initialNote"
|
||||
:instant="instant"
|
||||
@posted="onPosted"
|
||||
@cancel="onCanceled"/>
|
||||
@cancel="onCanceled"
|
||||
style="border-radius: var(--radius);"/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,7 +6,7 @@
|
||||
@drop.stop="onDrop"
|
||||
>
|
||||
<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>
|
||||
<span class="text-count" :class="{ over: trimmedLength(text) > max }">{{ max - trimmedLength(text) }}</span>
|
||||
<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>
|
||||
</div>
|
||||
</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="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>
|
||||
@ -108,6 +108,11 @@ export default Vue.extend({
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
@ -581,8 +586,6 @@ export default Vue.extend({
|
||||
<style lang="scss" scoped>
|
||||
.gafaadew {
|
||||
background: var(--panel);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: 0 0 2px rgba(#000, 0.1);
|
||||
|
||||
> header {
|
||||
z-index: 1000;
|
||||
@ -651,6 +654,10 @@ export default Vue.extend({
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
|
||||
&.fixed {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
> .preview {
|
||||
padding: 16px;
|
||||
}
|
||||
|
@ -1,20 +1,9 @@
|
||||
<template>
|
||||
<x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
|
||||
<div class="rdfaahpb">
|
||||
<transition-group
|
||||
name="reaction-fade"
|
||||
tag="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>
|
||||
<div class="buttons" ref="buttons" :class="{ showFocus }">
|
||||
<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>
|
||||
</div>
|
||||
<input class="text" v-model="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
|
||||
</div>
|
||||
</x-popup>
|
||||
@ -84,7 +73,7 @@ export default Vue.extend({
|
||||
|
||||
watch: {
|
||||
focus(i) {
|
||||
this.$refs.buttons.children[i].elm.focus();
|
||||
this.$refs.buttons.children[i].focus();
|
||||
}
|
||||
},
|
||||
|
||||
@ -129,21 +118,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
choose() {
|
||||
this.$refs.buttons.children[this.focus].elm.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)
|
||||
this.$refs.buttons.children[this.focus].click();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
@ -1,16 +1,17 @@
|
||||
<template>
|
||||
<span
|
||||
class="reaction _button"
|
||||
<button
|
||||
class="hkzvhatu _button"
|
||||
:class="{ reacted: note.myReaction == reaction }"
|
||||
@click="toggleReaction(reaction)"
|
||||
v-if="count > 0"
|
||||
@mouseover="onMouseover"
|
||||
@mouseleave="onMouseleave"
|
||||
ref="reaction"
|
||||
v-particle
|
||||
>
|
||||
<x-reaction-icon :reaction="reaction" ref="icon"/>
|
||||
<span>{{ count }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -136,7 +137,7 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.reaction {
|
||||
.hkzvhatu {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
margin: 2px;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<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"/>
|
||||
</div>
|
||||
</template>
|
||||
@ -32,7 +32,7 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-reactions-viewer {
|
||||
.tdflqwzn {
|
||||
margin: 4px -2px 0 -2px;
|
||||
|
||||
&:empty {
|
||||
|
36
src/client/components/remote-caution.vue
Normal file
36
src/client/components/remote-caution.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="jmgmzlwq _panel"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="href" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
props: {
|
||||
href: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
faExclamationTriangle
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.jmgmzlwq {
|
||||
font-size: 0.8em;
|
||||
padding: 16px;
|
||||
|
||||
> a {
|
||||
margin-left: 4px;
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<transition-group v-if="$store.state.device.animation"
|
||||
class="uupnnhew"
|
||||
:data-direction="direction"
|
||||
:data-reversed="reversed ? 'true' : 'false'"
|
||||
name="staggered"
|
||||
tag="div"
|
||||
appear
|
||||
>
|
||||
<slot></slot>
|
||||
</transition-group>
|
||||
<div v-else>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
this.$slots.default[0].elm.focus();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.staggered-move {
|
||||
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) !important;
|
||||
}
|
||||
|
||||
.uupnnhew[data-direction="up"] {
|
||||
.staggered-enter {
|
||||
opacity: 0;
|
||||
transform: translateY(64px);
|
||||
}
|
||||
}
|
||||
|
||||
.uupnnhew[data-direction="down"] {
|
||||
.staggered-enter {
|
||||
opacity: 0;
|
||||
transform: translateY(-64px);
|
||||
}
|
||||
}
|
||||
|
||||
.uupnnhew[data-reversed="true"] {
|
||||
@for $i from 1 through 30 {
|
||||
.staggered-enter-active:nth-last-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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uupnnhew[data-reversed="false"] {
|
||||
@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>
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<x-window ref="window" @closed="() => { $emit('closed'); destroyDom(); }">
|
||||
<template #header>{{ $t('login') }}</template>
|
||||
<x-signin :auto-set="autoSet" @login="onLogin"/>
|
||||
<mk-signin :auto-set="autoSet" @login="onLogin"/>
|
||||
</x-window>
|
||||
</template>
|
||||
|
||||
@ -9,13 +9,13 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../i18n';
|
||||
import XWindow from './window.vue';
|
||||
import XSignin from './signin.vue';
|
||||
import MkSignin from './signin.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
|
||||
components: {
|
||||
XSignin,
|
||||
MkSignin,
|
||||
XWindow,
|
||||
},
|
||||
|
||||
|
0
src/client/components/signin.vue
Normal file → Executable file
0
src/client/components/signin.vue
Normal file → Executable file
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>
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
@ -21,6 +21,11 @@ export default Vue.extend({
|
||||
},
|
||||
antenna: {
|
||||
required: false
|
||||
},
|
||||
sound: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
|
||||
@ -46,6 +51,10 @@ export default Vue.extend({
|
||||
|
||||
const 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 = () => {
|
||||
|
@ -124,7 +124,6 @@ export default Vue.extend({
|
||||
&.primary {
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 6px 16px var(--accentShadow);
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background: var(--jkhztclx);
|
||||
|
@ -110,6 +110,7 @@ export default Vue.extend({
|
||||
> header {
|
||||
position: relative;
|
||||
box-shadow: 0 1px 0 0 var(--divider);
|
||||
z-index: 1;
|
||||
|
||||
> .title {
|
||||
margin: 0;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<sequential-entrance class="cxiknjgy" :class="{ autoMargin }">
|
||||
<div class="cxiknjgy" :class="{ autoMargin }">
|
||||
<slot :items="items"></slot>
|
||||
<div class="empty" v-if="empty" key="_empty_">
|
||||
<slot name="empty"></slot>
|
||||
@ -10,7 +10,7 @@
|
||||
<template v-if="moreFetching"><mk-loading inline/></template>
|
||||
</mk-button>
|
||||
</div>
|
||||
</sequential-entrance>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
138
src/client/components/ui/range.vue
Normal file
138
src/client/components/ui/range.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="timctyfi" :class="{ focused, disabled }">
|
||||
<div class="icon"><slot name="icon"></slot></div>
|
||||
<span class="title"><slot name="title"></slot></span>
|
||||
<input
|
||||
type="range"
|
||||
ref="input"
|
||||
v-model="v"
|
||||
:disabled="disabled"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
:autofocus="autofocus"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@input="$emit('input', $event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 100
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 1
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
v: this.value,
|
||||
focused: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(v) {
|
||||
this.v = parseFloat(v);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.autofocus) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.input.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.timctyfi {
|
||||
position: relative;
|
||||
margin: 8px;
|
||||
|
||||
> .icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> .title {
|
||||
pointer-events: none;
|
||||
font-size: 16px;
|
||||
color: var(--inputLabel);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> input {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background: var(--xxubwiul);
|
||||
height: 7px;
|
||||
margin: 0 8px;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -56,7 +56,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
filled(): boolean {
|
||||
return this.v != '' && this.v != null;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -100,6 +100,7 @@ export default Vue.extend({
|
||||
|
||||
> .input {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
@ -151,12 +152,17 @@ export default Vue.extend({
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
height: 32px;
|
||||
background: var(--panel);
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
color: var(--fg);
|
||||
|
||||
option,
|
||||
optgroup {
|
||||
background: var(--bg);
|
||||
}
|
||||
}
|
||||
|
||||
> .prefix,
|
||||
|
@ -230,8 +230,8 @@ export default Vue.extend({
|
||||
position: relative;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 1px 4px var(--tyvedwbe);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 0 1px var(--divider);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
|
@ -51,6 +51,7 @@ export default Vue.extend({
|
||||
target: self ? null : '_blank',
|
||||
showTimer: null,
|
||||
hideTimer: null,
|
||||
checkTimer: null,
|
||||
preview: null,
|
||||
faExternalLinkSquareAlt
|
||||
};
|
||||
@ -78,9 +79,14 @@ export default Vue.extend({
|
||||
}).$mount();
|
||||
|
||||
document.body.appendChild(this.preview.$el);
|
||||
|
||||
this.checkTimer = setInterval(() => {
|
||||
if (!document.body.contains(this.$el)) this.closePreview();
|
||||
}, 1000);
|
||||
},
|
||||
closePreview() {
|
||||
if (this.preview) {
|
||||
clearInterval(this.checkTimer);
|
||||
this.preview.destroyDom();
|
||||
this.preview = null;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<template #header><mk-user-name :user="user"/></template>
|
||||
<div class="vrcsvlkm">
|
||||
<mk-button @click="resetPassword()" primary>{{ $t('resetPassword') }}</mk-button>
|
||||
<mk-switch v-if="$store.state.i.isAdmin && !user.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="toggleSuspend()" v-model="suspended">{{ $t('suspend') }}</mk-switch>
|
||||
</div>
|
||||
|
@ -53,6 +53,7 @@ export default Vue.extend({
|
||||
return {
|
||||
u: null,
|
||||
show: false,
|
||||
closed: false,
|
||||
top: 0,
|
||||
left: 0,
|
||||
};
|
||||
@ -68,6 +69,7 @@ export default Vue.extend({
|
||||
{ userId: this.user };
|
||||
|
||||
this.$root.api('users/show', query).then(user => {
|
||||
if (this.closed) return;
|
||||
this.u = user;
|
||||
this.show = true;
|
||||
});
|
||||
@ -83,6 +85,7 @@ export default Vue.extend({
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.closed = true;
|
||||
this.show = false;
|
||||
if (this.$refs.content) (this.$refs.content as any).style.pointerEvents = 'none';
|
||||
}
|
||||
|
@ -6,15 +6,15 @@
|
||||
<button class="_button" @click="close()"><fa :icon="faTimes"/></button>
|
||||
</div>
|
||||
|
||||
<sequential-entrance class="users">
|
||||
<router-link v-for="(item, i) in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage">
|
||||
<div class="users">
|
||||
<router-link v-for="item in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage">
|
||||
<mk-avatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
|
||||
<div class="body">
|
||||
<mk-user-name :user="extract ? extract(item) : item" class="name"/>
|
||||
<mk-acct :user="extract ? extract(item) : item" class="acct"/>
|
||||
</div>
|
||||
</router-link>
|
||||
</sequential-entrance>
|
||||
</div>
|
||||
|
||||
<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
||||
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }">
|
||||
<sequential-entrance class="gqyayizv" :delay="30">
|
||||
<div class="gqyayizv">
|
||||
<button class="_button" @click="choose('public')" :class="{ active: v == 'public' }" data-index="1" key="public">
|
||||
<div><fa :icon="faGlobe"/></div>
|
||||
<div>
|
||||
@ -29,7 +29,7 @@
|
||||
<span>{{ $t('_visibility.specifiedDescription') }}</span>
|
||||
</div>
|
||||
</button>
|
||||
</sequential-entrance>
|
||||
</div>
|
||||
</x-popup>
|
||||
</template>
|
||||
|
||||
|
@ -3,8 +3,10 @@ import Vue from 'vue';
|
||||
import userPreview from './user-preview';
|
||||
import autocomplete from './autocomplete';
|
||||
import size from './size';
|
||||
import particle from './particle';
|
||||
|
||||
Vue.directive('autocomplete', autocomplete);
|
||||
Vue.directive('userPreview', userPreview);
|
||||
Vue.directive('user-preview', userPreview);
|
||||
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);
|
||||
});
|
||||
}
|
||||
};
|
@ -59,7 +59,7 @@ export default {
|
||||
const ro = new ResizeObserver((entries, observer) => {
|
||||
calc();
|
||||
});
|
||||
|
||||
|
||||
ro.observe(el);
|
||||
|
||||
el._ro_ = ro;
|
||||
|
@ -8,9 +8,11 @@ export default {
|
||||
self.tag = null;
|
||||
self.showTimer = null;
|
||||
self.hideTimer = null;
|
||||
self.checkTimer = null;
|
||||
|
||||
self.close = () => {
|
||||
if (self.tag) {
|
||||
clearInterval(self.checkTimer);
|
||||
self.tag.close();
|
||||
self.tag = null;
|
||||
}
|
||||
@ -38,6 +40,14 @@ export default {
|
||||
});
|
||||
|
||||
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', () => {
|
||||
@ -60,8 +70,6 @@ export default {
|
||||
|
||||
unbind(el, binding, vn) {
|
||||
const self = el._userPreviewDirective_;
|
||||
clearTimeout(self.showTimer);
|
||||
clearTimeout(self.hideTimer);
|
||||
self.close();
|
||||
clearInterval(self.checkTimer);
|
||||
}
|
||||
};
|
||||
|
@ -81,14 +81,14 @@ if (lang == null) {
|
||||
|
||||
// Detect the user agent
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
let isMobile = /mobile|iphone|ipad|android/.test(ua);
|
||||
const isMobile = /mobile|iphone|ipad|android/.test(ua);
|
||||
|
||||
// Get the <head> element
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
|
||||
// If mobile, insert the viewport meta tag
|
||||
if (isMobile || window.innerWidth <= 1024) {
|
||||
const viewport = document.getElementsByName("viewport").item(0);
|
||||
const viewport = document.getElementsByName('viewport').item(0);
|
||||
viewport.setAttribute('content',
|
||||
`${viewport.getAttribute('content')},minimum-scale=1,maximum-scale=1,user-scalable=no`);
|
||||
head.appendChild(viewport);
|
||||
@ -136,6 +136,14 @@ document.body.innerHTML = '<div id="app"></div>';
|
||||
const os = new MiOS();
|
||||
|
||||
os.init(async () => {
|
||||
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.permission === 'default') {
|
||||
@ -189,6 +197,14 @@ os.init(async () => {
|
||||
if (cb) vm.$once('closed', cb);
|
||||
(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,
|
||||
render: createEl => createEl(App)
|
||||
@ -198,4 +214,96 @@ os.init(async () => {
|
||||
|
||||
// マウント
|
||||
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 initStore from './store';
|
||||
import { apiUrl, version, locale } from './config';
|
||||
import { apiUrl, version } from './config';
|
||||
import Progress from './scripts/loading';
|
||||
|
||||
import Stream from './scripts/stream';
|
||||
@ -123,8 +123,13 @@ export default class MiOS extends EventEmitter {
|
||||
});
|
||||
} else {
|
||||
// Get token from localStorage
|
||||
const i = localStorage.getItem('i');
|
||||
|
||||
let i = localStorage.getItem('i');
|
||||
|
||||
// 連携ログインの場合用にCookieを参照する
|
||||
if (i == null || i === 'null') {
|
||||
i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1];
|
||||
}
|
||||
|
||||
fetchme(i, me => {
|
||||
if (me) {
|
||||
this.store.dispatch('login', me);
|
||||
@ -142,94 +147,6 @@ export default class MiOS extends EventEmitter {
|
||||
@autobind
|
||||
private initStream() {
|
||||
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', () => {
|
||||
this.signout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
9
src/client/pages/auth.vue
Normal file → Executable file
9
src/client/pages/auth.vue
Normal file → Executable file
@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div class="signin" v-else>
|
||||
<h1>{{ $t('sign-in') }}</h1>
|
||||
<mk-signin/>
|
||||
<mk-signin @login="onLogin"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -34,11 +34,13 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../i18n';
|
||||
import XForm from './auth.form.vue';
|
||||
import MkSignin from '../components/signin.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
components: {
|
||||
XForm
|
||||
XForm,
|
||||
MkSignin,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -83,6 +85,9 @@ export default Vue.extend({
|
||||
if (this.session.app.callbackUrl) {
|
||||
location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`;
|
||||
}
|
||||
}, onLogin(res) {
|
||||
localStorage.setItem('i', res.i);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="naked full">
|
||||
<portal to="header">
|
||||
<button @click="menu" class="_button _jmoebdiw_">
|
||||
<fa :icon="faCloud" style="margin-right: 8px;"/>
|
||||
|
@ -14,9 +14,12 @@
|
||||
</button>
|
||||
</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-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>
|
||||
</template>
|
||||
|
||||
@ -27,6 +30,7 @@ import { faComments } from '@fortawesome/free-regular-svg-icons';
|
||||
import Progress from '../scripts/loading';
|
||||
import XTimeline from '../components/timeline.vue';
|
||||
import XTutorial from './index.home.tutorial.vue';
|
||||
import XPostForm from '../components/post-form.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
metaInfo() {
|
||||
@ -38,6 +42,7 @@ export default Vue.extend({
|
||||
components: {
|
||||
XTimeline,
|
||||
XTutorial,
|
||||
XPostForm,
|
||||
},
|
||||
|
||||
props: {
|
||||
@ -53,6 +58,8 @@ export default Vue.extend({
|
||||
list: null,
|
||||
antenna: null,
|
||||
menuOpened: false,
|
||||
queue: 0,
|
||||
width: 0,
|
||||
faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faComments, faListUl, faSatellite, faCircle
|
||||
};
|
||||
},
|
||||
@ -63,6 +70,10 @@ export default Vue.extend({
|
||||
't': this.focus
|
||||
};
|
||||
},
|
||||
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
@ -91,6 +102,10 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.width = this.$el.offsetWidth;
|
||||
},
|
||||
|
||||
methods: {
|
||||
before() {
|
||||
Progress.start();
|
||||
@ -100,7 +115,17 @@ export default Vue.extend({
|
||||
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) {
|
||||
if (this.meta == null) return;
|
||||
this.menuOpened = true;
|
||||
const [antennas, lists] = await Promise.all([
|
||||
this.$root.api('antennas/list'),
|
||||
@ -128,15 +153,15 @@ export default Vue.extend({
|
||||
text: this.$t('_timelines.home'),
|
||||
icon: faHome,
|
||||
action: () => { this.setSrc('home') }
|
||||
}, {
|
||||
}, this.meta.disableLocalTimeline && !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin ? undefined : {
|
||||
text: this.$t('_timelines.local'),
|
||||
icon: faComments,
|
||||
action: () => { this.setSrc('local') }
|
||||
}, {
|
||||
}, this.meta.disableLocalTimeline && !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin ? undefined : {
|
||||
text: this.$t('_timelines.social'),
|
||||
icon: faShareAlt,
|
||||
action: () => { this.setSrc('social') }
|
||||
}, {
|
||||
}, this.meta.disableGlobalTimeline && !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin ? undefined : {
|
||||
text: this.$t('_timelines.global'),
|
||||
icon: faGlobe,
|
||||
action: () => { this.setSrc('global') }
|
||||
@ -169,9 +194,26 @@ export default Vue.extend({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-home {
|
||||
> .new {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
|
||||
> button {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
padding: 8px 16px;
|
||||
border-radius: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
> .tutorial {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
|
||||
> .post-form {
|
||||
position: relative;
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
}
|
||||
|
||||
._kjvfvyph_ {
|
||||
|
@ -5,6 +5,38 @@
|
||||
|
||||
<mk-instance-stats style="margin-bottom: var(--margin);"/>
|
||||
|
||||
<section class="_card logs">
|
||||
<div class="_title"><fa :icon="faStream"/> {{ $t('serverLogs') }}</div>
|
||||
<div class="_content">
|
||||
<div class="_inputs">
|
||||
<mk-input v-model="logDomain" :debounce="true">
|
||||
<span>{{ $t('domain') }}</span>
|
||||
</mk-input>
|
||||
<mk-select v-model="logLevel">
|
||||
<template #label>{{ $t('level') }}</template>
|
||||
<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 class="_footer">
|
||||
<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;">
|
||||
@ -67,9 +99,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faServer, faExchangeAlt, faMicrochip, faHdd } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import Chart from 'chart.js';
|
||||
import VueJsonPretty from 'vue-json-pretty';
|
||||
import MkInstanceStats from '../../components/instance-stats.vue';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkSelect from '../../components/ui/select.vue';
|
||||
import MkInput from '../../components/ui/input.vue';
|
||||
import { version, url } from '../../config';
|
||||
import i18n from '../../i18n';
|
||||
|
||||
@ -92,6 +128,10 @@ export default Vue.extend({
|
||||
|
||||
components: {
|
||||
MkInstanceStats,
|
||||
MkButton,
|
||||
MkSelect,
|
||||
MkInput,
|
||||
VueJsonPretty
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -104,7 +144,10 @@ export default Vue.extend({
|
||||
memUsage: 0,
|
||||
chartCpuMem: null,
|
||||
chartNet: null,
|
||||
faServer, faExchangeAlt, faMicrochip, faHdd
|
||||
logs: [],
|
||||
logLevel: 'all',
|
||||
logDomain: '',
|
||||
faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt
|
||||
}
|
||||
},
|
||||
|
||||
@ -114,7 +157,20 @@ export default Vue.extend({
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
logLevel() {
|
||||
this.logs = [];
|
||||
this.fetchLogs();
|
||||
},
|
||||
logDomain() {
|
||||
this.logs = [];
|
||||
this.fetchLogs();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchLogs();
|
||||
|
||||
Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
|
||||
|
||||
this.chartCpuMem = new Chart(this.$refs.cpumem, {
|
||||
@ -330,6 +386,25 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchLogs() {
|
||||
this.$root.api('admin/logs', {
|
||||
level: this.logLevel === 'all' ? null : this.logLevel,
|
||||
domain: this.logDomain === '' ? null : this.logDomain,
|
||||
limit: 30
|
||||
}).then(logs => {
|
||||
this.logs = logs.reverse();
|
||||
});
|
||||
},
|
||||
|
||||
deleteAllLogs() {
|
||||
this.$root.api('admin/delete-logs').then(() => {
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
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);
|
||||
@ -389,6 +464,37 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
|
||||
> .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 {
|
||||
|
@ -11,12 +11,12 @@
|
||||
<canvas ref="chart"></canvas>
|
||||
</div>
|
||||
<div class="_content" style="max-height: 180px; overflow: auto;">
|
||||
<sequential-entrance :delay="15" v-if="jobs.length > 0">
|
||||
<div v-for="(job, i) in jobs" :key="job[0]">
|
||||
<div v-if="jobs.length > 0">
|
||||
<div v-for="job in jobs" :key="job[0]">
|
||||
<span>{{ job[0] }}</span>
|
||||
<span style="margin-left: 8px; opacity: 0.7;">({{ job[1] | number }} jobs)</span>
|
||||
</div>
|
||||
</sequential-entrance>
|
||||
</div>
|
||||
<span v-else style="opacity: 0.5;">{{ $t('noJobs') }}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -61,10 +61,10 @@
|
||||
<div class="_content">
|
||||
<mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></mk-switch>
|
||||
<template v-if="enableServiceWorker">
|
||||
<mk-horizon-group inputs class="fit-bottom">
|
||||
<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>
|
||||
</mk-horizon-group>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
@ -97,6 +97,32 @@
|
||||
</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">{{ $t('objectStorageBaseUrl') }}<template #desc>{{ $t('objectStorageBaseUrlDesc') }}</template></mk-input>
|
||||
<div class="_inputs">
|
||||
<mk-input v-model="objectStorageBucket" :disabled="!useObjectStorage">{{ $t('objectStorageBucket') }}<template #desc>{{ $t('objectStorageBucketDesc') }}</template></mk-input>
|
||||
<mk-input v-model="objectStoragePrefix" :disabled="!useObjectStorage">{{ $t('objectStoragePrefix') }}<template #desc>{{ $t('objectStoragePrefixDesc') }}</template></mk-input>
|
||||
</div>
|
||||
<mk-input v-model="objectStorageEndpoint" :disabled="!useObjectStorage">{{ $t('objectStorageEndpoint') }}<template #desc>{{ $t('objectStorageEndpointDesc') }}</template></mk-input>
|
||||
<div class="_inputs">
|
||||
<mk-input v-model="objectStorageRegion" :disabled="!useObjectStorage">{{ $t('objectStorageRegion') }}<template #desc>{{ $t('objectStorageRegionDesc') }}</template></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">{{ $t('objectStorageUseSSL') }}<template #desc>{{ $t('objectStorageUseSSLDesc') }}</template></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">
|
||||
@ -213,6 +239,16 @@ export default Vue.extend({
|
||||
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,
|
||||
@ -257,6 +293,16 @@ export default Vue.extend({
|
||||
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;
|
||||
@ -341,6 +387,16 @@ export default Vue.extend({
|
||||
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,
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="thvuemwp" :data-is-me="isMe">
|
||||
<mk-avatar class="avatar" :user="message.user"/>
|
||||
<div class="content">
|
||||
<div class="balloon _panel" :data-no-text="message.text == null">
|
||||
<div class="balloon" :data-no-text="message.text == null">
|
||||
<button class="delete-button" v-if="isMe" :title="$t('delete')" @click="del">
|
||||
<img src="/assets/remove.png" alt="Delete"/>
|
||||
</button>
|
||||
@ -243,13 +243,14 @@ export default Vue.extend({
|
||||
}
|
||||
|
||||
&:not([data-is-me]) {
|
||||
padding-left: var(--margin);
|
||||
|
||||
> .content {
|
||||
padding-left: 16px;
|
||||
padding-right: 32px;
|
||||
|
||||
> .balloon {
|
||||
$color: var(--panel);
|
||||
$color: var(--messageBg);
|
||||
background: $color;
|
||||
|
||||
&[data-no-text] {
|
||||
@ -279,6 +280,7 @@ export default Vue.extend({
|
||||
|
||||
&[data-is-me] {
|
||||
flex-direction: row-reverse;
|
||||
padding-right: var(--margin);
|
||||
|
||||
> .content {
|
||||
padding-right: 16px;
|
||||
@ -287,7 +289,6 @@ export default Vue.extend({
|
||||
|
||||
> .balloon {
|
||||
background: $me-balloon-color;
|
||||
box-shadow: 0 6px 16px var(--accentShadow);
|
||||
text-align: left;
|
||||
|
||||
&[data-no-text] {
|
||||
@ -310,7 +311,7 @@ export default Vue.extend({
|
||||
}
|
||||
|
||||
> .text {
|
||||
&, * {
|
||||
&, ::v-deep * {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mk-messaging-room"
|
||||
<div class="mk-messaging-room naked"
|
||||
@dragover.prevent.stop="onDragover"
|
||||
@drop.prevent.stop="onDrop"
|
||||
>
|
||||
@ -184,12 +184,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
onMessage(message) {
|
||||
// サウンドを再生する
|
||||
if (this.$store.state.device.enableSounds) {
|
||||
const sound = new Audio(`${url}/assets/message.mp3`);
|
||||
sound.volume = this.$store.state.device.soundVolume;
|
||||
sound.play();
|
||||
}
|
||||
this.$root.sound('chat');
|
||||
|
||||
const isBottom = this.isBottom();
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
<mk-button @click="start" primary class="start"><fa :icon="faPlus"/> {{ $t('startMessaging') }}</mk-button>
|
||||
|
||||
<sequential-entrance class="history" v-if="messages.length > 0" :delay="30">
|
||||
<div class="history" v-if="messages.length > 0">
|
||||
<router-link v-for="(message, i) in messages"
|
||||
class="message _panel"
|
||||
:to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
|
||||
@ -30,7 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</sequential-entrance>
|
||||
</div>
|
||||
<div class="no-history" v-if="!fetching && messages.length == 0">
|
||||
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
|
||||
<div>{{ $t('noHistory') }}</div>
|
||||
|
@ -30,6 +30,10 @@
|
||||
<span>{{ $t('antennaKeywords') }}</span>
|
||||
<template #desc>{{ $t('antennaKeywordsDescription') }}</template>
|
||||
</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="withFile">{{ $t('withFileAntenna') }}</mk-switch>
|
||||
<mk-switch v-model="notify">{{ $t('notifyAntenna') }}</mk-switch>
|
||||
@ -75,6 +79,7 @@ export default Vue.extend({
|
||||
userGroupId: null,
|
||||
users: '',
|
||||
keywords: '',
|
||||
excludeKeywords: '',
|
||||
caseSensitive: false,
|
||||
withReplies: false,
|
||||
withFile: false,
|
||||
@ -107,6 +112,7 @@ export default Vue.extend({
|
||||
this.userGroupId = this.antenna.userGroupId;
|
||||
this.users = this.antenna.users.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.withReplies = this.antenna.withReplies;
|
||||
this.withFile = this.antenna.withFile;
|
||||
@ -126,7 +132,8 @@ export default Vue.extend({
|
||||
notify: this.notify,
|
||||
caseSensitive: this.caseSensitive,
|
||||
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');
|
||||
} else {
|
||||
@ -141,7 +148,8 @@ export default Vue.extend({
|
||||
notify: this.notify,
|
||||
caseSensitive: this.caseSensitive,
|
||||
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,
|
||||
users: [],
|
||||
keywords: [],
|
||||
excludeKeywords: [],
|
||||
withReplies: false,
|
||||
caseSensitive: false,
|
||||
withFile: false,
|
||||
|
@ -70,11 +70,10 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (!document.cookie.match(/i=(\w+)/)) {
|
||||
document.cookie = `i=${this.$store.state.i.token}; path=/;` +
|
||||
` domain=${document.location.hostname}; max-age=31536000;` +
|
||||
document.cookie = `igi=${this.$store.state.i.token}; path=/;` +
|
||||
` max-age=31536000;` +
|
||||
(document.location.protocol.startsWith('https') ? ' secure' : '');
|
||||
}
|
||||
|
||||
this.$watch('integrations', () => {
|
||||
if (this.integrations.twitter) {
|
||||
if (this.twitterForm) this.twitterForm.close();
|
||||
|
@ -2,7 +2,10 @@
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faLaugh"/> {{ $t('reaction') }}</div>
|
||||
<div class="_content">
|
||||
<mk-textarea v-model="reactions">{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }}</template></mk-textarea>
|
||||
<mk-input v-model="reactions" style="font-family: 'Segoe UI Emoji', 'Noto Color Emoji', Roboto, HelveticaNeue, Arial, sans-serif">
|
||||
{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></template>
|
||||
</mk-input>
|
||||
<mk-button inline @click="setDefault"><fa :icon="faUndo"/> {{ $t('default') }}</mk-button>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||
@ -14,24 +17,26 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons';
|
||||
import MkTextarea from '../../components/ui/textarea.vue';
|
||||
import { faUndo } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkInput from '../../components/ui/input.vue';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkReactionPicker from '../../components/reaction-picker.vue';
|
||||
import i18n from '../../i18n';
|
||||
import { emojiRegexWithCustom } from '../../../misc/emoji-regex';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
|
||||
components: {
|
||||
MkTextarea,
|
||||
MkInput,
|
||||
MkButton,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
reactions: this.$store.state.settings.reactions.join('\n'),
|
||||
reactions: this.$store.state.settings.reactions.join(''),
|
||||
changed: false,
|
||||
faLaugh, faSave, faEye
|
||||
faLaugh, faSave, faEye, faUndo
|
||||
}
|
||||
},
|
||||
|
||||
@ -41,21 +46,40 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
splited(): any {
|
||||
return this.reactions.match(emojiRegexWithCustom);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
this.$store.dispatch('settings/set', { key: 'reactions', value: this.reactions.trim().split('\n') });
|
||||
this.$store.dispatch('settings/set', { key: 'reactions', value: this.splited });
|
||||
this.changed = false;
|
||||
},
|
||||
|
||||
preview(ev) {
|
||||
const picker = this.$root.new(MkReactionPicker, {
|
||||
source: ev.currentTarget || ev.target,
|
||||
reactions: this.reactions.trim().split('\n'),
|
||||
reactions: this.splited,
|
||||
showFocus: false,
|
||||
});
|
||||
picker.$once('chosen', reaction => {
|
||||
picker.close();
|
||||
});
|
||||
},
|
||||
|
||||
setDefault() {
|
||||
this.reactions = '👍❤😆🤔😮🎉💢😥😇🍮';
|
||||
},
|
||||
|
||||
async chooseEmoji(ev) {
|
||||
const vm = this.$root.new(await import('../../components/emoji-picker.vue').then(m => m.default), {
|
||||
source: ev.currentTarget || ev.target
|
||||
}).$once('chosen', emoji => {
|
||||
this.reactions += emoji;
|
||||
vm.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,24 +1,28 @@
|
||||
<template>
|
||||
<div class="mk-note-page">
|
||||
<portal to="avatar" v-if="note"><mk-avatar class="avatar" :user="note.user" :disable-preview="true"/></portal>
|
||||
<portal to="title" v-if="note">{{ $t('noteOf', { user: note.user.name }) }}</portal>
|
||||
<portal to="title" v-if="note">
|
||||
<mfm
|
||||
:text="$t('noteOf', { user: note.user.name || note.user.username })"
|
||||
:plain="true" :nowrap="true" :custom-emojis="note.user.emojis" :is-note="false"
|
||||
/>
|
||||
</portal>
|
||||
|
||||
<transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in">
|
||||
<div v-if="note">
|
||||
<mk-button v-if="hasNext && !showNext" @click="showNext = true" primary style="margin: 0 auto var(--margin) auto;"><fa :icon="faChevronUp"/></mk-button>
|
||||
<x-notes v-if="showNext" ref="next" :pagination="next"/>
|
||||
<hr v-if="showNext"/>
|
||||
<div v-if="note">
|
||||
<button class="_panel _button" v-if="hasNext && !showNext" @click="showNext = true" style="margin: 0 auto var(--margin) auto;"><fa :icon="faChevronUp"/></button>
|
||||
<x-notes v-if="showNext" ref="next" :pagination="next"/>
|
||||
<hr v-if="showNext"/>
|
||||
|
||||
<x-note :note="note" :key="note.id" :detail="true"/>
|
||||
<div v-if="error">
|
||||
<mk-error @retry="fetch()"/>
|
||||
</div>
|
||||
|
||||
<mk-button v-if="hasPrev && !showPrev" @click="showPrev = true" primary style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></mk-button>
|
||||
<hr v-if="showPrev"/>
|
||||
<x-notes v-if="showPrev" ref="prev" :pagination="prev" style="margin-top: var(--margin);"/>
|
||||
<mk-remote-caution v-if="note.user.host != null" :href="note.uri" style="margin-bottom: var(--margin)"/>
|
||||
<x-note :note="note" :key="note.id" :detail="true"/>
|
||||
<div v-if="error">
|
||||
<mk-error @retry="fetch()"/>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<button class="_panel _button" v-if="hasPrev && !showPrev" @click="showPrev = true" style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></button>
|
||||
<hr v-if="showPrev"/>
|
||||
<x-notes v-if="showPrev" ref="prev" :pagination="prev" style="margin-top: var(--margin);"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -29,7 +33,7 @@ import i18n from '../i18n';
|
||||
import Progress from '../scripts/loading';
|
||||
import XNote from '../components/note.vue';
|
||||
import XNotes from '../components/notes.vue';
|
||||
import MkButton from '../components/ui/button.vue';
|
||||
import MkRemoteCaution from '../components/remote-caution.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
@ -41,7 +45,7 @@ export default Vue.extend({
|
||||
components: {
|
||||
XNote,
|
||||
XNotes,
|
||||
MkButton,
|
||||
MkRemoteCaution,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
42
src/client/pages/notifications.vue
Normal file
42
src/client/pages/notifications.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<portal to="icon"><fa :icon="faBell"/></portal>
|
||||
<portal to="title">{{ $t('notifications') }}</portal>
|
||||
<x-notifications @before="before" @after="after" page/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faBell } from '@fortawesome/free-solid-svg-icons';
|
||||
import Progress from '../scripts/loading';
|
||||
import XNotifications from '../components/notifications.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t('notifications') as string
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
XNotifications
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
faBell
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
before() {
|
||||
Progress.start();
|
||||
},
|
||||
|
||||
after() {
|
||||
Progress.done();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -17,8 +17,8 @@
|
||||
</template>
|
||||
<router-link :to="`./${page.name}/view-source`">{{ $t('_pages.viewSource') }}</router-link>
|
||||
<div class="like">
|
||||
<button @click="unlike()" v-if="page.isLiked" :title="$t('_pages.unlike')"><fa :icon="faHeartS"/></button>
|
||||
<button @click="like()" v-else :title="$t('_pages.like')"><fa :icon="faHeart"/></button>
|
||||
<button class="_button" @click="unlike()" v-if="page.isLiked" :title="$t('_pages.unlike')"><fa :icon="faHeartS"/></button>
|
||||
<button class="_button" @click="like()" v-else :title="$t('_pages.like')"><fa :icon="faHeartR"/></button>
|
||||
<span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -28,6 +28,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faHeart as faHeartR } from '@fortawesome/free-regular-svg-icons';
|
||||
import XPage from '../components/page/page.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -49,6 +51,7 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
page: null,
|
||||
faHeartS, faHeartR
|
||||
};
|
||||
},
|
||||
|
||||
@ -102,7 +105,7 @@ export default Vue.extend({
|
||||
}).then(() => {
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
splash: true
|
||||
iconOnly: true, autoClose: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
260
src/client/pages/preferences/index.vue
Normal file
260
src/client/pages/preferences/index.vue
Normal file
@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<div>
|
||||
<portal to="icon"><fa :icon="faCog"/></portal>
|
||||
<portal to="title">{{ $t('clinetSettings') }}</portal>
|
||||
|
||||
<x-theme/>
|
||||
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faMusic"/> {{ $t('sounds') }}</div>
|
||||
<div class="_content">
|
||||
<mk-range v-model="sfxVolume" min="0" max="1" step="0.1">
|
||||
<fa slot="icon" :icon="volumeIcon"/>
|
||||
<span slot="title">{{ $t('volume') }}</span>
|
||||
</mk-range>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-select v-model="sfxNote">
|
||||
<template #label>{{ $t('_sfx.note') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxNote)" v-if="sfxNote"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</mk-select>
|
||||
<mk-select v-model="sfxNoteMy">
|
||||
<template #label>{{ $t('_sfx.noteMy') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxNoteMy)" v-if="sfxNoteMy"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</mk-select>
|
||||
<mk-select v-model="sfxNotification">
|
||||
<template #label>{{ $t('_sfx.notification') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxNotification)" v-if="sfxNotification"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</mk-select>
|
||||
<mk-select v-model="sfxChat">
|
||||
<template #label>{{ $t('_sfx.chat') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxChat)" v-if="sfxChat"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</mk-select>
|
||||
<mk-select v-model="sfxChatBg">
|
||||
<template #label>{{ $t('_sfx.chatBg') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxChatBg)" v-if="sfxChatBg"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</mk-select>
|
||||
<mk-select v-model="sfxAntenna">
|
||||
<template #label>{{ $t('_sfx.antenna') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxAntenna)" v-if="sfxAntenna"><fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</mk-select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faCog"/> {{ $t('accessibility') }}</div>
|
||||
<div class="_content">
|
||||
<mk-switch v-model="autoReload">
|
||||
{{ $t('autoReloadWhenDisconnected') }}
|
||||
</mk-switch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-switch v-model="imageNewTab">{{ $t('openImageInNewTab') }}</mk-switch>
|
||||
<mk-switch v-model="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</mk-switch>
|
||||
<mk-switch v-model="reduceAnimation">{{ $t('reduceUiAnimation') }}</mk-switch>
|
||||
<mk-switch v-model="useOsNativeEmojis">
|
||||
{{ $t('useOsNativeEmojis') }}
|
||||
<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
|
||||
</mk-switch>
|
||||
<mk-switch v-model="showFixedPostForm">{{ $t('showFixedPostForm') }}</mk-switch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-select v-model="lang">
|
||||
<template #label>{{ $t('uiLanguage') }}</template>
|
||||
|
||||
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
|
||||
</mk-select>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('fontSize') }}</div>
|
||||
<mk-radio v-model="fontSize" value="small"><span style="font-size: 14px;">Aa</span></mk-radio>
|
||||
<mk-radio v-model="fontSize" :value="null"><span style="font-size: 16px;">Aa</span></mk-radio>
|
||||
<mk-radio v-model="fontSize" value="large"><span style="font-size: 18px;">Aa</span></mk-radio>
|
||||
<mk-radio v-model="fontSize" value="veryLarge"><span style="font-size: 20px;">Aa</span></mk-radio>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<mk-button @click="cacheClear()" primary style="margin: var(--margin) auto;">{{ $t('cacheClear') }}</mk-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faImage, faCog, faMusic, faPlay, faVolumeUp, faVolumeMute } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkInput from '../../components/ui/input.vue';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkSwitch from '../../components/ui/switch.vue';
|
||||
import MkSelect from '../../components/ui/select.vue';
|
||||
import MkRadio from '../../components/ui/radio.vue';
|
||||
import MkRange from '../../components/ui/range.vue';
|
||||
import XTheme from './theme.vue';
|
||||
import i18n from '../../i18n';
|
||||
import { langs } from '../../config';
|
||||
|
||||
const sounds = [
|
||||
null,
|
||||
'syuilo/up',
|
||||
'syuilo/down',
|
||||
'syuilo/pope1',
|
||||
'syuilo/pope2',
|
||||
'syuilo/waon',
|
||||
'syuilo/popo',
|
||||
'syuilo/triple',
|
||||
'syuilo/poi1',
|
||||
'syuilo/poi2',
|
||||
'aisha/1',
|
||||
'aisha/2',
|
||||
'aisha/3',
|
||||
'noizenecio/kick_gaba',
|
||||
];
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t('settings') as string
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
XTheme,
|
||||
MkInput,
|
||||
MkButton,
|
||||
MkSwitch,
|
||||
MkSelect,
|
||||
MkRadio,
|
||||
MkRange
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
langs,
|
||||
lang: localStorage.getItem('lang'),
|
||||
fontSize: localStorage.getItem('fontSize'),
|
||||
sounds,
|
||||
faImage, faCog, faMusic, faPlay, faVolumeUp, faVolumeMute
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
autoReload: {
|
||||
get() { return this.$store.state.device.autoReload; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); }
|
||||
},
|
||||
|
||||
reduceAnimation: {
|
||||
get() { return !this.$store.state.device.animation; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); }
|
||||
},
|
||||
|
||||
disableAnimatedMfm: {
|
||||
get() { return !this.$store.state.device.animatedMfm; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'animatedMfm', value: !value }); }
|
||||
},
|
||||
|
||||
useOsNativeEmojis: {
|
||||
get() { return this.$store.state.device.useOsNativeEmojis; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'useOsNativeEmojis', value }); }
|
||||
},
|
||||
|
||||
imageNewTab: {
|
||||
get() { return this.$store.state.device.imageNewTab; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'imageNewTab', value }); }
|
||||
},
|
||||
|
||||
showFixedPostForm: {
|
||||
get() { return this.$store.state.device.showFixedPostForm; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); }
|
||||
},
|
||||
|
||||
sfxVolume: {
|
||||
get() { return this.$store.state.device.sfxVolume; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxVolume', value: parseFloat(value, 10) }); }
|
||||
},
|
||||
|
||||
sfxNote: {
|
||||
get() { return this.$store.state.device.sfxNote; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxNote', value }); }
|
||||
},
|
||||
|
||||
sfxNoteMy: {
|
||||
get() { return this.$store.state.device.sfxNoteMy; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxNoteMy', value }); }
|
||||
},
|
||||
|
||||
sfxNotification: {
|
||||
get() { return this.$store.state.device.sfxNotification; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxNotification', value }); }
|
||||
},
|
||||
|
||||
sfxChat: {
|
||||
get() { return this.$store.state.device.sfxChat; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxChat', value }); }
|
||||
},
|
||||
|
||||
sfxChatBg: {
|
||||
get() { return this.$store.state.device.sfxChatBg; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxChatBg', value }); }
|
||||
},
|
||||
|
||||
sfxAntenna: {
|
||||
get() { return this.$store.state.device.sfxAntenna; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxAntenna', value }); }
|
||||
},
|
||||
|
||||
volumeIcon: {
|
||||
get() {
|
||||
return this.sfxVolume === 0 ? faVolumeMute : faVolumeUp;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
lang() {
|
||||
localStorage.setItem('lang', this.lang);
|
||||
localStorage.removeItem('locale');
|
||||
location.reload();
|
||||
},
|
||||
|
||||
fontSize() {
|
||||
if (this.fontSize == null) {
|
||||
localStorage.removeItem('fontSize');
|
||||
} else {
|
||||
localStorage.setItem('fontSize', this.fontSize);
|
||||
}
|
||||
location.reload();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
listen(sound) {
|
||||
const audio = new Audio(`/assets/sounds/${sound}.mp3`);
|
||||
audio.volume = this.$store.state.device.sfxVolume;
|
||||
audio.play();
|
||||
},
|
||||
|
||||
cacheClear() {
|
||||
// Clear cache (service worker)
|
||||
try {
|
||||
navigator.serviceWorker.controller.postMessage('clear');
|
||||
|
||||
navigator.serviceWorker.getRegistrations().then(registrations => {
|
||||
for (const registration of registrations) registration.unregister();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
// Force reload
|
||||
location.reload(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -73,7 +73,6 @@ export default Vue.extend({
|
||||
applyTheme(this.themes.find(x => x.id === this.theme));
|
||||
},
|
||||
|
||||
|
||||
wallpaper() {
|
||||
if (this.wallpaper == null) {
|
||||
localStorage.removeItem('wallpaper');
|
@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<portal to="icon"><fa :icon="faCog"/></portal>
|
||||
<portal to="title">{{ $t('clinetSettings') }}</portal>
|
||||
|
||||
<x-theme/>
|
||||
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faCog"/> {{ $t('accessibility') }}</div>
|
||||
<div class="_content">
|
||||
<mk-switch v-model="autoReload">
|
||||
{{ $t('autoReloadWhenDisconnected') }}
|
||||
</mk-switch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-switch v-model="imageNewTab">{{ $t('openImageInNewTab') }}</mk-switch>
|
||||
<mk-switch v-model="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</mk-switch>
|
||||
<mk-switch v-model="reduceAnimation">{{ $t('reduceUiAnimation') }}</mk-switch>
|
||||
<mk-switch v-model="useOsNativeEmojis">
|
||||
{{ $t('useOsNativeEmojis') }}
|
||||
<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
|
||||
</mk-switch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-select v-model="lang">
|
||||
<template #label>{{ $t('uiLanguage') }}</template>
|
||||
|
||||
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
|
||||
</mk-select>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('fontSize') }}</div>
|
||||
<mk-radio v-model="fontSize" value="small"><span style="font-size: 14px;">Aa</span></mk-radio>
|
||||
<mk-radio v-model="fontSize" :value="null"><span style="font-size: 16px;">Aa</span></mk-radio>
|
||||
<mk-radio v-model="fontSize" value="large"><span style="font-size: 18px;">Aa</span></mk-radio>
|
||||
<mk-radio v-model="fontSize" value="veryLarge"><span style="font-size: 20px;">Aa</span></mk-radio>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<mk-button @click="cacheClear()" primary style="margin: var(--margin) auto;">{{ $t('cacheClear') }}</mk-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faImage, faCog } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkInput from '../../components/ui/input.vue';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkSwitch from '../../components/ui/switch.vue';
|
||||
import MkSelect from '../../components/ui/select.vue';
|
||||
import MkRadio from '../../components/ui/radio.vue';
|
||||
import XTheme from './theme.vue';
|
||||
import i18n from '../../i18n';
|
||||
import { langs } from '../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t('settings') as string
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
XTheme,
|
||||
MkInput,
|
||||
MkButton,
|
||||
MkSwitch,
|
||||
MkSelect,
|
||||
MkRadio,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
langs,
|
||||
lang: localStorage.getItem('lang'),
|
||||
fontSize: localStorage.getItem('fontSize'),
|
||||
faImage, faCog
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
autoReload: {
|
||||
get() { return this.$store.state.device.autoReload; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); }
|
||||
},
|
||||
|
||||
reduceAnimation: {
|
||||
get() { return !this.$store.state.device.animation; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); }
|
||||
},
|
||||
|
||||
disableAnimatedMfm: {
|
||||
get() { return !this.$store.state.device.animatedMfm; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'animatedMfm', value: !value }); }
|
||||
},
|
||||
|
||||
useOsNativeEmojis: {
|
||||
get() { return this.$store.state.device.useOsNativeEmojis; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'useOsNativeEmojis', value }); }
|
||||
},
|
||||
|
||||
imageNewTab: {
|
||||
get() { return this.$store.state.device.imageNewTab; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'imageNewTab', value }); }
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
lang() {
|
||||
localStorage.setItem('lang', this.lang);
|
||||
localStorage.removeItem('locale');
|
||||
location.reload();
|
||||
},
|
||||
|
||||
fontSize() {
|
||||
if (this.fontSize == null) {
|
||||
localStorage.removeItem('fontSize');
|
||||
} else {
|
||||
localStorage.setItem('fontSize', this.fontSize);
|
||||
}
|
||||
location.reload();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
cacheClear() {
|
||||
// Clear cache (service worker)
|
||||
try {
|
||||
navigator.serviceWorker.controller.postMessage('clear');
|
||||
|
||||
navigator.serviceWorker.getRegistrations().then(registrations => {
|
||||
for (const registration of registrations) registration.unregister();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
// Force reload
|
||||
location.reload(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@ -3,31 +3,13 @@
|
||||
<portal to="title" v-if="user"><mk-user-name :user="user" :nowrap="false" class="name"/></portal>
|
||||
<portal to="avatar" v-if="user"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
|
||||
|
||||
<div class="remote-caution _panel" v-if="user.host != null"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div>
|
||||
<transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in" appear>
|
||||
<div class="profile _panel" :key="user.id">
|
||||
<div class="banner-container" :style="style">
|
||||
<div class="banner" ref="banner" :style="style"></div>
|
||||
<div class="fade"></div>
|
||||
<div class="title">
|
||||
<mk-user-name class="name" :user="user" :nowrap="true"/>
|
||||
<div class="bottom">
|
||||
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
|
||||
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span>
|
||||
<span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span>
|
||||
<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span>
|
||||
<div class="actions" v-if="$store.getters.isSignedIn">
|
||||
<button @click="menu" class="menu _button" ref="menu"><fa :icon="faEllipsisH"/></button>
|
||||
<mk-follow-button v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
||||
</div>
|
||||
</div>
|
||||
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
||||
<mk-remote-caution v-if="user.host != null" :href="user.url" style="margin-bottom: var(--margin)"/>
|
||||
<div class="profile _panel" :key="user.id">
|
||||
<div class="banner-container" :style="style">
|
||||
<div class="banner" ref="banner" :style="style"></div>
|
||||
<div class="fade"></div>
|
||||
<div class="title">
|
||||
<mk-user-name :user="user" :nowrap="false" class="name"/>
|
||||
<mk-user-name class="name" :user="user" :nowrap="true"/>
|
||||
<div class="bottom">
|
||||
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
|
||||
@ -36,55 +18,71 @@
|
||||
<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
||||
<p v-else class="empty">{{ $t('noAccountDescription') }}</p>
|
||||
</div>
|
||||
<div class="fields system">
|
||||
<dl class="field" v-if="user.location">
|
||||
<dt class="name"><fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt>
|
||||
<dd class="value">{{ user.location }}</dd>
|
||||
</dl>
|
||||
<dl class="field" v-if="user.birthday">
|
||||
<dt class="name"><fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt>
|
||||
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
|
||||
</dl>
|
||||
<dl class="field">
|
||||
<dt class="name"><fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt>
|
||||
<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<mk-time :time="user.createdAt"/>)</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="fields" v-if="user.fields.length > 0">
|
||||
<dl class="field" v-for="(field, i) in user.fields" :key="i">
|
||||
<dt class="name">
|
||||
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
|
||||
</dt>
|
||||
<dd class="value">
|
||||
<mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="status" v-if="user.host === null">
|
||||
<router-link :to="user | userPage()" :class="{ active: $route.name === 'user' }">
|
||||
<b>{{ user.notesCount | number }}</b>
|
||||
<span>{{ $t('notes') }}</span>
|
||||
</router-link>
|
||||
<router-link :to="user | userPage('following')" :class="{ active: $route.name === 'userFollowing' }">
|
||||
<b>{{ user.followingCount | number }}</b>
|
||||
<span>{{ $t('following') }}</span>
|
||||
</router-link>
|
||||
<router-link :to="user | userPage('followers')" :class="{ active: $route.name === 'userFollowers' }">
|
||||
<b>{{ user.followersCount | number }}</b>
|
||||
<span>{{ $t('followers') }}</span>
|
||||
</router-link>
|
||||
<span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span>
|
||||
<div class="actions" v-if="$store.getters.isSignedIn">
|
||||
<button @click="menu" class="menu _button" ref="menu"><fa :icon="faEllipsisH"/></button>
|
||||
<mk-follow-button v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
||||
<div class="title">
|
||||
<mk-user-name :user="user" :nowrap="false" class="name"/>
|
||||
<div class="bottom">
|
||||
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
|
||||
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span>
|
||||
<span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span>
|
||||
<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
||||
<p v-else class="empty">{{ $t('noAccountDescription') }}</p>
|
||||
</div>
|
||||
<div class="fields system">
|
||||
<dl class="field" v-if="user.location">
|
||||
<dt class="name"><fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt>
|
||||
<dd class="value">{{ user.location }}</dd>
|
||||
</dl>
|
||||
<dl class="field" v-if="user.birthday">
|
||||
<dt class="name"><fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt>
|
||||
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
|
||||
</dl>
|
||||
<dl class="field">
|
||||
<dt class="name"><fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt>
|
||||
<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<mk-time :time="user.createdAt"/>)</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="fields" v-if="user.fields.length > 0">
|
||||
<dl class="field" v-for="(field, i) in user.fields" :key="i">
|
||||
<dt class="name">
|
||||
<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
|
||||
</dt>
|
||||
<dd class="value">
|
||||
<mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="status">
|
||||
<router-link :to="user | userPage()" :class="{ active: $route.name === 'user' }">
|
||||
<b>{{ user.notesCount | number }}</b>
|
||||
<span>{{ $t('notes') }}</span>
|
||||
</router-link>
|
||||
<router-link :to="user | userPage('following')" :class="{ active: $route.name === 'userFollowing' }">
|
||||
<b>{{ user.followingCount | number }}</b>
|
||||
<span>{{ $t('following') }}</span>
|
||||
</router-link>
|
||||
<router-link :to="user | userPage('followers')" :class="{ active: $route.name === 'userFollowers' }">
|
||||
<b>{{ user.followersCount | number }}</b>
|
||||
<span>{{ $t('followers') }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<router-view :user="user"></router-view>
|
||||
<template v-if="$route.name == 'user'">
|
||||
<sequential-entrance class="pins">
|
||||
<x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
|
||||
</sequential-entrance>
|
||||
<div class="pins">
|
||||
<x-note v-for="note in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
|
||||
</div>
|
||||
<mk-container :body-togglable="true" class="content">
|
||||
<template #header><fa :icon="faImage"/>{{ $t('images') }}</template>
|
||||
<div>
|
||||
@ -107,7 +105,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faEllipsisH, faRobot, faLock, faBookmark, faExclamationTriangle, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faEllipsisH, faRobot, faLock, faBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faCalendarAlt, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons';
|
||||
import * as age from 's-age';
|
||||
import XUserTimeline from './index.timeline.vue';
|
||||
@ -115,6 +113,7 @@ import XUserMenu from '../../components/user-menu.vue';
|
||||
import XNote from '../../components/note.vue';
|
||||
import MkFollowButton from '../../components/follow-button.vue';
|
||||
import MkContainer from '../../components/ui/container.vue';
|
||||
import MkRemoteCaution from '../../components/remote-caution.vue';
|
||||
import Progress from '../../scripts/loading';
|
||||
import parseAcct from '../../../misc/acct/parse';
|
||||
|
||||
@ -124,6 +123,7 @@ export default Vue.extend({
|
||||
XNote,
|
||||
MkFollowButton,
|
||||
MkContainer,
|
||||
MkRemoteCaution,
|
||||
XPhotos: () => import('./index.photos.vue').then(m => m.default),
|
||||
XActivity: () => import('./index.activity.vue').then(m => m.default),
|
||||
},
|
||||
@ -139,7 +139,7 @@ export default Vue.extend({
|
||||
user: null,
|
||||
error: null,
|
||||
parallaxAnimationId: null,
|
||||
faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faExclamationTriangle, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt
|
||||
faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt
|
||||
};
|
||||
},
|
||||
|
||||
@ -217,17 +217,6 @@ export default Vue.extend({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-user-page {
|
||||
> .remote-caution {
|
||||
font-size: 0.8em;
|
||||
padding: 16px;
|
||||
margin-bottom: var(--margin);
|
||||
|
||||
> a {
|
||||
margin-left: 4px;
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
> .profile {
|
||||
position: relative;
|
||||
margin-bottom: var(--margin);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user