Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bb7edfee04 | ||
![]() |
caa14c70ef | ||
![]() |
3a0f72867f | ||
![]() |
10d72742f5 | ||
![]() |
1b9f8a87d3 | ||
![]() |
d4a630902d | ||
![]() |
fef5ec874b | ||
![]() |
f2e347fec1 | ||
![]() |
cd3c2484ee | ||
![]() |
6a396ef5e3 | ||
![]() |
eec1af1f52 | ||
![]() |
99fc77b678 | ||
![]() |
8bb311df51 | ||
![]() |
a77df249c2 | ||
![]() |
2883bca257 | ||
![]() |
dd3af6886b | ||
![]() |
33bcf2d1ea | ||
![]() |
795fb0eb60 | ||
![]() |
9e9d378bf1 | ||
![]() |
4a6b0edce6 | ||
![]() |
356225af14 | ||
![]() |
331305e6c7 | ||
![]() |
917b9475a5 | ||
![]() |
e895fc954b | ||
![]() |
6d3e18a6a1 | ||
![]() |
79354f4faf | ||
![]() |
28f8933c3c | ||
![]() |
10356b4041 | ||
![]() |
6a732ab1cd | ||
![]() |
47322b35ff | ||
![]() |
4c6d0386b9 | ||
![]() |
a448172952 | ||
![]() |
244ef0cb8f | ||
![]() |
cc66a1f9c7 | ||
![]() |
e2183400e5 | ||
![]() |
afc531bd26 | ||
![]() |
02cc1891f2 | ||
![]() |
09e3ddbd57 | ||
![]() |
d0fff562ea | ||
![]() |
8ce5366e80 | ||
![]() |
bfcda7cc02 | ||
![]() |
c52aeb6618 | ||
![]() |
f5ebfdca61 | ||
![]() |
db93838729 | ||
![]() |
bb835a6e8a | ||
![]() |
52feba0e3a | ||
![]() |
a1076c3108 | ||
![]() |
bad068b20e | ||
![]() |
ec41d461c0 | ||
![]() |
a826cd6845 | ||
![]() |
a950b6193a | ||
![]() |
2cc4de2b23 | ||
![]() |
03ef6996ff | ||
![]() |
d1e5def30e | ||
![]() |
02fbda2154 | ||
![]() |
c21694a24a | ||
![]() |
cb98336b0a | ||
![]() |
97d25bc6a3 | ||
![]() |
b36a1a9d0e | ||
![]() |
cd44ff0aaa | ||
![]() |
032571c326 | ||
![]() |
6b890e3f82 | ||
![]() |
9998845b21 | ||
![]() |
7ee4385deb | ||
![]() |
695277c9eb | ||
![]() |
f014a79f8d | ||
![]() |
1a6d47a633 | ||
![]() |
12eed8f859 | ||
![]() |
549092d9aa | ||
![]() |
b245393bc4 | ||
![]() |
dcd43a17ba | ||
![]() |
b8088dc01a | ||
![]() |
8e1b90ab43 | ||
![]() |
614a1d74dd | ||
![]() |
9ea1ed8559 | ||
![]() |
3e1e234799 | ||
![]() |
62f5ecd278 | ||
![]() |
27733e2119 | ||
![]() |
6be127e18b | ||
![]() |
4bf1c23b3c | ||
![]() |
608b8bb741 | ||
![]() |
ef01eec36e | ||
![]() |
5dbdd0e685 | ||
![]() |
5273050ab3 | ||
![]() |
fae3b02e5a | ||
![]() |
3489e4af1e | ||
![]() |
8e9bd0bbd5 | ||
![]() |
3725b5bc34 | ||
![]() |
998a59aa5e | ||
![]() |
86c017674a | ||
![]() |
cbae87cd11 | ||
![]() |
5bc1f8d468 | ||
![]() |
d3a355e164 | ||
![]() |
45413c9d28 | ||
![]() |
082ee8836f | ||
![]() |
2f5bd5e6d7 | ||
![]() |
639e0137cc | ||
![]() |
2f898aa037 | ||
![]() |
a43a225740 | ||
![]() |
833c39969b | ||
![]() |
e25dea27e7 | ||
![]() |
dac962580b | ||
![]() |
b12bf78c6d | ||
![]() |
a44b005f7c | ||
![]() |
4ea65dbe41 | ||
![]() |
90ba51ee9c |
74
CHANGELOG.md
@@ -1,6 +1,80 @@
|
|||||||
ChangeLog
|
ChangeLog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
12.28.0 (2020/3/29)
|
||||||
|
-------------------
|
||||||
|
### ✨Improvements
|
||||||
|
* インストールされたアプリのページでアプリの権限を確認できるように
|
||||||
|
* API: api/meta.features.miauthを追加
|
||||||
|
MiAuthに対応しているかどうかを確認するために利用できます。
|
||||||
|
値はつねにtrueを取ります。
|
||||||
|
* インスタンス一覧でソートできるように
|
||||||
|
|
||||||
|
12.27.1 (2020/03/28)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### ✨Improvements
|
||||||
|
* MiAuthのバグを修正
|
||||||
|
|
||||||
|
12.27.0 (2020/03/28)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### ✨Improvements
|
||||||
|
* サードパーティーアプリケーションの認証方法にMiAuthを追加 ([Misskey API ドキュメント](https://github.com/syuilo/misskey/blob/b8088dc01a0c53b264c0697082ff5b16b06c4cda/src/docs/api.ja-JP.md#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A8%E3%81%97%E3%81%A6%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B))
|
||||||
|
従来の、API `app/create` => `auth/session/generate` => `auth/session/userkey` を使用する方法は依然として使用可能です。
|
||||||
|
UIからアプリを作成する画面 (`/dev/apps`) は廃止されました、同等の操作を行いたい場合は API `app/create` で可能です。
|
||||||
|
MiAuthに対応しているかどうかは`api/meta.features.miauth`で確認できます(12.28.0~)。
|
||||||
|
* テーマをインポートする前にプレビューできるように
|
||||||
|
* アプリから通知を作成できるように
|
||||||
|
* インストールしたアプリを見たり削除したりできるように
|
||||||
|
|
||||||
|
12.26.0 (2020/03/25)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### ✨Improvements
|
||||||
|
* ロゴが新しく
|
||||||
|
* インスタンス設定の「ユーザー」が登録の逆順で表示されるように
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* 新規登録フォームの「利用規約」のリンク色が通常の文字と同じだった問題を修正
|
||||||
|
* ダークモードの同期の問題を修正
|
||||||
|
|
||||||
|
12.25.0 (2020/03/24)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### ✨Improvements
|
||||||
|
* テーマインポート機能を実装
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* 誰もフォローしていないときにタイムラインの読み込みが遅い問題を修正
|
||||||
|
|
||||||
|
|
||||||
|
12.24.2 (2020/03/22)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* ダークモードの同期を修正
|
||||||
|
|
||||||
|
12.24.1 (2020/03/22)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### ✨Improvements
|
||||||
|
* SVG形式のアイコンファイルを追加
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* iOSで起動できない問題を修正
|
||||||
|
* Pages画面にタイトルがない問題を修正
|
||||||
|
|
||||||
|
12.24.0 (2020/03/22)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### ✨Improvements
|
||||||
|
* クライアント設定にアカウント設定へのリンクを追加
|
||||||
|
* ダークモードの同期を強化
|
||||||
|
|
||||||
|
### 🐛Fixes
|
||||||
|
* 画面が小さいとメニューがすべて見えない問題を修正
|
||||||
|
|
||||||
12.23.0 (2020/03/22)
|
12.23.0 (2020/03/22)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
108
README.md
@@ -109,100 +109,102 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
<!-- PATREON_START -->
|
<!-- PATREON_START -->
|
||||||
<table><tr>
|
<table><tr>
|
||||||
<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo " width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/605366/c9dc408fdcbf412fb183ca5b06235f8d/1.jpeg?token-time=2145916800&token-hash=oaqsjLqOFjWN5I9hm2epOaTXaEtKwQUy5OW-EpAz6-g%3D" alt="Jon Leibowitz" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/605366/c9dc408fdcbf412fb183ca5b06235f8d/1.jpeg?token-time=2145916800&token-hash=oaqsjLqOFjWN5I9hm2epOaTXaEtKwQUy5OW-EpAz6-g%3D" alt="Jon Leibowitz" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/3.png?token-time=2145916800&token-hash=Zq1TCK2tdY7xudEm_aV70bc_wxmol6pNj3ZWbpFUNbI%3D" alt="Nesakko" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/3.png?token-time=2145916800&token-hash=Zq1TCK2tdY7xudEm_aV70bc_wxmol6pNj3ZWbpFUNbI%3D" alt="Nesakko " width="100"></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/user?u=20832595">Roujo</a></td>
|
<td><a href="https://www.patreon.com/user?u=20832595">Roujo </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td>
|
<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td>
|
||||||
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
|
<td><a href="https://www.patreon.com/weepjp">weepjp </a></td>
|
||||||
<td><a href="https://www.patreon.com/jonleibowitz">Jon Leibowitz</a></td>
|
<td><a href="https://www.patreon.com/jonleibowitz">Jon Leibowitz</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
|
<td><a href="https://www.patreon.com/user?u=19045173">kiritan </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
|
<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
|
||||||
<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
|
<td><a href="https://www.patreon.com/Nesakko">Nesakko </a></td>
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<table><tr>
|
||||||
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/21285325" alt="Nie(sha) " width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon " 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://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/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
|
<td><a href="https://www.patreon.com/user?u=776209">Denshi </a></td>
|
||||||
<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
|
<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td>
|
<td><a href="https://www.patreon.com/user?u=557245">mkatze </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y</a></td>
|
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y </a></td>
|
||||||
<td><a href="https://www.patreon.com/AureoleArk">AureoleArk</a></td>
|
<td><a href="https://www.patreon.com/AureoleArk">AureoleArk </a></td>
|
||||||
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
|
<td><a href="https://www.patreon.com/user?u=21285325">Nie(sha) </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
|
<td><a href="https://www.patreon.com/osapon">osapon </a></td>
|
||||||
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
|
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ </a></td>
|
||||||
|
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61 </a></td>
|
||||||
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
|
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<table><tr>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1 " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura" width="100"></td>
|
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
|
<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
|
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
|
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin </a></td>
|
||||||
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
|
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=26340354">totokoro</a></td>
|
<td><a href="https://www.patreon.com/user?u=26340354">totokoro </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
|
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=5827393">motcha</a></td>
|
<td><a href="https://www.patreon.com/user?u=5827393">motcha </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=20494440">axtuki1</a></td>
|
<td><a href="https://www.patreon.com/user?u=20494440">axtuki1 </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
|
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
|
||||||
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
|
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<table><tr>
|
||||||
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/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/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/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/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
|
<td><a href="https://www.patreon.com/takimura">takimura </a></td>
|
||||||
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
|
<td><a href="https://www.patreon.com/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/user?u=28295158">012 </a></td>
|
||||||
<td><a href="https://www.patreon.com/nijimiss">nafuchoco</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=16900731">Atsuko Tominaga</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
|
<td><a href="https://www.patreon.com/user?u=4389829">natalie </a></td>
|
||||||
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
|
<td><a href="https://www.patreon.com/noellabo">noellabo </a></td>
|
||||||
<td><a href="https://www.patreon.com/Corset">CG</a></td>
|
<td><a href="https://www.patreon.com/Corset">CG </a></td>
|
||||||
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
|
<td><a href="https://www.patreon.com/hekovic">Hekovic </a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
|
<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<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://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi " width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/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/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>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
|
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
|
<td><a href="https://www.patreon.com/user?u=23932002">nenohi </a></td>
|
||||||
<td><a href="https://www.patreon.com/efertone">Efertone</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>
|
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||||
</tr></table>
|
</tr></table>
|
||||||
|
|
||||||
**Last updated:** Tue, 17 Mar 2020 18:57:08 UTC
|
**Last updated:** Fri, 03 Apr 2020 11:52:08 UTC
|
||||||
<!-- PATREON_END -->
|
<!-- PATREON_END -->
|
||||||
|
|
||||||
[backer-url]: #backers
|
[backer-url]: #backers
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.2 KiB |
@@ -1,20 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
<path d="M416,199.998C416,177.908 398.092,160 376.002,160L312.038,160C301.419,160 291.235,164.218 283.727,171.727C276.218,179.235 272,189.419 272,200.038L272,228C272,234.627 277.373,240 284,240L376.002,240C398.092,240 416,222.092 416,200.002L416,199.998Z" style="fill:url(#_Linear1);"/>
|
<g transform="matrix(0.413372,0,0,0.469741,64.564,40.5821)">
|
||||||
<g transform="matrix(6.12323e-17,1,-1,6.12323e-17,512,-4.54747e-13)">
|
<rect x="-156.189" y="-86.393" width="619.297" height="544.981" style="fill:rgb(27,30,31);"/>
|
||||||
<path d="M416,199.998C416,177.908 398.092,160 376.002,160L312.038,160C301.419,160 291.235,164.218 283.727,171.727C276.218,179.235 272,189.419 272,200.038L272,228C272,234.627 277.373,240 284,240L376.002,240C398.092,240 416,222.092 416,200.002L416,199.998Z" style="fill:url(#_Linear2);"/>
|
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(-1,1.22465e-16,-1.22465e-16,-1,512,512)">
|
<g transform="matrix(0.898356,0,0,0.898356,-130.722,-120.968)">
|
||||||
<path d="M416,199.998C416,177.908 398.092,160 376.002,160L312.038,160C301.419,160 291.235,164.218 283.727,171.727C276.218,179.235 272,189.419 272,200.038L272,228C272,234.627 277.373,240 284,240L376.002,240C398.092,240 416,222.092 416,200.002L416,199.998Z" style="fill:url(#_Linear3);"/>
|
<g transform="matrix(0.5,0.866025,-0.866025,0.5,288,-166.277)">
|
||||||
|
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653Z" style="fill:white;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,-96,166.277)">
|
||||||
|
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653Z" style="fill:white;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,-0.866025,0.866025,0.5,-96,498.831)">
|
||||||
|
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653Z" style="fill:white;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,-95.9902,55.4086)">
|
||||||
|
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653ZM385.681,139.653C385.332,139.049 384.688,138.677 383.99,138.677C383.293,138.677 382.648,139.049 382.299,139.653C378.289,146.599 373.342,155.168 369.8,161.303C368.017,164.391 368.017,168.196 369.8,171.285C373.339,177.414 378.28,185.972 382.288,192.915C382.639,193.523 383.288,193.898 383.99,193.898C384.692,193.898 385.341,193.523 385.692,192.915C389.701,185.972 394.642,177.414 398.181,171.284C399.964,168.196 399.964,164.391 398.181,161.303L385.681,139.653Z" style="fill:rgb(150,208,74);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,-0.866025,0.866025,0.5,-2.64322e-11,554.256)">
|
||||||
|
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653ZM385.681,139.653C385.332,139.049 384.688,138.677 383.99,138.677C383.293,138.677 382.648,139.049 382.299,139.653C378.289,146.599 373.342,155.168 369.8,161.303C368.017,164.391 368.017,168.196 369.8,171.285C373.339,177.414 378.28,185.972 382.288,192.915C382.639,193.523 383.288,193.898 383.99,193.898C384.692,193.898 385.341,193.523 385.692,192.915C389.701,185.972 394.642,177.414 398.181,171.284C399.964,168.196 399.964,164.391 398.181,161.303L385.681,139.653Z" style="fill:rgb(150,208,74);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.5,0.866025,-0.866025,0.5,192,-110.851)">
|
||||||
|
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653ZM385.681,139.653C385.332,139.049 384.688,138.677 383.99,138.677C383.293,138.677 382.648,139.049 382.299,139.653C378.289,146.599 373.342,155.168 369.8,161.303C368.017,164.391 368.017,168.196 369.8,171.285C373.339,177.414 378.28,185.972 382.288,192.915C382.639,193.523 383.288,193.898 383.99,193.898C384.692,193.898 385.341,193.523 385.692,192.915C389.701,185.972 394.642,177.414 398.181,171.284C399.964,168.196 399.964,164.391 398.181,161.303L385.681,139.653Z" style="fill:rgb(150,208,74);"/>
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(6.12323e-17,-1,1,6.12323e-17,0,512)">
|
|
||||||
<path d="M416,199.998C416,177.908 398.092,160 376.002,160L312.038,160C301.419,160 291.235,164.218 283.727,171.727C276.218,179.235 272,189.419 272,200.038L272,228C272,234.627 277.373,240 284,240L376.002,240C398.092,240 416,222.092 416,200.002L416,199.998Z" style="fill:url(#_Linear4);"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(144,0,0,80,272,200)"><stop offset="0" style="stop-color:rgb(198,230,111);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(90,200,113);stop-opacity:1"/></linearGradient>
|
|
||||||
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(144,0,0,80,272,200)"><stop offset="0" style="stop-color:rgb(97,232,195);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(90,200,113);stop-opacity:1"/></linearGradient>
|
|
||||||
<linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(144,0,0,80,272,200)"><stop offset="0" style="stop-color:rgb(198,230,111);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(90,200,113);stop-opacity:1"/></linearGradient>
|
|
||||||
<linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(144,0,0,80,272,200)"><stop offset="0" style="stop-color:rgb(98,232,195);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(90,200,113);stop-opacity:1"/></linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 18 KiB |
@@ -34,14 +34,16 @@ unpin: "Lösen"
|
|||||||
copyContent: "Inhalt kopieren"
|
copyContent: "Inhalt kopieren"
|
||||||
copyLink: "Link kopieren"
|
copyLink: "Link kopieren"
|
||||||
delete: "Löschen"
|
delete: "Löschen"
|
||||||
|
deleteAndEdit: "Löschen und Bearbeiten"
|
||||||
|
deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten? Alle Reaktionen, Renotes und Antworten dieser Notiz werden verloren gehen."
|
||||||
addToList: "Zur Liste hinzufügen"
|
addToList: "Zur Liste hinzufügen"
|
||||||
sendMessage: "Nachricht senden"
|
sendMessage: "Nachricht senden"
|
||||||
copyUsername: "Benutzernamen kopieren"
|
copyUsername: "Benutzernamen kopieren"
|
||||||
reply: "Antworten"
|
reply: "Antworten"
|
||||||
loadMore: "Zeige mehr"
|
loadMore: "Zeige mehr"
|
||||||
youGotNewFollower: "Sie haben einen neuen Follower"
|
youGotNewFollower: "Sie haben einen neuen Follower"
|
||||||
receiveFollowRequest: "Follow Request erhalten."
|
receiveFollowRequest: "Follow-Anfrage erhalten."
|
||||||
followRequestAccepted: "FollowRequestAkzeptiert"
|
followRequestAccepted: "Follow-Anfrage akzeptiert"
|
||||||
mentions: "Erwähnungen"
|
mentions: "Erwähnungen"
|
||||||
directNotes: "Direktnachrichten"
|
directNotes: "Direktnachrichten"
|
||||||
importAndExport: "Importieren und Exportieren"
|
importAndExport: "Importieren und Exportieren"
|
||||||
@@ -51,6 +53,8 @@ files: "Dateien"
|
|||||||
download: "Download"
|
download: "Download"
|
||||||
driveFileDeleteConfirm: "Möchtest du die Datei \"{name}\" löschen? Die zugehörige Notiz wird ebenso verschwinden."
|
driveFileDeleteConfirm: "Möchtest du die Datei \"{name}\" löschen? Die zugehörige Notiz wird ebenso verschwinden."
|
||||||
unfollowConfirm: "Möchtest du {name} nicht mehr folgen?"
|
unfollowConfirm: "Möchtest du {name} nicht mehr folgen?"
|
||||||
|
exportRequested: "Du hast einen Export angefragt. Dies kann etwas Zeit in Anspruch nehmen. Sobald der Export abgeschlossen ist, wird er deiner Drive hinzugefügt."
|
||||||
|
importRequested: "Du hast einen Import angefragt. Dies kann etwas Zeit in Anspruch nehmen."
|
||||||
lists: "Listen"
|
lists: "Listen"
|
||||||
noLists: "Keine Liste!"
|
noLists: "Keine Liste!"
|
||||||
note: "Notiz"
|
note: "Notiz"
|
||||||
@@ -109,24 +113,33 @@ addAcount: "Benutzerkonto hinzufügen"
|
|||||||
loginFailed: "Login fehlgeschlagen"
|
loginFailed: "Login fehlgeschlagen"
|
||||||
general: "Allgemein"
|
general: "Allgemein"
|
||||||
wallpaper: "Hintergrund"
|
wallpaper: "Hintergrund"
|
||||||
|
setWallpaper: "Hintergrund festlegen"
|
||||||
removeWallpaper: "Hintergrund entfernen"
|
removeWallpaper: "Hintergrund entfernen"
|
||||||
searchWith: "Suche: {q}"
|
searchWith: "Suche: {q}"
|
||||||
youHaveNoLists: "Du hast keine Listen"
|
youHaveNoLists: "Du hast keine Listen"
|
||||||
followConfirm: "Möchtest du {name} wirklich folgen?"
|
followConfirm: "Möchtest du {name} wirklich folgen?"
|
||||||
|
proxyAccount: "Proxy-Benutzerkonto"
|
||||||
|
host: "Host"
|
||||||
selectUser: "Benutzer wählen"
|
selectUser: "Benutzer wählen"
|
||||||
recipient: "Empfänger"
|
recipient: "Empfänger"
|
||||||
annotation: "Anmerkung"
|
annotation: "Anmerkung"
|
||||||
federation: "Föderation"
|
federation: "Föderation"
|
||||||
instances: "Instanz"
|
instances: "Instanz"
|
||||||
|
registeredAt: "Registriert am"
|
||||||
|
latestRequestSentAt: "Letzte Anfrage gesendet am"
|
||||||
|
latestRequestReceivedAt: "Letzte Anfrage erhalten am"
|
||||||
latestStatus: "Neuester Status"
|
latestStatus: "Neuester Status"
|
||||||
storageUsage: "Speicherplatzverbrauch"
|
storageUsage: "Speicherplatzverbrauch"
|
||||||
charts: "Charts"
|
charts: "Charts"
|
||||||
perHour: "Pro Stunde"
|
perHour: "Pro Stunde"
|
||||||
perDay: "Pro Tag"
|
perDay: "Pro Tag"
|
||||||
|
stopActivityDelivery: "Senden von Aktivitäten einstellen"
|
||||||
blockThisInstance: "Diese Instanz blockieren"
|
blockThisInstance: "Diese Instanz blockieren"
|
||||||
|
software: "Software"
|
||||||
version: "Version"
|
version: "Version"
|
||||||
metadata: "Metadaten"
|
metadata: "Metadaten"
|
||||||
withNFiles: "{n} Datei(en)"
|
withNFiles: "{n} Datei(en)"
|
||||||
|
monitor: "Beobachten"
|
||||||
jobQueue: "Job-Warteschlange"
|
jobQueue: "Job-Warteschlange"
|
||||||
cpuAndMemory: "CPU und Arbeitsspeicher"
|
cpuAndMemory: "CPU und Arbeitsspeicher"
|
||||||
network: "Netzwerk"
|
network: "Netzwerk"
|
||||||
@@ -157,6 +170,7 @@ federating: "Föderiert"
|
|||||||
blocked: "Blockiert"
|
blocked: "Blockiert"
|
||||||
suspended: "Gesperrt"
|
suspended: "Gesperrt"
|
||||||
all: "Alles"
|
all: "Alles"
|
||||||
|
notResponding: "Antwortet nicht"
|
||||||
changePassword: "Passwort ändern"
|
changePassword: "Passwort ändern"
|
||||||
security: "Sicherheit"
|
security: "Sicherheit"
|
||||||
retypedNotMatch: "Eingaben stimmen nicht überein."
|
retypedNotMatch: "Eingaben stimmen nicht überein."
|
||||||
@@ -178,6 +192,10 @@ messaging: "Nachrichten"
|
|||||||
upload: "Hochladen"
|
upload: "Hochladen"
|
||||||
fromDrive: "Aus Drive"
|
fromDrive: "Aus Drive"
|
||||||
fromUrl: "Von einer URL"
|
fromUrl: "Von einer URL"
|
||||||
|
uploadFromUrl: "Von einer URL hochladen"
|
||||||
|
uploadFromUrlDescription: "URL der hochzuladenden Datei"
|
||||||
|
uploadFromUrlRequested: "Upload angefordert"
|
||||||
|
uploadFromUrlMayTakeTime: "Es kann eine Weile dauern, bis der Upload abgeschlossen ist."
|
||||||
explore: "Erkunden"
|
explore: "Erkunden"
|
||||||
games: "Misskey Spiele"
|
games: "Misskey Spiele"
|
||||||
messageRead: "Gelesen"
|
messageRead: "Gelesen"
|
||||||
@@ -191,21 +209,32 @@ activity: "Aktivität"
|
|||||||
images: "Bilder"
|
images: "Bilder"
|
||||||
birthday: "Geburtstag"
|
birthday: "Geburtstag"
|
||||||
yearsOld: "{age} Jahre alt"
|
yearsOld: "{age} Jahre alt"
|
||||||
registeredDate: "Registierdatum"
|
registeredDate: "Registrationsdatum"
|
||||||
location: "Ort"
|
location: "Ort"
|
||||||
theme: "Farbthemen"
|
theme: "Farbthemen"
|
||||||
|
themeForLightMode: "Farbthema, das im Hellmodus genutzt wird"
|
||||||
|
themeForDarkMode: "Farbthema, das im Dunkelmodus genutzt wird"
|
||||||
|
light: "Hell"
|
||||||
|
dark: "Dunkel"
|
||||||
lightThemes: "Helle Farbthemen"
|
lightThemes: "Helle Farbthemen"
|
||||||
darkThemes: "Dunkle Farbthemen"
|
darkThemes: "Dunkle Farbthemen"
|
||||||
drive: "Drive"
|
drive: "Drive"
|
||||||
|
fileName: "Dateiname"
|
||||||
selectFile: "Datei auswählen"
|
selectFile: "Datei auswählen"
|
||||||
selectFiles: "Dateien auswählen"
|
selectFiles: "Dateien auswählen"
|
||||||
renameFile: "Datei umbenennen"
|
renameFile: "Datei umbenennen"
|
||||||
|
folderName: "Ordnername"
|
||||||
createFolder: "Ordner erstellen"
|
createFolder: "Ordner erstellen"
|
||||||
renameFolder: "Ordner umbenennen"
|
renameFolder: "Ordner umbenennen"
|
||||||
deleteFolder: "Ordner löschen"
|
deleteFolder: "Ordner löschen"
|
||||||
addFile: "Datei hinzufügen"
|
addFile: "Datei hinzufügen"
|
||||||
emptyDrive: "Drive ist leer"
|
emptyDrive: "Drive ist leer"
|
||||||
emptyFolder: "Der Ordner ist leer"
|
emptyFolder: "Der Ordner ist leer"
|
||||||
|
unableToDelete: "Nicht löschbar"
|
||||||
|
inputNewFileName: "Gib einen neuen Dateinamen ein"
|
||||||
|
inputNewFolderName: "Gib einen neuen Ordnernamen ein"
|
||||||
|
circularReferenceFolder: "Der Zielordner ist ein Unterorder des Ordners, den du verschieben möchtest."
|
||||||
|
hasChildFilesOrFolders: "Dieser Ordner kann nicht gelöscht werden, da er nicht leer ist."
|
||||||
copyUrl: "URL kopieren"
|
copyUrl: "URL kopieren"
|
||||||
rename: "Umbenennen"
|
rename: "Umbenennen"
|
||||||
avatar: "Profilbild"
|
avatar: "Profilbild"
|
||||||
@@ -226,6 +255,9 @@ tosUrl: "URL der Nutzungsbedingungen"
|
|||||||
thisYear: "Dieses Jahr"
|
thisYear: "Dieses Jahr"
|
||||||
thisMonth: "Dieser Monat"
|
thisMonth: "Dieser Monat"
|
||||||
today: "Heute"
|
today: "Heute"
|
||||||
|
dayX: "{day}"
|
||||||
|
monthX: "{month}"
|
||||||
|
yearX: "{year}"
|
||||||
pages: "Seiten"
|
pages: "Seiten"
|
||||||
integration: "Integration"
|
integration: "Integration"
|
||||||
connectSerice: "Verbinden"
|
connectSerice: "Verbinden"
|
||||||
@@ -242,6 +274,7 @@ pinnedUsers: "Angepinnte Benutzer"
|
|||||||
pinnedUsersDescription: "Gib einen Benutzernamen pro Zeile ein. Diese werden im \"Erkunden\" Tab angezeigt."
|
pinnedUsersDescription: "Gib einen Benutzernamen pro Zeile ein. Diese werden im \"Erkunden\" Tab angezeigt."
|
||||||
recaptcha: "reCAPTCHA"
|
recaptcha: "reCAPTCHA"
|
||||||
enableRecaptcha: "reCAPTCHA aktivieren"
|
enableRecaptcha: "reCAPTCHA aktivieren"
|
||||||
|
recaptchaSecretKey: "Secret key"
|
||||||
antennas: "Antennen"
|
antennas: "Antennen"
|
||||||
manageAntennas: "Antennen verwalten"
|
manageAntennas: "Antennen verwalten"
|
||||||
name: "Name"
|
name: "Name"
|
||||||
@@ -249,6 +282,8 @@ antennaSource: "Antennenquelle"
|
|||||||
antennaKeywords: "Schlüsselwörter, die beobachtet werden sollen"
|
antennaKeywords: "Schlüsselwörter, die beobachtet werden sollen"
|
||||||
antennaExcludeKeywords: "Schlüsselwörter, die ignoriert werden sollen"
|
antennaExcludeKeywords: "Schlüsselwörter, die ignoriert werden sollen"
|
||||||
antennaKeywordsDescription: "Mit Leerzeichen für eine \"UND\"-Verknüpfung trennen, durch Zeilenumbrüche für eine \"ODER\"-Verknüpfung trennen."
|
antennaKeywordsDescription: "Mit Leerzeichen für eine \"UND\"-Verknüpfung trennen, durch Zeilenumbrüche für eine \"ODER\"-Verknüpfung trennen."
|
||||||
|
notifyAntenna: "Über neue Notizen benachrichtigen"
|
||||||
|
withFileAntenna: "Nur Notizen mit Dateien"
|
||||||
serviceworker: "ServiceWorker"
|
serviceworker: "ServiceWorker"
|
||||||
enableServiceworker: "ServiceWorker aktivieren"
|
enableServiceworker: "ServiceWorker aktivieren"
|
||||||
antennaUsersDescription: "Benutzernamen getrennt durch Zeilenumbrüche angeben"
|
antennaUsersDescription: "Benutzernamen getrennt durch Zeilenumbrüche angeben"
|
||||||
@@ -263,8 +298,24 @@ recentlyRegisteredUsers: "Vor kurzem registrierte Benutzer"
|
|||||||
recentlyDiscoveredUsers: "Vor kurzem gefundene Benutzer"
|
recentlyDiscoveredUsers: "Vor kurzem gefundene Benutzer"
|
||||||
exploreUsersCount: "Es gibt {count} Benutzer"
|
exploreUsersCount: "Es gibt {count} Benutzer"
|
||||||
exploreFediverse: "Das Fediverse erkunden"
|
exploreFediverse: "Das Fediverse erkunden"
|
||||||
|
popularTags: "Beliebte Schlagwörter"
|
||||||
userList: "Listen"
|
userList: "Listen"
|
||||||
|
about: "Über"
|
||||||
aboutMisskey: "Über Misskey"
|
aboutMisskey: "Über Misskey"
|
||||||
|
misskeySource: "Der Quelltext ist hier verfügbar:"
|
||||||
|
misskeyTranslation: "Hilf dabei, Misskey zu übersetzen:"
|
||||||
|
misskeyDonate: "Spende an Misskey, um die Weiterentwicklung zu unterstützen:"
|
||||||
|
morePatrons: "Wir schätzen ebenso die Unterstützung vieler anderer hier nicht gelisteter Personen sehr. Danke! 🥰"
|
||||||
|
patrons: "UnterstützerInnen"
|
||||||
|
administrator: "Administrator"
|
||||||
|
twoStepAuthentication: "Zwei-Faktor-Authentifizierung"
|
||||||
|
moderator: "Moderator"
|
||||||
|
nUsersMentioned: "{n} Benutzer erwähnt"
|
||||||
|
securityKey: "Sicherheitsschlüssel"
|
||||||
|
securityKeyName: "Schlüsselname"
|
||||||
|
registerSecurityKey: "Sicherheitsschlüssel registrieren"
|
||||||
|
lastUsed: "Zuletzt benutzt"
|
||||||
|
passwordLessLogin: "Passwortloses Anmelden einrichten"
|
||||||
resetPassword: "Passwort zurücksetzen"
|
resetPassword: "Passwort zurücksetzen"
|
||||||
newPasswordIs: "Das neue Passwort ist \"{password}\""
|
newPasswordIs: "Das neue Passwort ist \"{password}\""
|
||||||
posted: "Gesendet"
|
posted: "Gesendet"
|
||||||
@@ -279,7 +330,18 @@ uploadFolder: "Standardordner für Uploads"
|
|||||||
cacheClear: "Cache leeren"
|
cacheClear: "Cache leeren"
|
||||||
markAsReadAllNotifications: "Alle Benachrichtigungen als gelesen markieren"
|
markAsReadAllNotifications: "Alle Benachrichtigungen als gelesen markieren"
|
||||||
markAsReadAllUnreadNotes: "Alle Notizen als gelesen markieren"
|
markAsReadAllUnreadNotes: "Alle Notizen als gelesen markieren"
|
||||||
|
help: "Hilfe"
|
||||||
|
inputMessageHere: "Hier Nachricht eingeben"
|
||||||
|
close: "Schließen"
|
||||||
|
group: "Gruppe"
|
||||||
|
groups: "Gruppen"
|
||||||
|
createGroup: "Gruppe erstellen"
|
||||||
|
ownedGroups: "Eigene Gruppen"
|
||||||
|
joinedGroups: "Beigetretene Gruppen"
|
||||||
invites: "Einladen"
|
invites: "Einladen"
|
||||||
|
groupName: "Gruppenname"
|
||||||
|
members: "Mitglieder"
|
||||||
|
transfer: "Übertragen"
|
||||||
title: "Betreff"
|
title: "Betreff"
|
||||||
text: "Text"
|
text: "Text"
|
||||||
enable: "Aktivieren"
|
enable: "Aktivieren"
|
||||||
@@ -288,12 +350,40 @@ retype: "Erneut eingeben"
|
|||||||
noteOf: "Notiz von {user}"
|
noteOf: "Notiz von {user}"
|
||||||
inviteToGroup: "Zu Gruppe einladen"
|
inviteToGroup: "Zu Gruppe einladen"
|
||||||
maxNoteTextLength: "Maximale Länge von Notizen"
|
maxNoteTextLength: "Maximale Länge von Notizen"
|
||||||
|
quoteAttached: "Zitiert"
|
||||||
|
quoteQuestion: "Als Zitat anfügen?"
|
||||||
|
noMessagesYet: "Noch keine Nachrichten"
|
||||||
|
newMessageExists: "Du hast eine neue Nachricht"
|
||||||
|
onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden"
|
||||||
|
signinRequired: "Anmeldung erforderlich"
|
||||||
|
invitationCode: "Einladungscode"
|
||||||
|
checking: "Wird überprüft..."
|
||||||
|
available: "Verfügbar"
|
||||||
|
unavailable: "Unverfügbar"
|
||||||
|
usernameInvalidFormat: "Buchstaben, Zahlen und Unterstriche sind verwendbar."
|
||||||
|
tooShort: "Zu kurz"
|
||||||
|
tooLong: "Zu lang"
|
||||||
|
weakPassword: "Schwaches Passwort"
|
||||||
|
normalPassword: "Standardpasswort"
|
||||||
|
strongPassword: "Starkes Passwort"
|
||||||
|
passwordMatched: "Stimmt überein"
|
||||||
|
passwordNotMatched: "Stimmt nicht überein"
|
||||||
|
signinWith: "Mit {x} anmelden"
|
||||||
|
signinFailed: "Anmeldung fehlgeschlagen. Überprüfe Benutzername und Passswort."
|
||||||
|
tapSecurityKey: "Tippe deinen Sicherheitsschlüssel an"
|
||||||
|
or: "Oder"
|
||||||
|
uiLanguage: "Sprache der Benutzeroberfläche"
|
||||||
|
groupInvited: "Du wurdest in eine Gruppe eingeladen"
|
||||||
|
aboutX: "Über {x}"
|
||||||
useOsNativeEmojis: "Eingebaute Emojis des Betriebssystems benutzen"
|
useOsNativeEmojis: "Eingebaute Emojis des Betriebssystems benutzen"
|
||||||
|
youHaveNoGroups: "Keine Gruppen vorhanden"
|
||||||
joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigene."
|
joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigene."
|
||||||
noHistory: "Kein Verlauf"
|
noHistory: "Kein Verlauf"
|
||||||
disableAnimatedMfm: "MFM, die Animationen enthalten, deaktivieren"
|
disableAnimatedMfm: "MFM, die Animationen enthalten, deaktivieren"
|
||||||
|
doing: "In Bearbeitung"
|
||||||
category: "Kategorie"
|
category: "Kategorie"
|
||||||
tags: "Schlagwörter"
|
tags: "Schlagwörter"
|
||||||
|
docSource: "Quelle dieses Dokuments"
|
||||||
createAccount: "Benutzerkonto erstellen"
|
createAccount: "Benutzerkonto erstellen"
|
||||||
existingAcount: "Bestehendes Benutzerkonto"
|
existingAcount: "Bestehendes Benutzerkonto"
|
||||||
regenerate: "Regenerieren"
|
regenerate: "Regenerieren"
|
||||||
@@ -312,14 +402,43 @@ promotion: "Hervorgehoben"
|
|||||||
promote: "Hervorheben"
|
promote: "Hervorheben"
|
||||||
numberOfDays: "Anzahl der Tage"
|
numberOfDays: "Anzahl der Tage"
|
||||||
hideThisNote: "Diese Notiz verstecken"
|
hideThisNote: "Diese Notiz verstecken"
|
||||||
|
objectStorage: "Objektspeicher"
|
||||||
|
useObjectStorage: "Objektspeicher verwenden"
|
||||||
|
objectStorageBaseUrl: "Basis-URL"
|
||||||
|
objectStorageBucket: "Bucket"
|
||||||
|
objectStoragePrefix: "Prefix"
|
||||||
|
objectStorageEndpoint: "Endpoint"
|
||||||
|
objectStorageRegion: "Region"
|
||||||
|
objectStorageUseSSL: "SSL verwenden"
|
||||||
serverLogs: "Serverprotokolle"
|
serverLogs: "Serverprotokolle"
|
||||||
deleteAll: "Alle löschen"
|
deleteAll: "Alle löschen"
|
||||||
|
newNoteRecived: "Du hast eine neue Notiz empfangen"
|
||||||
sounds: "Töne"
|
sounds: "Töne"
|
||||||
listen: "Anhören"
|
listen: "Anhören"
|
||||||
none: "Keine"
|
none: "Keine"
|
||||||
volume: "Lautstärke"
|
volume: "Lautstärke"
|
||||||
details: "Details"
|
details: "Details"
|
||||||
chooseEmoji: "Wähle ein Emoji"
|
chooseEmoji: "Wähle ein Emoji"
|
||||||
|
unableToProcess: "Der Vorgang konnte nicht abgeschlossen werden."
|
||||||
|
recentUsed: "Vor kurzem verwendet"
|
||||||
|
install: "Installieren"
|
||||||
|
uninstall: "Uninstallieren"
|
||||||
|
installedApps: "Authorisierte Anwendungen"
|
||||||
|
nothing: "Hier gibt es nichts zu sehen"
|
||||||
|
installedDate: "Authorisiert"
|
||||||
|
lastUsedDate: "Zuletzt verwendet"
|
||||||
|
state: "Status"
|
||||||
|
sort: "Sortieren"
|
||||||
|
ascendingOrder: "Aufsteigende Reihenfolge"
|
||||||
|
descendingOrder: "Absteigende Reihenfolge"
|
||||||
|
_theme:
|
||||||
|
explore: "Themen erforschen"
|
||||||
|
install: "Thema installieren"
|
||||||
|
manage: "Themaverwaltung"
|
||||||
|
code: "Themencode"
|
||||||
|
installed: "{name} wurde installiert"
|
||||||
|
alreadyInstalled: "Dieses Thema ist bereits installiert"
|
||||||
|
invalid: "Themenformat ist ungültig"
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "Notizen"
|
note: "Notizen"
|
||||||
noteMy: "Meine Notizen"
|
noteMy: "Meine Notizen"
|
||||||
@@ -342,11 +461,47 @@ _time:
|
|||||||
second: "Sekunde"
|
second: "Sekunde"
|
||||||
minute: "Minute"
|
minute: "Minute"
|
||||||
hour: "Stunde"
|
hour: "Stunde"
|
||||||
|
day: "t"
|
||||||
|
_2fa:
|
||||||
|
alreadyRegistered: "Du hast bereits ein Gerät für Zwei-Faktor-Authentifizierung registriert"
|
||||||
|
registerDevice: "Neues Gerät registrieren"
|
||||||
|
registerKey: "Neuen Sicherheitsschlüssel registrieren"
|
||||||
_permissions:
|
_permissions:
|
||||||
|
"read:account": "Deine Benutzerkontoinformationen lesen"
|
||||||
|
"write:account": "Deine Benutzerkontoinformationen bearbeiten"
|
||||||
|
"read:blocks": "Die Liste deiner blockierten Benutzer lesen"
|
||||||
|
"write:blocks": "Die Liste deiner blockierten Benutzer bearbeiten"
|
||||||
|
"read:drive": "Deine Drive-Dateien und Ordner lesen"
|
||||||
|
"write:drive": "Deine Drive-Dateien und Ordner bearbeiten oder löschen"
|
||||||
|
"read:favorites": "Deine Favoriten-Liste lesen"
|
||||||
|
"write:favorites": "Deine Favoriten-Liste bearbeiten"
|
||||||
|
"read:following": "Deine Follower-Liste lesen"
|
||||||
|
"write:following": "Anderen Benutzern folgen oder entfolgen"
|
||||||
"read:messaging": "Nachrichten lesen"
|
"read:messaging": "Nachrichten lesen"
|
||||||
"write:messaging": "Nachrichten schicken oder löschen"
|
"write:messaging": "Nachrichten schicken oder löschen"
|
||||||
|
"read:mutes": "Stummschaltungen sehen"
|
||||||
|
"write:mutes": "Stummschaltungen bearbeiten"
|
||||||
|
"write:notes": "Notizen schreiben oder löschen"
|
||||||
|
"read:notifications": "Benachrichtigungen lesen"
|
||||||
|
"write:notifications": "Mit Benachrichtigungen arbeiten"
|
||||||
"read:reactions": "Reaktionen sehen"
|
"read:reactions": "Reaktionen sehen"
|
||||||
"write:reactions": "Reaktionen hinzufügen und bearbeiten"
|
"write:reactions": "Reaktionen hinzufügen und bearbeiten"
|
||||||
|
"write:votes": "In Umfragen abstimmen"
|
||||||
|
"read:pages": "Deine Seiten lesen"
|
||||||
|
"write:pages": "Deine Seiten bearbeiten oder löschen"
|
||||||
|
"read:page-likes": "Seiten-Likes lesen"
|
||||||
|
"write:page-likes": "Seiten-Likes bearbeiten"
|
||||||
|
"read:user-groups": "Deine Benutzergruppen lesen"
|
||||||
|
"write:user-groups": "Benutzergruppen bearbeiten oder löschen"
|
||||||
|
_auth:
|
||||||
|
shareAccess: "Möchtest du \"{name}\" authorisieren, auf dieses Benuzerkonto zugreifen zu können?"
|
||||||
|
shareAccessAsk: "Bist du dir sicher, dass du diese Anwendung authorisieren möchtest, auf dein Benutzerkonto zugreifen zu können?"
|
||||||
|
permissionAsk: "Diese Anwendung erfordert folgende Berechtigungen:"
|
||||||
|
pleaseGoBack: "Bitte gehe zurück zur Anwendung"
|
||||||
|
callback: "Rückkehr zur Anwendung"
|
||||||
|
denied: "Zugriff verweigert"
|
||||||
|
_antennaSources:
|
||||||
|
all: "Alle Notizen"
|
||||||
_weekday:
|
_weekday:
|
||||||
sunday: "Sonntag"
|
sunday: "Sonntag"
|
||||||
monday: "Montag"
|
monday: "Montag"
|
||||||
@@ -442,31 +597,35 @@ _charts:
|
|||||||
_instanceCharts:
|
_instanceCharts:
|
||||||
requests: "Anfragen"
|
requests: "Anfragen"
|
||||||
users: "Unterschied in der Anzahl von Benutzern"
|
users: "Unterschied in der Anzahl von Benutzern"
|
||||||
usersTotal: "Anzahl aller Benutzer"
|
|
||||||
notes: "Unterschied in der Anzahl von Notizen"
|
notes: "Unterschied in der Anzahl von Notizen"
|
||||||
notesTotal: "Anzahl aller Notizen"
|
|
||||||
ff: "Unterschied in der Anzahl von Followern"
|
ff: "Unterschied in der Anzahl von Followern"
|
||||||
ffTotal: "Gesamtanzahl der Follower"
|
|
||||||
cacheSize: "Unterschied in der Größe des Caches"
|
cacheSize: "Unterschied in der Größe des Caches"
|
||||||
cacheSizeTotal: "Gesamtgröße des Caches"
|
|
||||||
files: "Unterschied in der Anzahl der Dateien"
|
files: "Unterschied in der Anzahl der Dateien"
|
||||||
filesTotal: "Gesamtanzahl der Dateien"
|
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "Startseite"
|
home: "Startseite"
|
||||||
local: "Lokal"
|
local: "Lokal"
|
||||||
|
social: "Sozial"
|
||||||
global: "Global"
|
global: "Global"
|
||||||
_pages:
|
_pages:
|
||||||
|
viewPage: "Deine Seiten lesen"
|
||||||
content: "Inhalt"
|
content: "Inhalt"
|
||||||
title: "Titel"
|
title: "Titel"
|
||||||
url: "Seiten-URL"
|
url: "Seiten-URL"
|
||||||
summary: "Zusammenfassung"
|
summary: "Zusammenfassung"
|
||||||
alignCenter: "Mittig ausrichten"
|
alignCenter: "Mittig ausrichten"
|
||||||
|
hideTitleWhenPinned: "Seitentitel ausblenden, wenn an dein Profil angepinnt "
|
||||||
font: "Schriftart"
|
font: "Schriftart"
|
||||||
fontSerif: "Serif"
|
fontSerif: "Serif"
|
||||||
fontSansSerif: "Sans Serif"
|
fontSansSerif: "Sans Serif"
|
||||||
|
eyeCatchingImageSet: "Vorschaubild festlegen"
|
||||||
|
eyeCatchingImageRemove: "Vorschaubild entfernen"
|
||||||
chooseBlock: "Block hinzufügen"
|
chooseBlock: "Block hinzufügen"
|
||||||
selectType: "Wähle einen Typ"
|
selectType: "Wähle einen Typ"
|
||||||
enterVariableName: "Gib einen Namen für deine Variable ein"
|
enterVariableName: "Gib einen Namen für deine Variable ein"
|
||||||
|
variableNameIsAlreadyUsed: "Dieser Name wird bereits von einer anderen Variable verwendet"
|
||||||
|
contentBlocks: "Inhalt"
|
||||||
|
inputBlocks: "Eingabe"
|
||||||
|
specialBlocks: "Spezial"
|
||||||
blocks:
|
blocks:
|
||||||
text: "Text"
|
text: "Text"
|
||||||
textarea: "Textfeld"
|
textarea: "Textfeld"
|
||||||
@@ -481,22 +640,27 @@ _pages:
|
|||||||
text: "Inhalt"
|
text: "Inhalt"
|
||||||
textInput: "Texteingabe"
|
textInput: "Texteingabe"
|
||||||
_textInput:
|
_textInput:
|
||||||
|
name: "Variablenname"
|
||||||
text: "Titel"
|
text: "Titel"
|
||||||
default: "Standardwert"
|
default: "Standardwert"
|
||||||
textareaInput: "Eingabe des mehrzeiligen Textfelds"
|
textareaInput: "Eingabe des mehrzeiligen Textfelds"
|
||||||
_textareaInput:
|
_textareaInput:
|
||||||
|
name: "Variablenname"
|
||||||
text: "Titel"
|
text: "Titel"
|
||||||
default: "Standardwert"
|
default: "Standardwert"
|
||||||
numberInput: "Nummereingabe"
|
numberInput: "Nummereingabe"
|
||||||
_numberInput:
|
_numberInput:
|
||||||
|
name: "Variablenname"
|
||||||
text: "Titel"
|
text: "Titel"
|
||||||
default: "Standardwert"
|
default: "Standardwert"
|
||||||
switch: "Fallunterscheidung"
|
switch: "Fallunterscheidung"
|
||||||
_switch:
|
_switch:
|
||||||
|
name: "Variablenname"
|
||||||
text: "Titel"
|
text: "Titel"
|
||||||
default: "Standardwert"
|
default: "Standardwert"
|
||||||
counter: "Zähler"
|
counter: "Zähler"
|
||||||
_counter:
|
_counter:
|
||||||
|
name: "Variablenname"
|
||||||
text: "Titel"
|
text: "Titel"
|
||||||
inc: "Erhöhen um"
|
inc: "Erhöhen um"
|
||||||
_button:
|
_button:
|
||||||
@@ -516,6 +680,7 @@ _pages:
|
|||||||
no-variable: "Keine"
|
no-variable: "Keine"
|
||||||
radioButton: "Optionsfeld"
|
radioButton: "Optionsfeld"
|
||||||
_radioButton:
|
_radioButton:
|
||||||
|
name: "Variablenname"
|
||||||
title: "Titel"
|
title: "Titel"
|
||||||
values: "Auswahlmöglichkeiten (getrennt durch Zeilenumbrüche)"
|
values: "Auswahlmöglichkeiten (getrennt durch Zeilenumbrüche)"
|
||||||
default: "Standardwert"
|
default: "Standardwert"
|
||||||
@@ -671,6 +836,7 @@ _pages:
|
|||||||
splitStrByLine: "Text nach Zeilenumbrüchen aufteilen"
|
splitStrByLine: "Text nach Zeilenumbrüchen aufteilen"
|
||||||
_splitStrByLine:
|
_splitStrByLine:
|
||||||
arg1: "Text"
|
arg1: "Text"
|
||||||
|
ref: "Variablen"
|
||||||
fn: "Funktionen"
|
fn: "Funktionen"
|
||||||
_fn:
|
_fn:
|
||||||
arg1: "Ausgabe"
|
arg1: "Ausgabe"
|
||||||
|
@@ -18,7 +18,7 @@ instance: "Instance"
|
|||||||
settings: "Settings"
|
settings: "Settings"
|
||||||
profile: "Profile"
|
profile: "Profile"
|
||||||
timeline: "Timeline"
|
timeline: "Timeline"
|
||||||
noAccountDescription: "This user has not created their bio yet."
|
noAccountDescription: "This user has not written their bio yet."
|
||||||
login: "Sign In"
|
login: "Sign In"
|
||||||
loggingIn: "Signing In"
|
loggingIn: "Signing In"
|
||||||
logout: "Sign Out"
|
logout: "Sign Out"
|
||||||
@@ -257,7 +257,7 @@ rename: "Rename"
|
|||||||
avatar: "Avatar"
|
avatar: "Avatar"
|
||||||
banner: "Banner"
|
banner: "Banner"
|
||||||
nsfw: "NSFW"
|
nsfw: "NSFW"
|
||||||
disconnectedFromServer: "Connection to the server was inturrupted"
|
disconnectedFromServer: "Connection to the server was interrupted."
|
||||||
reload: "Refresh"
|
reload: "Refresh"
|
||||||
doNothing: "Ignore"
|
doNothing: "Ignore"
|
||||||
reloadConfirm: "Would you like to retry?"
|
reloadConfirm: "Would you like to retry?"
|
||||||
@@ -331,7 +331,7 @@ userList: "Lists"
|
|||||||
about: "About"
|
about: "About"
|
||||||
aboutMisskey: "About Misskey"
|
aboutMisskey: "About Misskey"
|
||||||
aboutMisskeyText: "Misskey is an open-source software developed by syuilo since 2014."
|
aboutMisskeyText: "Misskey is an open-source software developed by syuilo since 2014."
|
||||||
misskeyMembers: "It is currently developed an maintained by the members listed below:"
|
misskeyMembers: "It is currently developed and maintained by the members listed below:"
|
||||||
misskeySource: "Source code is available here:"
|
misskeySource: "Source code is available here:"
|
||||||
misskeyTranslation: "Help us with your contribution to translate Misskey:"
|
misskeyTranslation: "Help us with your contribution to translate Misskey:"
|
||||||
misskeyDonate: "Help us to keep improving the software by donating here:"
|
misskeyDonate: "Help us to keep improving the software by donating here:"
|
||||||
@@ -352,10 +352,10 @@ resetPassword: "Reset password"
|
|||||||
newPasswordIs: "The new password is \"{password}\""
|
newPasswordIs: "The new password is \"{password}\""
|
||||||
post: "Post"
|
post: "Post"
|
||||||
posted: "Posted!"
|
posted: "Posted!"
|
||||||
autoReloadWhenDisconnected: "Auto reload when disconnected with server"
|
autoReloadWhenDisconnected: "Auto reload when disconnected from server"
|
||||||
autoNoteWatch: "Watch note automatically"
|
autoNoteWatch: "Watch note automatically"
|
||||||
autoNoteWatchDescription: "Get notified about the notes which you reactioned or replied."
|
autoNoteWatchDescription: "Get notified about the notes which you reactioned or replied."
|
||||||
reduceUiAnimation: "Reduce animations of User Interface"
|
reduceUiAnimation: "Reduce UI animation"
|
||||||
share: "Share"
|
share: "Share"
|
||||||
notFound: "Not found"
|
notFound: "Not found"
|
||||||
notFoundDescription: "There was no page corresponding to the specified URL."
|
notFoundDescription: "There was no page corresponding to the specified URL."
|
||||||
@@ -411,11 +411,11 @@ or: "Or"
|
|||||||
uiLanguage: "UI display language"
|
uiLanguage: "UI display language"
|
||||||
groupInvited: "Invited to group"
|
groupInvited: "Invited to group"
|
||||||
aboutX: "About {x}"
|
aboutX: "About {x}"
|
||||||
useOsNativeEmojis: "Use the OS native Emojis"
|
useOsNativeEmojis: "Use OS native Emojis"
|
||||||
youHaveNoGroups: "You have no groups"
|
youHaveNoGroups: "You have no groups"
|
||||||
joinOrCreateGroup: "Get invited to join the groups or you can create your own group."
|
joinOrCreateGroup: "Get invited to join the groups or you can create your own group."
|
||||||
noHistory: "No history items"
|
noHistory: "No history items"
|
||||||
disableAnimatedMfm: "Disable MFM which has animations"
|
disableAnimatedMfm: "Disable MFM with animation"
|
||||||
doing: "On my way"
|
doing: "On my way"
|
||||||
category: "Category"
|
category: "Category"
|
||||||
tags: "Tags"
|
tags: "Tags"
|
||||||
@@ -466,6 +466,24 @@ details: "Details"
|
|||||||
chooseEmoji: "Choose an emoji"
|
chooseEmoji: "Choose an emoji"
|
||||||
unableToProcess: "The operation could not be completed."
|
unableToProcess: "The operation could not be completed."
|
||||||
recentUsed: "Recently used"
|
recentUsed: "Recently used"
|
||||||
|
install: "Install"
|
||||||
|
uninstall: "Uninstall"
|
||||||
|
installedApps: "Authorized Applications"
|
||||||
|
nothing: "There's nothing to see here"
|
||||||
|
installedDate: "Authorized"
|
||||||
|
lastUsedDate: "Last used"
|
||||||
|
state: "State"
|
||||||
|
sort: "Sort"
|
||||||
|
ascendingOrder: "Ascending"
|
||||||
|
descendingOrder: "Descending"
|
||||||
|
_theme:
|
||||||
|
explore: "Explore Themes"
|
||||||
|
install: "Install theme"
|
||||||
|
manage: "Themes manager"
|
||||||
|
code: "Theme code"
|
||||||
|
installed: "{name} has been installed"
|
||||||
|
alreadyInstalled: "The theme is already installed"
|
||||||
|
invalid: "Theme format is invalid"
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "New note"
|
note: "New note"
|
||||||
noteMy: "My note"
|
noteMy: "My note"
|
||||||
@@ -550,7 +568,11 @@ _permissions:
|
|||||||
"write:user-groups": "Edit or delete user groups"
|
"write:user-groups": "Edit or delete user groups"
|
||||||
_auth:
|
_auth:
|
||||||
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
|
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
|
||||||
|
shareAccessAsk: "Are you sure you want to authorize this application to access your account?"
|
||||||
permissionAsk: "This application requires following permissions:"
|
permissionAsk: "This application requires following permissions:"
|
||||||
|
pleaseGoBack: "Please go back to the application"
|
||||||
|
callback: "Returning back to the application"
|
||||||
|
denied: "Access Denied"
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "All notes"
|
all: "All notes"
|
||||||
homeTimeline: "Notes from following users"
|
homeTimeline: "Notes from following users"
|
||||||
@@ -654,15 +676,15 @@ _charts:
|
|||||||
_instanceCharts:
|
_instanceCharts:
|
||||||
requests: "Requests"
|
requests: "Requests"
|
||||||
users: "Difference in # of users"
|
users: "Difference in # of users"
|
||||||
usersTotal: "Total # of users"
|
usersTotal: "Cumulative total # of users"
|
||||||
notes: "Difference in # of notes"
|
notes: "Difference in # of notes"
|
||||||
notesTotal: "Total # of notes"
|
notesTotal: "Cumulative total # of notes"
|
||||||
ff: "Difference in # of followers"
|
ff: "Difference in # of followers"
|
||||||
ffTotal: "Total # of followers"
|
ffTotal: "Cumulative total # of followers"
|
||||||
cacheSize: "Difference in cache size"
|
cacheSize: "Difference in cache size"
|
||||||
cacheSizeTotal: "Total accumulated cache"
|
cacheSizeTotal: "Cumulative total cache size"
|
||||||
files: "Difference in # of files"
|
files: "Difference in # of files"
|
||||||
filesTotal: "Total # of files"
|
filesTotal: "Cumulative total # of files"
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "Home"
|
home: "Home"
|
||||||
local: "Local"
|
local: "Local"
|
||||||
|
@@ -466,6 +466,24 @@ details: "Detalles"
|
|||||||
chooseEmoji: "Elije un emoji"
|
chooseEmoji: "Elije un emoji"
|
||||||
unableToProcess: "La operación no se puede llevar a cabo"
|
unableToProcess: "La operación no se puede llevar a cabo"
|
||||||
recentUsed: "Usado recientemente"
|
recentUsed: "Usado recientemente"
|
||||||
|
install: "Instalación"
|
||||||
|
uninstall: "Desinstalar"
|
||||||
|
installedApps: "Aplicaciones Autorizadas"
|
||||||
|
nothing: "No hay nada que ver aqui"
|
||||||
|
installedDate: "Autorizado"
|
||||||
|
lastUsedDate: "Utilizado el"
|
||||||
|
state: "Estado"
|
||||||
|
sort: "Ordenar"
|
||||||
|
ascendingOrder: "Ascendente"
|
||||||
|
descendingOrder: "Descendente"
|
||||||
|
_theme:
|
||||||
|
explore: "Explorar temas"
|
||||||
|
install: "Instalar tema"
|
||||||
|
manage: "Gestor de temas"
|
||||||
|
code: "Código del tema"
|
||||||
|
installed: "{name} ha sido instalado"
|
||||||
|
alreadyInstalled: "Este tema ya está instalado"
|
||||||
|
invalid: "El formato del tema no es válido"
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "Notas"
|
note: "Notas"
|
||||||
noteMy: "Nota (a mí mismo)"
|
noteMy: "Nota (a mí mismo)"
|
||||||
@@ -550,7 +568,11 @@ _permissions:
|
|||||||
"write:user-groups": "Administrar grupos de usuarios"
|
"write:user-groups": "Administrar grupos de usuarios"
|
||||||
_auth:
|
_auth:
|
||||||
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
|
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
|
||||||
|
shareAccessAsk: "¿Está seguro de que desea autorizar esta aplicación para acceder a su cuenta?"
|
||||||
permissionAsk: "Esta aplicación requiere los siguientes permisos"
|
permissionAsk: "Esta aplicación requiere los siguientes permisos"
|
||||||
|
pleaseGoBack: "Por favor, vuelve a la aplicación"
|
||||||
|
callback: "Volviendo a la aplicación"
|
||||||
|
denied: "Acceso denegado"
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "Todas las notas"
|
all: "Todas las notas"
|
||||||
homeTimeline: "Notas de los usuarios que sigues"
|
homeTimeline: "Notas de los usuarios que sigues"
|
||||||
@@ -654,15 +676,15 @@ _charts:
|
|||||||
_instanceCharts:
|
_instanceCharts:
|
||||||
requests: "Pedidos"
|
requests: "Pedidos"
|
||||||
users: "Variación de usuarios"
|
users: "Variación de usuarios"
|
||||||
usersTotal: "Total de usuarios"
|
usersTotal: "Total acumulado de usuarios"
|
||||||
notes: "Variación de la cantidad de notas"
|
notes: "Variación de la cantidad de notas"
|
||||||
notesTotal: "Estimación de notas"
|
notesTotal: "Total acumulado de la cantidad de notas"
|
||||||
ff: "Variación de cantidad de seguidos/seguidores"
|
ff: "Variación de cantidad de seguidos/seguidores"
|
||||||
ffTotal: "Total de seguidos/seguidores"
|
ffTotal: "Total acumulado de cantidad de seguidos/seguidores"
|
||||||
cacheSize: "Variación del tamaño de la caché"
|
cacheSize: "Variación del tamaño de la caché"
|
||||||
cacheSizeTotal: "Total del tamaño de la caché"
|
cacheSizeTotal: "Total acumulado del tamaño de la caché"
|
||||||
files: "Variación de cantidad de archivos"
|
files: "Variación de cantidad de archivos"
|
||||||
filesTotal: "Total de archivos"
|
filesTotal: "Total acumulado de cantidad de archivos"
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "Inicio"
|
home: "Inicio"
|
||||||
local: "Local"
|
local: "Local"
|
||||||
|
@@ -42,8 +42,8 @@ sendMessage: "Envoyer un message"
|
|||||||
copyUsername: "Copier le nom d'utilisateur"
|
copyUsername: "Copier le nom d'utilisateur"
|
||||||
reply: "Répondre"
|
reply: "Répondre"
|
||||||
loadMore: "Voir plus"
|
loadMore: "Voir plus"
|
||||||
youGotNewFollower: "Vous a abonnés"
|
youGotNewFollower: "Vous suit"
|
||||||
receiveFollowRequest: "Demande de abonnés reçue"
|
receiveFollowRequest: "Demande de suivi reçue"
|
||||||
followRequestAccepted: "L'abonne la demande acceptée"
|
followRequestAccepted: "L'abonne la demande acceptée"
|
||||||
mentions: "Mentions"
|
mentions: "Mentions"
|
||||||
directNotes: "Messages directs"
|
directNotes: "Messages directs"
|
||||||
@@ -53,7 +53,7 @@ export: "Exporter"
|
|||||||
files: "Fichier·s"
|
files: "Fichier·s"
|
||||||
download: "Télécharger"
|
download: "Télécharger"
|
||||||
driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{name}\" ? Les notes avec ce fichier joint seront aussi supprimées."
|
driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{name}\" ? Les notes avec ce fichier joint seront aussi supprimées."
|
||||||
unfollowConfirm: "Êtes-vous sûr·de ne plus vouloir abonne {name} ?"
|
unfollowConfirm: "Se désabonner de {name} ?"
|
||||||
exportRequested: "Vous avez demandé une exportation. Cela pourrait prendre un peu de temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive."
|
exportRequested: "Vous avez demandé une exportation. Cela pourrait prendre un peu de temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive."
|
||||||
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
|
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
|
||||||
lists: "Listes"
|
lists: "Listes"
|
||||||
@@ -62,17 +62,17 @@ note: "Note"
|
|||||||
notes: "Notes"
|
notes: "Notes"
|
||||||
following: "Abonnements"
|
following: "Abonnements"
|
||||||
followers: "Abonné·e·s"
|
followers: "Abonné·e·s"
|
||||||
followsYou: "Votre abonné"
|
followsYou: "Vous suit"
|
||||||
createList: "Créer une liste"
|
createList: "Créer une liste"
|
||||||
manageLists: "Gérer les listes"
|
manageLists: "Gérer les listes"
|
||||||
error: "Une erreur est survenue"
|
error: "Une erreur est survenue"
|
||||||
retry: "Réessayer"
|
retry: "Réessayer"
|
||||||
enterListName: "Nom de la liste"
|
enterListName: "Nom de la liste"
|
||||||
privacy: "Vie privée"
|
privacy: "Vie privée"
|
||||||
makeFollowManuallyApprove: "Demandes d’abonnements requiert l’approbation"
|
makeFollowManuallyApprove: "Demandes d’suivi requiert l'approbation"
|
||||||
defaultNoteVisibility: "Visibilité par défaut"
|
defaultNoteVisibility: "Visibilité par défaut"
|
||||||
follow: "Abonnement"
|
follow: "Suivre"
|
||||||
followRequest: "Demande d’abonnement"
|
followRequest: "Demande d’suivre"
|
||||||
followRequests: "Demandes d’abonnement"
|
followRequests: "Demandes d’abonnement"
|
||||||
unfollow: "Se désabonner"
|
unfollow: "Se désabonner"
|
||||||
followRequestPending: "En attente d’approbation"
|
followRequestPending: "En attente d’approbation"
|
||||||
@@ -121,7 +121,7 @@ setWallpaper: "Définir le fond d'écran"
|
|||||||
removeWallpaper: "Supprimer l'arrière plan"
|
removeWallpaper: "Supprimer l'arrière plan"
|
||||||
searchWith: "Recherche : {q}"
|
searchWith: "Recherche : {q}"
|
||||||
youHaveNoLists: "Vous n'avez aucune liste"
|
youHaveNoLists: "Vous n'avez aucune liste"
|
||||||
followConfirm: "Désirez-vous abonne {name} ?"
|
followConfirm: "Désirez-vous suivre {name} ?"
|
||||||
proxyAccount: "Compte proxy"
|
proxyAccount: "Compte proxy"
|
||||||
proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions, comme un·e abonné·e distant pour les utilisateurs d'autres instances.\nExemple : quand un·e utilisateur·rice distant·e est ajouté·e à une liste, ses notes ne serait pas visibles sur l'instance si personne ne le·la abonné. Le compte proxy va donc le·la abonne pour que ses notes soient acheminées."
|
proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions, comme un·e abonné·e distant pour les utilisateurs d'autres instances.\nExemple : quand un·e utilisateur·rice distant·e est ajouté·e à une liste, ses notes ne serait pas visibles sur l'instance si personne ne le·la abonné. Le compte proxy va donc le·la abonne pour que ses notes soient acheminées."
|
||||||
host: "Hôte"
|
host: "Hôte"
|
||||||
@@ -192,7 +192,7 @@ newPassword: "Nouveau mot de passe"
|
|||||||
newPasswordRetype: "Nouveau mot de passe (répéter)"
|
newPasswordRetype: "Nouveau mot de passe (répéter)"
|
||||||
attachFile: "Joindre un fichier"
|
attachFile: "Joindre un fichier"
|
||||||
more: "Plus !"
|
more: "Plus !"
|
||||||
featured: "Surlignage"
|
featured: "Tendances"
|
||||||
usernameOrUserId: "Nom d'utilisateur ou ID utilisateur"
|
usernameOrUserId: "Nom d'utilisateur ou ID utilisateur"
|
||||||
noSuchUser: "Utilisateur non trouvé"
|
noSuchUser: "Utilisateur non trouvé"
|
||||||
lookup: "Recherche"
|
lookup: "Recherche"
|
||||||
@@ -214,7 +214,7 @@ explore: "Découvrir"
|
|||||||
games: "Jeux de Misskey"
|
games: "Jeux de Misskey"
|
||||||
messageRead: "Lus"
|
messageRead: "Lus"
|
||||||
noMoreHistory: "Plus d'histoire passée"
|
noMoreHistory: "Plus d'histoire passée"
|
||||||
startMessaging: "Commencer à écrire un discutez"
|
startMessaging: "Commencer à discuter"
|
||||||
nUsersRead: "{n} personnes ont lu"
|
nUsersRead: "{n} personnes ont lu"
|
||||||
agreeTo: "D'accord {0}"
|
agreeTo: "D'accord {0}"
|
||||||
tos: "Conditions d'utilisation"
|
tos: "Conditions d'utilisation"
|
||||||
@@ -466,6 +466,24 @@ details: "Détails"
|
|||||||
chooseEmoji: "Choisissez des emojis"
|
chooseEmoji: "Choisissez des emojis"
|
||||||
unableToProcess: "L'opération n'a pas pu être complétée"
|
unableToProcess: "L'opération n'a pas pu être complétée"
|
||||||
recentUsed: "Récemment utilisé"
|
recentUsed: "Récemment utilisé"
|
||||||
|
install: "Installation"
|
||||||
|
uninstall: "Désinstaller"
|
||||||
|
installedApps: "Applications Autorisées"
|
||||||
|
nothing: "Il n'y a rien à voir ici"
|
||||||
|
installedDate: "Autorisé"
|
||||||
|
lastUsedDate: "Dernière utilisation"
|
||||||
|
state: "État"
|
||||||
|
sort: "Trier"
|
||||||
|
ascendingOrder: "Ascendant"
|
||||||
|
descendingOrder: "Descendant"
|
||||||
|
_theme:
|
||||||
|
explore: "Explorer les thèmes"
|
||||||
|
install: "Installer un thème"
|
||||||
|
manage: "Gestion des thèmes"
|
||||||
|
code: "Code du thème"
|
||||||
|
installed: "{name} a été installé"
|
||||||
|
alreadyInstalled: "Ce thème est déjà installé"
|
||||||
|
invalid: "Le format du thème n'est pas valide"
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "Nouvelle note"
|
note: "Nouvelle note"
|
||||||
noteMy: "Ma note"
|
noteMy: "Ma note"
|
||||||
@@ -514,7 +532,7 @@ _tutorial:
|
|||||||
step7_3: "Alors, profitez de Misskey 🚀"
|
step7_3: "Alors, profitez de Misskey 🚀"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "Cette étape à déjà été complétée"
|
alreadyRegistered: "Cette étape à déjà été complétée"
|
||||||
registerDevice: "S’inscrire l'appareil"
|
registerDevice: "Ajouter un appareil"
|
||||||
registerKey: "S’inscrire la clé"
|
registerKey: "S’inscrire la clé"
|
||||||
step1: "Tout d'abord, installez une application d'authentification, telle que {a} ou {b}, sur votre appareil."
|
step1: "Tout d'abord, installez une application d'authentification, telle que {a} ou {b}, sur votre appareil."
|
||||||
step2: "Ensuite, scannez le code QR affiché avec l'application."
|
step2: "Ensuite, scannez le code QR affiché avec l'application."
|
||||||
@@ -550,7 +568,11 @@ _permissions:
|
|||||||
"write:user-groups": "Éditer les groupes des utilisateur·rice·s"
|
"write:user-groups": "Éditer les groupes des utilisateur·rice·s"
|
||||||
_auth:
|
_auth:
|
||||||
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
|
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
|
||||||
|
shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre compte?"
|
||||||
permissionAsk: "Cette application nécessite les autorisations suivantes "
|
permissionAsk: "Cette application nécessite les autorisations suivantes "
|
||||||
|
pleaseGoBack: "Veillez retourner à l'application"
|
||||||
|
callback: "Retour vers l’application"
|
||||||
|
denied: "Accès refusé"
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "Toutes les notes"
|
all: "Toutes les notes"
|
||||||
homeTimeline: "Notes de l'utilisateur auquel je m'abonne"
|
homeTimeline: "Notes de l'utilisateur auquel je m'abonne"
|
||||||
@@ -641,7 +663,7 @@ _charts:
|
|||||||
federationInstancesIncDec: "Variation du nombre d'instances"
|
federationInstancesIncDec: "Variation du nombre d'instances"
|
||||||
federationInstancesTotal: "Nombre d'instances au total"
|
federationInstancesTotal: "Nombre d'instances au total"
|
||||||
usersIncDec: "Variation du nombre d'utilisateur·rice·s"
|
usersIncDec: "Variation du nombre d'utilisateur·rice·s"
|
||||||
usersTotal: "Nombre d'utilsateur·rice·s au total"
|
usersTotal: "Nombre d'utilisateur·rice·s au total"
|
||||||
activeUsers: "Utilisateur·rice·s actif·ve·s"
|
activeUsers: "Utilisateur·rice·s actif·ve·s"
|
||||||
notesIncDec: "Variation du nombre d'notes"
|
notesIncDec: "Variation du nombre d'notes"
|
||||||
localNotesIncDec: "Variation du nombre de notes local"
|
localNotesIncDec: "Variation du nombre de notes local"
|
||||||
@@ -654,15 +676,15 @@ _charts:
|
|||||||
_instanceCharts:
|
_instanceCharts:
|
||||||
requests: "Requêtes"
|
requests: "Requêtes"
|
||||||
users: "Variation du nombre d'utilisateur·rice·s"
|
users: "Variation du nombre d'utilisateur·rice·s"
|
||||||
usersTotal: "Somme du nombre d'utilisateur·rice·s accumulés"
|
usersTotal: "Nombre d'utilisateur·rice·s au total cumulé"
|
||||||
notes: "Variation du nombre d'notes"
|
notes: "Variation du nombre d'notes"
|
||||||
notesTotal: "Somme du nombre d’notes accumulés"
|
notesTotal: "Nombre d'notes au total cumulé"
|
||||||
ff: "Variation des abonné·e·s"
|
ff: "Variation des abonné·e·s"
|
||||||
ffTotal: "Somme du nombre d'abonnements accumulés"
|
ffTotal: "Nombre d'abonné·e·s au total cumulé"
|
||||||
cacheSize: "Variation de la taille du cache"
|
cacheSize: "Variation de la taille du cache"
|
||||||
cacheSizeTotal: "Somme de la taille du cache accumulé"
|
cacheSizeTotal: "La taille du cache au total cumulé"
|
||||||
files: "Variation du nombre de fichiers"
|
files: "Variation du nombre de fichiers"
|
||||||
filesTotal: "Somme du nombre de fichiers accumulés"
|
filesTotal: "Nombre de fichiers au total cumulé"
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "Principal"
|
home: "Principal"
|
||||||
local: "Local"
|
local: "Local"
|
||||||
|
@@ -466,6 +466,25 @@ details: "詳細"
|
|||||||
chooseEmoji: "絵文字を選択"
|
chooseEmoji: "絵文字を選択"
|
||||||
unableToProcess: "操作を完了できません"
|
unableToProcess: "操作を完了できません"
|
||||||
recentUsed: "最近使用"
|
recentUsed: "最近使用"
|
||||||
|
install: "インストール"
|
||||||
|
uninstall: "アンインストール"
|
||||||
|
installedApps: "インストールされたアプリ"
|
||||||
|
nothing: "ありません"
|
||||||
|
installedDate: "インストール日時"
|
||||||
|
lastUsedDate: "最終使用日時"
|
||||||
|
state: "状態"
|
||||||
|
sort: "ソート"
|
||||||
|
ascendingOrder: "昇順"
|
||||||
|
descendingOrder: "降順"
|
||||||
|
|
||||||
|
_theme:
|
||||||
|
explore: "テーマを探す"
|
||||||
|
install: "テーマのインストール"
|
||||||
|
manage: "テーマの管理"
|
||||||
|
code: "テーマコード"
|
||||||
|
installed: "{name}をインストールしました"
|
||||||
|
alreadyInstalled: "そのテーマは既にインストールされています"
|
||||||
|
invalid: "テーマの形式が間違っています"
|
||||||
|
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "ノート"
|
note: "ノート"
|
||||||
@@ -557,7 +576,11 @@ _permissions:
|
|||||||
|
|
||||||
_auth:
|
_auth:
|
||||||
shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?"
|
shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?"
|
||||||
|
shareAccessAsk: "アカウントへのアクセスを許可しますか?"
|
||||||
permissionAsk: "このアプリは次の権限を要求しています"
|
permissionAsk: "このアプリは次の権限を要求しています"
|
||||||
|
pleaseGoBack: "アプリケーションに戻ってやっていってください"
|
||||||
|
callback: "アプリケーションに戻っています"
|
||||||
|
denied: "アクセスを拒否しました"
|
||||||
|
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "全てのノート"
|
all: "全てのノート"
|
||||||
@@ -672,15 +695,15 @@ _charts:
|
|||||||
_instanceCharts:
|
_instanceCharts:
|
||||||
requests: "リクエスト"
|
requests: "リクエスト"
|
||||||
users: "ユーザーの増減"
|
users: "ユーザーの増減"
|
||||||
usersTotal: "ユーザーの積算"
|
usersTotal: "ユーザーの累積"
|
||||||
notes: "ノートの増減"
|
notes: "ノートの増減"
|
||||||
notesTotal: "ノートの積算"
|
notesTotal: "ノートの累積"
|
||||||
ff: "フォロー/フォロワーの増減"
|
ff: "フォロー/フォロワーの増減"
|
||||||
ffTotal: "フォロー/フォロワーの積算"
|
ffTotal: "フォロー/フォロワーの累積"
|
||||||
cacheSize: "キャッシュサイズの増減"
|
cacheSize: "キャッシュサイズの増減"
|
||||||
cacheSizeTotal: "キャッシュサイズの積算"
|
cacheSizeTotal: "キャッシュサイズの累積"
|
||||||
files: "ファイル数の増減"
|
files: "ファイル数の増減"
|
||||||
filesTotal: "ファイル数の積算"
|
filesTotal: "ファイル数の累積"
|
||||||
|
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
|
@@ -466,6 +466,24 @@ details: "자세히"
|
|||||||
chooseEmoji: "이모지 선택"
|
chooseEmoji: "이모지 선택"
|
||||||
unableToProcess: "작업을 완료할 수 없습니다"
|
unableToProcess: "작업을 완료할 수 없습니다"
|
||||||
recentUsed: "최근 사용"
|
recentUsed: "최근 사용"
|
||||||
|
install: "설치"
|
||||||
|
uninstall: "삭제"
|
||||||
|
installedApps: "인증된 애플리케이션"
|
||||||
|
nothing: "아무것도 없습니다"
|
||||||
|
installedDate: "승인한 날짜"
|
||||||
|
lastUsedDate: "마지막 사용"
|
||||||
|
state: "상태"
|
||||||
|
sort: "정렬"
|
||||||
|
ascendingOrder: "오름차순"
|
||||||
|
descendingOrder: "내림차순"
|
||||||
|
_theme:
|
||||||
|
explore: "테마 찾아보기"
|
||||||
|
install: "테마 설치"
|
||||||
|
manage: "테마 관리"
|
||||||
|
code: "테마 코드"
|
||||||
|
installed: "{name} 테마가 설치되었습니다"
|
||||||
|
alreadyInstalled: "이미 설치된 테마입니다"
|
||||||
|
invalid: "테마 형식이 올바르지 않습니다"
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "새 노트"
|
note: "새 노트"
|
||||||
noteMy: "내 노트"
|
noteMy: "내 노트"
|
||||||
@@ -550,7 +568,11 @@ _permissions:
|
|||||||
"write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
|
"write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
|
||||||
_auth:
|
_auth:
|
||||||
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||||
|
shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||||
permissionAsk: "이 앱은 다음의 권한을 요청합니다"
|
permissionAsk: "이 앱은 다음의 권한을 요청합니다"
|
||||||
|
pleaseGoBack: "앱으로 돌아가서 시도해 주세요"
|
||||||
|
callback: "앱으로 돌아갑니다"
|
||||||
|
denied: "접근이 거부되었습니다"
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "모든 노트"
|
all: "모든 노트"
|
||||||
homeTimeline: "팔로우중인 유저의 노트"
|
homeTimeline: "팔로우중인 유저의 노트"
|
||||||
@@ -639,26 +661,26 @@ _exportOrImport:
|
|||||||
userLists: "리스트"
|
userLists: "리스트"
|
||||||
_charts:
|
_charts:
|
||||||
federationInstancesIncDec: "연합 인스턴스 수 증감"
|
federationInstancesIncDec: "연합 인스턴스 수 증감"
|
||||||
federationInstancesTotal: "총 연합 인스턴스 수"
|
federationInstancesTotal: "연합 인스턴스 수 합계"
|
||||||
usersIncDec: "유저 수 증감"
|
usersIncDec: "유저 수 증감"
|
||||||
usersTotal: "유저 수 합계"
|
usersTotal: "유저 수 합계"
|
||||||
activeUsers: "활성 유저 수"
|
activeUsers: "활성 유저 수"
|
||||||
notesIncDec: "노트 수 증감"
|
notesIncDec: "노트 수 증감"
|
||||||
localNotesIncDec: "로컬 노트 수 증감"
|
localNotesIncDec: "로컬 노트 수 증감"
|
||||||
remoteNotesIncDec: "리모트 노트 수 증감"
|
remoteNotesIncDec: "리모트 노트 수 증감"
|
||||||
notesTotal: "총 노트 수"
|
notesTotal: "노트 수 합계"
|
||||||
filesIncDec: "파일 수 증감"
|
filesIncDec: "파일 수 증감"
|
||||||
filesTotal: "총 파일 수"
|
filesTotal: "파일 수 합계"
|
||||||
storageUsageIncDec: "스토리지 사용량 증감"
|
storageUsageIncDec: "스토리지 사용량 증감"
|
||||||
storageUsageTotal: "총 스토리지 사용량"
|
storageUsageTotal: "스토리지 사용량 합계"
|
||||||
_instanceCharts:
|
_instanceCharts:
|
||||||
requests: "요청"
|
requests: "요청"
|
||||||
users: "유저 수 증감"
|
users: "유저 수 증감"
|
||||||
usersTotal: "누적 유저 수"
|
usersTotal: "누적 유저 수"
|
||||||
notes: "노트 수 증감"
|
notes: "노트 수 증감"
|
||||||
notesTotal: "총 노트 수"
|
notesTotal: "누적 노트 수"
|
||||||
ff: "팔로잉/팔로워 증감"
|
ff: "팔로잉/팔로워 증감"
|
||||||
ffTotal: "팔로잉/팔로워 누적"
|
ffTotal: "누적 팔로잉/팔로워 수"
|
||||||
cacheSize: "캐시 용량 증감"
|
cacheSize: "캐시 용량 증감"
|
||||||
cacheSizeTotal: "누적 캐시 용량"
|
cacheSizeTotal: "누적 캐시 용량"
|
||||||
files: "파일 수 증감"
|
files: "파일 수 증감"
|
||||||
|
@@ -230,8 +230,8 @@ location: "位置"
|
|||||||
theme: "主题"
|
theme: "主题"
|
||||||
themeForLightMode: "在轻便模式下使用的主题"
|
themeForLightMode: "在轻便模式下使用的主题"
|
||||||
themeForDarkMode: "在黑暗模式下使用的主题"
|
themeForDarkMode: "在黑暗模式下使用的主题"
|
||||||
light: "轻便"
|
light: "浅色"
|
||||||
dark: "黑暗"
|
dark: "深色"
|
||||||
lightThemes: "亮色主题"
|
lightThemes: "亮色主题"
|
||||||
darkThemes: "暗色主题"
|
darkThemes: "暗色主题"
|
||||||
syncDeviceDarkMode: "将黑暗模式与设备设置同步"
|
syncDeviceDarkMode: "将黑暗模式与设备设置同步"
|
||||||
@@ -466,9 +466,27 @@ details: "详情"
|
|||||||
chooseEmoji: "选择表情符号"
|
chooseEmoji: "选择表情符号"
|
||||||
unableToProcess: "操作无法完成"
|
unableToProcess: "操作无法完成"
|
||||||
recentUsed: "最近使用"
|
recentUsed: "最近使用"
|
||||||
|
install: "安装"
|
||||||
|
uninstall: "卸载"
|
||||||
|
installedApps: "已授权的应用"
|
||||||
|
nothing: "没什么"
|
||||||
|
installedDate: "授权日期"
|
||||||
|
lastUsedDate: "最近使用"
|
||||||
|
state: "状态"
|
||||||
|
sort: "排序"
|
||||||
|
ascendingOrder: "升序"
|
||||||
|
descendingOrder: "降序"
|
||||||
|
_theme:
|
||||||
|
explore: "寻找主题"
|
||||||
|
install: "安装主题"
|
||||||
|
manage: "主题管理"
|
||||||
|
code: "主题代码"
|
||||||
|
installed: "{name} 已安装"
|
||||||
|
alreadyInstalled: "此主题已经安装"
|
||||||
|
invalid: "主题格式错误"
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "帖子"
|
note: "帖子"
|
||||||
noteMy: "我的笔记"
|
noteMy: "我的帖子"
|
||||||
notification: "通知"
|
notification: "通知"
|
||||||
chat: "聊天"
|
chat: "聊天"
|
||||||
chatBg: "聊天背景"
|
chatBg: "聊天背景"
|
||||||
@@ -550,7 +568,11 @@ _permissions:
|
|||||||
"write:user-groups": "操作用户组"
|
"write:user-groups": "操作用户组"
|
||||||
_auth:
|
_auth:
|
||||||
shareAccess: "您要授权允许“{name}”访问您的帐户吗?"
|
shareAccess: "您要授权允许“{name}”访问您的帐户吗?"
|
||||||
|
shareAccessAsk: "您确定要授权此应用程序访问您的帐户吗?"
|
||||||
permissionAsk: "这个应用程序需要以下权限"
|
permissionAsk: "这个应用程序需要以下权限"
|
||||||
|
pleaseGoBack: "请返回到应用程序"
|
||||||
|
callback: "回到应用程序"
|
||||||
|
denied: "拒绝访问"
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "所有帖子"
|
all: "所有帖子"
|
||||||
homeTimeline: "已关注用户的帖子"
|
homeTimeline: "已关注用户的帖子"
|
||||||
@@ -654,15 +676,15 @@ _charts:
|
|||||||
_instanceCharts:
|
_instanceCharts:
|
||||||
requests: "请求"
|
requests: "请求"
|
||||||
users: "用户数量:增加/减少"
|
users: "用户数量:增加/减少"
|
||||||
usersTotal: "用户总数"
|
usersTotal: "用户总计"
|
||||||
notes: "帖子:增加/减少"
|
notes: "帖子:增加/减少"
|
||||||
notesTotal: "帖子:总数"
|
notesTotal: "帖子总计"
|
||||||
ff: "关注/被关注:数量变化"
|
ff: "关注/被关注:数量变化"
|
||||||
ffTotal: "关注/被关注:总数"
|
ffTotal: "关注/被关注者总计"
|
||||||
cacheSize: "缓存大小:增加/减少"
|
cacheSize: "缓存大小:增加/减少"
|
||||||
cacheSizeTotal: "合计缓存大小"
|
cacheSizeTotal: "缓存大小总计"
|
||||||
files: "文件总数增减"
|
files: "文件总数增减"
|
||||||
filesTotal: "合计文件总数"
|
filesTotal: "文件数总计"
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "首页"
|
home: "首页"
|
||||||
local: "本地"
|
local: "本地"
|
||||||
|
36
migration/1585361548360-miauth.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class miauth1585361548360 implements MigrationInterface {
|
||||||
|
name = 'miauth1585361548360'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ADD "lastUsedAt" TIMESTAMP WITH TIME ZONE DEFAULT null`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ADD "session" character varying(128) DEFAULT null`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ADD "name" character varying(128) DEFAULT null`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ADD "description" character varying(512) DEFAULT null`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ADD "iconUrl" character varying(512) DEFAULT null`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ADD "permission" character varying(64) array NOT NULL DEFAULT '{}'::varchar[]`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ADD "fetched" boolean NOT NULL DEFAULT false`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" DROP CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" DROP NOT NULL`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" SET DEFAULT null`, undefined);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_bf3a053c07d9fb5d87317c56ee" ON "access_token" ("session") `, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ADD CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" DROP CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560"`, undefined);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_bf3a053c07d9fb5d87317c56ee"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" DROP DEFAULT`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" SET NOT NULL`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" ADD CONSTRAINT "FK_a3ff16c90cc87a82a0b5959e560" FOREIGN KEY ("appId") REFERENCES "app"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "fetched"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "permission"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "iconUrl"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "description"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "name"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "session"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "lastUsedAt"`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
48
migration/1585385921215-custom-notification.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class customNotification1585385921215 implements MigrationInterface {
|
||||||
|
name = 'customNotification1585385921215'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ADD "customBody" character varying(2048)`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ADD "customHeader" character varying(256)`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ADD "customIcon" character varying(1024)`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ADD "appAccessTokenId" character varying(32)`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "notifierId" DROP NOT NULL`, undefined);
|
||||||
|
await queryRunner.query(`COMMENT ON COLUMN "notification"."notifierId" IS 'The ID of sender user of the Notification.'`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`, undefined);
|
||||||
|
await queryRunner.query(`CREATE TYPE "notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum" USING "type"::"text"::"notification_type_enum"`, undefined);
|
||||||
|
await queryRunner.query(`DROP TYPE "notification_type_enum_old"`, undefined);
|
||||||
|
await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS 'The type of the Notification.'`, undefined);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_3b4e96eec8d36a8bbb9d02aa71" ON "notification" ("notifierId") `, undefined);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_33f33cc8ef29d805a97ff4628b" ON "notification" ("type") `, undefined);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_080ab397c379af09b9d2169e5b" ON "notification" ("isRead") `, undefined);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_e22bf6bda77b6adc1fd9e75c8c" ON "notification" ("appAccessTokenId") `, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710" FOREIGN KEY ("notifierId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_e22bf6bda77b6adc1fd9e75c8c9" FOREIGN KEY ("appAccessTokenId") REFERENCES "access_token"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_e22bf6bda77b6adc1fd9e75c8c9"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710"`, undefined);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_e22bf6bda77b6adc1fd9e75c8c"`, undefined);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_080ab397c379af09b9d2169e5b"`, undefined);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_33f33cc8ef29d805a97ff4628b"`, undefined);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_3b4e96eec8d36a8bbb9d02aa71"`, undefined);
|
||||||
|
await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS ''`, undefined);
|
||||||
|
await queryRunner.query(`CREATE TYPE "notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited')`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum_old" USING "type"::"text"::"notification_type_enum_old"`, undefined);
|
||||||
|
await queryRunner.query(`DROP TYPE "notification_type_enum"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TYPE "notification_type_enum_old" RENAME TO "notification_type_enum"`, undefined);
|
||||||
|
await queryRunner.query(`COMMENT ON COLUMN "notification"."notifierId" IS ''`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "notifierId" SET NOT NULL`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_3b4e96eec8d36a8bbb9d02aa710" FOREIGN KEY ("notifierId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "appAccessTokenId"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "customIcon"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "customHeader"`, undefined);
|
||||||
|
await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "customBody"`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
15
migration/1585772678853-ap-url.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* tslint:disable:quotemark class-name indent */
|
||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class apUrl1585772678853 implements MigrationInterface {
|
||||||
|
name = 'apUrl1585772678853'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "note" ADD "url" character varying(512)`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "url"`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
122
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||||
"version": "12.24.1",
|
"version": "12.29.0",
|
||||||
"codename": "indigo",
|
"codename": "indigo",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -11,11 +11,13 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./index.js",
|
"start": "node ./index.js",
|
||||||
|
"start-product": "cross-env NODE_ENV=production node ./index.js",
|
||||||
"init": "npm run migrate",
|
"init": "npm run migrate",
|
||||||
"ormconfig": "node ./built/ormconfig.js",
|
"ormconfig": "node ./built/ormconfig.js",
|
||||||
"migrate": "ts-node ./node_modules/typeorm/cli.js migration:run",
|
"migrate": "ts-node ./node_modules/typeorm/cli.js migration:run",
|
||||||
"migrateandstart": "npm run migrate && npm run start",
|
"migrateandstart": "npm run migrate && npm run start",
|
||||||
"build": "webpack && gulp build",
|
"build": "webpack && gulp build",
|
||||||
|
"build-product": "cross-env NODE_ENV=production webpack && gulp build",
|
||||||
"webpack": "webpack",
|
"webpack": "webpack",
|
||||||
"watch": "webpack --watch",
|
"watch": "webpack --watch",
|
||||||
"gulp": "gulp build",
|
"gulp": "gulp build",
|
||||||
@@ -30,18 +32,18 @@
|
|||||||
"lodash": "^4.17.13"
|
"lodash": "^4.17.13"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elastic/elasticsearch": "7.6.0",
|
"@elastic/elasticsearch": "7.6.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.27",
|
"@fortawesome/fontawesome-svg-core": "1.2.28",
|
||||||
"@fortawesome/free-brands-svg-icons": "5.12.1",
|
"@fortawesome/free-brands-svg-icons": "5.13.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "5.12.1",
|
"@fortawesome/free-regular-svg-icons": "5.13.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.12.1",
|
"@fortawesome/free-solid-svg-icons": "5.13.0",
|
||||||
"@fortawesome/vue-fontawesome": "0.1.9",
|
"@fortawesome/vue-fontawesome": "0.1.9",
|
||||||
"@juggle/resize-observer": "3.0.2",
|
"@juggle/resize-observer": "3.1.3",
|
||||||
"@koa/cors": "3.0.0",
|
"@koa/cors": "3.0.0",
|
||||||
"@koa/multer": "2.0.2",
|
"@koa/multer": "2.0.2",
|
||||||
"@koa/router": "8.0.8",
|
"@koa/router": "8.0.8",
|
||||||
"@types/bcryptjs": "2.4.2",
|
"@types/bcryptjs": "2.4.2",
|
||||||
"@types/bull": "3.12.0",
|
"@types/bull": "3.12.1",
|
||||||
"@types/cbor": "5.0.0",
|
"@types/cbor": "5.0.0",
|
||||||
"@types/dateformat": "3.0.1",
|
"@types/dateformat": "3.0.1",
|
||||||
"@types/double-ended-queue": "2.1.1",
|
"@types/double-ended-queue": "2.1.1",
|
||||||
@@ -51,10 +53,10 @@
|
|||||||
"@types/gulp-rename": "0.0.33",
|
"@types/gulp-rename": "0.0.33",
|
||||||
"@types/gulp-replace": "0.0.31",
|
"@types/gulp-replace": "0.0.31",
|
||||||
"@types/is-url": "1.2.28",
|
"@types/is-url": "1.2.28",
|
||||||
"@types/js-yaml": "3.12.2",
|
"@types/js-yaml": "3.12.3",
|
||||||
"@types/jsdom": "12.2.4",
|
"@types/jsdom": "16.2.0",
|
||||||
"@types/katex": "0.11.0",
|
"@types/katex": "0.11.0",
|
||||||
"@types/koa": "2.11.1",
|
"@types/koa": "2.11.3",
|
||||||
"@types/koa-bodyparser": "4.3.0",
|
"@types/koa-bodyparser": "4.3.0",
|
||||||
"@types/koa-compress": "2.0.9",
|
"@types/koa-compress": "2.0.9",
|
||||||
"@types/koa-cors": "0.0.0",
|
"@types/koa-cors": "0.0.0",
|
||||||
@@ -68,8 +70,8 @@
|
|||||||
"@types/koa__router": "8.0.2",
|
"@types/koa__router": "8.0.2",
|
||||||
"@types/lolex": "5.1.0",
|
"@types/lolex": "5.1.0",
|
||||||
"@types/markdown-it": "0.0.9",
|
"@types/markdown-it": "0.0.9",
|
||||||
"@types/mocha": "7.0.1",
|
"@types/mocha": "7.0.2",
|
||||||
"@types/node": "13.7.1",
|
"@types/node": "13.11.0",
|
||||||
"@types/nodemailer": "6.4.0",
|
"@types/nodemailer": "6.4.0",
|
||||||
"@types/nprogress": "0.2.0",
|
"@types/nprogress": "0.2.0",
|
||||||
"@types/oauth": "0.9.1",
|
"@types/oauth": "0.9.1",
|
||||||
@@ -80,7 +82,7 @@
|
|||||||
"@types/qrcode": "1.3.4",
|
"@types/qrcode": "1.3.4",
|
||||||
"@types/random-seed": "0.3.3",
|
"@types/random-seed": "0.3.3",
|
||||||
"@types/ratelimiter": "2.1.28",
|
"@types/ratelimiter": "2.1.28",
|
||||||
"@types/redis": "2.8.15",
|
"@types/redis": "2.8.17",
|
||||||
"@types/rename": "1.0.1",
|
"@types/rename": "1.0.1",
|
||||||
"@types/request": "2.48.4",
|
"@types/request": "2.48.4",
|
||||||
"@types/request-promise-native": "1.0.17",
|
"@types/request-promise-native": "1.0.17",
|
||||||
@@ -93,26 +95,26 @@
|
|||||||
"@types/systeminformation": "3.54.1",
|
"@types/systeminformation": "3.54.1",
|
||||||
"@types/tinycolor2": "1.4.2",
|
"@types/tinycolor2": "1.4.2",
|
||||||
"@types/tmp": "0.1.0",
|
"@types/tmp": "0.1.0",
|
||||||
"@types/uuid": "3.4.7",
|
"@types/uuid": "7.0.2",
|
||||||
"@types/web-push": "3.3.0",
|
"@types/web-push": "3.3.0",
|
||||||
"@types/webpack": "4.41.6",
|
"@types/webpack": "4.41.10",
|
||||||
"@types/webpack-stream": "3.2.10",
|
"@types/webpack-stream": "3.2.10",
|
||||||
"@types/websocket": "1.0.0",
|
"@types/websocket": "1.0.0",
|
||||||
"@types/ws": "7.2.1",
|
"@types/ws": "7.2.3",
|
||||||
"@typescript-eslint/parser": "2.19.2",
|
"@typescript-eslint/parser": "2.26.0",
|
||||||
"agentkeepalive": "4.1.0",
|
"agentkeepalive": "4.1.0",
|
||||||
"animejs": "3.1.0",
|
"animejs": "3.1.0",
|
||||||
"apexcharts": "3.15.6",
|
"apexcharts": "3.17.1",
|
||||||
"autobind-decorator": "2.4.0",
|
"autobind-decorator": "2.4.0",
|
||||||
"autosize": "4.0.2",
|
"autosize": "4.0.2",
|
||||||
"autwh": "0.1.0",
|
"autwh": "0.1.0",
|
||||||
"aws-sdk": "2.617.0",
|
"aws-sdk": "2.653.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bull": "3.12.1",
|
"bull": "3.13.0",
|
||||||
"cafy": "15.2.1",
|
"cafy": "15.2.1",
|
||||||
"cbor": "5.0.1",
|
"cbor": "5.0.1",
|
||||||
"chai": "4.2.0",
|
"chai": "4.2.0",
|
||||||
"chalk": "3.0.0",
|
"chalk": "4.0.0",
|
||||||
"chart.js": "2.9.3",
|
"chart.js": "2.9.3",
|
||||||
"cli-highlight": "2.1.4",
|
"cli-highlight": "2.1.4",
|
||||||
"commander": "4.1.1",
|
"commander": "4.1.1",
|
||||||
@@ -124,16 +126,16 @@
|
|||||||
"diskusage": "1.1.3",
|
"diskusage": "1.1.3",
|
||||||
"double-ended-queue": "2.1.0-0",
|
"double-ended-queue": "2.1.0-0",
|
||||||
"eslint": "6.8.0",
|
"eslint": "6.8.0",
|
||||||
"eslint-plugin-vue": "6.1.2",
|
"eslint-plugin-vue": "6.2.2",
|
||||||
"eventemitter3": "4.0.0",
|
"eventemitter3": "4.0.0",
|
||||||
"feed": "4.1.0",
|
"feed": "4.1.0",
|
||||||
"fibers": "4.0.2",
|
"fibers": "4.0.2",
|
||||||
"file-type": "14.1.2",
|
"file-type": "14.1.4",
|
||||||
"fluent-ffmpeg": "2.1.2",
|
"fluent-ffmpeg": "2.1.2",
|
||||||
"glob": "7.1.6",
|
"glob": "7.1.6",
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
"gulp-clean-css": "4.2.0",
|
"gulp-clean-css": "4.3.0",
|
||||||
"gulp-dart-sass": "0.9.1",
|
"gulp-dart-sass": "1.0.0",
|
||||||
"gulp-mocha": "7.0.2",
|
"gulp-mocha": "7.0.2",
|
||||||
"gulp-rename": "2.0.0",
|
"gulp-rename": "2.0.0",
|
||||||
"gulp-replace": "1.0.0",
|
"gulp-replace": "1.0.0",
|
||||||
@@ -143,21 +145,21 @@
|
|||||||
"gulp-typescript": "5.0.1",
|
"gulp-typescript": "5.0.1",
|
||||||
"hard-source-webpack-plugin": "0.13.1",
|
"hard-source-webpack-plugin": "0.13.1",
|
||||||
"html-minifier": "4.0.0",
|
"html-minifier": "4.0.0",
|
||||||
"http-signature": "1.3.1",
|
"http-signature": "1.3.4",
|
||||||
"https-proxy-agent": "5.0.0",
|
"https-proxy-agent": "5.0.0",
|
||||||
"insert-text-at-cursor": "0.3.0",
|
"insert-text-at-cursor": "0.3.0",
|
||||||
"is-root": "2.1.0",
|
"is-root": "2.1.0",
|
||||||
"is-svg": "4.2.1",
|
"is-svg": "4.2.1",
|
||||||
"js-yaml": "3.13.1",
|
"js-yaml": "3.13.1",
|
||||||
"jsdom": "16.1.0",
|
"jsdom": "16.2.2",
|
||||||
"json5": "2.1.1",
|
"json5": "2.1.2",
|
||||||
"json5-loader": "3.0.0",
|
"json5-loader": "3.0.0",
|
||||||
"jsrsasign": "8.0.12",
|
"jsrsasign": "8.0.13",
|
||||||
"katex": "0.11.1",
|
"katex": "0.11.1",
|
||||||
"koa": "2.11.0",
|
"koa": "2.11.0",
|
||||||
"koa-bodyparser": "4.2.1",
|
"koa-bodyparser": "4.3.0",
|
||||||
"koa-compress": "3.0.0",
|
"koa-compress": "3.0.0",
|
||||||
"koa-favicon": "2.0.1",
|
"koa-favicon": "2.1.0",
|
||||||
"koa-json-body": "5.3.0",
|
"koa-json-body": "5.3.0",
|
||||||
"koa-logger": "3.2.1",
|
"koa-logger": "3.2.1",
|
||||||
"koa-mount": "4.0.0",
|
"koa-mount": "4.0.0",
|
||||||
@@ -165,23 +167,23 @@
|
|||||||
"koa-slow": "2.1.0",
|
"koa-slow": "2.1.0",
|
||||||
"koa-views": "6.2.1",
|
"koa-views": "6.2.1",
|
||||||
"langmap": "0.0.16",
|
"langmap": "0.0.16",
|
||||||
"loader-utils": "1.2.3",
|
|
||||||
"lolex": "5.1.2",
|
"lolex": "5.1.2",
|
||||||
"lookup-dns-cache": "2.1.0",
|
"lookup-dns-cache": "2.1.0",
|
||||||
"markdown-it": "10.0.0",
|
"markdown-it": "10.0.0",
|
||||||
"mocha": "7.0.1",
|
"markdown-it-anchor": "5.2.7",
|
||||||
|
"mocha": "7.1.1",
|
||||||
"moji": "0.5.1",
|
"moji": "0.5.1",
|
||||||
"ms": "2.1.2",
|
"ms": "2.1.2",
|
||||||
"multer": "1.4.2",
|
"multer": "1.4.2",
|
||||||
"nested-property": "1.0.4",
|
"nested-property": "1.0.4",
|
||||||
"node-fetch": "2.6.0",
|
"node-fetch": "2.6.0",
|
||||||
"nodemailer": "6.4.2",
|
"nodemailer": "6.4.6",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"object-assign-deep": "0.4.0",
|
"object-assign-deep": "0.4.0",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"parse5": "5.1.1",
|
"parse5": "5.1.1",
|
||||||
"parsimmon": "1.13.0",
|
"parsimmon": "1.13.0",
|
||||||
"pg": "7.18.1",
|
"pg": "8.0.0",
|
||||||
"portal-vue": "2.1.7",
|
"portal-vue": "2.1.7",
|
||||||
"portscanner": "2.2.0",
|
"portscanner": "2.2.0",
|
||||||
"postcss-loader": "3.0.0",
|
"postcss-loader": "3.0.0",
|
||||||
@@ -192,11 +194,11 @@
|
|||||||
"promise-sequential": "1.1.1",
|
"promise-sequential": "1.1.1",
|
||||||
"pug": "2.0.4",
|
"pug": "2.0.4",
|
||||||
"punycode": "2.1.1",
|
"punycode": "2.1.1",
|
||||||
"pureimage": "0.1.6",
|
"pureimage": "0.2.1",
|
||||||
"qrcode": "1.4.4",
|
"qrcode": "1.4.4",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"randomcolor": "0.5.4",
|
"randomcolor": "0.5.4",
|
||||||
"ratelimiter": "3.4.0",
|
"ratelimiter": "3.4.1",
|
||||||
"recaptcha-promise": "0.1.3",
|
"recaptcha-promise": "0.1.3",
|
||||||
"reconnecting-websocket": "4.4.0",
|
"reconnecting-websocket": "4.4.0",
|
||||||
"redis": "3.0.2",
|
"redis": "3.0.2",
|
||||||
@@ -210,57 +212,57 @@
|
|||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"rndstr": "1.0.0",
|
"rndstr": "1.0.0",
|
||||||
"s-age": "1.1.2",
|
"s-age": "1.1.2",
|
||||||
"sass": "1.25.0",
|
"sass": "1.26.3",
|
||||||
"sass-loader": "8.0.2",
|
"sass-loader": "8.0.2",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"sharp": "0.24.0",
|
"sharp": "0.25.2",
|
||||||
"showdown": "1.9.1",
|
"showdown": "1.9.1",
|
||||||
"showdown-highlightjs-extension": "0.1.2",
|
"showdown-highlightjs-extension": "0.1.2",
|
||||||
"speakeasy": "2.0.0",
|
"speakeasy": "2.0.0",
|
||||||
"stringz": "2.0.0",
|
"stringz": "2.1.0",
|
||||||
"style-loader": "1.1.3",
|
"style-loader": "1.1.3",
|
||||||
"summaly": "2.3.1",
|
"summaly": "2.3.1",
|
||||||
"syslog-pro": "1.0.0",
|
"syslog-pro": "1.0.0",
|
||||||
"systeminformation": "4.21.2",
|
"systeminformation": "4.23.1",
|
||||||
"syuilo-password-strength": "0.0.1",
|
"syuilo-password-strength": "0.0.1",
|
||||||
"terser-webpack-plugin": "2.3.4",
|
"terser-webpack-plugin": "2.3.5",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.113.2",
|
"three": "0.115.0",
|
||||||
"tinycolor2": "1.4.1",
|
"tinycolor2": "1.4.1",
|
||||||
"tmp": "0.1.0",
|
"tmp": "0.1.0",
|
||||||
"ts-loader": "6.2.1",
|
"ts-loader": "6.2.2",
|
||||||
"ts-node": "8.6.2",
|
"ts-node": "8.8.1",
|
||||||
"tslint": "6.0.0",
|
"tslint": "6.1.1",
|
||||||
"tslint-sonarts": "1.9.0",
|
"tslint-sonarts": "1.9.0",
|
||||||
"typeorm": "0.2.22",
|
"typeorm": "0.2.24",
|
||||||
"typescript": "3.7.5",
|
"typescript": "3.8.3",
|
||||||
"ulid": "2.3.0",
|
"ulid": "2.3.0",
|
||||||
"url-loader": "3.0.0",
|
"url-loader": "3.0.0",
|
||||||
"uuid": "3.4.0",
|
"uuid": "7.0.3",
|
||||||
"v-animate-css": "0.0.3",
|
"v-animate-css": "0.0.3",
|
||||||
"v-debounce": "0.1.2",
|
"v-debounce": "0.1.2",
|
||||||
"vue": "2.6.11",
|
"vue": "2.6.11",
|
||||||
"vue-color": "2.7.0",
|
"vue-color": "2.7.1",
|
||||||
"vue-content-loading": "1.6.0",
|
"vue-content-loading": "1.6.0",
|
||||||
"vue-cropperjs": "4.0.1",
|
"vue-cropperjs": "4.0.1",
|
||||||
"vue-i18n": "8.15.3",
|
"vue-i18n": "8.16.0",
|
||||||
"vue-json-pretty": "1.6.3",
|
"vue-json-pretty": "1.6.3",
|
||||||
"vue-loader": "15.9.0",
|
"vue-loader": "15.9.1",
|
||||||
"vue-marquee-text-component": "1.1.1",
|
"vue-marquee-text-component": "1.1.1",
|
||||||
"vue-meta": "2.3.2",
|
"vue-meta": "2.3.3",
|
||||||
"vue-prism-component": "1.1.1",
|
"vue-prism-component": "1.1.1",
|
||||||
"vue-router": "3.1.5",
|
"vue-router": "3.1.6",
|
||||||
"vue-style-loader": "4.1.2",
|
"vue-style-loader": "4.1.2",
|
||||||
"vue-svg-inline-loader": "1.4.5",
|
"vue-svg-inline-loader": "1.5.0",
|
||||||
"vue-template-compiler": "2.6.11",
|
"vue-template-compiler": "2.6.11",
|
||||||
"vuedraggable": "2.23.2",
|
"vuedraggable": "2.23.2",
|
||||||
"vuex": "3.1.2",
|
"vuex": "3.1.3",
|
||||||
"vuex-persistedstate": "2.7.1",
|
"vuex-persistedstate": "3.0.1",
|
||||||
"web-push": "3.4.3",
|
"web-push": "3.4.3",
|
||||||
"webpack": "4.41.6",
|
"webpack": "4.42.1",
|
||||||
"webpack-cli": "3.3.11",
|
"webpack-cli": "3.3.11",
|
||||||
"websocket": "1.0.31",
|
"websocket": "1.0.31",
|
||||||
"ws": "7.2.1",
|
"ws": "7.2.3",
|
||||||
"xev": "2.0.1"
|
"xev": "2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@@ -99,7 +99,7 @@ async function isPortAvailable(port: number): Promise<boolean> {
|
|||||||
function showEnvironment(): void {
|
function showEnvironment(): void {
|
||||||
const env = process.env.NODE_ENV;
|
const env = process.env.NODE_ENV;
|
||||||
const logger = bootLogger.createSubLogger('env');
|
const logger = bootLogger.createSubLogger('env');
|
||||||
logger.info(typeof env == 'undefined' ? 'NODE_ENV is not set' : `NODE_ENV: ${env}`);
|
logger.info(typeof env === 'undefined' ? 'NODE_ENV is not set' : `NODE_ENV: ${env}`);
|
||||||
|
|
||||||
if (env !== 'production') {
|
if (env !== 'production') {
|
||||||
logger.warn('The environment is not in production mode.');
|
logger.warn('The environment is not in production mode.');
|
||||||
|
@@ -310,7 +310,7 @@ export default Vue.extend({
|
|||||||
title: this.$t('search'),
|
title: this.$t('search'),
|
||||||
input: true
|
input: true
|
||||||
}).then(async ({ canceled, result: query }) => {
|
}).then(async ({ canceled, result: query }) => {
|
||||||
if (canceled || query == null || query == '') return;
|
if (canceled || query == null || query === '') return;
|
||||||
|
|
||||||
this.searching = true;
|
this.searching = true;
|
||||||
search(this, query).finally(() => {
|
search(this, query).finally(() => {
|
||||||
@@ -320,7 +320,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
searchKeypress(e) {
|
searchKeypress(e) {
|
||||||
if (e.keyCode == 13) {
|
if (e.keyCode === 13) {
|
||||||
this.searchWait = true;
|
this.searchWait = true;
|
||||||
search(this, this.searchQuery).finally(() => {
|
search(this, this.searchQuery).finally(() => {
|
||||||
this.searchWait = false;
|
this.searchWait = false;
|
||||||
@@ -895,24 +895,25 @@ export default Vue.extend({
|
|||||||
color: var(--navActive);
|
color: var(--navActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child, &:last-child {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
top: 0;
|
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
background: var(--wboyroyc);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
top: 0;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
background: var(--navBg);
|
|
||||||
border-bottom: solid 1px var(--divider);
|
border-bottom: solid 1px var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
position: sticky;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
background: var(--navBg);
|
|
||||||
border-top: solid 1px var(--divider);
|
border-top: solid 1px var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -974,6 +975,10 @@ export default Vue.extend({
|
|||||||
&:not(.naked) {
|
&:not(.naked) {
|
||||||
background: var(--pageBg);
|
background: var(--pageBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.naked {
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<redoc spec-url='/api.json'></redoc>
|
<redoc spec-url="/api.json" expand-responses="200" expand-single-schema-field="true"></redoc>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="$store.state.device.animation ? 'transition-group' : 'div'" class="sqadhkmv" name="list" tag="div" :data-direction="direction" :data-reversed="reversed ? 'true' : 'false'">
|
<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">
|
<template v-for="(item, i) in items">
|
||||||
<slot :item="item" :i="i"></slot>
|
<slot :item="item"></slot>
|
||||||
<div class="separator" :key="item.id + '_date'" v-if="showDate(i, item)">
|
<div class="separator" v-if="showDate(i, item)" :key="item.id + '_date'">
|
||||||
<p class="date">
|
<p class="date">
|
||||||
<span><fa class="icon" :icon="faAngleUp"/>{{ getDateText(item.createdAt) }}</span>
|
<span><fa class="icon" :icon="faAngleUp"/>{{ getDateText(item.createdAt) }}</span>
|
||||||
<span>{{ getDateText(items[i + 1].createdAt) }}<fa class="icon" :icon="faAngleDown"/></span>
|
<span>{{ getDateText(items[i + 1].createdAt) }}<fa class="icon" :icon="faAngleDown"/></span>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mjndxjcg _panel">
|
<div class="mjndxjcg _panel">
|
||||||
<img src="https://xn--931a.moe/assets/error.png" class="_ghost"/>
|
<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
|
||||||
<p><fa :icon="faExclamationTriangle"/> {{ $t('error') }}</p>
|
<p><fa :icon="faExclamationTriangle"/> {{ $t('error') }}</p>
|
||||||
<mk-button @click="() => $emit('retry')" class="button">{{ $t('retry') }}</mk-button>
|
<mk-button @click="() => $emit('retry')" class="button">{{ $t('retry') }}</mk-button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -517,11 +517,11 @@ export default Vue.extend({
|
|||||||
icon: faLink,
|
icon: faLink,
|
||||||
text: this.$t('copyLink'),
|
text: this.$t('copyLink'),
|
||||||
action: this.copyLink
|
action: this.copyLink
|
||||||
}, this.appearNote.uri ? {
|
}, (this.appearNote.url || this.appearNote.uri) ? {
|
||||||
icon: faExternalLinkSquareAlt,
|
icon: faExternalLinkSquareAlt,
|
||||||
text: this.$t('showOnRemote'),
|
text: this.$t('showOnRemote'),
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(this.appearNote.uri, '_blank');
|
window.open(this.appearNote.url || this.appearNote.uri, '_blank');
|
||||||
}
|
}
|
||||||
} : undefined,
|
} : undefined,
|
||||||
null,
|
null,
|
||||||
@@ -585,11 +585,11 @@ export default Vue.extend({
|
|||||||
icon: faLink,
|
icon: faLink,
|
||||||
text: this.$t('copyLink'),
|
text: this.$t('copyLink'),
|
||||||
action: this.copyLink
|
action: this.copyLink
|
||||||
}, this.appearNote.uri ? {
|
}, (this.appearNote.url || this.appearNote.uri) ? {
|
||||||
icon: faExternalLinkSquareAlt,
|
icon: faExternalLinkSquareAlt,
|
||||||
text: this.$t('showOnRemote'),
|
text: this.$t('showOnRemote'),
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(this.appearNote.uri, '_blank');
|
window.open(this.appearNote.url || this.appearNote.uri, '_blank');
|
||||||
}
|
}
|
||||||
} : undefined]
|
} : undefined]
|
||||||
.filter(x => x !== undefined);
|
.filter(x => x !== undefined);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-notes" v-size="[{ max: 500 }]">
|
<div class="mk-notes" v-size="[{ max: 500 }]">
|
||||||
<div class="empty" v-if="empty">
|
<div class="_fullinfo" v-if="empty">
|
||||||
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
|
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||||
<div>{{ $t('noNotes') }}</div>
|
<div>{{ $t('noNotes') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -90,18 +90,6 @@ export default Vue.extend({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mk-notes {
|
.mk-notes {
|
||||||
> .empty {
|
|
||||||
padding: 32px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
> img {
|
|
||||||
vertical-align: bottom;
|
|
||||||
height: 128px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .notes {
|
> .notes {
|
||||||
> ::v-deep *:not(:last-child) {
|
> ::v-deep *:not(:last-child) {
|
||||||
margin-bottom: var(--marginFull);
|
margin-bottom: var(--marginFull);
|
||||||
|
@@ -1,22 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-notification" :class="notification.type" v-size="[{ max: 500 }, { max: 600 }]">
|
<div class="mk-notification" :class="notification.type" v-size="[{ max: 500 }, { max: 600 }]">
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<mk-avatar class="avatar" :user="notification.user"/>
|
<mk-avatar v-if="notification.user" class="icon" :user="notification.user"/>
|
||||||
<div class="icon" :class="notification.type">
|
<img v-else class="icon" :src="notification.icon" alt=""/>
|
||||||
|
<div class="sub-icon" :class="notification.type">
|
||||||
<fa :icon="faPlus" v-if="notification.type === 'follow'"/>
|
<fa :icon="faPlus" v-if="notification.type === 'follow'"/>
|
||||||
<fa :icon="faClock" v-if="notification.type === 'receiveFollowRequest'"/>
|
<fa :icon="faClock" v-else-if="notification.type === 'receiveFollowRequest'"/>
|
||||||
<fa :icon="faCheck" v-if="notification.type === 'followRequestAccepted'"/>
|
<fa :icon="faCheck" v-else-if="notification.type === 'followRequestAccepted'"/>
|
||||||
<fa :icon="faIdCardAlt" v-if="notification.type === 'groupInvited'"/>
|
<fa :icon="faIdCardAlt" v-else-if="notification.type === 'groupInvited'"/>
|
||||||
<fa :icon="faRetweet" v-if="notification.type === 'renote'"/>
|
<fa :icon="faRetweet" v-else-if="notification.type === 'renote'"/>
|
||||||
<fa :icon="faReply" v-if="notification.type === 'reply'"/>
|
<fa :icon="faReply" v-else-if="notification.type === 'reply'"/>
|
||||||
<fa :icon="faAt" v-if="notification.type === 'mention'"/>
|
<fa :icon="faAt" v-else-if="notification.type === 'mention'"/>
|
||||||
<fa :icon="faQuoteLeft" v-if="notification.type === 'quote'"/>
|
<fa :icon="faQuoteLeft" v-else-if="notification.type === 'quote'"/>
|
||||||
<x-reaction-icon v-if="notification.type === 'reaction'" :reaction="notification.reaction" :no-style="true"/>
|
<x-reaction-icon v-else-if="notification.type === 'reaction'" :reaction="notification.reaction" :no-style="true"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tail">
|
<div class="tail">
|
||||||
<header>
|
<header>
|
||||||
<router-link class="name" :to="notification.user | userPage" v-user-preview="notification.user.id"><mk-user-name :user="notification.user"/></router-link>
|
<router-link v-if="notification.user" class="name" :to="notification.user | userPage" v-user-preview="notification.user.id"><mk-user-name :user="notification.user"/></router-link>
|
||||||
|
<span v-else>{{ notification.header }}</span>
|
||||||
<mk-time :time="notification.createdAt" v-if="withTime"/>
|
<mk-time :time="notification.createdAt" v-if="withTime"/>
|
||||||
</header>
|
</header>
|
||||||
<router-link v-if="notification.type === 'reaction'" class="text" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
<router-link v-if="notification.type === 'reaction'" class="text" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
||||||
@@ -42,6 +44,9 @@
|
|||||||
<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span>
|
<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span>
|
||||||
<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span>
|
<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span>
|
||||||
<span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ $t('groupInvited') }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ $t('reject') }}</button></div></span>
|
<span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ $t('groupInvited') }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ $t('reject') }}</button></div></span>
|
||||||
|
<span v-if="notification.type === 'app'" class="text">
|
||||||
|
<mfm :text="notification.body" :nowrap="!full"/>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -142,14 +147,14 @@ export default Vue.extend({
|
|||||||
height: 42px;
|
height: 42px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
|
||||||
> .avatar {
|
> .icon {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .icon {
|
> .sub-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
bottom: -2px;
|
bottom: -2px;
|
||||||
@@ -163,6 +168,10 @@ export default Vue.extend({
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@@ -35,6 +35,7 @@ export default Vue.extend({
|
|||||||
border: solid var(--lineWidth) var(--urlPreviewBorder);
|
border: solid var(--lineWidth) var(--urlPreviewBorder);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--divider);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -42,9 +43,8 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .thumbnail {
|
> .thumbnail {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
width: 100px;
|
height: 200px;
|
||||||
height: 100%;
|
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@@ -12,9 +12,9 @@
|
|||||||
<template #prefix><fa :icon="faLock"/></template>
|
<template #prefix><fa :icon="faLock"/></template>
|
||||||
</mk-input>
|
</mk-input>
|
||||||
<mk-button type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</mk-button>
|
<mk-button type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</mk-button>
|
||||||
<p v-if="meta && meta.enableTwitterIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/twitter`"><fa :icon="faTwitter"/> {{ $t('signinWith', { x: 'Twitter' }) }}</a></p>
|
<a class="_panel _button" style="margin: 8px auto;" v-if="meta && meta.enableTwitterIntegration" :href="`${apiUrl}/signin/twitter`"><fa :icon="faTwitter" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Twitter' }) }}</a>
|
||||||
<p v-if="meta && meta.enableGithubIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/github`"><fa :icon="faGithub"/> {{ $t('signinWith', { x: 'GitHub' }) }}</a></p>
|
<a class="_panel _button" style="margin: 8px auto;" v-if="meta && meta.enableGithubIntegration" :href="`${apiUrl}/signin/github`"><fa :icon="faGithub" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'GitHub' }) }}</a>
|
||||||
<p v-if="meta && meta.enableDiscordIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/discord`"><fa :icon="faDiscord"/> {{ $t('signinWith', { x: 'Discord' }) }}</a></p>
|
<a class="_panel _button" style="margin: 8px auto;" v-if="meta && meta.enableDiscordIntegration" :href="`${apiUrl}/signin/discord`"><fa :icon="faDiscord" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Discord' }) }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }">
|
<div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }">
|
||||||
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
||||||
|
@@ -38,7 +38,7 @@
|
|||||||
</mk-input>
|
</mk-input>
|
||||||
<mk-switch v-model="ToSAgreement" v-if="meta.tosUrl">
|
<mk-switch v-model="ToSAgreement" v-if="meta.tosUrl">
|
||||||
<i18n path="agreeTo">
|
<i18n path="agreeTo">
|
||||||
<a :href="meta.tosUrl" target="_blank">{{ $t('tos') }}</a>
|
<a :href="meta.tosUrl" class="_link" target="_blank">{{ $t('tos') }}</a>
|
||||||
</i18n>
|
</i18n>
|
||||||
</mk-switch>
|
</mk-switch>
|
||||||
<div v-if="meta.enableRecaptcha" class="g-recaptcha" :data-sitekey="meta.recaptchaSiteKey" style="margin: 16px 0;"></div>
|
<div v-if="meta.enableRecaptcha" class="g-recaptcha" :data-sitekey="meta.recaptchaSiteKey" style="margin: 16px 0;"></div>
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<div class="efvhhmdq">
|
<div class="efvhhmdq">
|
||||||
<div class="no-users" v-if="empty">
|
<div class="no-users" v-if="empty">
|
||||||
<p>{{ $t('no-users') }}</p>
|
<p>{{ $t('noUsers') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="user" v-for="user in users" :key="user.id">
|
<div class="user" v-for="user in users" :key="user.id">
|
||||||
<mk-avatar class="avatar" :user="user"/>
|
<mk-avatar class="avatar" :user="user"/>
|
||||||
|
@@ -18,7 +18,9 @@ import PostFormDialog from './components/post-form-dialog.vue';
|
|||||||
import Dialog from './components/dialog.vue';
|
import Dialog from './components/dialog.vue';
|
||||||
import Menu from './components/menu.vue';
|
import Menu from './components/menu.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
import { applyTheme, lightTheme, builtinThemes } from './theme';
|
import { applyTheme, lightTheme } from './theme';
|
||||||
|
import { isDeviceDarkmode } from './scripts/is-device-darkmode';
|
||||||
|
import createStore from './store';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
Vue.use(VueHotkey);
|
Vue.use(VueHotkey);
|
||||||
@@ -133,24 +135,39 @@ document.body.setAttribute('ontouchstart', '');
|
|||||||
// アプリ基底要素マウント
|
// アプリ基底要素マウント
|
||||||
document.body.innerHTML = '<div id="app"></div>';
|
document.body.innerHTML = '<div id="app"></div>';
|
||||||
|
|
||||||
const os = new MiOS();
|
const store = createStore();
|
||||||
|
|
||||||
|
const os = new MiOS(store);
|
||||||
|
|
||||||
os.init(async () => {
|
os.init(async () => {
|
||||||
window.addEventListener('storage', e => {
|
window.addEventListener('storage', e => {
|
||||||
if (e.key === 'vuex') {
|
if (e.key === 'vuex') {
|
||||||
os.store.replaceState(JSON.parse(localStorage['vuex']));
|
store.replaceState(JSON.parse(localStorage['vuex']));
|
||||||
} else if (e.key === 'i') {
|
} else if (e.key === 'i') {
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => {
|
store.watch(state => state.device.darkMode, darkMode => {
|
||||||
if (os.store.state.device.syncDeviceDarkMode) {
|
import('./theme').then(({ builtinThemes }) => {
|
||||||
os.store.commit('device/set', { key: 'darkMode', value: mql.matches });
|
const themes = builtinThemes.concat(store.state.device.themes);
|
||||||
}
|
applyTheme(themes.find(x => x.id === (darkMode ? store.state.device.darkTheme : store.state.device.lightTheme)));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if ('Notification' in window && os.store.getters.isSignedIn) {
|
//#region Sync dark mode
|
||||||
|
if (store.state.device.syncDeviceDarkMode) {
|
||||||
|
store.commit('device/set', { key: 'darkMode', value: isDeviceDarkmode() });
|
||||||
|
}
|
||||||
|
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => {
|
||||||
|
if (store.state.device.syncDeviceDarkMode) {
|
||||||
|
store.commit('device/set', { key: 'darkMode', value: mql.matches });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
if ('Notification' in window && store.getters.isSignedIn) {
|
||||||
// 許可を得ていなかったらリクエスト
|
// 許可を得ていなかったらリクエスト
|
||||||
if (Notification.permission === 'default') {
|
if (Notification.permission === 'default') {
|
||||||
Notification.requestPermission();
|
Notification.requestPermission();
|
||||||
@@ -158,7 +175,7 @@ os.init(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
store: os.store,
|
store: store,
|
||||||
metaInfo: {
|
metaInfo: {
|
||||||
title: null,
|
title: null,
|
||||||
titleTemplate: title => title ? `${title} | ${(instanceName || 'Misskey')}` : (instanceName || 'Misskey')
|
titleTemplate: title => title ? `${title} | ${(instanceName || 'Misskey')}` : (instanceName || 'Misskey')
|
||||||
@@ -169,14 +186,8 @@ os.init(async () => {
|
|||||||
isMobile: isMobile
|
isMobile: isMobile
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
'$store.state.device.darkMode'() {
|
|
||||||
const themes = builtinThemes.concat(this.$store.state.device.themes);
|
|
||||||
applyTheme(themes.find(x => x.id === (this.$store.state.device.darkMode ? this.$store.state.device.darkTheme : this.$store.state.device.lightTheme)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
api: os.api,
|
api: (endpoint: string, data: { [x: string]: any } = {}, token?) => store.dispatch('api', { endpoint, data, token }),
|
||||||
signout: os.signout,
|
signout: os.signout,
|
||||||
new(vm, props) {
|
new(vm, props) {
|
||||||
const x = new vm({
|
const x = new vm({
|
||||||
@@ -227,58 +238,63 @@ os.init(async () => {
|
|||||||
// マウント
|
// マウント
|
||||||
app.$mount('#app');
|
app.$mount('#app');
|
||||||
|
|
||||||
if (app.$store.getters.isSignedIn) {
|
os.stream.on('emojiAdded', data => {
|
||||||
|
// TODO
|
||||||
|
//store.commit('instance/set', );
|
||||||
|
});
|
||||||
|
|
||||||
|
if (store.getters.isSignedIn) {
|
||||||
const main = os.stream.useSharedConnection('main');
|
const main = os.stream.useSharedConnection('main');
|
||||||
|
|
||||||
// 自分の情報が更新されたとき
|
// 自分の情報が更新されたとき
|
||||||
main.on('meUpdated', i => {
|
main.on('meUpdated', i => {
|
||||||
app.$store.dispatch('mergeMe', i);
|
store.dispatch('mergeMe', i);
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('readAllNotifications', () => {
|
main.on('readAllNotifications', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadNotification: false
|
hasUnreadNotification: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('unreadNotification', () => {
|
main.on('unreadNotification', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadNotification: true
|
hasUnreadNotification: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('unreadMention', () => {
|
main.on('unreadMention', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadMentions: true
|
hasUnreadMentions: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('readAllUnreadMentions', () => {
|
main.on('readAllUnreadMentions', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadMentions: false
|
hasUnreadMentions: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('unreadSpecifiedNote', () => {
|
main.on('unreadSpecifiedNote', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadSpecifiedNotes: true
|
hasUnreadSpecifiedNotes: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('readAllUnreadSpecifiedNotes', () => {
|
main.on('readAllUnreadSpecifiedNotes', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadSpecifiedNotes: false
|
hasUnreadSpecifiedNotes: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('readAllMessagingMessages', () => {
|
main.on('readAllMessagingMessages', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadMessagingMessage: false
|
hasUnreadMessagingMessage: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('unreadMessagingMessage', () => {
|
main.on('unreadMessagingMessage', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadMessagingMessage: true
|
hasUnreadMessagingMessage: true
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -286,13 +302,13 @@ os.init(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
main.on('readAllAntennas', () => {
|
main.on('readAllAntennas', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadAntenna: false
|
hasUnreadAntenna: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('unreadAntenna', () => {
|
main.on('unreadAntenna', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadAntenna: true
|
hasUnreadAntenna: true
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -300,13 +316,13 @@ os.init(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
main.on('readAllAnnouncements', () => {
|
main.on('readAllAnnouncements', () => {
|
||||||
app.$store.dispatch('mergeMe', {
|
store.dispatch('mergeMe', {
|
||||||
hasUnreadAnnouncement: false
|
hasUnreadAnnouncement: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('clientSettingUpdated', x => {
|
main.on('clientSettingUpdated', x => {
|
||||||
app.$store.commit('settings/set', {
|
store.commit('settings/set', {
|
||||||
key: x.key,
|
key: x.key,
|
||||||
value: x.value
|
value: x.value
|
||||||
});
|
});
|
||||||
|
@@ -2,16 +2,11 @@ import autobind from 'autobind-decorator';
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
|
||||||
import initStore from './store';
|
|
||||||
import { apiUrl, version } from './config';
|
import { apiUrl, version } from './config';
|
||||||
import Progress from './scripts/loading';
|
import Progress from './scripts/loading';
|
||||||
|
|
||||||
import Stream from './scripts/stream';
|
import Stream from './scripts/stream';
|
||||||
|
import store from './store';
|
||||||
//#region api requests
|
|
||||||
let spinner = null;
|
|
||||||
let pending = 0;
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Misskey Operating System
|
* Misskey Operating System
|
||||||
@@ -19,7 +14,7 @@ let pending = 0;
|
|||||||
export default class MiOS extends EventEmitter {
|
export default class MiOS extends EventEmitter {
|
||||||
public app: Vue;
|
public app: Vue;
|
||||||
|
|
||||||
public store: ReturnType<typeof initStore>;
|
public store: ReturnType<typeof store>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A connection manager of home stream
|
* A connection manager of home stream
|
||||||
@@ -31,6 +26,11 @@ export default class MiOS extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
private swRegistration: ServiceWorkerRegistration = null;
|
private swRegistration: ServiceWorkerRegistration = null;
|
||||||
|
|
||||||
|
constructor(vuex: MiOS['store']) {
|
||||||
|
super();
|
||||||
|
this.store = vuex;
|
||||||
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public signout() {
|
public signout() {
|
||||||
this.store.dispatch('logout');
|
this.store.dispatch('logout');
|
||||||
@@ -52,8 +52,6 @@ export default class MiOS extends EventEmitter {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.store = initStore(this);
|
|
||||||
|
|
||||||
// ユーザーをフェッチしてコールバックする
|
// ユーザーをフェッチしてコールバックする
|
||||||
const fetchme = (token, cb) => {
|
const fetchme = (token, cb) => {
|
||||||
let me = null;
|
let me = null;
|
||||||
@@ -187,16 +185,19 @@ export default class MiOS extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register
|
// Register
|
||||||
this.api('sw/register', {
|
this.store.dispatch('api', {
|
||||||
endpoint: subscription.endpoint,
|
endpoint: 'sw/register',
|
||||||
auth: encode(subscription.getKey('auth')),
|
data: {
|
||||||
publickey: encode(subscription.getKey('p256dh'))
|
endpoint: subscription.endpoint,
|
||||||
|
auth: encode(subscription.getKey('auth')),
|
||||||
|
publickey: encode(subscription.getKey('p256dh'))
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
// When subscribe failed
|
// When subscribe failed
|
||||||
.catch(async (err: Error) => {
|
.catch(async (err: Error) => {
|
||||||
// 通知が許可されていなかったとき
|
// 通知が許可されていなかったとき
|
||||||
if (err.name == 'NotAllowedError') {
|
if (err.name === 'NotAllowedError') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,52 +215,6 @@ export default class MiOS extends EventEmitter {
|
|||||||
// Register service worker
|
// Register service worker
|
||||||
navigator.serviceWorker.register(sw);
|
navigator.serviceWorker.register(sw);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Misskey APIにリクエストします
|
|
||||||
* @param endpoint エンドポイント名
|
|
||||||
* @param data パラメータ
|
|
||||||
*/
|
|
||||||
@autobind
|
|
||||||
public api(endpoint: string, data: { [x: string]: any } = {}, token?): Promise<{ [x: string]: any }> {
|
|
||||||
if (++pending === 1) {
|
|
||||||
spinner = document.createElement('div');
|
|
||||||
spinner.setAttribute('id', 'wait');
|
|
||||||
document.body.appendChild(spinner);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFinally = () => {
|
|
||||||
if (--pending === 0) spinner.parentNode.removeChild(spinner);
|
|
||||||
};
|
|
||||||
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
|
||||||
// Append a credential
|
|
||||||
if (this.store.getters.isSignedIn) (data as any).i = this.store.state.i.token;
|
|
||||||
if (token) (data as any).i = token;
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
credentials: 'omit',
|
|
||||||
cache: 'no-cache'
|
|
||||||
}).then(async (res) => {
|
|
||||||
const body = res.status === 204 ? null : await res.json();
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
|
||||||
resolve(body);
|
|
||||||
} else if (res.status === 204) {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
reject(body.error);
|
|
||||||
}
|
|
||||||
}).catch(reject);
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.then(onFinally, onFinally);
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,9 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="znqjceqz">
|
<div class="znqjceqz">
|
||||||
<portal to="title">🍀 {{ $t('aboutMisskey') }}</portal>
|
<portal to="title">{{ $t('aboutMisskey') }}</portal>
|
||||||
|
|
||||||
<section class="_card">
|
<section class="_card">
|
||||||
<div class="_title">🍀 {{ $t('aboutMisskey') }}</div>
|
<div class="_title">{{ $t('aboutMisskey') }}</div>
|
||||||
|
<div class="_content" style="text-align: center;">
|
||||||
|
<img src="/assets/icons/512.png" alt="" style="display: block; width: 100px; margin: 0 auto; border-radius: 16px;"/>
|
||||||
|
<div style="margin-top: 0.75em;">Misskey</div>
|
||||||
|
<div style="opacity: 0.5;">v{{ version }}</div>
|
||||||
|
</div>
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<div style="margin-bottom: 1em;">{{ $t('aboutMisskeyText') }}</div>
|
<div style="margin-bottom: 1em;">{{ $t('aboutMisskeyText') }}</div>
|
||||||
<div>🛠️ {{ $t('misskeyMembers') }}</div>
|
<div>🛠️ {{ $t('misskeyMembers') }}</div>
|
||||||
@@ -44,6 +49,9 @@
|
|||||||
<li>wara</li>
|
<li>wara</li>
|
||||||
<li>Takashi Shibuya</li>
|
<li>Takashi Shibuya</li>
|
||||||
<li>Noizeman</li>
|
<li>Noizeman</li>
|
||||||
|
<li>mydarkstar</li>
|
||||||
|
<li>nenohi</li>
|
||||||
|
<li>Eduardo Quiros</li>
|
||||||
</ul>
|
</ul>
|
||||||
<span>{{ $t('morePatrons') }}</span>
|
<span>{{ $t('morePatrons') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
107
src/client/pages/apps.vue
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<portal to="icon"><fa :icon="faPlug"/></portal>
|
||||||
|
<portal to="title">{{ $t('installedApps') }}</portal>
|
||||||
|
|
||||||
|
<mk-pagination :pagination="pagination" class="bfomjevm" ref="list">
|
||||||
|
<template #empty>
|
||||||
|
<div class="_fullinfo">
|
||||||
|
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||||
|
<div>{{ $t('nothing') }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default="{items}">
|
||||||
|
<div class="token _panel" v-for="token in items" :key="token.id">
|
||||||
|
<img class="icon" :src="token.iconUrl" alt=""/>
|
||||||
|
<div class="body">
|
||||||
|
<div class="name">{{ token.name }}</div>
|
||||||
|
<div class="description">{{ token.description }}</div>
|
||||||
|
<div class="_keyValue">
|
||||||
|
<div>{{ $t('installedDate') }}:</div>
|
||||||
|
<div><mk-time :time="token.createdAt"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="_keyValue">
|
||||||
|
<div>{{ $t('lastUsedDate') }}:</div>
|
||||||
|
<div><mk-time :time="token.lastUsedAt"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="_button" @click="revoke(token)"><fa :icon="faTrashAlt"/></button>
|
||||||
|
</div>
|
||||||
|
<details>
|
||||||
|
<summary>{{ $t('details') }}</summary>
|
||||||
|
<ul>
|
||||||
|
<li v-for="p in token.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</mk-pagination>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { faTrashAlt, faPlug } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import MkPagination from '../components/ui/pagination.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
metaInfo() {
|
||||||
|
return {
|
||||||
|
title: this.$t('installedApps') as string
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
MkPagination
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
pagination: {
|
||||||
|
endpoint: 'i/apps',
|
||||||
|
limit: 100,
|
||||||
|
params: {
|
||||||
|
sort: '+lastUsedAt'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
faTrashAlt, faPlug
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
revoke(token) {
|
||||||
|
this.$root.api('i/revoke-token', { tokenId: token.id }).then(() => {
|
||||||
|
this.$refs.list.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.bfomjevm {
|
||||||
|
> .token {
|
||||||
|
display: flex;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
> .icon {
|
||||||
|
display: block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0 12px 0 0;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .body {
|
||||||
|
width: calc(100% - 62px);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> .name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@@ -12,20 +12,18 @@
|
|||||||
@accepted="accepted"
|
@accepted="accepted"
|
||||||
/>
|
/>
|
||||||
<div class="denied _panel" v-if="state == 'denied'">
|
<div class="denied _panel" v-if="state == 'denied'">
|
||||||
<h1>{{ $t('denied') }}</h1>
|
<h1>{{ $t('_auth.denied') }}</h1>
|
||||||
<p>{{ $t('denied-paragraph') }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="accepted _panel" v-if="state == 'accepted'">
|
<div class="accepted _panel" v-if="state == 'accepted'">
|
||||||
<h1>{{ session.app.isAuthorized ? this.$t('already-authorized') : this.$t('allowed') }}</h1>
|
<h1>{{ session.app.isAuthorized ? this.$t('already-authorized') : this.$t('allowed') }}</h1>
|
||||||
<p v-if="session.app.callbackUrl">{{ $t('callback-url') }}<mk-ellipsis/></p>
|
<p v-if="session.app.callbackUrl">{{ $t('_auth.callback') }}<mk-ellipsis/></p>
|
||||||
<p v-if="!session.app.callbackUrl">{{ $t('please-go-back') }}</p>
|
<p v-if="!session.app.callbackUrl">{{ $t('_auth.pleaseGoBack') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="error _panel" v-if="state == 'fetch-session-error'">
|
<div class="error _panel" v-if="state == 'fetch-session-error'">
|
||||||
<p>{{ $t('error') }}</p>
|
<p>{{ $t('error') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="signin" v-else>
|
<div class="signin" v-else>
|
||||||
<h1>{{ $t('sign-in') }}</h1>
|
|
||||||
<mk-signin @login="onLogin"/>
|
<mk-signin @login="onLogin"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { faFileAlt } from '@fortawesome/free-solid-svg-icons'
|
import { faFileAlt } from '@fortawesome/free-solid-svg-icons'
|
||||||
import MarkdownIt from 'markdown-it';
|
import MarkdownIt from 'markdown-it';
|
||||||
|
import MarkdownItAnchor from 'markdown-it-anchor';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
import { url, lang } from '../config';
|
import { url, lang } from '../config';
|
||||||
import MkLink from '../components/link.vue';
|
import MkLink from '../components/link.vue';
|
||||||
@@ -26,6 +27,10 @@ const markdown = MarkdownIt({
|
|||||||
html: true
|
html: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
markdown.use(MarkdownItAnchor, {
|
||||||
|
slugify: (s) => encodeURIComponent(String(s).trim().replace(/\s+/g, '-'))
|
||||||
|
});
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
|
|
||||||
@@ -72,6 +77,9 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
parse(md: string) {
|
parse(md: string) {
|
||||||
|
// 変数置換
|
||||||
|
md = md.replace(/\{_URL_\}/g, url);
|
||||||
|
|
||||||
// markdown の全容をパースする
|
// markdown の全容をパースする
|
||||||
const parsed = markdown.parse(md, {});
|
const parsed = markdown.parse(md, {});
|
||||||
if (parsed.length === 0) return;
|
if (parsed.length === 0) return;
|
||||||
@@ -115,6 +123,23 @@ export default Vue.extend({
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep a {
|
||||||
|
color: var(--link);
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep blockquote {
|
||||||
|
display: block;
|
||||||
|
margin: 8px;
|
||||||
|
padding: 6px 0 6px 12px;
|
||||||
|
color: var(--fg);
|
||||||
|
border-left: solid 3px var(--fg);
|
||||||
|
opacity: 0.7;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::v-deep h2 {
|
::v-deep h2 {
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
padding: 0 0 0.5em 0;
|
padding: 0 0 0.5em 0;
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
<mk-pagination :pagination="pagination" class="mk-follow-requests" ref="list">
|
<mk-pagination :pagination="pagination" class="mk-follow-requests" ref="list">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="tkdrhpxr">
|
<div class="_fullinfo">
|
||||||
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
|
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||||
<div>{{ $t('noFollowRequests') }}</div>
|
<div>{{ $t('noFollowRequests') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -75,18 +75,6 @@ export default Vue.extend({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mk-follow-requests {
|
.mk-follow-requests {
|
||||||
.tkdrhpxr {
|
|
||||||
padding: 32px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
> img {
|
|
||||||
vertical-align: bottom;
|
|
||||||
height: 128px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .user {
|
> .user {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
@@ -1,11 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-federation">
|
<div class="mk-federation">
|
||||||
|
<portal to="icon"><fa :icon="faGlobe"/></portal>
|
||||||
|
<portal to="title">{{ $t('federation') }}</portal>
|
||||||
|
|
||||||
<section class="_card instances">
|
<section class="_card instances">
|
||||||
<div class="_title"><fa :icon="faGlobe"/> {{ $t('instances') }}</div>
|
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
|
<mk-input v-model="host" :debounce="true"><span>{{ $t('host') }}</span></mk-input>
|
||||||
<div class="inputs" style="display: flex;">
|
<div class="inputs" style="display: flex;">
|
||||||
<mk-input v-model="host" :debounce="true" style="margin: 0; flex: 1;"><span>{{ $t('host') }}</span></mk-input>
|
<mk-select v-model="state" style="margin: 0; flex: 1;">
|
||||||
<mk-select v-model="state" style="margin: 0;">
|
<template #label>{{ $t('state') }}</template>
|
||||||
<option value="all">{{ $t('all') }}</option>
|
<option value="all">{{ $t('all') }}</option>
|
||||||
<option value="federating">{{ $t('federating') }}</option>
|
<option value="federating">{{ $t('federating') }}</option>
|
||||||
<option value="subscribing">{{ $t('subscribing') }}</option>
|
<option value="subscribing">{{ $t('subscribing') }}</option>
|
||||||
@@ -14,11 +17,32 @@
|
|||||||
<option value="blocked">{{ $t('blocked') }}</option>
|
<option value="blocked">{{ $t('blocked') }}</option>
|
||||||
<option value="notResponding">{{ $t('notResponding') }}</option>
|
<option value="notResponding">{{ $t('notResponding') }}</option>
|
||||||
</mk-select>
|
</mk-select>
|
||||||
|
<mk-select v-model="sort" style="margin: 0; flex: 1;">
|
||||||
|
<template #label>{{ $t('sort') }}</template>
|
||||||
|
<option value="+pubSub">{{ $t('pubSub') }} ({{ $t('descendingOrder') }})</option>
|
||||||
|
<option value="-pubSub">{{ $t('pubSub') }} ({{ $t('ascendingOrder') }})</option>
|
||||||
|
<option value="+notes">{{ $t('notes') }} ({{ $t('descendingOrder') }})</option>
|
||||||
|
<option value="-notes">{{ $t('notes') }} ({{ $t('ascendingOrder') }})</option>
|
||||||
|
<option value="+users">{{ $t('users') }} ({{ $t('descendingOrder') }})</option>
|
||||||
|
<option value="-users">{{ $t('users') }} ({{ $t('ascendingOrder') }})</option>
|
||||||
|
<option value="+following">{{ $t('following') }} ({{ $t('descendingOrder') }})</option>
|
||||||
|
<option value="-following">{{ $t('following') }} ({{ $t('ascendingOrder') }})</option>
|
||||||
|
<option value="+followers">{{ $t('followers') }} ({{ $t('descendingOrder') }})</option>
|
||||||
|
<option value="-followers">{{ $t('followers') }} ({{ $t('ascendingOrder') }})</option>
|
||||||
|
<option value="+caughtAt">{{ $t('caughtAt') }} ({{ $t('descendingOrder') }})</option>
|
||||||
|
<option value="-caughtAt">{{ $t('caughtAt') }} ({{ $t('ascendingOrder') }})</option>
|
||||||
|
<option value="+lastCommunicatedAt">{{ $t('lastCommunicatedAt') }} ({{ $t('descendingOrder') }})</option>
|
||||||
|
<option value="-lastCommunicatedAt">{{ $t('lastCommunicatedAt') }} ({{ $t('ascendingOrder') }})</option>
|
||||||
|
<option value="+driveUsage">{{ $t('driveUsage') }} ({{ $t('descendingOrder') }})</option>
|
||||||
|
<option value="-driveUsage">{{ $t('driveUsage') }} ({{ $t('ascendingOrder') }})</option>
|
||||||
|
<option value="+driveFiles">{{ $t('driveFiles') }} ({{ $t('descendingOrder') }})</option>
|
||||||
|
<option value="-driveFiles">{{ $t('driveFiles') }} ({{ $t('ascendingOrder') }})</option>
|
||||||
|
</mk-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<mk-pagination :pagination="pagination" #default="{items}" class="instances" ref="instances" :key="host + state">
|
<mk-pagination :pagination="pagination" #default="{items}" class="instances" ref="instances" :key="host + state">
|
||||||
<div class="instance" v-for="(instance, i) in items" :key="instance.id" @click="info(instance)">
|
<div class="instance" v-for="instance in items" :key="instance.id" @click="info(instance)">
|
||||||
<div class="host"><fa :icon="faCircle" class="indicator" :class="getStatus(instance)"/><b>{{ instance.host }}</b></div>
|
<div class="host"><fa :icon="faCircle" class="indicator" :class="getStatus(instance)"/><b>{{ instance.host }}</b></div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<span class="sub" v-if="instance.followersCount > 0"><fa :icon="faCaretDown" class="icon"/>Sub</span>
|
<span class="sub" v-if="instance.followersCount > 0"><fa :icon="faCaretDown" class="icon"/>Sub</span>
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<portal to="icon"><fa :icon="faExchangeAlt"/></portal>
|
||||||
|
<portal to="title">{{ $t('jobQueue') }}</portal>
|
||||||
|
|
||||||
<x-queue :connection="connection" domain="inbox">
|
<x-queue :connection="connection" domain="inbox">
|
||||||
<template #title><fa :icon="faExchangeAlt"/> In</template>
|
<template #title><fa :icon="faExchangeAlt"/> In</template>
|
||||||
</x-queue>
|
</x-queue>
|
||||||
|
@@ -64,6 +64,9 @@ export default Vue.extend({
|
|||||||
pagination: {
|
pagination: {
|
||||||
endpoint: 'admin/show-users',
|
endpoint: 'admin/show-users',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
params: () => ({
|
||||||
|
sort: '+createdAt'
|
||||||
|
}),
|
||||||
offsetMode: true
|
offsetMode: true
|
||||||
},
|
},
|
||||||
target: '',
|
target: '',
|
||||||
|
@@ -31,8 +31,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="no-history" v-if="!fetching && messages.length == 0">
|
<div class="_fullinfo" v-if="!fetching && messages.length == 0">
|
||||||
<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
|
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||||
<div>{{ $t('noHistory') }}</div>
|
<div>{{ $t('noHistory') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<mk-loading v-if="fetching"/>
|
<mk-loading v-if="fetching"/>
|
||||||
@@ -287,18 +287,6 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .no-history {
|
|
||||||
padding: 32px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
> img {
|
|
||||||
vertical-align: bottom;
|
|
||||||
height: 128px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 400px) {
|
@media (max-width: 400px) {
|
||||||
> .history {
|
> .history {
|
||||||
> .message {
|
> .message {
|
||||||
|
@@ -214,6 +214,7 @@ export default Vue.extend({
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 512px;
|
max-height: 512px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
> p {
|
> p {
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
<button class="more _button" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
|
<button class="more _button" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
|
||||||
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('loading') : $t('loadMore') }}
|
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('loading') : $t('loadMore') }}
|
||||||
</button>
|
</button>
|
||||||
<x-list class="messages" :items="messages" v-slot="{ item: message, i }" direction="up" reversed>
|
<x-list class="messages" :items="messages" v-slot="{ item: message }" direction="up" reversed>
|
||||||
<x-message :message="message" :is-group="group != null" :key="message.id"/>
|
<x-message :message="message" :is-group="group != null" :key="message.id"/>
|
||||||
</x-list>
|
</x-list>
|
||||||
</div>
|
</div>
|
||||||
|
103
src/client/pages/miauth.vue
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="$store.getters.isSignedIn">
|
||||||
|
<div class="waiting _card" v-if="state == 'waiting'">
|
||||||
|
<div class="_content">
|
||||||
|
<mk-loading/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="denied _card" v-if="state == 'denied'">
|
||||||
|
<div class="_content">
|
||||||
|
<p>{{ $t('_auth.denied') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accepted _card" v-else-if="state == 'accepted'">
|
||||||
|
<div class="_content">
|
||||||
|
<p v-if="callback">{{ $t('_auth.callback') }}<mk-ellipsis/></p>
|
||||||
|
<p v-else>{{ $t('_auth.pleaseGoBack') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="_card" v-else>
|
||||||
|
<div class="_title" v-if="name">{{ $t('_auth.shareAccess', { name: name }) }}</div>
|
||||||
|
<div class="_title" v-else>{{ $t('_auth.shareAccessAsk') }}</div>
|
||||||
|
<div class="_content">
|
||||||
|
<p>{{ $t('_auth.permissionAsk') }}</p>
|
||||||
|
<ul>
|
||||||
|
<template v-for="p in permission">
|
||||||
|
<li :key="p">{{ $t(`_permissions.${p}`) }}</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="_footer">
|
||||||
|
<mk-button @click="deny" inline>{{ $t('cancel') }}</mk-button>
|
||||||
|
<mk-button @click="accept" inline primary>{{ $t('accept') }}</mk-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="signin" v-else>
|
||||||
|
<mk-signin @login="onLogin"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
import MkSignin from '../components/signin.vue';
|
||||||
|
import MkButton from '../components/ui/button.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n,
|
||||||
|
components: {
|
||||||
|
MkSignin,
|
||||||
|
MkButton,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
state: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
session(): string {
|
||||||
|
return this.$route.params.session;
|
||||||
|
},
|
||||||
|
callback(): string {
|
||||||
|
return this.$route.query.callback;
|
||||||
|
},
|
||||||
|
name(): string {
|
||||||
|
return this.$route.query.name;
|
||||||
|
},
|
||||||
|
icon(): string {
|
||||||
|
return this.$route.query.icon;
|
||||||
|
},
|
||||||
|
permission(): string[] {
|
||||||
|
return this.$route.query.permission ? this.$route.query.permission.split(',') : [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async accept() {
|
||||||
|
this.state = 'waiting';
|
||||||
|
await this.$root.api('miauth/gen-token', {
|
||||||
|
session: this.session,
|
||||||
|
name: this.name,
|
||||||
|
iconUrl: this.icon,
|
||||||
|
permission: this.permission,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.state = 'accepted';
|
||||||
|
if (this.callback) {
|
||||||
|
location.href = `${this.callback}?session=${this.session}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deny() {
|
||||||
|
this.state = 'denied';
|
||||||
|
},
|
||||||
|
onLogin(res) {
|
||||||
|
localStorage.setItem('i', res.i);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
@@ -32,7 +32,9 @@
|
|||||||
<x-integration/>
|
<x-integration/>
|
||||||
<x-api/>
|
<x-api/>
|
||||||
|
|
||||||
<mk-button @click="$root.signout()" primary style="margin: var(--margin) auto;">{{ $t('logout') }}</mk-button>
|
<router-link class="_panel _buttonPrimary" to="/my/apps" style="margin: var(--margin) auto;">{{ $t('installedApps') }}</router-link>
|
||||||
|
|
||||||
|
<button class="_panel _buttonPrimary" @click="$root.signout()" style="margin: var(--margin) auto;">{{ $t('logout') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<section class="_card">
|
<section class="_card">
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<img src="https://xn--931a.moe/assets/not-found.png" class="_ghost"/>
|
<img src="https://xn--931a.moe/assets/not-found.jpg" class="_ghost"/>
|
||||||
<div>{{ $t('notFoundDescription') }}</div>
|
<div>{{ $t('notFoundDescription') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
<x-notes v-if="showNext" ref="next" :pagination="next"/>
|
<x-notes v-if="showNext" ref="next" :pagination="next"/>
|
||||||
<hr v-if="showNext"/>
|
<hr v-if="showNext"/>
|
||||||
|
|
||||||
<mk-remote-caution v-if="note.user.host != null" :href="note.uri" style="margin-bottom: var(--margin)"/>
|
<mk-remote-caution v-if="note.user.host != null" :href="note.url || note.uri" style="margin-bottom: var(--margin)"/>
|
||||||
<x-note :note="note" :key="note.id" :detail="true"/>
|
<x-note :note="note" :key="note.id" :detail="true"/>
|
||||||
<div v-if="error">
|
<div v-if="error">
|
||||||
<mk-error @retry="fetch()"/>
|
<mk-error @retry="fetch()"/>
|
||||||
|
@@ -102,6 +102,7 @@ import { blockDefs } from '../../scripts/aiscript/index';
|
|||||||
import { ASTypeChecker } from '../../scripts/aiscript/type-checker';
|
import { ASTypeChecker } from '../../scripts/aiscript/type-checker';
|
||||||
import { url } from '../../config';
|
import { url } from '../../config';
|
||||||
import { collectPageVars } from '../../scripts/collect-page-vars';
|
import { collectPageVars } from '../../scripts/collect-page-vars';
|
||||||
|
import { selectDriveFile } from '../../scripts/select-drive-file';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n,
|
i18n,
|
||||||
@@ -405,9 +406,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setEyeCatchingImage() {
|
setEyeCatchingImage() {
|
||||||
this.$chooseDriveFile({
|
selectDriveFile(this.$root, false).then(file => {
|
||||||
multiple: false
|
|
||||||
}).then(file => {
|
|
||||||
this.eyeCatchingImageId = file.id;
|
this.eyeCatchingImageId = file.id;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
<div class="_card" v-if="page" :key="page.id">
|
<div class="_card" v-if="page" :key="page.id">
|
||||||
<div class="_title">{{ page.title }}</div>
|
<div class="_title">{{ page.title }}</div>
|
||||||
|
<img class="header" :src="page.eyeCatchingImage.url" v-if="page.eyeCatchingImageId" />
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<x-page :page="page"/>
|
<x-page :page="page"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,6 +116,8 @@ export default Vue.extend({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.xcukqgmh {
|
.xcukqgmh {
|
||||||
|
> ._card > .header {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -42,6 +42,7 @@
|
|||||||
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</mk-select>
|
</mk-select>
|
||||||
|
<a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank" class="_link">{{ $t('_theme.explore') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<mk-switch v-model="syncDeviceDarkMode">{{ $t('syncDeviceDarkMode') }}</mk-switch>
|
<mk-switch v-model="syncDeviceDarkMode">{{ $t('syncDeviceDarkMode') }}</mk-switch>
|
||||||
@@ -50,18 +51,44 @@
|
|||||||
<mk-button primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</mk-button>
|
<mk-button primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</mk-button>
|
||||||
<mk-button primary v-else @click="wallpaper = null">{{ $t('removeWallpaper') }}</mk-button>
|
<mk-button primary v-else @click="wallpaper = null">{{ $t('removeWallpaper') }}</mk-button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="_content">
|
||||||
|
<details>
|
||||||
|
<summary><fa :icon="faDownload"/> {{ $t('_theme.install') }}</summary>
|
||||||
|
<mk-textarea v-model="installThemeCode">
|
||||||
|
<span>{{ $t('_theme.code') }}</span>
|
||||||
|
</mk-textarea>
|
||||||
|
<mk-button @click="() => install(this.installThemeCode)" :disabled="installThemeCode == null" primary inline><fa :icon="faCheck"/> {{ $t('install') }}</mk-button>
|
||||||
|
<mk-button @click="() => preview(this.installThemeCode)" :disabled="installThemeCode == null" inline><fa :icon="faEye"/> {{ $t('preview') }}</mk-button>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
<div class="_content">
|
||||||
|
<details>
|
||||||
|
<summary><fa :icon="faFolderOpen"/> {{ $t('_theme.manage') }}</summary>
|
||||||
|
<mk-select v-model="selectedThemeId">
|
||||||
|
<option v-for="x in installedThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
|
</mk-select>
|
||||||
|
<template v-if="selectedTheme">
|
||||||
|
<mk-textarea readonly tall :value="selectedThemeCode">
|
||||||
|
<span>{{ $t('_theme.code') }}</span>
|
||||||
|
</mk-textarea>
|
||||||
|
<mk-button @click="uninstall()" v-if="!builtinThemes.some(t => t.id == selectedTheme.id)"><fa :icon="faTrashAlt"/> {{ $t('uninstall') }}</mk-button>
|
||||||
|
</template>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { faPalette } from '@fortawesome/free-solid-svg-icons';
|
import { faPalette, faDownload, faFolderOpen, faCheck, faTrashAlt, faEye } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import * as JSON5 from 'json5';
|
||||||
import MkInput from '../../components/ui/input.vue';
|
import MkInput from '../../components/ui/input.vue';
|
||||||
import MkButton from '../../components/ui/button.vue';
|
import MkButton from '../../components/ui/button.vue';
|
||||||
import MkSelect from '../../components/ui/select.vue';
|
import MkSelect from '../../components/ui/select.vue';
|
||||||
import MkSwitch from '../../components/ui/switch.vue';
|
import MkSwitch from '../../components/ui/switch.vue';
|
||||||
|
import MkTextarea from '../../components/ui/textarea.vue';
|
||||||
import i18n from '../../i18n';
|
import i18n from '../../i18n';
|
||||||
import { Theme, builtinThemes, applyTheme } from '../../theme';
|
import { Theme, builtinThemes, applyTheme, validateTheme } from '../../theme';
|
||||||
import { selectFile } from '../../scripts/select-file';
|
import { selectFile } from '../../scripts/select-file';
|
||||||
import { isDeviceDarkmode } from '../../scripts/is-device-darkmode';
|
import { isDeviceDarkmode } from '../../scripts/is-device-darkmode';
|
||||||
|
|
||||||
@@ -73,12 +100,16 @@ export default Vue.extend({
|
|||||||
MkButton,
|
MkButton,
|
||||||
MkSelect,
|
MkSelect,
|
||||||
MkSwitch,
|
MkSwitch,
|
||||||
|
MkTextarea,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
builtinThemes,
|
||||||
|
installThemeCode: null,
|
||||||
|
selectedThemeId: null,
|
||||||
wallpaper: localStorage.getItem('wallpaper'),
|
wallpaper: localStorage.getItem('wallpaper'),
|
||||||
faPalette
|
faPalette, faDownload, faFolderOpen, faCheck, faTrashAlt, faEye
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -118,6 +149,16 @@ export default Vue.extend({
|
|||||||
get() { return this.$store.state.device.syncDeviceDarkMode; },
|
get() { return this.$store.state.device.syncDeviceDarkMode; },
|
||||||
set(value) { this.$store.commit('device/set', { key: 'syncDeviceDarkMode', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'syncDeviceDarkMode', value }); }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectedTheme() {
|
||||||
|
if (this.selectedThemeId == null) return null;
|
||||||
|
return this.themes.find(x => x.id === this.selectedThemeId);
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedThemeCode() {
|
||||||
|
if (this.selectedTheme == null) return null;
|
||||||
|
return JSON5.stringify(this.selectedTheme, null, '\t');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@@ -155,6 +196,66 @@ export default Vue.extend({
|
|||||||
this.wallpaper = file.url;
|
this.wallpaper = file.url;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
parseThemeCode(code) {
|
||||||
|
let theme;
|
||||||
|
|
||||||
|
try {
|
||||||
|
theme = JSON5.parse(code);
|
||||||
|
} catch (e) {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: this.$t('_theme.invalid')
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!validateTheme(theme)) {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: this.$t('_theme.invalid')
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.$store.state.device.themes.some(t => t.id === theme.id)) {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'info',
|
||||||
|
text: this.$t('_theme.alreadyInstalled')
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
},
|
||||||
|
|
||||||
|
preview(code) {
|
||||||
|
const theme = this.parseThemeCode(code);
|
||||||
|
if (theme) applyTheme(theme, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
install(code) {
|
||||||
|
const theme = this.parseThemeCode(code);
|
||||||
|
if (!theme) return;
|
||||||
|
const themes = this.$store.state.device.themes.concat(theme);
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'themes', value: themes
|
||||||
|
});
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'success',
|
||||||
|
text: this.$t('_theme.installed', { name: theme.name })
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
uninstall() {
|
||||||
|
const theme = this.selectedTheme;
|
||||||
|
const themes = this.$store.state.device.themes.filter(t => t.id != theme.id);
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'themes', value: themes
|
||||||
|
});
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'info',
|
||||||
|
iconOnly: true, autoClose: true
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -179,7 +280,7 @@ export default Vue.extend({
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0 200px;
|
padding: 0 100px;
|
||||||
transform: translate3d(-50%, -50%, 0);
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
@@ -46,6 +46,7 @@ export const router = new VueRouter({
|
|||||||
{ path: '/my/groups', component: page('my-groups/index') },
|
{ path: '/my/groups', component: page('my-groups/index') },
|
||||||
{ path: '/my/groups/:group', component: page('my-groups/group') },
|
{ path: '/my/groups/:group', component: page('my-groups/group') },
|
||||||
{ path: '/my/antennas', component: page('my-antennas/index') },
|
{ path: '/my/antennas', component: page('my-antennas/index') },
|
||||||
|
{ path: '/my/apps', component: page('apps') },
|
||||||
{ path: '/preferences', component: page('preferences/index') },
|
{ path: '/preferences', component: page('preferences/index') },
|
||||||
{ path: '/instance', component: page('instance/index') },
|
{ path: '/instance', component: page('instance/index') },
|
||||||
{ path: '/instance/emojis', component: page('instance/emojis') },
|
{ path: '/instance/emojis', component: page('instance/emojis') },
|
||||||
@@ -58,6 +59,7 @@ export const router = new VueRouter({
|
|||||||
{ path: '/notes/:note', name: 'note', component: page('note') },
|
{ path: '/notes/:note', name: 'note', component: page('note') },
|
||||||
{ path: '/tags/:tag', component: page('tag') },
|
{ path: '/tags/:tag', component: page('tag') },
|
||||||
{ path: '/auth/:token', component: page('auth') },
|
{ path: '/auth/:token', component: page('auth') },
|
||||||
|
{ path: '/miauth/:session', component: page('miauth') },
|
||||||
{ path: '/authorize-follow', component: page('follow') },
|
{ path: '/authorize-follow', component: page('follow') },
|
||||||
{ path: '/share', component: page('share') },
|
{ path: '/share', component: page('share') },
|
||||||
{ path: '*', component: page('not-found') }
|
{ path: '*', component: page('not-found') }
|
||||||
|
@@ -57,9 +57,9 @@ const ignoreElemens = ['input', 'textarea'];
|
|||||||
function match(e: KeyboardEvent, patterns: action['patterns']): boolean {
|
function match(e: KeyboardEvent, patterns: action['patterns']): boolean {
|
||||||
const key = e.code.toLowerCase();
|
const key = e.code.toLowerCase();
|
||||||
return patterns.some(pattern => pattern.which.includes(key) &&
|
return patterns.some(pattern => pattern.which.includes(key) &&
|
||||||
pattern.ctrl == e.ctrlKey &&
|
pattern.ctrl === e.ctrlKey &&
|
||||||
pattern.shift == e.shiftKey &&
|
pattern.shift === e.shiftKey &&
|
||||||
pattern.alt == e.altKey &&
|
pattern.alt === e.altKey &&
|
||||||
!e.metaKey
|
!e.metaKey
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
export default (input: string): string[] => {
|
export default (input: string): string[] => {
|
||||||
if (Object.keys(aliases).some(a => a.toLowerCase() == input.toLowerCase())) {
|
if (Object.keys(aliases).some(a => a.toLowerCase() === input.toLowerCase())) {
|
||||||
const codes = aliases[input];
|
const codes = aliases[input];
|
||||||
return Array.isArray(codes) ? codes : [codes];
|
return Array.isArray(codes) ? codes : [codes];
|
||||||
} else {
|
} else {
|
||||||
|
@@ -20,7 +20,7 @@ export default (opts) => ({
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
empty(): boolean {
|
empty(): boolean {
|
||||||
return this.items.length == 0 && !this.fetching && this.inited;
|
return this.items.length === 0 && !this.fetching && this.inited;
|
||||||
},
|
},
|
||||||
|
|
||||||
error(): boolean {
|
error(): boolean {
|
||||||
|
@@ -47,9 +47,9 @@ export async function search(v: any, q: string) {
|
|||||||
uri: q
|
uri: q
|
||||||
});
|
});
|
||||||
dialog.close();
|
dialog.close();
|
||||||
if (res.type == 'User') {
|
if (res.type === 'User') {
|
||||||
v.$router.push(`/@${res.object.username}@${res.object.host}`);
|
v.$router.push(`/@${res.object.username}@${res.object.host}`);
|
||||||
} else if (res.type == 'Note') {
|
} else if (res.type === 'Note') {
|
||||||
v.$router.push(`/notes/${res.object.id}`);
|
v.$router.push(`/notes/${res.object.id}`);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@@ -68,7 +68,7 @@ export default class Stream extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
private onOpen() {
|
private onOpen() {
|
||||||
const isReconnect = this.state == 'reconnecting';
|
const isReconnect = this.state === 'reconnecting';
|
||||||
|
|
||||||
this.state = 'connected';
|
this.state = 'connected';
|
||||||
this.emit('_connected_');
|
this.emit('_connected_');
|
||||||
@@ -87,7 +87,7 @@ export default class Stream extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
private onClose() {
|
private onClose() {
|
||||||
if (this.state == 'connected') {
|
if (this.state === 'connected') {
|
||||||
this.state = 'reconnecting';
|
this.state = 'reconnecting';
|
||||||
this.emit('_disconnected_');
|
this.emit('_disconnected_');
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ export default class Stream extends EventEmitter {
|
|||||||
private onMessage(message) {
|
private onMessage(message) {
|
||||||
const { type, body } = JSON.parse(message.data);
|
const { type, body } = JSON.parse(message.data);
|
||||||
|
|
||||||
if (type == 'channel') {
|
if (type === 'channel') {
|
||||||
const id = body.id;
|
const id = body.id;
|
||||||
|
|
||||||
let connections: Connection[];
|
let connections: Connection[];
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import createPersistedState from 'vuex-persistedstate';
|
import createPersistedState from 'vuex-persistedstate';
|
||||||
import * as nestedProperty from 'nested-property';
|
import * as nestedProperty from 'nested-property';
|
||||||
|
import { apiUrl } from './config';
|
||||||
import MiOS from './mios';
|
|
||||||
|
|
||||||
const defaultSettings = {
|
const defaultSettings = {
|
||||||
tutorial: 0,
|
tutorial: 0,
|
||||||
@@ -57,13 +56,15 @@ function copy<T>(data: T): T {
|
|||||||
return JSON.parse(JSON.stringify(data));
|
return JSON.parse(JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (os: MiOS) => new Vuex.Store({
|
export default () => new Vuex.Store({
|
||||||
plugins: [createPersistedState({
|
plugins: [createPersistedState({
|
||||||
paths: ['i', 'device', 'deviceUser', 'settings', 'instance']
|
paths: ['i', 'device', 'deviceUser', 'settings', 'instance']
|
||||||
})],
|
})],
|
||||||
|
|
||||||
state: {
|
state: {
|
||||||
i: null,
|
i: null,
|
||||||
|
pendingApiRequestsCount: 0,
|
||||||
|
spinner: null
|
||||||
},
|
},
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
@@ -121,6 +122,47 @@ export default (os: MiOS) => new Vuex.Store({
|
|||||||
ctx.commit('settings/init', me.clientData);
|
ctx.commit('settings/init', me.clientData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
api(ctx, { endpoint, data, token }) {
|
||||||
|
if (++ctx.state.pendingApiRequestsCount === 1) {
|
||||||
|
// TODO: spinnerの表示はstoreでやらない
|
||||||
|
ctx.state.spinner = document.createElement('div');
|
||||||
|
ctx.state.spinner.setAttribute('id', 'wait');
|
||||||
|
document.body.appendChild(ctx.state.spinner);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFinally = () => {
|
||||||
|
if (--ctx.state.pendingApiRequestsCount === 0) ctx.state.spinner.parentNode.removeChild(ctx.state.spinner);
|
||||||
|
};
|
||||||
|
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
// Append a credential
|
||||||
|
if (ctx.getters.isSignedIn) (data as any).i = ctx.state.i.token;
|
||||||
|
if (token) (data as any).i = token;
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: 'omit',
|
||||||
|
cache: 'no-cache'
|
||||||
|
}).then(async (res) => {
|
||||||
|
const body = res.status === 204 ? null : await res.json();
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
resolve(body);
|
||||||
|
} else if (res.status === 204) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(body.error);
|
||||||
|
}
|
||||||
|
}).catch(reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.then(onFinally, onFinally);
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
modules: {
|
modules: {
|
||||||
@@ -139,9 +181,12 @@ export default (os: MiOS) => new Vuex.Store({
|
|||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
async fetch(ctx) {
|
async fetch(ctx) {
|
||||||
const meta = await os.api('meta', {
|
const meta = await ctx.dispatch('api', {
|
||||||
detail: false
|
endpoint: 'meta',
|
||||||
});
|
data: {
|
||||||
|
detail: false
|
||||||
|
}
|
||||||
|
}, { root: true });
|
||||||
|
|
||||||
ctx.commit('set', meta);
|
ctx.commit('set', meta);
|
||||||
}
|
}
|
||||||
@@ -212,7 +257,7 @@ export default (os: MiOS) => new Vuex.Store({
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateWidget(state, x) {
|
updateWidget(state, x) {
|
||||||
const w = state.widgets.find(w => w.id == x.id);
|
const w = state.widgets.find(w => w.id === x.id);
|
||||||
if (w) {
|
if (w) {
|
||||||
w.data = x.data;
|
w.data = x.data;
|
||||||
}
|
}
|
||||||
@@ -246,10 +291,13 @@ export default (os: MiOS) => new Vuex.Store({
|
|||||||
ctx.commit('set', x);
|
ctx.commit('set', x);
|
||||||
|
|
||||||
if (ctx.rootGetters.isSignedIn) {
|
if (ctx.rootGetters.isSignedIn) {
|
||||||
os.api('i/update-client-setting', {
|
ctx.dispatch('api', {
|
||||||
name: x.key,
|
endpoint: 'i/update-client-setting',
|
||||||
value: x.value
|
data: {
|
||||||
});
|
name: x.key,
|
||||||
|
value: x.value
|
||||||
|
}
|
||||||
|
}, { root: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -412,6 +412,26 @@ main ._panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
._fullinfo {
|
||||||
|
padding: 32px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
vertical-align: bottom;
|
||||||
|
height: 128px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
._keyValue {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
._link {
|
._link {
|
||||||
color: var(--link);
|
color: var(--link);
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ export const builtinThemes = [
|
|||||||
require('./themes/danboard.json5'),
|
require('./themes/danboard.json5'),
|
||||||
require('./themes/olive.json5'),
|
require('./themes/olive.json5'),
|
||||||
require('./themes/tweetdeck.json5'),
|
require('./themes/tweetdeck.json5'),
|
||||||
];
|
] as Theme[];
|
||||||
|
|
||||||
let timeout = null;
|
let timeout = null;
|
||||||
|
|
||||||
@@ -66,16 +66,21 @@ export function applyTheme(theme: Theme, persist = true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function compile(theme: Theme): { [key: string]: string } {
|
function compile(theme: Theme): Record<string, string> {
|
||||||
function getColor(code: string): tinycolor.Instance {
|
function getColor(val: string): tinycolor.Instance {
|
||||||
// ref
|
// ref (prop)
|
||||||
if (code[0] == '@') {
|
if (val[0] === '@') {
|
||||||
return getColor(theme.props[code.substr(1)]);
|
return getColor(theme.props[val.substr(1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ref (const)
|
||||||
|
else if (val[0] === '$') {
|
||||||
|
return getColor(theme.props[val]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// func
|
// func
|
||||||
if (code[0] == ':') {
|
else if (val[0] === ':') {
|
||||||
const parts = code.split('<');
|
const parts = val.split('<');
|
||||||
const func = parts.shift().substr(1);
|
const func = parts.shift().substr(1);
|
||||||
const arg = parseFloat(parts.shift());
|
const arg = parseFloat(parts.shift());
|
||||||
const color = getColor(parts.join('<'));
|
const color = getColor(parts.join('<'));
|
||||||
@@ -87,12 +92,15 @@ function compile(theme: Theme): { [key: string]: string } {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tinycolor(code);
|
// other case
|
||||||
|
return tinycolor(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = {};
|
const props = {};
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(theme.props)) {
|
for (const [k, v] of Object.entries(theme.props)) {
|
||||||
|
if (k.startsWith('$')) continue; // ignore const
|
||||||
|
|
||||||
props[k] = genValue(getColor(v));
|
props[k] = genValue(getColor(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,3 +110,11 @@ function compile(theme: Theme): { [key: string]: string } {
|
|||||||
function genValue(c: tinycolor.Instance): string {
|
function genValue(c: tinycolor.Instance): string {
|
||||||
return c.toRgbString();
|
return c.toRgbString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateTheme(theme: Record<string, any>): boolean {
|
||||||
|
if (theme.id == null || typeof theme.id !== 'string') return false;
|
||||||
|
if (theme.name == null || typeof theme.name !== 'string') return false;
|
||||||
|
if (theme.base == null || !['light', 'dark'].includes(theme.base)) return false;
|
||||||
|
if (theme.props == null || typeof theme.props !== 'object') return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@@ -65,5 +65,6 @@
|
|||||||
aupeazdm: 'rgba(0, 0, 0, 0.3)',
|
aupeazdm: 'rgba(0, 0, 0, 0.3)',
|
||||||
jvhmlskx: 'rgba(255, 255, 255, 0.1)',
|
jvhmlskx: 'rgba(255, 255, 255, 0.1)',
|
||||||
yakfpmhl: 'rgba(255, 255, 255, 0.15)',
|
yakfpmhl: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
wboyroyc: ':alpha<0.5<@navBg',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -65,5 +65,6 @@
|
|||||||
aupeazdm: 'rgba(0, 0, 0, 0.1)',
|
aupeazdm: 'rgba(0, 0, 0, 0.1)',
|
||||||
jvhmlskx: 'rgba(0, 0, 0, 0.1)',
|
jvhmlskx: 'rgba(0, 0, 0, 0.1)',
|
||||||
yakfpmhl: 'rgba(0, 0, 0, 0.15)',
|
yakfpmhl: 'rgba(0, 0, 0, 0.15)',
|
||||||
|
wboyroyc: ':alpha<0.5<@navBg',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -51,14 +51,14 @@ export default Vue.extend({
|
|||||||
weekday: date.getDay()
|
weekday: date.getDay()
|
||||||
};
|
};
|
||||||
|
|
||||||
d.v = peak == 0 ? 0 : d.total / (peak / 2);
|
d.v = peak === 0 ? 0 : d.total / (peak / 2);
|
||||||
if (d.v > 1) d.v = 1;
|
if (d.v > 1) d.v = 1;
|
||||||
const ch = d.date.weekday == 0 || d.date.weekday == 6 ? 275 : 170;
|
const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170;
|
||||||
const cs = d.v * 100;
|
const cs = d.v * 100;
|
||||||
const cl = 15 + ((1 - d.v) * 80);
|
const cl = 15 + ((1 - d.v) * 80);
|
||||||
d.color = `hsl(${ch}, ${cs}%, ${cl}%)`;
|
d.color = `hsl(${ch}, ${cs}%, ${cl}%)`;
|
||||||
|
|
||||||
if (d.date.weekday == 0) x--;
|
if (d.date.weekday === 0) x--;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -60,7 +60,7 @@ export default define({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
func() {
|
func() {
|
||||||
if (this.props.design == 2) {
|
if (this.props.design === 2) {
|
||||||
this.props.design = 0;
|
this.props.design = 0;
|
||||||
} else {
|
} else {
|
||||||
this.props.design++;
|
this.props.design++;
|
||||||
@@ -68,7 +68,7 @@ export default define({
|
|||||||
this.save();
|
this.save();
|
||||||
},
|
},
|
||||||
toggleView() {
|
toggleView() {
|
||||||
if (this.props.view == 1) {
|
if (this.props.view === 1) {
|
||||||
this.props.view = 0;
|
this.props.view = 0;
|
||||||
} else {
|
} else {
|
||||||
this.props.view++;
|
this.props.view++;
|
||||||
|
@@ -65,7 +65,7 @@ export default define({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
func() {
|
func() {
|
||||||
if (this.props.design == 2) {
|
if (this.props.design === 2) {
|
||||||
this.props.design = 0;
|
this.props.design = 0;
|
||||||
} else {
|
} else {
|
||||||
this.props.design++;
|
this.props.design++;
|
||||||
@@ -102,7 +102,7 @@ export default define({
|
|||||||
this.monthP = monthNumer / monthDenom * 100;
|
this.monthP = monthNumer / monthDenom * 100;
|
||||||
this.yearP = yearNumer / yearDenom * 100;
|
this.yearP = yearNumer / yearDenom * 100;
|
||||||
|
|
||||||
this.isHoliday = now.getDay() == 0 || now.getDay() == 6;
|
this.isHoliday = now.getDay() === 0 || now.getDay() === 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<div class="otgbylcu">
|
<div class="otgbylcu">
|
||||||
<textarea v-model="text" :placeholder="$t('placeholder')" @input="onChange"></textarea>
|
<textarea v-model="text" :placeholder="$t('placeholder')" @input="onChange"></textarea>
|
||||||
<button @click="saveMemo" :disabled="!changed">{{ $t('save') }}</button>
|
<button @click="saveMemo" :disabled="!changed" class="_buttonPrimary">{{ $t('save') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</mk-container>
|
</mk-container>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,6 +84,7 @@ export default define({
|
|||||||
border: none;
|
border: none;
|
||||||
border-bottom: solid var(--lineWidth) var(--faceDivider);
|
border-bottom: solid var(--lineWidth) var(--faceDivider);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
> button {
|
> button {
|
||||||
@@ -94,22 +95,8 @@ export default define({
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
color: #fff;
|
|
||||||
background: var(--accent) !important;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: background 0.1s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--accentLighten10) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: var(--accentDarken) !important;
|
|
||||||
transition: background 0s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<mk-container :show-header="props.design === 0" :naked="props.design === 2" :class="$style.root" :data-melt="props.design == 2">
|
<mk-container :show-header="props.design === 0" :naked="props.design === 2" :class="$style.root" :data-melt="props.design === 2">
|
||||||
<template #header><fa :icon="faCamera"/>{{ $t('_widgets.photos') }}</template>
|
<template #header><fa :icon="faCamera"/>{{ $t('_widgets.photos') }}</template>
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
@@ -66,7 +66,7 @@ export default define({
|
|||||||
},
|
},
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
if (this.props.design == 2) {
|
if (this.props.design === 2) {
|
||||||
this.props.design = 0;
|
this.props.design = 0;
|
||||||
} else {
|
} else {
|
||||||
this.props.design++;
|
this.props.design++;
|
||||||
|
@@ -15,7 +15,7 @@ const dir = `${__dirname}/../../.config`;
|
|||||||
/**
|
/**
|
||||||
* Path of configuration file
|
* Path of configuration file
|
||||||
*/
|
*/
|
||||||
const path = process.env.NODE_ENV == 'test'
|
const path = process.env.NODE_ENV === 'test'
|
||||||
? `${dir}/test.yml`
|
? `${dir}/test.yml`
|
||||||
: `${dir}/default.yml`;
|
: `${dir}/default.yml`;
|
||||||
|
|
||||||
|
@@ -57,6 +57,7 @@ import { Antenna } from '../models/entities/antenna';
|
|||||||
import { AntennaNote } from '../models/entities/antenna-note';
|
import { AntennaNote } from '../models/entities/antenna-note';
|
||||||
import { PromoNote } from '../models/entities/promo-note';
|
import { PromoNote } from '../models/entities/promo-note';
|
||||||
import { PromoRead } from '../models/entities/promo-read';
|
import { PromoRead } from '../models/entities/promo-read';
|
||||||
|
import { program } from '../argv';
|
||||||
|
|
||||||
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
|
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
|
||||||
|
|
||||||
@@ -68,7 +69,9 @@ class MyCustomLogger implements Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public logQuery(query: string, parameters?: any[]) {
|
public logQuery(query: string, parameters?: any[]) {
|
||||||
sqlLogger.info(this.highlight(query));
|
if (program.verbose) {
|
||||||
|
sqlLogger.info(this.highlight(query));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public logQueryError(error: string, query: string, parameters?: any[]) {
|
public logQueryError(error: string, query: string, parameters?: any[]) {
|
||||||
@@ -149,7 +152,7 @@ export const entities = [
|
|||||||
...charts as any
|
...charts as any
|
||||||
];
|
];
|
||||||
|
|
||||||
export function initDb(justBorrow = false, sync = false, log = false, forceRecreate = false) {
|
export function initDb(justBorrow = false, sync = false, forceRecreate = false) {
|
||||||
if (!forceRecreate) {
|
if (!forceRecreate) {
|
||||||
try {
|
try {
|
||||||
const conn = getConnection();
|
const conn = getConnection();
|
||||||
@@ -157,6 +160,8 @@ export function initDb(justBorrow = false, sync = false, log = false, forceRecre
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const log = process.env.NODE_ENV != 'production';
|
||||||
|
|
||||||
return createConnection({
|
return createConnection({
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
host: config.db.host,
|
host: config.db.host,
|
||||||
|
4
src/docs/aiscript.ja-JP.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# AiScript
|
||||||
|
|
||||||
|
## 関数
|
||||||
|
デフォルトで値渡しです。
|
@@ -1,3 +1,64 @@
|
|||||||
# Misskey API
|
# Misskey API
|
||||||
|
|
||||||
[APIリファレンス](/api-doc)
|
MisskeyAPIを使ってMisskeyクライアント、Misskey連携Webサービス、Bot等(以下「アプリケーション」と呼びます)を開発できます。
|
||||||
|
ストリーミングAPIもあるので、リアルタイム性のあるアプリケーションを作ることも可能です。
|
||||||
|
|
||||||
|
APIを使い始めるには、まずアクセストークンを取得する必要があります。
|
||||||
|
このドキュメントでは、アクセストークンを取得する手順を説明した後、基本的なAPIの使い方を説明します。
|
||||||
|
|
||||||
|
## アクセストークンの取得
|
||||||
|
基本的に、APIはリクエストにはアクセストークンが必要となります。
|
||||||
|
あなたの作ろうとしているアプリケーションが、あなた専用のものなのか、それとも不特定多数の人に使ってもらうものなのかによって、アクセストークンの取得手順は異なります。
|
||||||
|
|
||||||
|
* あなた専用の場合: [「自分のアカウントのアクセストークンを取得する」](#自分のアカウントのアクセストークンを取得する)に進む
|
||||||
|
* 皆に使ってもらう場合: [「アプリケーションとしてアクセストークンを取得する」](#アプリケーションとしてアクセストークンを取得する)に進む
|
||||||
|
|
||||||
|
### 自分のアカウントのアクセストークンを取得する
|
||||||
|
「設定 > API」で、自分のアクセストークンを取得できます。
|
||||||
|
|
||||||
|
> この方法で入手したアクセストークンは強力なので、第三者に教えないでください(アプリなどにも入力しないでください)。
|
||||||
|
|
||||||
|
[「APIの使い方」へ進む](#APIの使い方)
|
||||||
|
|
||||||
|
### アプリケーションとしてアクセストークンを取得する
|
||||||
|
アプリケーションを使ってもらうには、ユーザーのアクセストークンを以下の手順で取得する必要があります。
|
||||||
|
|
||||||
|
#### Step 1
|
||||||
|
|
||||||
|
UUIDを生成する。以後これをセッションIDと呼びます。
|
||||||
|
|
||||||
|
> このセッションIDは毎回生成し、使いまわさないようにしてください。
|
||||||
|
|
||||||
|
#### Step 2
|
||||||
|
|
||||||
|
`{_URL_}/miauth/{session}`をユーザーのブラウザで表示させる。`{session}`の部分は、セッションIDに置き換えてください。
|
||||||
|
> 例: `{_URL_}/miauth/c1f6d42b-468b-4fd2-8274-e58abdedef6f`
|
||||||
|
|
||||||
|
表示する際、URLにクエリパラメータとしていくつかのオプションを設定できます:
|
||||||
|
* `name` ... アプリケーション名
|
||||||
|
* > 例: `MissDeck`
|
||||||
|
* `icon` ... アプリケーションのアイコン画像URL
|
||||||
|
* > 例: `https://missdeck.example.com/icon.png`
|
||||||
|
* `callback` ... 認証が終わった後にリダイレクトするURL
|
||||||
|
* > 例: `https://missdeck.example.com/callback`
|
||||||
|
* リダイレクト時には、`session`というクエリパラメータでセッションIDが付きます
|
||||||
|
* `permission` ... アプリケーションが要求する権限
|
||||||
|
* > 例: `write:notes,write:following,read:drive`
|
||||||
|
* 要求する権限を`,`で区切って列挙します
|
||||||
|
* どのような権限があるかは[APIリファレンス](/api-doc)で確認できます
|
||||||
|
|
||||||
|
#### Step 3
|
||||||
|
ユーザーが連携を許可した後、`{_URL_}/miauth/{session}/check`にPOSTリクエストすると、レスポンスとしてアクセストークンを含むJSONが返ります。
|
||||||
|
|
||||||
|
レスポンスに含まれるプロパティ:
|
||||||
|
* `token` ... ユーザーのアクセストークン
|
||||||
|
* `user` ... ユーザーの情報
|
||||||
|
|
||||||
|
[「APIの使い方」へ進む](#APIの使い方)
|
||||||
|
|
||||||
|
## APIの使い方
|
||||||
|
**APIはすべてPOSTで、リクエスト/レスポンスともにJSON形式です。RESTではありません。**
|
||||||
|
アクセストークンは、`i`というパラメータ名でリクエストに含めます。
|
||||||
|
|
||||||
|
* [APIリファレンス](/api-doc)
|
||||||
|
* [ストリーミングAPI](./stream)
|
||||||
|
74
src/docs/theme.ja-JP.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# テーマ
|
||||||
|
|
||||||
|
テーマを設定して、Misskeyクライアントの見た目を変更できます。
|
||||||
|
|
||||||
|
## テーマの設定
|
||||||
|
設定 > テーマ
|
||||||
|
|
||||||
|
## テーマを作成する
|
||||||
|
テーマコードはJSON5で記述されたテーマオブジェクトです。
|
||||||
|
テーマは以下のようなオブジェクトです。
|
||||||
|
``` js
|
||||||
|
{
|
||||||
|
id: '17587283-dd92-4a2c-a22c-be0637c9e22a',
|
||||||
|
|
||||||
|
name: 'Danboard',
|
||||||
|
author: 'syuilo',
|
||||||
|
|
||||||
|
base: 'light',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
accent: 'rgb(218, 141, 49)',
|
||||||
|
bg: 'rgb(218, 212, 190)',
|
||||||
|
fg: 'rgb(115, 108, 92)',
|
||||||
|
panel: 'rgb(236, 232, 220)',
|
||||||
|
renote: 'rgb(100, 152, 106)',
|
||||||
|
link: 'rgb(100, 152, 106)',
|
||||||
|
mention: '@accent',
|
||||||
|
hashtag: 'rgb(100, 152, 106)',
|
||||||
|
header: 'rgba(239, 227, 213, 0.75)',
|
||||||
|
navBg: 'rgb(216, 206, 182)',
|
||||||
|
inputBorder: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
* `id` ... テーマの一意なID。UUIDをおすすめします。
|
||||||
|
* `name` ... テーマ名
|
||||||
|
* `author` ... テーマの作者
|
||||||
|
* `desc` ... テーマの説明(オプション)
|
||||||
|
* `base` ... 明るいテーマか、暗いテーマか
|
||||||
|
* `light`にすると明るいテーマになり、`dark`にすると暗いテーマになります。
|
||||||
|
* テーマはここで設定されたベーステーマを継承します。
|
||||||
|
* `props` ... テーマのスタイル定義。これから説明します。
|
||||||
|
|
||||||
|
### テーマのスタイル定義
|
||||||
|
`props`下にはテーマのスタイルを定義します。
|
||||||
|
キーがCSSの変数名になり、バリューで中身を指定します。
|
||||||
|
なお、この`props`オブジェクトはベーステーマから継承されます。
|
||||||
|
ベーステーマは、このテーマの`base`が`light`なら[_light.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_light.json5)で、`dark`なら[_dark.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_dark.json5)です。
|
||||||
|
つまり、このテーマ内の`props`に`panel`というキーが無くても、そこにはベーステーマの`panel`があると見なされます。
|
||||||
|
|
||||||
|
#### バリューで使える構文
|
||||||
|
* 16進数で表された色
|
||||||
|
* 例: `#00ff00`
|
||||||
|
* `rgb(r, g, b)`形式で表された色
|
||||||
|
* 例: `rgb(0, 255, 0)`
|
||||||
|
* `rgb(r, g, b, a)`形式で表された透明度を含む色
|
||||||
|
* 例: `rgba(0, 255, 0, 0.5)`
|
||||||
|
* 他のキーの値の参照
|
||||||
|
* `@{キー名}`と書くと他のキーの値の参照になります。`{キー名}`は参照したいキーの名前に置き換えます。
|
||||||
|
* 例: `@panel`
|
||||||
|
* 定数(後述)の参照
|
||||||
|
* `${定数名}`と書くと定数の参照になります。`{定数名}`は参照したい定数の名前に置き換えます。
|
||||||
|
* 例: `$main`
|
||||||
|
* 関数(後述)
|
||||||
|
* `:{関数名}<{引数}<{色}`
|
||||||
|
|
||||||
|
#### 定数
|
||||||
|
「CSS変数として出力はしたくないが、他のCSS変数の値として使いまわしたい」値があるときは、定数を使うと便利です。
|
||||||
|
キー名を`$`で始めると、そのキーはCSS変数として出力されません。
|
||||||
|
|
||||||
|
#### 関数
|
||||||
|
wip
|
@@ -226,10 +226,10 @@ export default class Reversi {
|
|||||||
// 座標が指し示す位置がボード外に出たとき
|
// 座標が指し示す位置がボード外に出たとき
|
||||||
if (this.opts.loopedBoard && this.transformXyToPos(
|
if (this.opts.loopedBoard && this.transformXyToPos(
|
||||||
(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
|
(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
|
||||||
(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) == initPos)
|
(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos)
|
||||||
// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
|
// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
|
||||||
return found;
|
return found;
|
||||||
else if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight)
|
else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight)
|
||||||
return []; // 挟めないことが確定 (盤面外に到達)
|
return []; // 挟めないことが確定 (盤面外に到達)
|
||||||
|
|
||||||
const pos = this.transformXyToPos(x, y);
|
const pos = this.transformXyToPos(x, y);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
|
import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
|
||||||
import { urlRegex } from './prelude';
|
import { urlRegex } from './prelude';
|
||||||
|
|
||||||
export function fromHtml(html: string): string {
|
export function fromHtml(html: string, hashtagNames?: string[]): string {
|
||||||
const dom = parseFragment(html) as DefaultTreeDocumentFragment;
|
const dom = parseFragment(html) as DefaultTreeDocumentFragment;
|
||||||
|
|
||||||
let text = '';
|
let text = '';
|
||||||
@@ -13,7 +13,7 @@ export function fromHtml(html: string): string {
|
|||||||
return text.trim();
|
return text.trim();
|
||||||
|
|
||||||
function getText(node: any): string {
|
function getText(node: any): string {
|
||||||
if (node.nodeName == '#text') return node.value;
|
if (node.nodeName === '#text') return node.value;
|
||||||
|
|
||||||
if (node.childNodes) {
|
if (node.childNodes) {
|
||||||
return node.childNodes.map((n: any) => getText(n)).join('');
|
return node.childNodes.map((n: any) => getText(n)).join('');
|
||||||
@@ -34,29 +34,27 @@ export function fromHtml(html: string): string {
|
|||||||
|
|
||||||
case 'a':
|
case 'a':
|
||||||
const txt = getText(node);
|
const txt = getText(node);
|
||||||
const rel = node.attrs.find((x: any) => x.name == 'rel');
|
const rel = node.attrs.find((x: any) => x.name === 'rel');
|
||||||
const href = node.attrs.find((x: any) => x.name == 'href');
|
const href = node.attrs.find((x: any) => x.name === 'href');
|
||||||
const _class = node.attrs.find((x: any) => x.name == 'class');
|
|
||||||
const isHashtag = rel?.value?.match('tag') || _class?.value?.match('hashtag');
|
|
||||||
|
|
||||||
// ハッシュタグ / hrefがない / txtがURL
|
// ハッシュタグ
|
||||||
if (isHashtag || !href || href.value == txt) {
|
if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) {
|
||||||
text += isHashtag || txt.match(urlRegex) ? txt : `<${txt}>`;
|
text += txt;
|
||||||
// メンション
|
// メンション
|
||||||
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
|
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
|
||||||
const part = txt.split('@');
|
const part = txt.split('@');
|
||||||
|
|
||||||
if (part.length == 2) {
|
if (part.length === 2) {
|
||||||
//#region ホスト名部分が省略されているので復元する
|
//#region ホスト名部分が省略されているので復元する
|
||||||
const acct = `${txt}@${(new URL(href.value)).hostname}`;
|
const acct = `${txt}@${(new URL(href.value)).hostname}`;
|
||||||
text += acct;
|
text += acct;
|
||||||
//#endregion
|
//#endregion
|
||||||
} else if (part.length == 3) {
|
} else if (part.length === 3) {
|
||||||
text += txt;
|
text += txt;
|
||||||
}
|
}
|
||||||
// その他
|
// その他
|
||||||
} else {
|
} else {
|
||||||
text += `[${txt}](${href.value})`;
|
text += (!href || (txt === href.value && txt.match(urlRegex))) ? txt : `[${txt}](${href.value})`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ export const mfmLanguage = P.createLanguage({
|
|||||||
r.center,
|
r.center,
|
||||||
),
|
),
|
||||||
startOfLine: () => P((input, i) => {
|
startOfLine: () => P((input, i) => {
|
||||||
if (i == 0 || input[i] == '\n' || input[i - 1] == '\n') {
|
if (i === 0 || input[i] === '\n' || input[i - 1] === '\n') {
|
||||||
return P.makeSuccess(i, null);
|
return P.makeSuccess(i, null);
|
||||||
} else {
|
} else {
|
||||||
return P.makeFailure(i, 'not newline');
|
return P.makeFailure(i, 'not newline');
|
||||||
@@ -50,7 +50,7 @@ export const mfmLanguage = P.createLanguage({
|
|||||||
if (!text.match(/^>[\s\S]+?/)) return P.makeFailure(i, 'not a quote');
|
if (!text.match(/^>[\s\S]+?/)) return P.makeFailure(i, 'not a quote');
|
||||||
const quote = takeWhile(line => line.startsWith('>'), text.split('\n'));
|
const quote = takeWhile(line => line.startsWith('>'), text.split('\n'));
|
||||||
const qInner = quote.join('\n').replace(/^>/gm, '').replace(/^ /gm, '');
|
const qInner = quote.join('\n').replace(/^>/gm, '').replace(/^ /gm, '');
|
||||||
if (qInner == '') return P.makeFailure(i, 'not a quote');
|
if (qInner === '') return P.makeFailure(i, 'not a quote');
|
||||||
const contents = r.root.tryParse(qInner);
|
const contents = r.root.tryParse(qInner);
|
||||||
return P.makeSuccess(i + quote.join('\n').length + 1, createTree('quote', contents, {}));
|
return P.makeSuccess(i + quote.join('\n').length + 1, createTree('quote', contents, {}));
|
||||||
})),
|
})),
|
||||||
|
@@ -4,7 +4,7 @@ import { MfmForest, MfmTree } from './prelude';
|
|||||||
import { createTree, createLeaf } from '../prelude/tree';
|
import { createTree, createLeaf } from '../prelude/tree';
|
||||||
|
|
||||||
function isEmptyTextTree(t: MfmTree): boolean {
|
function isEmptyTextTree(t: MfmTree): boolean {
|
||||||
return t.node.type == 'text' && t.node.props.text === '';
|
return t.node.type === 'text' && t.node.props.text === '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function concatTextTrees(ts: MfmForest): MfmTree {
|
function concatTextTrees(ts: MfmForest): MfmTree {
|
||||||
|
@@ -3,7 +3,7 @@ import { MfmForest } from './prelude';
|
|||||||
import { normalize } from './normalize';
|
import { normalize } from './normalize';
|
||||||
|
|
||||||
export function parse(source: string | null): MfmForest | null {
|
export function parse(source: string | null): MfmForest | null {
|
||||||
if (source == null || source == '') {
|
if (source == null || source === '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ export function parse(source: string | null): MfmForest | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parsePlain(source: string | null): MfmForest | null {
|
export function parsePlain(source: string | null): MfmForest | null {
|
||||||
if (source == null || source == '') {
|
if (source == null || source === '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
src/misc/secure-rndstr.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
|
const L_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||||
|
const LU_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
|
||||||
|
export function secureRndstr(length = 32, useLU = true): string {
|
||||||
|
const chars = useLU ? LU_CHARS : L_CHARS;
|
||||||
|
const chars_len = chars.length;
|
||||||
|
|
||||||
|
let str = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
let rand = Math.floor((crypto.randomBytes(1).readUInt8(0) / 0xFF) * chars_len);
|
||||||
|
if (rand === chars_len) {
|
||||||
|
rand = chars_len - 1;
|
||||||
|
}
|
||||||
|
str += chars.charAt(rand);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
@@ -13,12 +13,26 @@ export class AccessToken {
|
|||||||
})
|
})
|
||||||
public createdAt: Date;
|
public createdAt: Date;
|
||||||
|
|
||||||
|
@Column('timestamp with time zone', {
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
public lastUsedAt: Date | null;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128
|
length: 128
|
||||||
})
|
})
|
||||||
public token: string;
|
public token: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 128,
|
||||||
|
nullable: true,
|
||||||
|
default: null
|
||||||
|
})
|
||||||
|
public session: string | null;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128
|
length: 128
|
||||||
@@ -35,12 +49,48 @@ export class AccessToken {
|
|||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
public user: User | null;
|
public user: User | null;
|
||||||
|
|
||||||
@Column(id())
|
@Column({
|
||||||
public appId: App['id'];
|
...id(),
|
||||||
|
nullable: true,
|
||||||
|
default: null
|
||||||
|
})
|
||||||
|
public appId: App['id'] | null;
|
||||||
|
|
||||||
@ManyToOne(type => App, {
|
@ManyToOne(type => App, {
|
||||||
onDelete: 'CASCADE'
|
onDelete: 'CASCADE'
|
||||||
})
|
})
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
public app: App | null;
|
public app: App | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 128,
|
||||||
|
nullable: true,
|
||||||
|
default: null
|
||||||
|
})
|
||||||
|
public name: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 512,
|
||||||
|
nullable: true,
|
||||||
|
default: null
|
||||||
|
})
|
||||||
|
public description: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 512,
|
||||||
|
nullable: true,
|
||||||
|
default: null
|
||||||
|
})
|
||||||
|
public iconUrl: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 64, array: true,
|
||||||
|
default: '{}'
|
||||||
|
})
|
||||||
|
public permission: string[];
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false
|
||||||
|
})
|
||||||
|
public fetched: boolean;
|
||||||
}
|
}
|
||||||
|
@@ -112,6 +112,12 @@ export class Note {
|
|||||||
})
|
})
|
||||||
public uri: string | null;
|
public uri: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 512, nullable: true,
|
||||||
|
comment: 'The human readable url of a note. it will be null when the note is local.'
|
||||||
|
})
|
||||||
|
public url: string | null;
|
||||||
|
|
||||||
@Column('integer', {
|
@Column('integer', {
|
||||||
default: 0, select: false
|
default: 0, select: false
|
||||||
})
|
})
|
||||||
|
@@ -4,6 +4,7 @@ import { id } from '../id';
|
|||||||
import { Note } from './note';
|
import { Note } from './note';
|
||||||
import { FollowRequest } from './follow-request';
|
import { FollowRequest } from './follow-request';
|
||||||
import { UserGroupInvitation } from './user-group-invitation';
|
import { UserGroupInvitation } from './user-group-invitation';
|
||||||
|
import { AccessToken } from './access-token';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Notification {
|
export class Notification {
|
||||||
@@ -35,11 +36,13 @@ export class Notification {
|
|||||||
/**
|
/**
|
||||||
* 通知の送信者(initiator)
|
* 通知の送信者(initiator)
|
||||||
*/
|
*/
|
||||||
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
|
nullable: true,
|
||||||
comment: 'The ID of sender user of the Notification.'
|
comment: 'The ID of sender user of the Notification.'
|
||||||
})
|
})
|
||||||
public notifierId: User['id'];
|
public notifierId: User['id'] | null;
|
||||||
|
|
||||||
@ManyToOne(type => User, {
|
@ManyToOne(type => User, {
|
||||||
onDelete: 'CASCADE'
|
onDelete: 'CASCADE'
|
||||||
@@ -59,16 +62,19 @@ export class Notification {
|
|||||||
* receiveFollowRequest - フォローリクエストされた
|
* receiveFollowRequest - フォローリクエストされた
|
||||||
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
||||||
* groupInvited - グループに招待された
|
* groupInvited - グループに招待された
|
||||||
|
* app - アプリ通知
|
||||||
*/
|
*/
|
||||||
|
@Index()
|
||||||
@Column('enum', {
|
@Column('enum', {
|
||||||
enum: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited'],
|
enum: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'],
|
||||||
comment: 'The type of the Notification.'
|
comment: 'The type of the Notification.'
|
||||||
})
|
})
|
||||||
public type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollVote' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited';
|
public type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollVote' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'app';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知が読まれたかどうか
|
* 通知が読まれたかどうか
|
||||||
*/
|
*/
|
||||||
|
@Index()
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
comment: 'Whether the Notification is read.'
|
comment: 'Whether the Notification is read.'
|
||||||
@@ -114,10 +120,52 @@ export class Notification {
|
|||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, nullable: true
|
length: 128, nullable: true
|
||||||
})
|
})
|
||||||
public reaction: string;
|
public reaction: string | null;
|
||||||
|
|
||||||
@Column('integer', {
|
@Column('integer', {
|
||||||
nullable: true
|
nullable: true
|
||||||
})
|
})
|
||||||
public choice: number;
|
public choice: number | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アプリ通知のbody
|
||||||
|
*/
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 2048, nullable: true
|
||||||
|
})
|
||||||
|
public customBody: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アプリ通知のheader
|
||||||
|
* (省略時はアプリ名で表示されることを期待)
|
||||||
|
*/
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 256, nullable: true
|
||||||
|
})
|
||||||
|
public customHeader: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アプリ通知のicon(URL)
|
||||||
|
* (省略時はアプリアイコンで表示されることを期待)
|
||||||
|
*/
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024, nullable: true
|
||||||
|
})
|
||||||
|
public customIcon: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アプリ通知のアプリ(のトークン)
|
||||||
|
*/
|
||||||
|
@Index()
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
nullable: true
|
||||||
|
})
|
||||||
|
public appAccessTokenId: AccessToken['id'] | null;
|
||||||
|
|
||||||
|
@ManyToOne(type => AccessToken, {
|
||||||
|
onDelete: 'CASCADE'
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public appAccessToken: AccessToken | null;
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ import { getRepository, getCustomRepository } from 'typeorm';
|
|||||||
import { Announcement } from './entities/announcement';
|
import { Announcement } from './entities/announcement';
|
||||||
import { AnnouncementRead } from './entities/announcement-read';
|
import { AnnouncementRead } from './entities/announcement-read';
|
||||||
import { Instance } from './entities/instance';
|
import { Instance } from './entities/instance';
|
||||||
import { Emoji } from './entities/emoji';
|
|
||||||
import { Poll } from './entities/poll';
|
import { Poll } from './entities/poll';
|
||||||
import { PollVote } from './entities/poll-vote';
|
import { PollVote } from './entities/poll-vote';
|
||||||
import { Meta } from './entities/meta';
|
import { Meta } from './entities/meta';
|
||||||
@@ -52,6 +51,7 @@ import { AntennaRepository } from './repositories/antenna';
|
|||||||
import { AntennaNote } from './entities/antenna-note';
|
import { AntennaNote } from './entities/antenna-note';
|
||||||
import { PromoNote } from './entities/promo-note';
|
import { PromoNote } from './entities/promo-note';
|
||||||
import { PromoRead } from './entities/promo-read';
|
import { PromoRead } from './entities/promo-read';
|
||||||
|
import { EmojiRepository } from './repositories/emoji';
|
||||||
|
|
||||||
export const Announcements = getRepository(Announcement);
|
export const Announcements = getRepository(Announcement);
|
||||||
export const AnnouncementReads = getRepository(AnnouncementRead);
|
export const AnnouncementReads = getRepository(AnnouncementRead);
|
||||||
@@ -79,7 +79,7 @@ export const UsedUsernames = getRepository(UsedUsername);
|
|||||||
export const Followings = getCustomRepository(FollowingRepository);
|
export const Followings = getCustomRepository(FollowingRepository);
|
||||||
export const FollowRequests = getCustomRepository(FollowRequestRepository);
|
export const FollowRequests = getCustomRepository(FollowRequestRepository);
|
||||||
export const Instances = getRepository(Instance);
|
export const Instances = getRepository(Instance);
|
||||||
export const Emojis = getRepository(Emoji);
|
export const Emojis = getCustomRepository(EmojiRepository);
|
||||||
export const DriveFiles = getCustomRepository(DriveFileRepository);
|
export const DriveFiles = getCustomRepository(DriveFileRepository);
|
||||||
export const DriveFolders = getCustomRepository(DriveFolderRepository);
|
export const DriveFolders = getCustomRepository(DriveFolderRepository);
|
||||||
export const Notifications = getCustomRepository(NotificationRepository);
|
export const Notifications = getCustomRepository(NotificationRepository);
|
||||||
|
27
src/models/repositories/emoji.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { EntityRepository, Repository } from 'typeorm';
|
||||||
|
import { Emoji } from '../entities/emoji';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
|
@EntityRepository(Emoji)
|
||||||
|
export class EmojiRepository extends Repository<Emoji> {
|
||||||
|
public async pack(
|
||||||
|
src: Emoji['id'] | Emoji,
|
||||||
|
) {
|
||||||
|
const emoji = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: emoji.id,
|
||||||
|
aliases: emoji.aliases,
|
||||||
|
name: emoji.name,
|
||||||
|
category: emoji.category,
|
||||||
|
host: emoji.host,
|
||||||
|
url: emoji.url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public packMany(
|
||||||
|
emojis: any[],
|
||||||
|
) {
|
||||||
|
return Promise.all(emojis.map(x => this.pack(x)));
|
||||||
|
}
|
||||||
|
}
|
@@ -39,7 +39,7 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
|
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
|
||||||
if (packedNote.visibility == 'followers') {
|
if (packedNote.visibility === 'followers') {
|
||||||
if (meId == null) {
|
if (meId == null) {
|
||||||
hide = true;
|
hide = true;
|
||||||
} else if (meId === packedNote.userId) {
|
} else if (meId === packedNote.userId) {
|
||||||
@@ -171,8 +171,8 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
|
|
||||||
let text = note.text;
|
let text = note.text;
|
||||||
|
|
||||||
if (note.name && note.uri) {
|
if (note.name && (note.url || note.uri)) {
|
||||||
text = `【${note.name}】\n${(note.text || '').trim()}\n${note.uri}`;
|
text = `【${note.name}】\n${(note.text || '').trim()}\n\n${note.url || note.uri}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packed = await awaitAll({
|
const packed = await awaitAll({
|
||||||
@@ -197,6 +197,7 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
renoteId: note.renoteId,
|
renoteId: note.renoteId,
|
||||||
mentions: note.mentions.length > 0 ? note.mentions : undefined,
|
mentions: note.mentions.length > 0 ? note.mentions : undefined,
|
||||||
uri: note.uri || undefined,
|
uri: note.uri || undefined,
|
||||||
|
url: note.url || undefined,
|
||||||
_featuredId_: (note as any)._featuredId_ || undefined,
|
_featuredId_: (note as any)._featuredId_ || undefined,
|
||||||
_prId_: (note as any)._prId_ || undefined,
|
_prId_: (note as any)._prId_ || undefined,
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { EntityRepository, Repository } from 'typeorm';
|
import { EntityRepository, Repository } from 'typeorm';
|
||||||
import { Users, Notes, UserGroupInvitations } from '..';
|
import { Users, Notes, UserGroupInvitations, AccessTokens } from '..';
|
||||||
import { Notification } from '../entities/notification';
|
import { Notification } from '../entities/notification';
|
||||||
import { ensure } from '../../prelude/ensure';
|
import { ensure } from '../../prelude/ensure';
|
||||||
import { awaitAll } from '../../prelude/await-all';
|
import { awaitAll } from '../../prelude/await-all';
|
||||||
@@ -13,13 +13,14 @@ export class NotificationRepository extends Repository<Notification> {
|
|||||||
src: Notification['id'] | Notification,
|
src: Notification['id'] | Notification,
|
||||||
): Promise<PackedNotification> {
|
): Promise<PackedNotification> {
|
||||||
const notification = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
const notification = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
const token = notification.appAccessTokenId ? await AccessTokens.findOne(notification.appAccessTokenId).then(ensure) : null;
|
||||||
|
|
||||||
return await awaitAll({
|
return await awaitAll({
|
||||||
id: notification.id,
|
id: notification.id,
|
||||||
createdAt: notification.createdAt.toISOString(),
|
createdAt: notification.createdAt.toISOString(),
|
||||||
type: notification.type,
|
type: notification.type,
|
||||||
userId: notification.notifierId,
|
userId: notification.notifierId,
|
||||||
user: Users.pack(notification.notifier || notification.notifierId),
|
user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null,
|
||||||
...(notification.type === 'mention' ? {
|
...(notification.type === 'mention' ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
|
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
|
||||||
} : {}),
|
} : {}),
|
||||||
@@ -43,6 +44,11 @@ export class NotificationRepository extends Repository<Notification> {
|
|||||||
...(notification.type === 'groupInvited' ? {
|
...(notification.type === 'groupInvited' ? {
|
||||||
invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!),
|
invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!),
|
||||||
} : {}),
|
} : {}),
|
||||||
|
...(notification.type === 'app' ? {
|
||||||
|
body: notification.customBody,
|
||||||
|
header: notification.customHeader || token?.name,
|
||||||
|
icon: notification.customIcon || token?.iconUrl,
|
||||||
|
} : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@ import { IFollow } from '../../type';
|
|||||||
import { Users } from '../../../../models';
|
import { Users } from '../../../../models';
|
||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||||
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
|
const id = typeof activity.actor === 'string' ? activity.actor : activity.actor.id;
|
||||||
if (id == null) throw new Error('missing id');
|
if (id == null) throw new Error('missing id');
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
|
@@ -5,7 +5,7 @@ import { IFollow } from '../type';
|
|||||||
import { Users } from '../../../models';
|
import { Users } from '../../../models';
|
||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object === 'string' ? activity.object : activity.object.id;
|
||||||
if (id == null) throw new Error('missing id');
|
if (id == null) throw new Error('missing id');
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
|
@@ -5,7 +5,7 @@ import { IFollow } from '../../type';
|
|||||||
import { Users } from '../../../../models';
|
import { Users } from '../../../../models';
|
||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||||
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
|
const id = typeof activity.actor === 'string' ? activity.actor : activity.actor.id;
|
||||||
if (id == null) throw new Error('missing id');
|
if (id == null) throw new Error('missing id');
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
|
@@ -8,7 +8,7 @@ import { Users } from '../../../../models';
|
|||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object === 'string' ? activity.object : activity.object.id;
|
||||||
if (id == null) throw new Error('missing id');
|
if (id == null) throw new Error('missing id');
|
||||||
|
|
||||||
const uri = activity.id || activity;
|
const uri = activity.id || activity;
|
||||||
|
@@ -6,7 +6,7 @@ import { IRemoteUser } from '../../../../models/entities/user';
|
|||||||
import { Users, FollowRequests, Followings } from '../../../../models';
|
import { Users, FollowRequests, Followings } from '../../../../models';
|
||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object === 'string' ? activity.object : activity.object.id;
|
||||||
if (id == null) throw new Error('missing id');
|
if (id == null) throw new Error('missing id');
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
|
9
src/remote/activitypub/misc/html-to-mfm.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { IObject } from '../type';
|
||||||
|
import { extractApHashtagObjects } from '../models/tag';
|
||||||
|
import { fromHtml } from '../../../mfm/fromHtml';
|
||||||
|
|
||||||
|
export function htmlToMfm(html: string, tag?: IObject | IObject[]) {
|
||||||
|
const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null);
|
||||||
|
|
||||||
|
return fromHtml(html, hashtagNames);
|
||||||
|
}
|
24
src/remote/activitypub/models/mention.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { toArray, unique } from '../../../prelude/array';
|
||||||
|
import { IObject, isMention, IApMention } from '../type';
|
||||||
|
import { resolvePerson } from './person';
|
||||||
|
import * as promiseLimit from 'promise-limit';
|
||||||
|
import Resolver from '../resolver';
|
||||||
|
import { User } from '../../../models/entities/user';
|
||||||
|
|
||||||
|
export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
|
||||||
|
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
|
||||||
|
|
||||||
|
const resolver = new Resolver();
|
||||||
|
|
||||||
|
const limit = promiseLimit<User | null>(2);
|
||||||
|
const mentionedUsers = (await Promise.all(
|
||||||
|
hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null)))
|
||||||
|
)).filter((x): x is User => x != null);
|
||||||
|
|
||||||
|
return mentionedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] {
|
||||||
|
if (tags == null) return [];
|
||||||
|
return toArray(tags).filter(isMention);
|
||||||
|
}
|
@@ -6,9 +6,9 @@ import post from '../../../services/note/create';
|
|||||||
import { resolvePerson, updatePerson } from './person';
|
import { resolvePerson, updatePerson } from './person';
|
||||||
import { resolveImage } from './image';
|
import { resolveImage } from './image';
|
||||||
import { IRemoteUser } from '../../../models/entities/user';
|
import { IRemoteUser } from '../../../models/entities/user';
|
||||||
import { fromHtml } from '../../../mfm/fromHtml';
|
import { htmlToMfm } from '../misc/html-to-mfm';
|
||||||
import { ITag, extractHashtags } from './tag';
|
import { extractApHashtags } from './tag';
|
||||||
import { unique } from '../../../prelude/array';
|
import { unique, toArray, toSingle } from '../../../prelude/array';
|
||||||
import { extractPollFromQuestion } from './question';
|
import { extractPollFromQuestion } from './question';
|
||||||
import vote from '../../../services/note/polls/vote';
|
import vote from '../../../services/note/polls/vote';
|
||||||
import { apLogger } from '../logger';
|
import { apLogger } from '../logger';
|
||||||
@@ -17,7 +17,7 @@ import { deliverQuestionUpdate } from '../../../services/note/polls/update';
|
|||||||
import { extractDbHost, toPuny } from '../../../misc/convert-host';
|
import { extractDbHost, toPuny } from '../../../misc/convert-host';
|
||||||
import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
|
import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
|
||||||
import { Note } from '../../../models/entities/note';
|
import { Note } from '../../../models/entities/note';
|
||||||
import { IObject, getOneApId, getApId, validPost, IPost } from '../type';
|
import { IObject, getOneApId, getApId, validPost, IPost, isEmoji } from '../type';
|
||||||
import { Emoji } from '../../../models/entities/emoji';
|
import { Emoji } from '../../../models/entities/emoji';
|
||||||
import { genId } from '../../../misc/gen-id';
|
import { genId } from '../../../misc/gen-id';
|
||||||
import { fetchMeta } from '../../../misc/fetch-meta';
|
import { fetchMeta } from '../../../misc/fetch-meta';
|
||||||
@@ -25,6 +25,7 @@ import { ensure } from '../../../prelude/ensure';
|
|||||||
import { getApLock } from '../../../misc/app-lock';
|
import { getApLock } from '../../../misc/app-lock';
|
||||||
import { createMessage } from '../../../services/messages/create';
|
import { createMessage } from '../../../services/messages/create';
|
||||||
import { parseAudience } from '../audience';
|
import { parseAudience } from '../audience';
|
||||||
|
import { extractApMentions } from './mention';
|
||||||
|
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
@@ -113,7 +114,6 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
|||||||
const noteAudience = await parseAudience(actor, note.to, note.cc);
|
const noteAudience = await parseAudience(actor, note.to, note.cc);
|
||||||
let visibility = noteAudience.visibility;
|
let visibility = noteAudience.visibility;
|
||||||
const visibleUsers = noteAudience.visibleUsers;
|
const visibleUsers = noteAudience.visibleUsers;
|
||||||
const apMentions = noteAudience.mentionedUsers;
|
|
||||||
|
|
||||||
// Audience (to, cc) が指定されてなかった場合
|
// Audience (to, cc) が指定されてなかった場合
|
||||||
if (visibility === 'specified' && visibleUsers.length === 0) {
|
if (visibility === 'specified' && visibleUsers.length === 0) {
|
||||||
@@ -125,7 +125,8 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
|||||||
|
|
||||||
let isTalk = note._misskey_talk && visibility === 'specified';
|
let isTalk = note._misskey_talk && visibility === 'specified';
|
||||||
|
|
||||||
const apHashtags = await extractHashtags(note.tag);
|
const apMentions = await extractApMentions(note.tag);
|
||||||
|
const apHashtags = await extractApHashtags(note.tag);
|
||||||
|
|
||||||
// 添付ファイル
|
// 添付ファイル
|
||||||
// TODO: attachmentは必ずしもImageではない
|
// TODO: attachmentは必ずしもImageではない
|
||||||
@@ -210,7 +211,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
|||||||
const cw = note.summary === '' ? null : note.summary;
|
const cw = note.summary === '' ? null : note.summary;
|
||||||
|
|
||||||
// テキストのパース
|
// テキストのパース
|
||||||
const text = note._misskey_content || (note.content ? fromHtml(note.content) : null);
|
const text = note._misskey_content || (note.content ? htmlToMfm(note.content, note.tag) : null);
|
||||||
|
|
||||||
// vote
|
// vote
|
||||||
if (reply && reply.hasPoll) {
|
if (reply && reply.hasPoll) {
|
||||||
@@ -280,7 +281,8 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
|||||||
apHashtags,
|
apHashtags,
|
||||||
apEmojis,
|
apEmojis,
|
||||||
poll,
|
poll,
|
||||||
uri: note.id
|
uri: note.id,
|
||||||
|
url: note.url,
|
||||||
}, silent);
|
}, silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +293,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
|||||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||||
*/
|
*/
|
||||||
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> {
|
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> {
|
||||||
const uri = typeof value == 'string' ? value : value.id;
|
const uri = typeof value === 'string' ? value : value.id;
|
||||||
if (uri == null) throw new Error('missing uri');
|
if (uri == null) throw new Error('missing uri');
|
||||||
|
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
@@ -318,15 +320,16 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> {
|
export async function extractEmojis(tags: IObject | IObject[], host: string): Promise<Emoji[]> {
|
||||||
host = toPuny(host);
|
host = toPuny(host);
|
||||||
|
|
||||||
if (!tags) return [];
|
if (!tags) return [];
|
||||||
|
|
||||||
const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url && tag.name);
|
const eomjiTags = toArray(tags).filter(isEmoji);
|
||||||
|
|
||||||
return await Promise.all(eomjiTags.map(async tag => {
|
return await Promise.all(eomjiTags.map(async tag => {
|
||||||
const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
|
const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
|
||||||
|
tag.icon = toSingle(tag.icon);
|
||||||
|
|
||||||
const exists = await Emojis.findOne({
|
const exists = await Emojis.findOne({
|
||||||
host,
|
host,
|
||||||
|
@@ -3,12 +3,12 @@ import * as promiseLimit from 'promise-limit';
|
|||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import Resolver from '../resolver';
|
import Resolver from '../resolver';
|
||||||
import { resolveImage } from './image';
|
import { resolveImage } from './image';
|
||||||
import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId } from '../type';
|
import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId, IObject, isPropertyValue, IApPropertyValue } from '../type';
|
||||||
import { fromHtml } from '../../../mfm/fromHtml';
|
import { fromHtml } from '../../../mfm/fromHtml';
|
||||||
|
import { htmlToMfm } from '../misc/html-to-mfm';
|
||||||
import { resolveNote, extractEmojis } from './note';
|
import { resolveNote, extractEmojis } from './note';
|
||||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
||||||
import { ITag, extractHashtags } from './tag';
|
import { extractApHashtags } from './tag';
|
||||||
import { IIdentifier } from './identifier';
|
|
||||||
import { apLogger } from '../logger';
|
import { apLogger } from '../logger';
|
||||||
import { Note } from '../../../models/entities/note';
|
import { Note } from '../../../models/entities/note';
|
||||||
import { updateUsertags } from '../../../services/update-hashtag';
|
import { updateUsertags } from '../../../services/update-hashtag';
|
||||||
@@ -134,9 +134,9 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
|
|
||||||
const { fields } = analyzeAttachments(person.attachment || []);
|
const { fields } = analyzeAttachments(person.attachment || []);
|
||||||
|
|
||||||
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
|
const tags = extractApHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
|
||||||
|
|
||||||
const isBot = object.type == 'Service';
|
const isBot = object.type === 'Service';
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
let user: IRemoteUser;
|
let user: IRemoteUser;
|
||||||
@@ -165,7 +165,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
|
|
||||||
await transactionalEntityManager.save(new UserProfile({
|
await transactionalEntityManager.save(new UserProfile({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
description: person.summary ? fromHtml(person.summary) : null,
|
description: person.summary ? htmlToMfm(person.summary, person.tag) : null,
|
||||||
url: person.url,
|
url: person.url,
|
||||||
fields,
|
fields,
|
||||||
userHost: host
|
userHost: host
|
||||||
@@ -180,11 +180,20 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// duplicate key error
|
// duplicate key error
|
||||||
if (isDuplicateKeyValueError(e)) {
|
if (isDuplicateKeyValueError(e)) {
|
||||||
throw new Error('already registered');
|
// /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応
|
||||||
}
|
const u = await Users.findOne({
|
||||||
|
uri: person.id
|
||||||
|
});
|
||||||
|
|
||||||
logger.error(e);
|
if (u) {
|
||||||
throw e;
|
user = u as IRemoteUser;
|
||||||
|
} else {
|
||||||
|
throw new Error('already registered');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register host
|
// Register host
|
||||||
@@ -308,7 +317,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
|||||||
|
|
||||||
const { fields } = analyzeAttachments(person.attachment || []);
|
const { fields } = analyzeAttachments(person.attachment || []);
|
||||||
|
|
||||||
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
|
const tags = extractApHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
|
||||||
|
|
||||||
const updates = {
|
const updates = {
|
||||||
lastFetchedAt: new Date(),
|
lastFetchedAt: new Date(),
|
||||||
@@ -318,7 +327,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
|||||||
emojis: emojiNames,
|
emojis: emojiNames,
|
||||||
name: person.name,
|
name: person.name,
|
||||||
tags,
|
tags,
|
||||||
isBot: object.type == 'Service',
|
isBot: object.type === 'Service',
|
||||||
isCat: (person as any).isCat === true,
|
isCat: (person as any).isCat === true,
|
||||||
isLocked: !!person.manuallyApprovesFollowers,
|
isLocked: !!person.manuallyApprovesFollowers,
|
||||||
} as Partial<User>;
|
} as Partial<User>;
|
||||||
@@ -346,7 +355,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
|||||||
await UserProfiles.update({ userId: exist.id }, {
|
await UserProfiles.update({ userId: exist.id }, {
|
||||||
url: person.url,
|
url: person.url,
|
||||||
fields,
|
fields,
|
||||||
description: person.summary ? fromHtml(person.summary) : null,
|
description: person.summary ? htmlToMfm(person.summary, person.tag) : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ハッシュタグ更新
|
// ハッシュタグ更新
|
||||||
@@ -384,16 +393,6 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<U
|
|||||||
return await createPerson(uri, resolver);
|
return await createPerson(uri, resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPropertyValue = (x: {
|
|
||||||
type: string,
|
|
||||||
name?: string,
|
|
||||||
value?: string
|
|
||||||
}) =>
|
|
||||||
x &&
|
|
||||||
x.type === 'PropertyValue' &&
|
|
||||||
typeof x.name === 'string' &&
|
|
||||||
typeof x.value === 'string';
|
|
||||||
|
|
||||||
const services: {
|
const services: {
|
||||||
[x: string]: (id: string, username: string) => any
|
[x: string]: (id: string, username: string) => any
|
||||||
} = {
|
} = {
|
||||||
@@ -409,7 +408,7 @@ const $discord = (id: string, name: string) => {
|
|||||||
return { id, username, discriminator };
|
return { id, username, discriminator };
|
||||||
};
|
};
|
||||||
|
|
||||||
function addService(target: { [x: string]: any }, source: IIdentifier) {
|
function addService(target: { [x: string]: any }, source: IApPropertyValue) {
|
||||||
const service = services[source.name];
|
const service = services[source.name];
|
||||||
|
|
||||||
if (typeof source.value !== 'string')
|
if (typeof source.value !== 'string')
|
||||||
@@ -421,7 +420,7 @@ function addService(target: { [x: string]: any }, source: IIdentifier) {
|
|||||||
target[source.name.split(':')[2]] = service(id, username);
|
target[source.name.split(':')[2]] = service(id, username);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function analyzeAttachments(attachments: ITag[]) {
|
export function analyzeAttachments(attachments: IObject | IObject[] | undefined) {
|
||||||
const fields: {
|
const fields: {
|
||||||
name: string,
|
name: string,
|
||||||
value: string
|
value: string
|
||||||
@@ -430,12 +429,12 @@ export function analyzeAttachments(attachments: ITag[]) {
|
|||||||
|
|
||||||
if (Array.isArray(attachments)) {
|
if (Array.isArray(attachments)) {
|
||||||
for (const attachment of attachments.filter(isPropertyValue)) {
|
for (const attachment of attachments.filter(isPropertyValue)) {
|
||||||
if (isPropertyValue(attachment.identifier!)) {
|
if (isPropertyValue(attachment.identifier)) {
|
||||||
addService(services, attachment.identifier!);
|
addService(services, attachment.identifier);
|
||||||
} else {
|
} else {
|
||||||
fields.push({
|
fields.push({
|
||||||
name: attachment.name!,
|
name: attachment.name,
|
||||||
value: fromHtml(attachment.value!)
|
value: fromHtml(attachment.value)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -41,7 +41,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver
|
|||||||
* @returns true if updated
|
* @returns true if updated
|
||||||
*/
|
*/
|
||||||
export async function updateQuestion(value: any) {
|
export async function updateQuestion(value: any) {
|
||||||
const uri = typeof value == 'string' ? value : value.id;
|
const uri = typeof value === 'string' ? value : value.id;
|
||||||
|
|
||||||
// URIがこのサーバーを指しているならスキップ
|
// URIがこのサーバーを指しているならスキップ
|
||||||
if (uri.startsWith(config.url + '/')) throw new Error('uri points local');
|
if (uri.startsWith(config.url + '/')) throw new Error('uri points local');
|
||||||
|
@@ -1,26 +1,18 @@
|
|||||||
import { IIcon } from './icon';
|
import { toArray } from '../../../prelude/array';
|
||||||
import { IIdentifier } from './identifier';
|
import { IObject, isHashtag, IApHashtag } from '../type';
|
||||||
|
|
||||||
/***
|
export function extractApHashtags(tags: IObject | IObject[] | null | undefined) {
|
||||||
* tag (ActivityPub)
|
|
||||||
*/
|
|
||||||
export type ITag = {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
name?: string;
|
|
||||||
value?: string;
|
|
||||||
updated?: Date;
|
|
||||||
icon?: IIcon;
|
|
||||||
identifier?: IIdentifier;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function extractHashtags(tags: ITag[] | null | undefined): string[] {
|
|
||||||
if (tags == null) return [];
|
if (tags == null) return [];
|
||||||
|
|
||||||
const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string');
|
const hashtags = extractApHashtagObjects(tags);
|
||||||
|
|
||||||
return hashtags.map(tag => {
|
return hashtags.map(tag => {
|
||||||
const m = tag.name ? tag.name.match(/^#(.+)/) : null;
|
const m = tag.name.match(/^#(.+)/);
|
||||||
return m ? m[1] : null;
|
return m ? m[1] : null;
|
||||||
}).filter(x => x != null) as string[];
|
}).filter((x): x is string => x != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] {
|
||||||
|
if (tags == null) return [];
|
||||||
|
return toArray(tags).filter(isHashtag);
|
||||||
}
|
}
|
||||||
|
@@ -7,10 +7,10 @@ export default (object: any, note: Note) => {
|
|||||||
let to: string[] = [];
|
let to: string[] = [];
|
||||||
let cc: string[] = [];
|
let cc: string[] = [];
|
||||||
|
|
||||||
if (note.visibility == 'public') {
|
if (note.visibility === 'public') {
|
||||||
to = ['https://www.w3.org/ns/activitystreams#Public'];
|
to = ['https://www.w3.org/ns/activitystreams#Public'];
|
||||||
cc = [`${attributedTo}/followers`];
|
cc = [`${attributedTo}/followers`];
|
||||||
} else if (note.visibility == 'home') {
|
} else if (note.visibility === 'home') {
|
||||||
to = [`${attributedTo}/followers`];
|
to = [`${attributedTo}/followers`];
|
||||||
cc = ['https://www.w3.org/ns/activitystreams#Public'];
|
cc = ['https://www.w3.org/ns/activitystreams#Public'];
|
||||||
} else {
|
} else {
|
||||||
|
@@ -4,6 +4,6 @@ import { Users } from '../../../models';
|
|||||||
|
|
||||||
export default (mention: User) => ({
|
export default (mention: User) => ({
|
||||||
type: 'Mention',
|
type: 'Mention',
|
||||||
href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/@${(mention as ILocalUser).username}`,
|
href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/users/${(mention as ILocalUser).id}`,
|
||||||
name: Users.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`,
|
name: Users.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`,
|
||||||
});
|
});
|
||||||
|
@@ -63,13 +63,13 @@ export default async function renderNote(note: Note, dive = true, isTalk = false
|
|||||||
let to: string[] = [];
|
let to: string[] = [];
|
||||||
let cc: string[] = [];
|
let cc: string[] = [];
|
||||||
|
|
||||||
if (note.visibility == 'public') {
|
if (note.visibility === 'public') {
|
||||||
to = ['https://www.w3.org/ns/activitystreams#Public'];
|
to = ['https://www.w3.org/ns/activitystreams#Public'];
|
||||||
cc = [`${attributedTo}/followers`].concat(mentions);
|
cc = [`${attributedTo}/followers`].concat(mentions);
|
||||||
} else if (note.visibility == 'home') {
|
} else if (note.visibility === 'home') {
|
||||||
to = [`${attributedTo}/followers`];
|
to = [`${attributedTo}/followers`];
|
||||||
cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions);
|
cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions);
|
||||||
} else if (note.visibility == 'followers') {
|
} else if (note.visibility === 'followers') {
|
||||||
to = [`${attributedTo}/followers`];
|
to = [`${attributedTo}/followers`];
|
||||||
cc = mentions;
|
cc = mentions;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -20,7 +20,8 @@ export interface IObject {
|
|||||||
icon?: any;
|
icon?: any;
|
||||||
image?: any;
|
image?: any;
|
||||||
url?: string;
|
url?: string;
|
||||||
tag?: any[];
|
href?: string;
|
||||||
|
tag?: IObject | IObject[];
|
||||||
sensitive?: boolean;
|
sensitive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +131,45 @@ export const isOrderedCollection = (object: IObject): object is IOrderedCollecti
|
|||||||
export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection =>
|
export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection =>
|
||||||
isCollection(object) || isOrderedCollection(object);
|
isCollection(object) || isOrderedCollection(object);
|
||||||
|
|
||||||
|
export interface IApPropertyValue extends IObject {
|
||||||
|
type: 'PropertyValue';
|
||||||
|
identifier: IApPropertyValue;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPropertyValue = (object: IObject): object is IApPropertyValue =>
|
||||||
|
object &&
|
||||||
|
object.type === 'PropertyValue' &&
|
||||||
|
typeof object.name === 'string' &&
|
||||||
|
typeof (object as any).value === 'string';
|
||||||
|
|
||||||
|
export interface IApMention extends IObject {
|
||||||
|
type: 'Mention';
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isMention = (object: IObject): object is IApMention=>
|
||||||
|
object.type === 'Mention' &&
|
||||||
|
typeof object.href === 'string';
|
||||||
|
|
||||||
|
export interface IApHashtag extends IObject {
|
||||||
|
type: 'Hashtag';
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isHashtag = (object: IObject): object is IApHashtag =>
|
||||||
|
object.type === 'Hashtag' &&
|
||||||
|
typeof object.name === 'string';
|
||||||
|
|
||||||
|
export interface IApEmoji extends IObject {
|
||||||
|
type: 'Emoji';
|
||||||
|
updated: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isEmoji = (object: IObject): object is IApEmoji =>
|
||||||
|
object.type === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null;
|
||||||
|
|
||||||
export interface ICreate extends IActivity {
|
export interface ICreate extends IActivity {
|
||||||
type: 'Create';
|
type: 'Create';
|
||||||
}
|
}
|
||||||
|