Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b719d43049 | ||
![]() |
f555aa734b | ||
![]() |
37fb685016 | ||
![]() |
09fe1b355a | ||
![]() |
9d530a5c38 | ||
![]() |
827c378ac1 | ||
![]() |
9b91b92bca | ||
![]() |
e7fcca0e06 | ||
![]() |
a36e5b4543 | ||
![]() |
ba474648a2 | ||
![]() |
a7b710a2f8 | ||
![]() |
c033464b92 | ||
![]() |
3ef6305c76 | ||
![]() |
511ef20b8a | ||
![]() |
4db710a4b0 | ||
![]() |
efe92724a4 | ||
![]() |
aa19767ebc | ||
![]() |
143190ab38 | ||
![]() |
baf94f86c4 | ||
![]() |
d36b129369 | ||
![]() |
f36d88246a | ||
![]() |
03f87140b3 | ||
![]() |
1dc07f6b72 | ||
![]() |
0aa0a9d24b | ||
![]() |
a9a93db2b4 | ||
![]() |
f187df3933 | ||
![]() |
8abe8042d7 | ||
![]() |
58fd46ff6f | ||
![]() |
fef8b662c1 | ||
![]() |
8de2f4ce76 | ||
![]() |
e5e344e1cd | ||
![]() |
e70d7edf41 | ||
![]() |
71d4d51fb2 | ||
![]() |
aaf38f1cbe | ||
![]() |
0e0d6692c0 | ||
![]() |
29f927fe72 | ||
![]() |
ee39d9594e | ||
![]() |
cefd2a4c54 | ||
![]() |
a08c20d9af | ||
![]() |
dc11f1afbf | ||
![]() |
b0f2b209a2 | ||
![]() |
a25fdfd519 | ||
![]() |
c1aa58596d | ||
![]() |
b6a3eb2445 | ||
![]() |
310f4d2edb | ||
![]() |
701fee3139 | ||
![]() |
593c2b9517 | ||
![]() |
96b2267cb8 | ||
![]() |
84730a071a | ||
![]() |
d0b0cf8dfb | ||
![]() |
749200d22b | ||
![]() |
a47baad943 | ||
![]() |
50abb51ece | ||
![]() |
1f890c5bed | ||
![]() |
97f23af86d | ||
![]() |
d77aa1f26a | ||
![]() |
0b075ad4e9 | ||
![]() |
423f776ed0 | ||
![]() |
084fd8152b | ||
![]() |
89d35c2e63 | ||
![]() |
be33581642 | ||
![]() |
2d6d9f30e1 | ||
![]() |
85721065fd | ||
![]() |
9d65768d4d | ||
![]() |
13f69e4291 | ||
![]() |
6a0affcec1 | ||
![]() |
ab6a84cd45 | ||
![]() |
ba93bf7478 | ||
![]() |
1c4e1af7c3 | ||
![]() |
a85f4c4fc4 | ||
![]() |
9d6c8806af | ||
![]() |
ff52ea2a7c | ||
![]() |
f247ee9dd3 | ||
![]() |
f4cec53ba1 | ||
![]() |
ad70b50fee | ||
![]() |
ea7b2b3141 | ||
![]() |
c2f932e28b | ||
![]() |
c637882578 | ||
![]() |
ef7221e39e | ||
![]() |
6b571a7799 | ||
![]() |
0638b6cb69 | ||
![]() |
a39c1706a1 | ||
![]() |
c741e27057 | ||
![]() |
ede854c215 | ||
![]() |
6a953b4d94 | ||
![]() |
1d763096c7 | ||
![]() |
630d873ec0 | ||
![]() |
d427957ea7 | ||
![]() |
2430cc0e2c | ||
![]() |
e57ee24864 | ||
![]() |
3bc05ab3f2 | ||
![]() |
4e50dcfa93 | ||
![]() |
fcf5531e5b | ||
![]() |
3bef69ee58 | ||
![]() |
311a4f28b0 | ||
![]() |
01e692b353 | ||
![]() |
3b445af6fc | ||
![]() |
1e43ece637 |
@@ -116,8 +116,25 @@ autoAdmin: true
|
||||
# Whether disable HSTS
|
||||
#disableHsts: true
|
||||
|
||||
# Clustering
|
||||
# Number of worker processes
|
||||
#clusterLimit: 1
|
||||
|
||||
# Job concurrency per worker
|
||||
# deliverJobConcurrency: 128;
|
||||
# inboxJobConcurrency: 16;
|
||||
|
||||
# IP address family used for outgoing request (ipv4, ipv6 or dual)
|
||||
#outgoingAddressFamily: ipv4
|
||||
|
||||
# Syslog option
|
||||
#syslog:
|
||||
# host: localhost
|
||||
# port: 514
|
||||
|
||||
# Proxy for HTTP/HTTPS
|
||||
#proxy: http://127.0.0.1:3128
|
||||
|
||||
# Proxy for SMTP/SMTPS
|
||||
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
|
||||
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
|
||||
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
|
||||
|
@@ -1 +1 @@
|
||||
v12.8.1
|
||||
v12.9.1
|
||||
|
82
CHANGELOG.md
82
CHANGELOG.md
@@ -1,6 +1,88 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
11.32.0 (2019/09/10)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* APでRenote/Boost取り消しをやりとりするように
|
||||
* リモートNote存在確認から作成までの間にLockをかけるように
|
||||
* 広告ブロッカーの警告を削除
|
||||
|
||||
### 🐛Fixes
|
||||
* ミュートしているユーザーからトークメッセージが来たときに通知インジケーターが点灯する問題を修正
|
||||
* リアクションした人が10人を超えるときツールチップの表示がおかしい問題を修正
|
||||
|
||||
11.31.4 (2019/09/03)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
* 誰がリアクションしたか見れるやつの表示を改善
|
||||
|
||||
11.31.3 (2019/09/03)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
* 誰がリアクションしたか見れるやつの表示を改善
|
||||
|
||||
11.31.2 (2019/09/03)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
* 誰がリアクションしたか見れるやつの表示を改善
|
||||
|
||||
11.31.1 (2019/09/03)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
* 誰がリアクションしたか見れるやつの表示を改善
|
||||
|
||||
11.31.0 (2019/09/02)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* Syslogサポート
|
||||
* チャートの同期機能をAPI経由で使えるように
|
||||
* SMTPでProxyを使用できるように
|
||||
* リアクションにホバーすることで誰がリアクションしたか見れるように
|
||||
* リプライ時、返信元のlocalOnly属性を引き継ぐように
|
||||
* 引用付き、ローカルのみなどの案内文にアイコン追加
|
||||
* AP deliver/inbox job の並列度を変更できるように
|
||||
* clusterLimitの既定値を1に
|
||||
* AP inbox ジョブの並列度を下げる
|
||||
* CWが付いた投稿はAP上でNote.sensitiveフラグを付けるように
|
||||
* メモウィジェットの内容を自動で保存するように
|
||||
* ページURLが他と重複してたらエラーを投げるように
|
||||
* ページURLが空の時エラーを投げるように
|
||||
* リアクションが解除されたときはアニメーションしないように
|
||||
* 設定の各セクションごとにURLを割り当てるように
|
||||
* 管理画面の各セクションごとにURLを割り当てるように
|
||||
|
||||
### 🐛Fixes
|
||||
* 未実装のTLのRenoteクエリを実装
|
||||
* タイムラインAPIのexcludeNsfwオプションを実装
|
||||
* ユーザーページの投稿一覧の私の投稿にRenoteが表示される問題を修正
|
||||
* meta APIでemojiプロパティに不要な情報が含まれているのを修正
|
||||
* モバイル版でドライブのファイルを削除したときの挙動がおかしい問題を修正
|
||||
* visiblity-chooserにlocalOnly属性が伝わらなかったのを修正
|
||||
* 言語指定したときコードブロックが表示されない問題を修正
|
||||
* トークのメッセージがはみ出す問題を修正
|
||||
* CWの中のサムネイルのサイズが変なのを少し修正
|
||||
* リアクションが初めて付いた時のエフェクトが消えている問題を修正
|
||||
* 無効になっているスイッチを操作できる問題を修正
|
||||
* Mキー連打で画面が真っ暗問題を修正
|
||||
* AmazonのURLプレビューが出来ない問題を修正
|
||||
* 表記ゆれを修正
|
||||
|
||||
11.30.0 (2019/08/24)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* Room: 家具をすべてしまうボタンを追加
|
||||
* Room: カップ麺追加
|
||||
* Room: ホログラフィックディスプレイ追加
|
||||
* Room: エナジードリンク追加
|
||||
* Room: ピンギンの色を変えられるように
|
||||
* Room: プレビューの見やすさを向上
|
||||
* Room保存時にダイアログを表示するように
|
||||
* Roomから移動するときに未保存ならば警告するように
|
||||
|
||||
### 🐛Fixes
|
||||
* MisskeyRoomからページを戻した時、テキスト入力画面で選択位置変更ができない問題を修正
|
||||
|
||||
11.29.0 (2019/08/19)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
|
@@ -39,10 +39,12 @@ Misskey uses CircleCI for executing automated tests.
|
||||
Configuration files are located in [`/.circleci`](/.circleci).
|
||||
|
||||
## Adding MisskeyRoom items
|
||||
* Use English for material, object and texture names
|
||||
* Use meter for unit of length
|
||||
* Your PR should include all source files of your models (for later editing)
|
||||
* Your PR must include the glTF binary files (.glb) of your models
|
||||
* Use English for material, object and texture names.
|
||||
* Use meter for unit of length.
|
||||
* Your PR should include all source files (e.g. `.png`, `.blend`) of your models (for later editing).
|
||||
* Your PR must include the glTF binary files (`.glb`) of your models.
|
||||
* Add a locale key `room.furnitures.YOUR_ITEM` at [`/locales/ja-JP.yml`](/locales/ja-JP.yml).
|
||||
* Add a furniture definition at [`/src/client/app/common/scripts/room/furnitures.json5`](/src/client/app/common/scripts/room/furnitures.json5).
|
||||
|
||||
If you have no experience on 3D modeling, we suggest to use the free 3DCG software [Blender](https://www.blender.org/).
|
||||
You can find information on glTF 2.0 at [glTF 2.0 — Blender Manual]( https://docs.blender.org/manual/en/dev/addons/io_scene_gltf2.html).
|
||||
@@ -268,3 +270,7 @@ npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前
|
||||
```
|
||||
|
||||
作成されたスクリプトは不必要な変更を含むため除去してください。
|
||||
|
||||
## その他
|
||||
### HTMLのクラス名で follow という単語は使わない
|
||||
広告ブロッカーで誤ってブロックされる
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM node:12.8-alpine AS base
|
||||
FROM node:12.9.1-alpine AS base
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
|
16
README.md
16
README.md
@@ -104,6 +104,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<!-- PATREON_START -->
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20010324/b8af4bd31ae34fbf8806cc0e6228e400/1.png?token-time=2145916800&token-hash=iyiocfousNIUwASmatsIDq8EOsmLUdrQNkWyktHlmJg%3D" alt="Nemo" 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/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
|
||||
@@ -111,6 +112,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=20010324">Nemo</a></td>
|
||||
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
|
||||
@@ -121,34 +123,36 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
|
||||
<td><img src="https://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/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
|
||||
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
|
||||
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
|
||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
|
||||
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<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/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.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" 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/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1.jpe?token-time=2145916800&token-hash=EWxXhVbZYH7KB4IDT3joc8TbIg8zPO40x1r5IDn3R7c%3D" alt="Hiratake" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
|
||||
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
|
||||
<td><a href="https://www.patreon.com/damillora">Damillora</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/hiratake">Hiratake</a></td>
|
||||
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
|
||||
<td><a href="https://www.patreon.com/Corset">CG</a></td>
|
||||
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
|
||||
@@ -157,15 +161,17 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
||||
<td><a href="https://www.patreon.com/mastodon">Gargron</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=12531784">Takashi Shibuya</a></td>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Mon, 05 Aug 2019 20:46:06 UTC
|
||||
**Last updated:** Sat, 07 Sep 2019 13:23:36 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
|
@@ -18,9 +18,6 @@ common:
|
||||
drive: "Disk"
|
||||
drive-desc: "Chcete sdílet obrázek, který jste již nahráli? Chcete vaše nahrané soubory organizovat, pojmenovávat a vytvářet pro ně složky? Misskey Disk je pro vás nejlepší řešení. Je velmi snadné sdílet vaše soubory online."
|
||||
outro: "Podívejte se na unikátní vlastnosti Misskey vlastníma očima! Pokud si myslíte, že tato instance není pro vás, zkuste jiné instance, neboť Misskey je decentralizovaná sociální síť, takže můžete snadno najít své přátele. Hodně štěstí a zábavy!"
|
||||
adblock:
|
||||
detected: "Prosím vypněte svůj blokovač reklam"
|
||||
warning: "Některé vlastnosti mohou být nedostupné nebo mohou způsobovat chyby, pokud máte povolený blokovač reklam. <strong>Misskey nepoužívá reklamy</strong>."
|
||||
application-authorization: "Autorizované aplikace"
|
||||
close: "Zavřít"
|
||||
do-not-copy-paste: "Prosím nezadávejte ani nevkládejte sem kód. Váš účet může být kompromitován."
|
||||
@@ -122,12 +119,15 @@ common:
|
||||
create-poll: "Vytvořit anketu"
|
||||
text-remain: "zbývá ještě {} znaků"
|
||||
recent-tags: "Nejnovější"
|
||||
click-to-tagging: "Klikni pro otágování"
|
||||
visibility: "Viditelnost"
|
||||
geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci"
|
||||
error: "Chyba"
|
||||
enter-username: "Zadejte své uživatelské jméno"
|
||||
specified-recipient: "Pro"
|
||||
add-visible-user: "Přidat uživatele"
|
||||
username-prompt: "Zadejte své uživatelské jméno"
|
||||
enter-file-name: "Upravit název souboru"
|
||||
weekday-short:
|
||||
sunday: "Ne"
|
||||
monday: "Po"
|
||||
@@ -232,7 +232,7 @@ common:
|
||||
deck-column-width-wide: "Široké"
|
||||
use-shadow: "Používat v rozhraní stíny"
|
||||
rounded-corners: "Zakulatit rohy v rozhraní"
|
||||
circle-icons: "Používat kulaté ikony"
|
||||
circle-icons: "Používat kulaté avatary"
|
||||
contrasted-acct: "Přidat uživatelskému účtu kontrast"
|
||||
wallpaper: "Obrázek na pozadí"
|
||||
choose-wallpaper: "Zvolit pozadí"
|
||||
@@ -280,6 +280,15 @@ common:
|
||||
sync: "Synchronizace"
|
||||
save: "Uložit"
|
||||
saved: "Uloženo"
|
||||
room: "Místnost"
|
||||
_room:
|
||||
graphicsQuality: "Kvalita grafiky"
|
||||
_graphicsQuality:
|
||||
ultra: "Nejvyšší"
|
||||
high: "Vysoká"
|
||||
medium: "Střední"
|
||||
low: "Nízká"
|
||||
cheep: "Nejnižší"
|
||||
search: "Hledání"
|
||||
delete: "Odstranit"
|
||||
loading: "Načítám..."
|
||||
@@ -372,6 +381,7 @@ common/views/components/games/reversi/reversi.vue:
|
||||
cancel: "Zrušit"
|
||||
common/views/components/games/reversi/reversi.game.vue:
|
||||
surrender: "Vzdát se"
|
||||
surrendered: "Vzdaním se"
|
||||
looped-map: "Zacyklená mapa"
|
||||
common/views/components/games/reversi/reversi.index.vue:
|
||||
title: "Misskey Reversi"
|
||||
@@ -405,6 +415,7 @@ common/views/components/connect-failed.vue:
|
||||
title: "Nelze se připojit k serveru"
|
||||
description: "Nastal problém s Vaším připojením k internetu, nebo server není dostupný nebo zrovna probíhá údržba. Prosím {zkuste to znova} za pár minut."
|
||||
thanks: "Děkujeme že jste použili Misskey."
|
||||
troubleshoot: "Odstranění problémů"
|
||||
common/views/components/connect-failed.troubleshooter.vue:
|
||||
title: "Poradce při potížích"
|
||||
network: "Síťové připojení"
|
||||
@@ -413,6 +424,8 @@ common/views/components/connect-failed.troubleshooter.vue:
|
||||
checking-internet: "Ověřuji připojení k internetu."
|
||||
server: "Připojení k serveru"
|
||||
checking-server: "Spojuji se se serverem"
|
||||
finding: "Vyšetřování problému"
|
||||
no-network: "Žádné připojení k síti"
|
||||
no-network-desc: "Ujistěte se že jste připojeni k Internetu."
|
||||
no-internet: "Nejste připojeni k internetu"
|
||||
no-internet-desc: "Jste připojen k síti, ale zdá se že stále chybí připojení k Internetu. Prosím zkontrolujte Vaše připojení k Internetu."
|
||||
@@ -937,6 +950,7 @@ desktop/views/components/ui.header.account.vue:
|
||||
lists: "Seznamy"
|
||||
groups: "Skupiny"
|
||||
admin: "Administrace"
|
||||
room: "Místnost"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
game: "Hry"
|
||||
desktop/views/components/ui.header.notifications.vue:
|
||||
@@ -1353,6 +1367,7 @@ pages:
|
||||
room:
|
||||
translate: "Přesunout"
|
||||
save: "Uložit"
|
||||
saved: "Uloženo"
|
||||
furnitures:
|
||||
moon: "Po"
|
||||
moon: "Měsíc"
|
||||
bin: "Koš"
|
||||
|
@@ -18,9 +18,6 @@ common:
|
||||
drive: "Drev"
|
||||
drive-desc: "Vil du poste et billede, som du tidligere har uploadet? Har du brug for at navngive filer og organisere dem i mapper, som du selv har navngivet? Så er Misskey Drev den bedste løsning for dig. Den gør det så let som ingenting at dele dine filer online."
|
||||
outro: "Tjek Misskey's unikke funktioner ved at se dem med dine egne øjne. Hvis du kommer frem til, at den ene server ikke er noget for dig, så kan du prøve en anden. Misskey er et decentraliseret SNS, så du kan lettere finde frem til brugere, som du klikker med. God fornøjelse!"
|
||||
adblock:
|
||||
detected: "Her kan du deaktivere ad blocker."
|
||||
warning: "Visse funktioner kan forsvinde eller medføre forstyrrelser, hvis ad blocking er aktiveret. <strong>Misskey kører ikke annoncer</strong>."
|
||||
application-authorization: "Adgangsstyring"
|
||||
close: "Luk"
|
||||
do-not-copy-paste: "Undgå venligst at skrive eller klistre kode ind her. I modsat fald kan din konto blive kompromitteret."
|
||||
@@ -213,7 +210,7 @@ common:
|
||||
deck-column-width-wide: "Bred"
|
||||
use-shadow: "Vis skygger"
|
||||
rounded-corners: "Vis afrundede hjørner"
|
||||
circle-icons: "Anvend cykliske ikoner"
|
||||
circle-icons: "Anvend cykliske avatar"
|
||||
contrasted-acct: "Tilføj kontrast til brugerkontoen"
|
||||
wallpaper: "Baggrundsbillede"
|
||||
choose-wallpaper: "Vælg en baggrund"
|
||||
@@ -680,7 +677,7 @@ common/views/components/profile-editor.vue:
|
||||
you-can-include-hashtags: "Du må gerne bruge hashtags i din profil beskrivelse"
|
||||
language: "Sprog"
|
||||
birthday: "Fødselsdag"
|
||||
avatar: "Ikon"
|
||||
avatar: "Avatar"
|
||||
banner: "Banner"
|
||||
is-cat: "Denne konto er en Kat"
|
||||
is-bot: "Denne konto er en Bot"
|
||||
@@ -1913,6 +1910,7 @@ pages:
|
||||
room:
|
||||
translate: "Flyt"
|
||||
save: "Gem"
|
||||
saved: "Gemt"
|
||||
furnitures:
|
||||
moon: "Man"
|
||||
moon: "Måne"
|
||||
bin: "Skraldespand"
|
||||
|
@@ -18,9 +18,6 @@ common:
|
||||
drive: "Drive"
|
||||
drive-desc: "Du willst ein hochgeladenes Foto nochmal posten? Deine Dateien benennen und in Ordnern sortieren? Misskeys Drive ist der beste Ort dafür. Damit wird das Teilen zum Kinderspiel!"
|
||||
outro: "Probiere Misskey aus und entdecke Misskeys einzigartige Funktionen. Wenn dir diese Instanz nicht zusagt, nimm einfach eine andere. Dank Misskeys dezentralem System kannst du dich überall mit deinen Freunden verbinden. Also dann, GLHF!"
|
||||
adblock:
|
||||
detected: "Bitte deaktiviere den Werbeblocker."
|
||||
warning: "<strong>Misskey hat keine Werbung.</strong> Allerdings können Werbeblocker für unerwartete Probleme und fehlende Funktionen sorgen."
|
||||
application-authorization: "Autorisierte Anwendungen"
|
||||
close: "Schließen"
|
||||
do-not-copy-paste: "Bitte keinen Code einfügen. Ihr Account könnte gefährdet werden."
|
||||
@@ -207,7 +204,7 @@ common:
|
||||
deck-column-width-wide: "Sehr breit"
|
||||
use-shadow: "Nutze Schatten"
|
||||
rounded-corners: "Abgerundete Ecken"
|
||||
circle-icons: "Kreisförmige Icons"
|
||||
circle-icons: "Kreisförmige Avatar"
|
||||
contrasted-acct: "Nutzernamen kontrastreicher darstellen"
|
||||
wallpaper: "Hintergrund"
|
||||
choose-wallpaper: "Hintergrund auswählen"
|
||||
@@ -605,6 +602,7 @@ common/views/widgets/memo.vue:
|
||||
save: "Speichern"
|
||||
desktop:
|
||||
banner: "Banner"
|
||||
avatar: "Avatar"
|
||||
unable-to-process: "Der Vorgang konnte nicht abgeschlossen werden"
|
||||
desktop/views/components/activity.chart.vue:
|
||||
total: "Schwarz ... komplett"
|
||||
@@ -948,6 +946,7 @@ pages:
|
||||
array: "Listen"
|
||||
room:
|
||||
save: "Speichern"
|
||||
saved: "Gespeichert"
|
||||
furnitures:
|
||||
moon: "Mo"
|
||||
moon: "Mond"
|
||||
bin: "Papierkorb"
|
||||
|
@@ -18,9 +18,6 @@ common:
|
||||
drive: "Drive"
|
||||
drive-desc: "Wanna post a picture you have already uploaded? Wish to organize, name and create a folder for your uploaded files? Misskey Drive is the best solution for you. Very easy to share your files online."
|
||||
outro: "Check Misskey-unique features by seeing them with your own eyes! If you feel like this instance is not for you, try other instances, as Misskey is a decentralized SNS, so that you can easily find your mates. Then, GLHF!"
|
||||
adblock:
|
||||
detected: "Please disable ad blocker."
|
||||
warning: "Some features may be unavailable or cause malfunctions if ad blocking features are enabled. <strong>Misskey is not running ads</strong>."
|
||||
application-authorization: "Application authorizations"
|
||||
close: "Close"
|
||||
do-not-copy-paste: "Please do not enter or paste the code here. Account may be compromised."
|
||||
@@ -133,6 +130,7 @@ common:
|
||||
geolocation-alert: "Your device does not provide location services"
|
||||
error: "Error"
|
||||
enter-username: "Please enter username"
|
||||
specified-recipient: "Recipient"
|
||||
add-visible-user: "Add a user"
|
||||
cw-placeholder: "Comments for the post (optional)"
|
||||
username-prompt: "Please enter username"
|
||||
@@ -246,7 +244,7 @@ common:
|
||||
deck-column-width-wide: "Wide"
|
||||
use-shadow: "Use shadows in the UI"
|
||||
rounded-corners: "Round the corners of the UI"
|
||||
circle-icons: "Use circular icons"
|
||||
circle-icons: "Use circular avatar icon"
|
||||
contrasted-acct: "Add contrast to user account"
|
||||
wallpaper: "Background image"
|
||||
choose-wallpaper: "Choose a background"
|
||||
@@ -390,6 +388,9 @@ common/views/pages/explore.vue:
|
||||
federated: "From the fediverse"
|
||||
explore: "Explore {host}"
|
||||
users-info: "Currently, {users} users are registered here"
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
few-users: "{users} reacted with {reaction}"
|
||||
many-users: "{users}, and {omitted} more reacted with {reaction}"
|
||||
common/views/components/url-preview.vue:
|
||||
enable-player: "Enable playback"
|
||||
disable-player: "Close the player"
|
||||
@@ -752,8 +753,8 @@ common/views/components/profile-editor.vue:
|
||||
uploading: "Uploading"
|
||||
upload-failed: "Failed to upload"
|
||||
unable-to-process: "The operation could not be completed."
|
||||
avatar-not-an-image: "The file specified as an avatar is not an image"
|
||||
banner-not-an-image: "The file specified as a banner is not an image"
|
||||
avatar-not-an-image: "The file you specified as an avatar is not an image"
|
||||
banner-not-an-image: "The file you specified as a banner is not an image"
|
||||
email: "Email settings"
|
||||
email-address: "Email Address"
|
||||
email-verified: "Your email has been verified."
|
||||
@@ -927,7 +928,7 @@ desktop/views/components/drive.file.vue:
|
||||
copy-url: "Copy URL"
|
||||
download: "Download"
|
||||
else-files: "Other"
|
||||
set-as-avatar: "Set as an avatar"
|
||||
set-as-avatar: "Set as avatar"
|
||||
set-as-banner: "Set as a banner"
|
||||
open-in-app: "Open in app"
|
||||
add-app: "Add app"
|
||||
@@ -1817,6 +1818,9 @@ pages:
|
||||
read-page: "Viewing the source"
|
||||
page-created: "Created the page!"
|
||||
page-updated: "Updated the page"
|
||||
name-already-exists: "The specified page name already exists"
|
||||
title-invalid-name: "The specified page URL is invalid"
|
||||
text-invalid-name: "Check whether that is not a blank"
|
||||
are-you-sure-delete: "Do you want to delete this page?"
|
||||
page-deleted: "The page has been deleted"
|
||||
edit-this-page: "Edit this page"
|
||||
@@ -2092,6 +2096,10 @@ room:
|
||||
exit: "Deselect"
|
||||
remove: "Remove"
|
||||
save: "Save"
|
||||
saved: "Saved"
|
||||
clear: "Remove All"
|
||||
clear-confirm: "Are you sure to remove all furnitures in your room?"
|
||||
leave-confirm: "There are unsaved changes. Do you really want to leave?"
|
||||
chooseImage: "Select an image"
|
||||
room-type: "Room type"
|
||||
carpet-color: "Color of carpet"
|
||||
@@ -2120,7 +2128,7 @@ room:
|
||||
piano: "Piano"
|
||||
facial-tissue: "Facial tissue"
|
||||
server: "Servers"
|
||||
moon: "M"
|
||||
moon: "Moon"
|
||||
corkboard: "Cork board"
|
||||
mousepad: "Mousepad"
|
||||
monitor: "Monitor"
|
||||
@@ -2136,4 +2144,9 @@ room:
|
||||
rubik-cube: "Rubik's Cube"
|
||||
poster-h: "Poster (Horizontal)"
|
||||
poster-v: "Poster (Vertical)"
|
||||
bin: "Trash"
|
||||
sofa: "Sofa"
|
||||
spiral: "Spiral Staircase"
|
||||
bin: "Waste bin"
|
||||
cup-noodle: "Cup noodle"
|
||||
holo-display: "Holographic display"
|
||||
energy-drink: "Energy drink"
|
||||
|
@@ -16,9 +16,6 @@ common:
|
||||
ui: "Interfaz"
|
||||
ui-desc: "No hay ninguna interfaz que le vaya bien a todos. Por eso, Misskey tiene una interfaz altamente personalizable para tus gustos. Puedes hacer tu página principal única editando la interfaz de tu timeline y moviendo varios widgets para conseguir hacer de este lugar uno propio."
|
||||
drive: "Drive"
|
||||
adblock:
|
||||
detected: "Por favor, desactive el bloqueador de publicidad."
|
||||
warning: "<strong>Misskey no tiene anuncios publicitarios.</strong> Sin embargo, algunas características podrían no estar disponibles si el bloqueador de publicidad está habilitado."
|
||||
application-authorization: "Autorizaciones de la aplicación."
|
||||
close: "Cerrar"
|
||||
do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
|
||||
@@ -177,7 +174,7 @@ common:
|
||||
deck-column-width-wide: "Ancho"
|
||||
use-shadow: "Usar sombras en la Interfaz de Usuario"
|
||||
rounded-corners: "Esquinas redondeadas en la Interfaz de Usuario"
|
||||
circle-icons: "Usar iconos circulares"
|
||||
circle-icons: "Usar avatar circulares"
|
||||
contrasted-acct: "Añadir contraste al nombre de usuario"
|
||||
wallpaper: "Fondo de pantalla"
|
||||
choose-wallpaper: "Escoge un fondo de pantalla"
|
||||
@@ -1137,6 +1134,7 @@ pages:
|
||||
array: "Listas"
|
||||
room:
|
||||
save: "Guardar"
|
||||
saved: "Guardado"
|
||||
furnitures:
|
||||
moon: "Luna"
|
||||
bin: "Papelera"
|
||||
|
@@ -18,9 +18,6 @@ common:
|
||||
drive: "Drive"
|
||||
drive-desc: "Vous voulez poster une photo que vous avez déjà transférée ? Vous souhaitez organiser, nommer et créer un dossier pour vos fichiers téléversés ? Misskey Drive est la meilleure solution pour vous. Très facile de partager vos fichiers en ligne."
|
||||
outro: "Découvrez vous-même les fonctionnalités de Misskey. Étant donné que Misskey est un réseau social fédéré, vous pouvez essayer d’autres instances afin de trouver vos amis si la présente instance ne vous correspond pas. Bonne chance et amusez-vous bien !"
|
||||
adblock:
|
||||
detected: "Veuillez désactiver votre bloqueur de publicités"
|
||||
warning: "<strong>Misskey n’utilise pas de publicités</strong>, mais quelques options peuvent être non disponibles ou fonctionneraient mal si un bloqueur de publicités est activé."
|
||||
application-authorization: "Autorisations de l’application"
|
||||
close: "Fermer"
|
||||
do-not-copy-paste: "Veuillez ne pas entrer ou coller le code ici. Le compte pourrait être compromis."
|
||||
@@ -37,17 +34,17 @@ common:
|
||||
unfollow-confirm: "Désirez-vous vous désabonner de {name} ?"
|
||||
delete-confirm: "Supprimer cette publication ?"
|
||||
signin-required: "Veuillez vous connecter"
|
||||
notification-type: "Type de notification"
|
||||
notification-type: "Type de notifications"
|
||||
notification-types:
|
||||
all: "Tout"
|
||||
pollVote: "Sondage"
|
||||
pollVote: "Sondages"
|
||||
follow: "Abonnements"
|
||||
receiveFollowRequest: "Demandes d’abonnements"
|
||||
reply: "Répondre"
|
||||
reply: "Réponses"
|
||||
quote: "Cité par"
|
||||
renote: "Republier"
|
||||
renote: "Partages"
|
||||
mention: "Mentions"
|
||||
reaction: "Réaction"
|
||||
reaction: "Réactions"
|
||||
got-it: "J’ai compris !"
|
||||
customization-tips:
|
||||
title: "Conseils de personnalisation"
|
||||
@@ -183,7 +180,7 @@ common:
|
||||
notification: "Notifications"
|
||||
apps: "Applications"
|
||||
tags: "Hashtags"
|
||||
mute-and-block: "Silencés / Bloqués"
|
||||
mute-and-block: "Masqués / Bloqués"
|
||||
blocking: "En cours blocage"
|
||||
security: "Sécurité"
|
||||
signin: "Historique des connexions"
|
||||
@@ -199,6 +196,7 @@ common:
|
||||
web-search-engine: "Moteur de recherche Web"
|
||||
web-search-engine-desc: "Exemple : https://www.google.com/?#q={{query}}"
|
||||
paste: "Coller"
|
||||
pasted-file-name-desc: "Exemple : \"yyyy-MM-dd HH-mm-ss [{{number}}]\" → \"2018-03-20 21-30-24 1\""
|
||||
keep-cw: "Maintenir l'avertissement de contenu"
|
||||
keep-cw-desc: "Lorsque vous répondez à un message, le même avertissement de contenu est reprit par défaut dans la réponse, le même que celui qui a été défini dans le message original."
|
||||
i-like-sushi: "Je préfère les sushis plutôt que le pudding"
|
||||
@@ -236,7 +234,7 @@ common:
|
||||
deck-column-width-wide: "Large"
|
||||
use-shadow: "Utiliser les ombres dans l'interface utilisateur"
|
||||
rounded-corners: "Coins arrondis de l'interface utilisateur"
|
||||
circle-icons: "Utiliser des icônes circulaires"
|
||||
circle-icons: "Utiliser des avatar circulaires"
|
||||
contrasted-acct: "Ajouter du contraste au nom de l’utilisateur"
|
||||
wallpaper: "Image du fond d'écran"
|
||||
choose-wallpaper: "Sélectionner un fond d'écran"
|
||||
@@ -284,6 +282,18 @@ common:
|
||||
sync: "Synchroniser"
|
||||
save: "Enregistrer"
|
||||
saved: "enregistré"
|
||||
home-profile: "Profil principal"
|
||||
deck-profile: "Profil deck"
|
||||
room: "Pièce"
|
||||
_room:
|
||||
graphicsQuality: "Qualité des graphismes"
|
||||
_graphicsQuality:
|
||||
ultra: "Très élevée"
|
||||
high: "Élevée"
|
||||
medium: "Moyenne"
|
||||
low: "Basse"
|
||||
cheep: "Minimale"
|
||||
useOrthographicCamera: "Utiliser une caméra orthographique"
|
||||
search: "Recherche"
|
||||
delete: "Supprimer"
|
||||
loading: "Chargement en cours …"
|
||||
@@ -368,6 +378,8 @@ common/views/pages/explore.vue:
|
||||
federated: "Du Fédiverse"
|
||||
explore: "Explorer {host}"
|
||||
users-info: "Actuellement, {users} utilisateur·rice·s se sont inscrit ici"
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
few-users: "{users} ont réagit avec {reaction}"
|
||||
common/views/components/url-preview.vue:
|
||||
enable-player: "Activer la lecture"
|
||||
disable-player: "Fermer le lecteur"
|
||||
@@ -533,8 +545,9 @@ common/views/components/note-menu.vue:
|
||||
unpin: "Désépingler"
|
||||
delete: "Supprimer"
|
||||
delete-confirm: "Supprimer cette publication ?"
|
||||
delete-and-edit: "Effacer et éditer"
|
||||
delete-and-edit: "Supprimer et réécrire"
|
||||
remote: "Afficher la note originale"
|
||||
pin-limit-exceeded: "Vous ne pouvez plus épingler davantage de publications."
|
||||
common/views/components/user-menu.vue:
|
||||
mention: "Mention"
|
||||
mute: "Silencier"
|
||||
@@ -588,7 +601,7 @@ common/views/components/poll-editor.vue:
|
||||
hour: "Heures"
|
||||
day: "D"
|
||||
common/views/components/reaction-picker.vue:
|
||||
choose-reaction: "Choisissez votre réaction"
|
||||
choose-reaction: "Envoyer une réaction"
|
||||
input-reaction-placeholder: "ou insérez un émoji"
|
||||
common/views/components/emoji-picker.vue:
|
||||
custom-emoji: "Émoji personnalisé"
|
||||
@@ -966,8 +979,8 @@ desktop/views/components/note-detail.vue:
|
||||
deleted: "cette publication a été supprimée"
|
||||
location: "Géolocalisation"
|
||||
renote: "Republier"
|
||||
add-reaction: "Ajouter votre reaction"
|
||||
undo-reaction: "Inverser la réaction"
|
||||
add-reaction: "Ajouter une réaction"
|
||||
undo-reaction: "Annuler la réaction"
|
||||
desktop/views/components/note.vue:
|
||||
reply: "Répondre"
|
||||
renote: "Partager"
|
||||
@@ -1063,9 +1076,9 @@ common/views/components/drive-settings.vue:
|
||||
default-upload-folder-name: "Dossier·s"
|
||||
change-default-upload-folder: "Changer de dossier"
|
||||
common/views/components/mute-and-block.vue:
|
||||
mute-and-block: "Silencés / Bloqués"
|
||||
mute: "Mettre en sourdine"
|
||||
block: "En cours blocage"
|
||||
mute-and-block: "Masqués / Bloqués"
|
||||
mute: "Mis en sourdine"
|
||||
block: "Vous avez bloqué"
|
||||
no-muted-users: "Aucun utilisateur n’est mis en sourdine"
|
||||
no-blocked-users: "Aucun utilisateur n’est bloqué"
|
||||
word-mute: "Filtre de mots"
|
||||
@@ -1115,6 +1128,7 @@ desktop/views/components/ui.header.account.vue:
|
||||
groups: "Groupes"
|
||||
follow-requests: "Demandes d’abonnement"
|
||||
admin: "Admin"
|
||||
room: "Pièce"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
game: "Jeux"
|
||||
desktop/views/components/ui.header.notifications.vue:
|
||||
@@ -1762,6 +1776,8 @@ pages:
|
||||
read-page: "Voir la source"
|
||||
page-created: "Page a été créée !"
|
||||
page-updated: "A mis à jour la page"
|
||||
name-already-exists: "Une page portant le même nom existe déjà"
|
||||
title-invalid-name: "L’URL de la page spécifiée n’est pas valide"
|
||||
are-you-sure-delete: "Confirmez-vous la suppression de cette page ?"
|
||||
page-deleted: "La page a bien été supprimée."
|
||||
edit-this-page: "Éditer cette page"
|
||||
@@ -1770,6 +1786,7 @@ pages:
|
||||
view-source: "Afficher la source"
|
||||
view-page: "Afficher la page"
|
||||
like: "Bien"
|
||||
unlike: "Je n’aime pas"
|
||||
liked-pages: "Pages favorites"
|
||||
my-pages: "Mes pages"
|
||||
inspector: "Inspecteur"
|
||||
@@ -2000,8 +2017,60 @@ pages:
|
||||
enviromentVariables: "Variables d'environnement"
|
||||
pageVariables: "Élément de page"
|
||||
room:
|
||||
add-furniture: "Placer des meubles"
|
||||
translate: "Déplacer"
|
||||
rotate: "Tourner"
|
||||
remove: "Enlever"
|
||||
save: "Enregistrer"
|
||||
saved: "enregistré"
|
||||
clear: "Tout enlever"
|
||||
clear-confirm: "Désirez-vous enlever tout les meubles de votre chambre ?"
|
||||
leave-confirm: "Vous avez des modifications non-sauvegardées. Voulez-vous vraiment quitter ?"
|
||||
chooseImage: "Sélectionnez une image"
|
||||
room-type: "Type de chambre"
|
||||
carpet-color: "Couleur du tapis"
|
||||
rooms:
|
||||
default: "Par défaut"
|
||||
washitsu: "Style japonnais"
|
||||
furnitures:
|
||||
moon: "L"
|
||||
milk: "Lait en carton"
|
||||
bed: "Lit"
|
||||
low-table: "Table basse"
|
||||
desk: "Bureau"
|
||||
chair: "Chaise"
|
||||
chair2: "Chaise 2"
|
||||
fan: "Ventilateur"
|
||||
pc: "Ordinateur"
|
||||
plant: "Plante d’intérieur"
|
||||
plant2: "Plante d’intérieur 2"
|
||||
eraser: "Gomme"
|
||||
pencil: "Crayon"
|
||||
cardboard-box: "Boîte en carton"
|
||||
cardboard-box2: "Boîte en carton 2"
|
||||
cardboard-box3: "Boîte en carton 3"
|
||||
book: "Livre"
|
||||
book2: "Livre 2"
|
||||
piano: "Piano"
|
||||
facial-tissue: "Mouchoirs en papier"
|
||||
server: "Serveurs"
|
||||
moon: "Lune"
|
||||
corkboard: "Tableau en liège"
|
||||
mousepad: "Tapis de souris"
|
||||
monitor: "Écran"
|
||||
keyboard: "Clavier"
|
||||
carpet-stripe: "Tapis (zébré)"
|
||||
color-box: "Étagère"
|
||||
wall-clock: "Horloge murale"
|
||||
photoframe: "Cadre photo"
|
||||
cube: "Cube"
|
||||
tv: "Téléviseur"
|
||||
pinguin: "Pingouin"
|
||||
rubik-cube: "Cube de Rubik"
|
||||
poster-h: "Affiche (horizontale)"
|
||||
poster-v: "Affiche (verticale)"
|
||||
sofa: "Canapé"
|
||||
spiral: "Escaliers en spirale"
|
||||
bin: "Corbeille"
|
||||
cup-noodle: "Bol de nouilles"
|
||||
holo-display: "Affichage holographique"
|
||||
energy-drink: "Boisson énergétique"
|
||||
|
@@ -19,9 +19,6 @@ common:
|
||||
drive: "ドライブ"
|
||||
drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
|
||||
outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
|
||||
adblock:
|
||||
detected: "広告ブロッカーを無効にしてください"
|
||||
warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
|
||||
application-authorization: "アプリの連携"
|
||||
close: "閉じる"
|
||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
|
||||
@@ -139,6 +136,7 @@ common:
|
||||
geolocation-alert: "お使いの端末は位置情報に対応していません"
|
||||
error: "エラー"
|
||||
enter-username: "ユーザー名を入力してください"
|
||||
specified-recipient: "宛先"
|
||||
add-visible-user: "ユーザーを追加"
|
||||
cw-placeholder: "内容への注釈 (オプション)"
|
||||
username-prompt: "ユーザー名を入力してください"
|
||||
@@ -258,7 +256,7 @@ common:
|
||||
deck-column-width-wide: "広"
|
||||
use-shadow: "UIに影を使用"
|
||||
rounded-corners: "UIの角を丸める"
|
||||
circle-icons: "円形のアイコンを使用"
|
||||
circle-icons: "円形のアバターを使用"
|
||||
contrasted-acct: "ユーザー名にコントラストを付ける"
|
||||
wallpaper: "壁紙"
|
||||
choose-wallpaper: "壁紙を選択"
|
||||
@@ -412,6 +410,10 @@ common/views/pages/explore.vue:
|
||||
explore: "{host}を探索"
|
||||
users-info: "現在{users}ユーザーが登録されています"
|
||||
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
few-users: "{users}が{reaction}をリアクション"
|
||||
many-users: "{users}と他{omitted}人が{reaction}をリアクション"
|
||||
|
||||
common/views/components/url-preview.vue:
|
||||
enable-player: "プレイヤーを開く"
|
||||
disable-player: "プレイヤーを閉じる"
|
||||
@@ -795,7 +797,7 @@ common/views/components/profile-editor.vue:
|
||||
you-can-include-hashtags: "ハッシュタグを含めることができます。"
|
||||
language: "言語"
|
||||
birthday: "誕生日"
|
||||
avatar: "アイコン"
|
||||
avatar: "アバター"
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-bot: "このアカウントはBotです"
|
||||
@@ -809,7 +811,7 @@ common/views/components/profile-editor.vue:
|
||||
uploading: "アップロード中"
|
||||
upload-failed: "アップロードに失敗しました"
|
||||
unable-to-process: "操作を完了できません"
|
||||
avatar-not-an-image: "アイコンとして指定したファイルは画像ではありません"
|
||||
avatar-not-an-image: "アバターとして指定したファイルは画像ではありません"
|
||||
banner-not-an-image: "バナーとして指定したファイルは画像ではありません"
|
||||
email: "メール設定"
|
||||
email-address: "メールアドレス"
|
||||
@@ -999,7 +1001,7 @@ desktop/views/components/drive-window.vue:
|
||||
used: "使用中"
|
||||
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "アイコン"
|
||||
avatar: "アバター"
|
||||
banner: "バナー"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
@@ -1009,7 +1011,7 @@ desktop/views/components/drive.file.vue:
|
||||
copy-url: "URLをコピー"
|
||||
download: "ダウンロード"
|
||||
else-files: "その他"
|
||||
set-as-avatar: "アイコンに設定"
|
||||
set-as-avatar: "アバターに設定"
|
||||
set-as-banner: "バナーに設定"
|
||||
open-in-app: "アプリで開く"
|
||||
add-app: "アプリを追加"
|
||||
@@ -2014,6 +2016,9 @@ pages:
|
||||
read-page: "ソースを表示中"
|
||||
page-created: "ページを作成しました"
|
||||
page-updated: "ページを更新しました"
|
||||
name-already-exists: "指定されたページURLは既に存在しています"
|
||||
title-invalid-name: "不正なページURLです"
|
||||
text-invalid-name: "空白でないか確認してください"
|
||||
are-you-sure-delete: "このページを削除しますか?"
|
||||
page-deleted: "ページを削除しました"
|
||||
edit-this-page: "このページを編集"
|
||||
@@ -2300,6 +2305,10 @@ room:
|
||||
exit: "戻る"
|
||||
remove: "しまう"
|
||||
save: "保存"
|
||||
saved: "保存しました"
|
||||
clear: "片付け"
|
||||
clear-confirm: "全ての家具をしまいますか?"
|
||||
leave-confirm: "未保存の変更があります、移動しますか?"
|
||||
chooseImage: "画像を選択"
|
||||
room-type: "部屋のタイプ"
|
||||
carpet-color: "床の色"
|
||||
@@ -2347,3 +2356,6 @@ room:
|
||||
sofa: "ソファ"
|
||||
spiral: "螺旋階段"
|
||||
bin: "ゴミ箱"
|
||||
cup-noodle: "カップ麺"
|
||||
holo-display: "ホログラフィックディスプレイ"
|
||||
energy-drink: "エナジードリンク"
|
||||
|
@@ -18,9 +18,6 @@ common:
|
||||
drive: "ドライブ"
|
||||
drive-desc: "「こないだの画像、どこやったかな……また投稿したいんやけど……」「さっきのファイルあのフォルダに直しといて」そんなこと言わんとって。Misskeyはもとからドライブ機能持っとるさかい、心配あらへん。ファイルの「わけわけ」したってな。"
|
||||
outro: "Misskeyの機能は無限大や!知らんけど。知らん言うとるやんけ、あんたが見に行けや!Misskeyは分散型SNSやから、ここがあかんくても他がある。阪神でもオリックスでもワイは応援するで!"
|
||||
adblock:
|
||||
detected: "広告ブロッカーを無効にしてや"
|
||||
warning: "<strong>Misskeyは広告を掲載してへん</strong>けど、広告をブロックしはる機能がおると一部の機能が利用できんくなったり、不具合が発生するかも分からん。知らんけど。"
|
||||
application-authorization: "アプリの連携"
|
||||
close: "さいなら"
|
||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりせんといてください。アカウントが不正利用されるかも分からん。知らんけど。"
|
||||
@@ -467,7 +464,7 @@ common/views/components/profile-editor.vue:
|
||||
description: "自己紹介"
|
||||
language: "言語"
|
||||
birthday: "誕生日"
|
||||
avatar: "アイコン"
|
||||
avatar: "アバター"
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatやで"
|
||||
is-bot: "このアカウントはBotやで"
|
||||
@@ -605,7 +602,7 @@ desktop/views/components/crop-window.vue:
|
||||
desktop/views/components/drive-window.vue:
|
||||
used: "使うとる"
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "アイコン"
|
||||
avatar: "アバター"
|
||||
banner: "バナー"
|
||||
nsfw: "見たらあかんで"
|
||||
contextmenu:
|
||||
@@ -615,7 +612,7 @@ desktop/views/components/drive.file.vue:
|
||||
copy-url: "URLをコピー"
|
||||
download: "ダウンロード"
|
||||
else-files: "その他"
|
||||
set-as-avatar: "アイコンにする"
|
||||
set-as-avatar: "アバターにする"
|
||||
set-as-banner: "バナーにする"
|
||||
open-in-app: "アプリで開く"
|
||||
add-app: "アプリ増やす"
|
||||
@@ -1287,6 +1284,7 @@ pages:
|
||||
room:
|
||||
translate: "移動"
|
||||
save: "保存"
|
||||
saved: "保存したで!"
|
||||
furnitures:
|
||||
moon: "月"
|
||||
bin: "ゴミ箱"
|
||||
|
@@ -18,9 +18,6 @@ common:
|
||||
drive: "드라이브"
|
||||
drive-desc: "이전에 올렸던 적 있는 이미지를 다시 올리고 싶을 때가 있지 않으신가요? 아니면 업로드했던 파일을 폴더로 정리하고 싶지 않으신가요? Misskey에 기본적으로 내장된 드라이브 기능으로 해결됩니다. 파일 공유도 쉽습니다."
|
||||
outro: "이외에도 Misskey에만 있는 기능이 아직도 더 있으니 부디 여러분 자신의 눈으로 확인해보시기 바랍니다. Misskey는 분산형 SNS라서 이 인스턴스가 마음에 들지 않으신다면 다른 인스턴스를 시도해보실 수도 있습니다. 그럼, GLHF!"
|
||||
adblock:
|
||||
detected: "광고 차단기를 해제하십시오"
|
||||
warning: "<strong>Misskey는 광고를 게재하지 않습니다</strong>. 하지만 광고 차단 기능을 사용할 경우 일부 기능을 사용할 수 없거나 문제가 발생할 수 있습니다."
|
||||
application-authorization: "앱 연계"
|
||||
close: "닫기"
|
||||
do-not-copy-paste: "여기에 코드를 입력하거나 붙여넣지 마십시오. 계정이 무단으로 사용될 수 있습니다."
|
||||
@@ -133,6 +130,7 @@ common:
|
||||
geolocation-alert: "사용 중이신 장치에서는 위치 정보를 사용할 수 없습니다"
|
||||
error: "오류"
|
||||
enter-username: "사용자명을 입력해주세요"
|
||||
specified-recipient: "수신인"
|
||||
add-visible-user: "사용자 추가"
|
||||
cw-placeholder: "내용에 대한 주석 (옵션)"
|
||||
username-prompt: "사용자명을 입력해주세요"
|
||||
@@ -246,7 +244,7 @@ common:
|
||||
deck-column-width-wide: "넓음"
|
||||
use-shadow: "UI에 그림자 효과 적용"
|
||||
rounded-corners: "UI의 모서리를 둥글게 설정"
|
||||
circle-icons: "원형 아이콘 사용"
|
||||
circle-icons: "원형 아바타를 사용"
|
||||
contrasted-acct: "사용자명에 대비 추가"
|
||||
wallpaper: "배경"
|
||||
choose-wallpaper: "배경 설정"
|
||||
@@ -390,6 +388,9 @@ common/views/pages/explore.vue:
|
||||
federated: "연합"
|
||||
explore: "{host}을(를) 탐색"
|
||||
users-info: "현재 {users} 사용자가 등록되어 있습니다"
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
few-users: "{users}님이 {reaction} 리액션"
|
||||
many-users: "{users}님 외 {omitted}명이 {reaction} 리액션"
|
||||
common/views/components/url-preview.vue:
|
||||
enable-player: "플레이어 열기"
|
||||
disable-player: "플레이어 닫기"
|
||||
@@ -1817,6 +1818,9 @@ pages:
|
||||
read-page: "소스 표시중"
|
||||
page-created: "페이지를 만들었습니다"
|
||||
page-updated: "페이지를 수정했습니다"
|
||||
name-already-exists: "지정한 페이지 URL은 이미 존재합니다"
|
||||
title-invalid-name: "유효하지 않은 페이지 URL입니다"
|
||||
text-invalid-name: "비어있지 않은지 확인해주세요"
|
||||
are-you-sure-delete: "이 페이지를 삭제하시겠습니까?"
|
||||
page-deleted: "페이지가 삭제되었습니다"
|
||||
edit-this-page: "이 페이지를 편집"
|
||||
@@ -2092,6 +2096,10 @@ room:
|
||||
exit: "선택 해제"
|
||||
remove: "치우기"
|
||||
save: "저장"
|
||||
saved: "저장하였습니다"
|
||||
clear: "모두 치우기"
|
||||
clear-confirm: "정말 방 안의 모든 가구를 치우시겠습니까?"
|
||||
leave-confirm: "저장되지 않은 변경 사항이 있습니다. 정말 떠나시겠습니까?"
|
||||
chooseImage: "이미지 선택"
|
||||
room-type: "룸 종류"
|
||||
carpet-color: "바닥 색상"
|
||||
@@ -2120,7 +2128,7 @@ room:
|
||||
piano: "피아노"
|
||||
facial-tissue: "휴지 상자"
|
||||
server: "서버"
|
||||
moon: "월"
|
||||
moon: "달"
|
||||
corkboard: "게시판"
|
||||
mousepad: "마우스 패드"
|
||||
monitor: "모니터"
|
||||
@@ -2136,4 +2144,9 @@ room:
|
||||
rubik-cube: "루빅스 큐브"
|
||||
poster-h: "포스터 (가로)"
|
||||
poster-v: "포스터 (세로)"
|
||||
sofa: "소파"
|
||||
spiral: "나선형 계단"
|
||||
bin: "휴지통"
|
||||
cup-noodle: "컵라면"
|
||||
holo-display: "홀로그램"
|
||||
energy-drink: "에너지 드링크"
|
||||
|
@@ -232,6 +232,7 @@ common/views/pages/follow.vue:
|
||||
follow: "Volgend"
|
||||
desktop:
|
||||
banner: "Omslagfoto"
|
||||
avatar: "Gebruikersafbeelding"
|
||||
unable-to-process: "De operatie kan niet worden voltooid."
|
||||
desktop/views/components/activity.chart.vue:
|
||||
total: "Zwart ... totaal"
|
||||
@@ -641,4 +642,4 @@ pages:
|
||||
room:
|
||||
translate: "Verplaatsen"
|
||||
furnitures:
|
||||
moon: "M"
|
||||
moon: "Maan"
|
||||
|
@@ -14,9 +14,6 @@ common:
|
||||
ui: "Interfejs"
|
||||
drive: "Dysk"
|
||||
drive-desc: "Chcesz opublikować zdjęcie, które już przesłałeś? Chcesz uporządkować, nazwać i utworzyć folder dla przesłanych plików? Dysk Misskey to najlepsze rozwiązanie dla Ciebie. Bardzo łatwo udostępniać swoje pliki online."
|
||||
adblock:
|
||||
detected: "Spróbuj wyłączyć blokadę reklam."
|
||||
warning: "<strong>Misskey nie zawiera reklam</strong>, ale część funkcji może nie działać prawidłowo z włączonym blokowaniem reklam."
|
||||
application-authorization: "Współpraca aplikacji"
|
||||
close: "Zamknij"
|
||||
load-more: "Załaduj więcej"
|
||||
@@ -98,8 +95,10 @@ common:
|
||||
visibility: "Widoczność"
|
||||
error: "Błąd"
|
||||
enter-username: "Wprowadź nazwę użytkownika"
|
||||
specified-recipient: "Adresat"
|
||||
add-visible-user: "Dodaj użytkownika"
|
||||
username-prompt: "Wprowadź nazwę użytkownika"
|
||||
enter-file-name: "Wprowadź nazwę pliku"
|
||||
weekday-short:
|
||||
sunday: "N"
|
||||
monday: "Pn"
|
||||
@@ -162,6 +161,7 @@ common:
|
||||
note-visibility: "Widoczność wpisów"
|
||||
remember-note-visibility: "Zapamiętaj widoczność wpisów"
|
||||
web-search-engine: "Wyszukiwarka internetowa"
|
||||
paste: "Wklej"
|
||||
line-width: "Szerokości linii"
|
||||
line-width-thin: "Cienka"
|
||||
line-width-normal: "Normalna"
|
||||
@@ -1259,6 +1259,7 @@ pages:
|
||||
room:
|
||||
translate: "Przenieś"
|
||||
save: "Zapisz"
|
||||
saved: "Zapisano"
|
||||
furnitures:
|
||||
moon: "Pn"
|
||||
moon: "Księżyc"
|
||||
bin: "Kosz"
|
||||
|
@@ -12,9 +12,7 @@ common:
|
||||
rich-contents: "Post"
|
||||
rich-contents-desc: "Apenas poste suas ideias, temas do momento e qualquer coisa que você queira compartilhar. Você pode querer decorar suas palavras, anexar suas imagens favoritas, enviar arquivos, inclusive vídeos ou criar uma enquete. Essas são as coisas que você pode fazer em Misskey."
|
||||
reaction: "Reações"
|
||||
adblock:
|
||||
detected: "Por favor, desative o bloqueador de anúncios."
|
||||
warning: "Alguns recursos podem não estar disponíveis ou apresentar mal funcionamento se o bloqueio de anúncios estiver ativado. <strong>Misskey não está usando anúncios</strong>"
|
||||
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません"
|
||||
application-authorization: "Aplicativos autorizados"
|
||||
close: "Fechar"
|
||||
do-not-copy-paste: "Por favor, não digite ou copie o código aqui. A conta pode ser comprometida."
|
||||
|
@@ -18,9 +18,6 @@ common:
|
||||
drive: "Хранилище файлов"
|
||||
drive-desc: "Хотите запостить картинку, которую уже отправляли ранее? Хочется сортировать, переименовать и создать папку для ваших выложенных файлов? Тогда Misskey Drive - это лучшее решение для вас. Очень лёгкий способ делиться своими файлами онлайн."
|
||||
outro: "Попробуйте будущие, уникальные для Misskey функции своими глазами! Если чувствуете, что это не в вашем вкусе, то попробуйте другие инстанции, ведь Misskey - это децентрализованная социальная сеть, так что ты можешь с лёгкостью найти себе товарищей. И наконец, GLHF!"
|
||||
adblock:
|
||||
detected: "Пожалуйста, отключите блокировщик рекламы."
|
||||
warning: "Некоторые функции перестанут быть доступными или будут работать неправильно, если включена блокировка рекламы. <strong>В Misskey нет рекламы</strong>."
|
||||
application-authorization: "Авторизация приложений"
|
||||
close: "Закрыть"
|
||||
do-not-copy-paste: "Пожалуйста, не вводите и не вставляйте сюда код. Аккаунту может угрожать опасность."
|
||||
|
@@ -18,9 +18,6 @@ common:
|
||||
drive: "网盘"
|
||||
drive-desc: "想要发布一张您已经上传过的照片吗?想要管理文件或为上传的文件创建文件夹吗?Misskey的网盘是一个最好的解决方案。"
|
||||
outro: "Misskey还有其他更多功能,请亲身体验一下吧。因为 Misskey 是一个分布式的 SNS,如果您感觉某个功能不适合自己,试试其他的吧。祝您玩得开心!"
|
||||
adblock:
|
||||
detected: "请关闭广告拦截器"
|
||||
warning: "<strong>Misskey不会发布广告</strong>如果您启用广告拦截器, 可能会导致某些功能无法正常使用。"
|
||||
application-authorization: "应用程序授权"
|
||||
close: "关闭"
|
||||
do-not-copy-paste: "请不要在这里输入或粘贴代码。您帐户可能会受到损害。"
|
||||
@@ -133,6 +130,7 @@ common:
|
||||
geolocation-alert: "您的设备不支持定位服务"
|
||||
error: "错误"
|
||||
enter-username: "输入用户名"
|
||||
specified-recipient: "收件人"
|
||||
add-visible-user: "添加用户"
|
||||
cw-placeholder: "评论帖子(可选)"
|
||||
username-prompt: "输入用户名"
|
||||
@@ -246,7 +244,7 @@ common:
|
||||
deck-column-width-wide: "宽"
|
||||
use-shadow: "在UI中使用阴影效果"
|
||||
rounded-corners: "UI界面圆角效果"
|
||||
circle-icons: "使用圆形图标"
|
||||
circle-icons: "使用圆形头像"
|
||||
contrasted-acct: "增加用户名的对比度"
|
||||
wallpaper: "壁纸"
|
||||
choose-wallpaper: "选择壁纸"
|
||||
@@ -390,6 +388,9 @@ common/views/pages/explore.vue:
|
||||
federated: "联合"
|
||||
explore: "查找{host}"
|
||||
users-info: "当前有{users}个注册用户"
|
||||
common/views/components/reactions-viewer.details.vue:
|
||||
few-users: "{users}作出了{reaction}的回应"
|
||||
many-users: "{users}和其他{omitted}人做出了{reaction}的回应"
|
||||
common/views/components/url-preview.vue:
|
||||
enable-player: "打开播放器"
|
||||
disable-player: "关闭播放器"
|
||||
@@ -927,7 +928,7 @@ desktop/views/components/drive.file.vue:
|
||||
copy-url: "复制链接"
|
||||
download: "下载"
|
||||
else-files: "其他"
|
||||
set-as-avatar: "设置为头像"
|
||||
set-as-avatar: "设为头像"
|
||||
set-as-banner: "设置为背景"
|
||||
open-in-app: "在应用程序中打开"
|
||||
add-app: "添加应用"
|
||||
@@ -1817,6 +1818,9 @@ pages:
|
||||
read-page: "查看源"
|
||||
page-created: "页面已创建"
|
||||
page-updated: "页面已更新"
|
||||
name-already-exists: "该页面URL已存在"
|
||||
title-invalid-name: "无效的页面URL"
|
||||
text-invalid-name: "请确认该项不为空"
|
||||
are-you-sure-delete: "是否删除此页面?"
|
||||
page-deleted: "该页面已被删除。"
|
||||
edit-this-page: "编辑此页面"
|
||||
@@ -2092,6 +2096,10 @@ room:
|
||||
exit: "返回"
|
||||
remove: "移除"
|
||||
save: "保存"
|
||||
saved: "已保存"
|
||||
clear: "清理"
|
||||
clear-confirm: "是否清除所有家具?"
|
||||
leave-confirm: "有尚未保存的修改。是否离开?"
|
||||
chooseImage: "选择图片"
|
||||
room-type: "房间类型"
|
||||
carpet-color: "地板颜色"
|
||||
@@ -2120,14 +2128,14 @@ room:
|
||||
piano: "钢琴"
|
||||
facial-tissue: "纸巾盒"
|
||||
server: "服务器"
|
||||
moon: "一"
|
||||
moon: "月球"
|
||||
corkboard: "软木板"
|
||||
mousepad: "鼠标垫"
|
||||
monitor: "显示器"
|
||||
keyboard: "键盘"
|
||||
carpet-stripe: "地毯(条纹)"
|
||||
mat: "垫子"
|
||||
color-box: "彩盒"
|
||||
color-box: "收纳柜"
|
||||
wall-clock: "挂钟"
|
||||
photoframe: "相框"
|
||||
cube: "立方体"
|
||||
@@ -2136,4 +2144,9 @@ room:
|
||||
rubik-cube: "魔方"
|
||||
poster-h: "海报(横向)"
|
||||
poster-v: "海报(纵向)"
|
||||
sofa: "沙发"
|
||||
spiral: "螺旋楼梯"
|
||||
bin: "垃圾箱"
|
||||
cup-noodle: "杯面"
|
||||
holo-display: "全息显示器"
|
||||
energy-drink: "能量饮料"
|
||||
|
@@ -7,8 +7,6 @@ common:
|
||||
rich-contents: "發佈"
|
||||
reaction: "回應"
|
||||
drive: "雲端硬碟"
|
||||
adblock:
|
||||
detected: "請禁用廣告封鎖器"
|
||||
close: "關閉"
|
||||
enter-password: "請輸入密碼"
|
||||
2fa: "雙重身份驗證"
|
||||
|
45
package.json
45
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "11.29.0",
|
||||
"version": "11.32.0",
|
||||
"codename": "daybreak",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -100,17 +100,17 @@
|
||||
"@typescript-eslint/parser": "1.11.0",
|
||||
"agentkeepalive": "4.0.2",
|
||||
"animejs": "3.1.0",
|
||||
"apexcharts": "3.8.4",
|
||||
"apexcharts": "3.8.6",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "4.0.2",
|
||||
"autwh": "0.1.0",
|
||||
"aws-sdk": "2.512.0",
|
||||
"aws-sdk": "2.525.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bootstrap": "4.3.1",
|
||||
"bootstrap-vue": "2.0.0-rc.13",
|
||||
"bull": "3.10.0",
|
||||
"cafy": "15.1.1",
|
||||
"cbor": "4.2.1",
|
||||
"cbor": "4.3.0",
|
||||
"chai": "4.2.0",
|
||||
"chalk": "2.4.2",
|
||||
"cli-highlight": "2.1.1",
|
||||
@@ -120,7 +120,7 @@
|
||||
"css-loader": "3.2.0",
|
||||
"cssnano": "4.1.10",
|
||||
"dateformat": "3.0.3",
|
||||
"deep-equal": "1.0.1",
|
||||
"deep-equal": "1.1.0",
|
||||
"diskusage": "1.1.3",
|
||||
"double-ended-queue": "2.1.0-0",
|
||||
"emojilib": "2.4.0",
|
||||
@@ -128,12 +128,11 @@
|
||||
"eslint-plugin-vue": "5.2.3",
|
||||
"eventemitter3": "4.0.0",
|
||||
"feed": "3.0.0",
|
||||
"file-type": "12.1.0",
|
||||
"file-type": "12.3.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"fuckadblock": "3.2.1",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
"gulp-imagemin": "6.0.0",
|
||||
"gulp-imagemin": "6.1.0",
|
||||
"gulp-mocha": "6.0.0",
|
||||
"gulp-rename": "1.4.0",
|
||||
"gulp-replace": "1.0.0",
|
||||
@@ -156,7 +155,7 @@
|
||||
"json5-loader": "3.0.0",
|
||||
"jsrsasign": "8.0.12",
|
||||
"katex": "0.11.0",
|
||||
"koa": "2.7.0",
|
||||
"koa": "2.8.1",
|
||||
"koa-bodyparser": "4.2.1",
|
||||
"koa-compress": "3.0.0",
|
||||
"koa-favicon": "2.0.1",
|
||||
@@ -167,7 +166,7 @@
|
||||
"koa-router": "7.4.0",
|
||||
"koa-send": "5.0.0",
|
||||
"koa-slow": "2.1.0",
|
||||
"koa-views": "6.2.0",
|
||||
"koa-views": "6.2.1",
|
||||
"langmap": "0.0.16",
|
||||
"loader-utils": "1.2.3",
|
||||
"lolex": "4.2.0",
|
||||
@@ -198,8 +197,9 @@
|
||||
"randomcolor": "0.5.4",
|
||||
"ratelimiter": "3.3.1",
|
||||
"recaptcha-promise": "0.1.3",
|
||||
"reconnecting-websocket": "4.1.10",
|
||||
"reconnecting-websocket": "4.2.0",
|
||||
"redis": "2.8.0",
|
||||
"redis-lock": "0.1.4",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rename": "1.0.4",
|
||||
"request": "2.88.0",
|
||||
@@ -216,14 +216,15 @@
|
||||
"speakeasy": "2.0.0",
|
||||
"stringz": "2.0.0",
|
||||
"style-loader": "0.23.1",
|
||||
"stylus": "0.54.5",
|
||||
"stylus": "0.54.7",
|
||||
"stylus-loader": "3.0.2",
|
||||
"summaly": "2.3.0",
|
||||
"systeminformation": "4.14.4",
|
||||
"summaly": "2.3.1",
|
||||
"syslog-pro": "1.0.0",
|
||||
"systeminformation": "4.14.8",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"terser-webpack-plugin": "1.4.1",
|
||||
"terser-webpack-plugin": "2.0.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.107.0",
|
||||
"three": "0.108.0",
|
||||
"tinycolor2": "1.4.1",
|
||||
"tmp": "0.1.0",
|
||||
"ts-loader": "5.3.3",
|
||||
@@ -235,7 +236,7 @@
|
||||
"uglify-es": "3.3.9",
|
||||
"ulid": "2.3.0",
|
||||
"url-loader": "2.1.0",
|
||||
"uuid": "3.3.2",
|
||||
"uuid": "3.3.3",
|
||||
"v-animate-css": "0.0.3",
|
||||
"v-debounce": "0.1.2",
|
||||
"vue": "2.6.10",
|
||||
@@ -244,22 +245,22 @@
|
||||
"vue-cropperjs": "4.0.0",
|
||||
"vue-i18n": "8.14.0",
|
||||
"vue-js-modal": "1.3.31",
|
||||
"vue-json-pretty": "1.6.0",
|
||||
"vue-json-pretty": "1.6.2",
|
||||
"vue-loader": "15.7.1",
|
||||
"vue-marquee-text-component": "1.1.1",
|
||||
"vue-prism-component": "1.1.1",
|
||||
"vue-router": "3.1.2",
|
||||
"vue-router": "3.1.3",
|
||||
"vue-sequential-entrance": "1.1.3",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-svg-inline-loader": "1.2.16",
|
||||
"vue-svg-inline-loader": "1.2.18",
|
||||
"vue-template-compiler": "2.6.10",
|
||||
"vuedraggable": "2.23.0",
|
||||
"vuewordcloud": "18.7.11",
|
||||
"vuex": "3.1.1",
|
||||
"vuex-persistedstate": "2.5.4",
|
||||
"web-push": "3.3.5",
|
||||
"webpack": "4.39.2",
|
||||
"webpack-cli": "3.3.7",
|
||||
"webpack": "4.39.3",
|
||||
"webpack-cli": "3.3.8",
|
||||
"websocket": "1.0.29",
|
||||
"ws": "7.1.2",
|
||||
"xev": "2.0.1"
|
||||
|
@@ -159,7 +159,7 @@ async function init(): Promise<Config> {
|
||||
return config;
|
||||
}
|
||||
|
||||
async function spawnWorkers(limit: number = Infinity) {
|
||||
async function spawnWorkers(limit: number = 1) {
|
||||
const workers = Math.min(limit, os.cpus().length);
|
||||
bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`);
|
||||
await Promise.all([...Array(workers)].map(spawnWorker));
|
||||
|
@@ -19,7 +19,8 @@ init(launch => {
|
||||
mode: 'history',
|
||||
base: '/admin/',
|
||||
routes: [
|
||||
{ path: '/', component: Index },
|
||||
{ path: '/:page', component: Index },
|
||||
{ path: '/', redirect: '/dashboard' },
|
||||
{ path: '*', component: NotFound }
|
||||
]
|
||||
});
|
||||
|
@@ -18,18 +18,18 @@
|
||||
<p class="name"><mk-user-name :user="$store.state.i"/></p>
|
||||
</div>
|
||||
<ul>
|
||||
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</li>
|
||||
<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
|
||||
<li @click="nav('queue')" :class="{ active: page == 'queue' }"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</li>
|
||||
<li @click="nav('logs')" :class="{ active: page == 'logs' }"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</li>
|
||||
<li @click="nav('db')" :class="{ active: page == 'db' }"><fa :icon="faDatabase" fixed-width/>{{ $t('db') }}</li>
|
||||
<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
|
||||
<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
|
||||
<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li>
|
||||
<li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faGlobe" fixed-width/>{{ $t('federation') }}</li>
|
||||
<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li>
|
||||
<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li>
|
||||
<li @click="nav('abuse')" :class="{ active: page == 'abuse' }"><fa :icon="faExclamationCircle" fixed-width/>{{ $t('abuse') }}</li>
|
||||
<li><router-link to="/dashboard" active-class="active"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</router-link></li>
|
||||
<li><router-link to="/instance" active-class="active"><fa icon="cog" fixed-width/>{{ $t('instance') }}</router-link></li>
|
||||
<li><router-link to="/queue" active-class="active"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</router-link></li>
|
||||
<li><router-link to="/logs" active-class="active"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</router-link></li>
|
||||
<li><router-link to="/db" active-class="active"><fa :icon="faDatabase" fixed-width/>{{ $t('db') }}</router-link></li>
|
||||
<li><router-link to="/moderators" active-class="active"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</router-link></li>
|
||||
<li><router-link to="/users" active-class="active"><fa icon="users" fixed-width/>{{ $t('users') }}</router-link></li>
|
||||
<li><router-link to="/drive" active-class="active"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</router-link></li>
|
||||
<li><router-link to="/federation" active-class="active"><fa :icon="faGlobe" fixed-width/>{{ $t('federation') }}</router-link></li>
|
||||
<li><router-link to="/emoji" active-class="active"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</router-link></li>
|
||||
<li><router-link to="/announcements" active-class="active"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</router-link></li>
|
||||
<li><router-link to="/abuse" active-class="active"><fa :icon="faExclamationCircle" fixed-width/>{{ $t('abuse') }}</router-link></li>
|
||||
</ul>
|
||||
<div class="back-to-misskey">
|
||||
<a href="/"><fa :icon="faArrowLeft"/> {{ $t('back-to-misskey') }}</a>
|
||||
@@ -102,7 +102,6 @@ export default Vue.extend({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: 'dashboard',
|
||||
version,
|
||||
isMobile,
|
||||
navOpend: !isMobile,
|
||||
@@ -116,9 +115,9 @@ export default Vue.extend({
|
||||
faDatabase,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
nav(page: string) {
|
||||
this.page = page;
|
||||
computed: {
|
||||
page() {
|
||||
return this.$route.params.page;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -240,11 +239,10 @@ export default Vue.extend({
|
||||
list-style none
|
||||
font-size 15px
|
||||
|
||||
> li
|
||||
> li > a
|
||||
display block
|
||||
padding 10px 16px
|
||||
margin 0
|
||||
cursor pointer
|
||||
user-select none
|
||||
color #eee
|
||||
transition margin-left 0.2s ease
|
||||
|
@@ -1,18 +0,0 @@
|
||||
declare const fuckAdBlock: any;
|
||||
|
||||
export default ($root: any) => {
|
||||
require('fuckadblock');
|
||||
|
||||
function adBlockDetected() {
|
||||
$root.dialog({
|
||||
title: $root.$t('@.adblock.detected'),
|
||||
text: $root.$t('@.adblock.warning')
|
||||
});
|
||||
}
|
||||
|
||||
if (fuckAdBlock === undefined) {
|
||||
adBlockDetected();
|
||||
} else {
|
||||
fuckAdBlock.onDetected(adBlockDetected);
|
||||
}
|
||||
};
|
@@ -27,7 +27,8 @@ export default (opts: Opts = {}) => ({
|
||||
data() {
|
||||
return {
|
||||
showContent: false,
|
||||
hideThisNote: false
|
||||
hideThisNote: false,
|
||||
openingMenu: false
|
||||
};
|
||||
},
|
||||
|
||||
@@ -192,11 +193,16 @@ export default (opts: Opts = {}) => ({
|
||||
},
|
||||
|
||||
menu(viaKeyboard = false) {
|
||||
if (this.openingMenu) return;
|
||||
this.openingMenu = true;
|
||||
this.$root.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.appearNote,
|
||||
animation: !viaKeyboard
|
||||
}).$once('closed', this.focus);
|
||||
}).$once('closed', () => {
|
||||
this.openingMenu = false;
|
||||
this.focus();
|
||||
});
|
||||
},
|
||||
|
||||
toggleShowContent() {
|
||||
|
@@ -153,6 +153,10 @@ export default (opts) => ({
|
||||
// デフォルト公開範囲
|
||||
this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility);
|
||||
|
||||
if (this.reply && this.reply.localOnly) {
|
||||
this.localOnly = true;
|
||||
}
|
||||
|
||||
// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
|
||||
if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) {
|
||||
this.visibility = this.reply.visibility;
|
||||
@@ -162,13 +166,13 @@ export default (opts) => ({
|
||||
}).then(users => {
|
||||
this.visibleUsers.push(...users);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.reply && this.reply.userId !== this.$store.state.i.id) {
|
||||
this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
|
||||
this.visibleUsers.push(user);
|
||||
});
|
||||
if (this.reply.userId !== this.$store.state.i.id) {
|
||||
this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
|
||||
this.visibleUsers.push(user);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keep cw when reply
|
||||
@@ -199,8 +203,9 @@ export default (opts) => ({
|
||||
this.$emit('change-attached-files', this.files);
|
||||
}
|
||||
}
|
||||
|
||||
// 削除して編集
|
||||
if (this.initialNote) {
|
||||
// 削除して編集
|
||||
const init = this.initialNote;
|
||||
this.text = init.text ? init.text : '';
|
||||
this.files = init.files;
|
||||
@@ -318,7 +323,7 @@ export default (opts) => ({
|
||||
setVisibility() {
|
||||
const w = this.$root.new(MkVisibilityChooser, {
|
||||
source: this.$refs.visibilityButton,
|
||||
currentVisibility: this.visibility
|
||||
currentVisibility: this.localOnly ? `local-${this.visibility}` : this.visibility
|
||||
});
|
||||
w.$once('chosen', v => {
|
||||
this.applyVisibility(v);
|
||||
|
@@ -279,7 +279,15 @@
|
||||
},
|
||||
{
|
||||
id: "pinguin",
|
||||
place: "floor"
|
||||
place: "floor",
|
||||
props: {
|
||||
body: 'color',
|
||||
belly: 'color'
|
||||
},
|
||||
color: {
|
||||
Body: 'body',
|
||||
Belly: 'belly',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "rubik-cube",
|
||||
@@ -351,4 +359,39 @@
|
||||
Bin: 'color'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "cup-noodle",
|
||||
place: "floor"
|
||||
},
|
||||
{
|
||||
id: "holo-display",
|
||||
place: "floor",
|
||||
props: {
|
||||
image: 'image'
|
||||
},
|
||||
texture: {
|
||||
Image_Front: {
|
||||
prop: 'image',
|
||||
uv: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
},
|
||||
},
|
||||
Image_Back: {
|
||||
prop: 'image',
|
||||
uv: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'energy-drink',
|
||||
place: "floor",
|
||||
}
|
||||
]
|
||||
|
@@ -102,7 +102,6 @@ export class Room {
|
||||
this.renderer.autoClear = false;
|
||||
this.renderer.setClearColor(new THREE.Color(0x051f2d));
|
||||
this.renderer.shadowMap.enabled = this.enableShadow;
|
||||
this.renderer.gammaOutput = true;
|
||||
this.renderer.shadowMap.type =
|
||||
this.graphicsQuality === 'ultra' ? THREE.PCFSoftShadowMap :
|
||||
this.graphicsQuality === 'high' ? THREE.PCFSoftShadowMap :
|
||||
@@ -343,7 +342,8 @@ export class Room {
|
||||
|
||||
@autobind
|
||||
private loadRoom() {
|
||||
new GLTFLoader().load(`/assets/room/rooms/${this.roomInfo.roomType}/${this.roomInfo.roomType}.glb`, gltf => {
|
||||
const type = this.roomInfo.roomType;
|
||||
new GLTFLoader().load(`/assets/room/rooms/${type}/${type}.glb`, gltf => {
|
||||
gltf.scene.traverse(child => {
|
||||
if (!(child instanceof THREE.Mesh)) return;
|
||||
|
||||
@@ -429,34 +429,42 @@ export class Room {
|
||||
private applyCarpetColor() {
|
||||
this.roomObj.traverse(child => {
|
||||
if (!(child instanceof THREE.Mesh)) return;
|
||||
if (child.material && (child.material as THREE.MeshStandardMaterial).name && (child.material as THREE.MeshStandardMaterial).name === 'Carpet') {
|
||||
(child.material as THREE.MeshStandardMaterial).color.setHex(parseInt(this.roomInfo.carpetColor.substr(1), 16));
|
||||
if (child.material &&
|
||||
(child.material as THREE.MeshStandardMaterial).name &&
|
||||
(child.material as THREE.MeshStandardMaterial).name === 'Carpet'
|
||||
) {
|
||||
const colorHex = parseInt(this.roomInfo.carpetColor.substr(1), 16);
|
||||
(child.material as THREE.MeshStandardMaterial).color.setHex(colorHex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
public applyCustomColor(model: THREE.Object3D) {
|
||||
private applyCustomColor(model: THREE.Object3D) {
|
||||
const furniture = this.furnitures.find(furniture => furniture.id === model.name);
|
||||
const def = furnitureDefs.find(d => d.id === furniture.type);
|
||||
if (def.color == null) return;
|
||||
model.traverse(child => {
|
||||
if (!(child instanceof THREE.Mesh)) return;
|
||||
for (const t of Object.keys(def.color)) {
|
||||
if (!child.material || !(child.material as THREE.MeshStandardMaterial).name || (child.material as THREE.MeshStandardMaterial).name !== t) continue;
|
||||
if (!child.material ||
|
||||
!(child.material as THREE.MeshStandardMaterial).name ||
|
||||
(child.material as THREE.MeshStandardMaterial).name !== t
|
||||
) continue;
|
||||
|
||||
const prop = def.color[t];
|
||||
const val = furniture.props ? furniture.props[prop] : undefined;
|
||||
|
||||
if (val == null) continue;
|
||||
|
||||
(child.material as THREE.MeshStandardMaterial).color.setHex(parseInt(val.substr(1), 16));
|
||||
const colorHex = parseInt(val.substr(1), 16);
|
||||
(child.material as THREE.MeshStandardMaterial).color.setHex(colorHex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
public applyCustomTexture(model: THREE.Object3D) {
|
||||
private applyCustomTexture(model: THREE.Object3D) {
|
||||
const furniture = this.furnitures.find(furniture => furniture.id === model.name);
|
||||
const def = furnitureDefs.find(d => d.id === furniture.type);
|
||||
if (def.texture == null) return;
|
||||
@@ -487,7 +495,9 @@ export class Room {
|
||||
const uvInfo = def.texture[t].uv;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height, uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height);
|
||||
ctx.drawImage(img,
|
||||
0, 0, img.width, img.height,
|
||||
uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height);
|
||||
|
||||
const texture = new THREE.Texture(canvas);
|
||||
texture.wrapS = THREE.RepeatWrapping;
|
||||
@@ -608,6 +618,10 @@ export class Room {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 家具の移動/回転モードにします
|
||||
* @param type 移動か回転か
|
||||
*/
|
||||
@autobind
|
||||
public enterTransformMode(type: 'translate' | 'rotate') {
|
||||
this.isTransformMode = true;
|
||||
@@ -615,12 +629,20 @@ export class Room {
|
||||
this.furnitureControl.attach(this.selectedObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 家具の移動/回転モードを終了します
|
||||
*/
|
||||
@autobind
|
||||
public exitTransformMode() {
|
||||
this.isTransformMode = false;
|
||||
this.furnitureControl.detach();
|
||||
}
|
||||
|
||||
/**
|
||||
* 家具プロパティを更新します
|
||||
* @param key プロパティ名
|
||||
* @param value 値
|
||||
*/
|
||||
@autobind
|
||||
public updateProp(key: string, value: any) {
|
||||
const furniture = this.furnitures.find(furniture => furniture.id === this.selectedObject.name);
|
||||
@@ -630,6 +652,10 @@ export class Room {
|
||||
this.applyCustomTexture(this.selectedObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 部屋に家具を追加します
|
||||
* @param type 家具の種類
|
||||
*/
|
||||
@autobind
|
||||
public addFurniture(type: string) {
|
||||
const furniture = {
|
||||
@@ -655,6 +681,9 @@ export class Room {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 現在選択されている家具を部屋から削除します
|
||||
*/
|
||||
@autobind
|
||||
public removeFurniture() {
|
||||
this.exitTransformMode();
|
||||
@@ -666,12 +695,35 @@ export class Room {
|
||||
this.onChangeSelect(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全ての家具を部屋から削除します
|
||||
*/
|
||||
@autobind
|
||||
public removeAllFurnitures() {
|
||||
this.exitTransformMode();
|
||||
for (const obj of this.objects) {
|
||||
this.scene.remove(obj);
|
||||
}
|
||||
this.objects = [];
|
||||
this.furnitures = [];
|
||||
this.selectedObject = null;
|
||||
this.onChangeSelect(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 部屋の床の色を変更します
|
||||
* @param color 色
|
||||
*/
|
||||
@autobind
|
||||
public updateCarpetColor(color: string) {
|
||||
this.roomInfo.carpetColor = color;
|
||||
this.applyCarpetColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 部屋の種類を変更します
|
||||
* @param type 種類
|
||||
*/
|
||||
@autobind
|
||||
public changeRoomType(type: string) {
|
||||
this.roomInfo.roomType = type;
|
||||
@@ -679,6 +731,9 @@ export class Room {
|
||||
this.loadRoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 部屋データを取得します
|
||||
*/
|
||||
@autobind
|
||||
public getRoomInfo() {
|
||||
for (const obj of this.objects) {
|
||||
@@ -694,6 +749,9 @@ export class Room {
|
||||
return this.roomInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 選択されている家具を取得します
|
||||
*/
|
||||
@autobind
|
||||
public getSelectedObject() {
|
||||
return this.selectedObject;
|
||||
@@ -704,9 +762,15 @@ export class Room {
|
||||
return this.furnitures.find(furniture => furniture.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* レンダリングを終了します
|
||||
*/
|
||||
@autobind
|
||||
public destroy() {
|
||||
// Stop render loop
|
||||
window.cancelAnimationFrame(this.renderFrameRequestId);
|
||||
|
||||
this.controls.dispose();
|
||||
this.scene.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<prism :inline="inline" :language="lang || 'js'">{{ code }}</prism>
|
||||
<x-prism :inline="inline" :language="prismLang">{{ code }}</x-prism>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import 'prismjs';
|
||||
import 'prismjs/themes/prism-okaidia.css';
|
||||
import Prism from 'vue-prism-component';
|
||||
import XPrism from 'vue-prism-component';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
Prism
|
||||
XPrism
|
||||
},
|
||||
props: {
|
||||
code: {
|
||||
@@ -25,6 +25,12 @@ export default Vue.extend({
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
prismLang() {
|
||||
return Prism.languages[this.lang] ? this.lang : 'js';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -57,7 +57,8 @@ export default Vue.extend({
|
||||
},
|
||||
fit: {
|
||||
type: String,
|
||||
required: true
|
||||
required: false,
|
||||
default: 'cover'
|
||||
},
|
||||
detail: {
|
||||
type: Boolean,
|
||||
|
@@ -35,7 +35,8 @@ export default Vue.extend({
|
||||
mounted() {
|
||||
//#region for Safari bug
|
||||
if (this.$refs.grid) {
|
||||
this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px` : '128px';
|
||||
this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px`
|
||||
: this.$store.state.device.inDeckMode ? '128px' : this.$root.isMobile ? '173px' : '287px';
|
||||
}
|
||||
//#endregion
|
||||
},
|
||||
|
@@ -139,6 +139,7 @@ export default Vue.extend({
|
||||
cursor pointer
|
||||
|
||||
> .content
|
||||
max-width 100%
|
||||
|
||||
> .is-deleted
|
||||
display block
|
||||
@@ -155,6 +156,7 @@ export default Vue.extend({
|
||||
padding 8px 16px
|
||||
overflow hidden
|
||||
overflow-wrap break-word
|
||||
word-break break-word
|
||||
font-size 1em
|
||||
color rgba(#000, 0.8)
|
||||
|
||||
|
@@ -227,6 +227,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
closed() {
|
||||
this.$emit('closed');
|
||||
this.$nextTick(() => {
|
||||
this.destroyDom();
|
||||
});
|
||||
|
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<transition name="zoom-in-top">
|
||||
<div class="buebdbiu" ref="popover" v-if="show">
|
||||
<i18n path="few-users" v-if="users.length <= 10">
|
||||
<span slot="users">
|
||||
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
|
||||
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
|
||||
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
|
||||
</b>
|
||||
</span>
|
||||
<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
|
||||
</i18n>
|
||||
<i18n path="many-users" v-if="10 < users.length">
|
||||
<span slot="users">
|
||||
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
|
||||
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
|
||||
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
|
||||
</b>
|
||||
</span>
|
||||
<span slot="omitted">{{ count - 10 }}</span>
|
||||
<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
|
||||
</i18n>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/reactions-viewer.details.vue'),
|
||||
props: {
|
||||
reaction: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
users: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
source: {
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.show = true;
|
||||
|
||||
this.$nextTick(() => {
|
||||
const popover = this.$refs.popover as any;
|
||||
|
||||
if (this.source == null) {
|
||||
this.destroyDom();
|
||||
return;
|
||||
}
|
||||
const rect = this.source.getBoundingClientRect();
|
||||
|
||||
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
|
||||
const y = rect.top + window.pageYOffset + this.source.offsetHeight;
|
||||
popover.style.left = (x - 28) + 'px';
|
||||
popover.style.top = (y + 16) + 'px';
|
||||
});
|
||||
}
|
||||
methods: {
|
||||
close() {
|
||||
this.show = false;
|
||||
setTimeout(this.destroyDom, 300);
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.buebdbiu
|
||||
$bgcolor = var(--popupBg)
|
||||
z-index 10000
|
||||
display block
|
||||
position absolute
|
||||
max-width 240px
|
||||
font-size 0.8em
|
||||
padding 6px 8px
|
||||
background $bgcolor
|
||||
text-align center
|
||||
color var(--text)
|
||||
border-radius 4px
|
||||
box-shadow 0 var(--lineWidth) 4px rgba(#000, 0.25)
|
||||
pointer-events none
|
||||
transform-origin center -16px
|
||||
|
||||
&:before
|
||||
content ""
|
||||
pointer-events none
|
||||
display block
|
||||
position absolute
|
||||
top -28px
|
||||
left 12px
|
||||
border-top solid 14px transparent
|
||||
border-right solid 14px transparent
|
||||
border-bottom solid 14px rgba(#000, 0.1)
|
||||
border-left solid 14px transparent
|
||||
|
||||
&:after
|
||||
content ""
|
||||
pointer-events none
|
||||
display block
|
||||
position absolute
|
||||
top -27px
|
||||
left 12px
|
||||
border-top solid 14px transparent
|
||||
border-right solid 14px transparent
|
||||
border-bottom solid 14px $bgcolor
|
||||
border-left solid 14px transparent
|
||||
</style>
|
@@ -5,6 +5,9 @@
|
||||
@click="toggleReaction(reaction)"
|
||||
v-if="count > 0"
|
||||
v-particle="!isMe"
|
||||
@mouseover="onMouseover"
|
||||
@mouseleave="onMouseleave"
|
||||
ref="reaction"
|
||||
>
|
||||
<mk-reaction-icon :reaction="reaction" ref="icon"/>
|
||||
<span>{{ count }}</span>
|
||||
@@ -15,6 +18,7 @@
|
||||
import Vue from 'vue';
|
||||
import Icon from './reaction-icon.vue';
|
||||
import anime from 'animejs';
|
||||
import XDetails from './reactions-viewer.details.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
@@ -26,6 +30,10 @@ export default Vue.extend({
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
isInitial: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
note: {
|
||||
type: Object,
|
||||
required: true,
|
||||
@@ -36,14 +44,25 @@ export default Vue.extend({
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
details: null,
|
||||
detailsTimeoutId: null,
|
||||
isHovering: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isMe(): boolean {
|
||||
return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (!this.isInitial) this.anime();
|
||||
},
|
||||
watch: {
|
||||
count() {
|
||||
this.anime();
|
||||
count(newCount, oldCount) {
|
||||
if (oldCount < newCount) this.anime();
|
||||
if (this.details != null) this.openDetails();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -70,11 +89,49 @@ export default Vue.extend({
|
||||
});
|
||||
}
|
||||
},
|
||||
onMouseover() {
|
||||
this.isHovering = true;
|
||||
this.detailsTimeoutId = setTimeout(this.openDetails, 300);
|
||||
},
|
||||
onMouseleave() {
|
||||
this.isHovering = false;
|
||||
clearTimeout(this.detailsTimeoutId);
|
||||
this.closeDetails();
|
||||
},
|
||||
openDetails() {
|
||||
if (this.$root.isMobile) return;
|
||||
this.$root.api('notes/reactions', {
|
||||
noteId: this.note.id,
|
||||
type: this.reaction,
|
||||
limit: 11
|
||||
}).then((reactions: any[]) => {
|
||||
const users = reactions
|
||||
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
|
||||
.map(x => x.user);
|
||||
|
||||
this.closeDetails();
|
||||
if (!this.isHovering) return;
|
||||
this.details = this.$root.new(XDetails, {
|
||||
reaction: this.reaction,
|
||||
users,
|
||||
count: this.count,
|
||||
source: this.$refs.reaction
|
||||
});
|
||||
});
|
||||
},
|
||||
closeDetails() {
|
||||
if (this.details != null) {
|
||||
this.details.close();
|
||||
this.details = null;
|
||||
}
|
||||
},
|
||||
anime() {
|
||||
if (this.$store.state.device.reduceMotion) return;
|
||||
if (document.hidden) return;
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.icon == null) return;
|
||||
|
||||
const rect = this.$refs.icon.$el.getBoundingClientRect();
|
||||
|
||||
const x = rect.left;
|
||||
@@ -120,6 +177,14 @@ export default Vue.extend({
|
||||
border-radius 4px
|
||||
cursor pointer
|
||||
|
||||
&, *
|
||||
-webkit-touch-callout none
|
||||
-webkit-user-select none
|
||||
-khtml-user-select none
|
||||
-moz-user-select none
|
||||
-ms-user-select none
|
||||
user-select none
|
||||
|
||||
*
|
||||
user-select none
|
||||
pointer-events none
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mk-reactions-viewer" :class="{ isMe }">
|
||||
<x-reaction v-for="(count, reaction) in reactions" :reaction="reaction" :count="count" :note="note" :key="reaction"/>
|
||||
<x-reaction v-for="(count, reaction) in note.reactions" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note" :key="reaction"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -12,6 +12,11 @@ export default Vue.extend({
|
||||
components: {
|
||||
XReaction
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialReactions: new Set(Object.keys(this.note.reactions))
|
||||
};
|
||||
},
|
||||
props: {
|
||||
note: {
|
||||
type: Object,
|
||||
@@ -19,9 +24,6 @@ export default Vue.extend({
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
reactions(): any {
|
||||
return this.note.reactions;
|
||||
},
|
||||
isMe(): boolean {
|
||||
return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId;
|
||||
},
|
||||
|
@@ -64,8 +64,7 @@ export default Vue.extend({
|
||||
methods: {
|
||||
onMousedown(e: MouseEvent) {
|
||||
function distance(p, q) {
|
||||
const sqrt = Math.sqrt, pow = Math.pow;
|
||||
return sqrt(pow(p.x - q.x, 2) + pow(p.y - q.y, 2));
|
||||
return Math.hypot(p.x - q.x, p.y - q.y);
|
||||
}
|
||||
|
||||
function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY) {
|
||||
|
@@ -49,6 +49,7 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
if (this.disabled) return;
|
||||
this.$emit('change', !this.checked);
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@
|
||||
<div class="description" v-if="user.description" :title="user.description">
|
||||
<mfm :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :plain="true" :nowrap="true"/>
|
||||
</div>
|
||||
<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
|
||||
<mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
|
||||
</div>
|
||||
</div>
|
||||
<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore()" :disabled="moreFetching">
|
||||
@@ -134,7 +134,7 @@ export default Vue.extend({
|
||||
font-size 14px
|
||||
padding-right 40px
|
||||
|
||||
> .follow-button
|
||||
> .koudoku-button
|
||||
position absolute
|
||||
top 8px
|
||||
right 0
|
||||
|
@@ -220,37 +220,48 @@ export default Vue.extend({
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
const options = {
|
||||
title: this.title.trim(),
|
||||
name: this.name.trim(),
|
||||
summary: this.summary,
|
||||
font: this.font,
|
||||
hideTitleWhenPinned: this.hideTitleWhenPinned,
|
||||
alignCenter: this.alignCenter,
|
||||
content: this.content,
|
||||
variables: this.variables,
|
||||
eyeCatchingImageId: this.eyeCatchingImageId,
|
||||
};
|
||||
|
||||
const onError = err => {
|
||||
if (err.id == '3d81ceae-475f-4600-b2a8-2bc116157532') {
|
||||
if (err.info.param == 'name') {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
title: this.$t('title-invalid-name'),
|
||||
text: this.$t('text-invalid-name')
|
||||
});
|
||||
}
|
||||
} else if (err.code == 'NAME_ALREADY_EXISTS') {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: this.$t('name-already-exists')
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (this.pageId) {
|
||||
this.$root.api('pages/update', {
|
||||
pageId: this.pageId,
|
||||
title: this.title.trim(),
|
||||
name: this.name.trim(),
|
||||
summary: this.summary,
|
||||
font: this.font,
|
||||
hideTitleWhenPinned: this.hideTitleWhenPinned,
|
||||
alignCenter: this.alignCenter,
|
||||
content: this.content,
|
||||
variables: this.variables,
|
||||
eyeCatchingImageId: this.eyeCatchingImageId,
|
||||
}).then(page => {
|
||||
options.pageId = this.pageId;
|
||||
this.$root.api('pages/update', options)
|
||||
.then(page => {
|
||||
this.currentName = this.name.trim();
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
text: this.$t('page-updated')
|
||||
});
|
||||
});
|
||||
}).catch(onError);
|
||||
} else {
|
||||
this.$root.api('pages/create', {
|
||||
title: this.title.trim(),
|
||||
name: this.name.trim(),
|
||||
summary: this.summary,
|
||||
font: this.font,
|
||||
hideTitleWhenPinned: this.hideTitleWhenPinned,
|
||||
alignCenter: this.alignCenter,
|
||||
content: this.content,
|
||||
variables: this.variables,
|
||||
eyeCatchingImageId: this.eyeCatchingImageId,
|
||||
}).then(page => {
|
||||
this.$root.api('pages/create', options)
|
||||
.then(page => {
|
||||
this.pageId = page.id;
|
||||
this.currentName = this.name.trim();
|
||||
this.$root.dialog({
|
||||
@@ -258,7 +269,7 @@ export default Vue.extend({
|
||||
text: this.$t('page-created')
|
||||
});
|
||||
this.$router.push(`/i/pages/edit/${this.pageId}`);
|
||||
});
|
||||
}).catch(onError);
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -10,7 +10,8 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
selected: null,
|
||||
objectHeight: 0
|
||||
objectHeight: 0,
|
||||
orbitRadius: 5
|
||||
};
|
||||
},
|
||||
|
||||
@@ -57,9 +58,9 @@ export default Vue.extend({
|
||||
const timer = Date.now() * 0.0004;
|
||||
requestAnimationFrame(render);
|
||||
|
||||
camera.position.y = 2 + this.objectHeight / 2;
|
||||
camera.position.z = Math.cos(timer) * 10;
|
||||
camera.position.x = Math.sin(timer) * 10;
|
||||
camera.position.y = Math.sin(Math.PI / 6) * this.orbitRadius; // Math.PI / 6 => 30deg
|
||||
camera.position.z = Math.cos(timer) * this.orbitRadius;
|
||||
camera.position.x = Math.sin(timer) * this.orbitRadius;
|
||||
camera.lookAt(new THREE.Vector3(0, this.objectHeight / 2, 0));
|
||||
renderer.render(scene, camera);
|
||||
};
|
||||
@@ -89,6 +90,13 @@ export default Vue.extend({
|
||||
});
|
||||
const objectBoundingBox = new THREE.Box3().setFromObject(obj);
|
||||
this.objectHeight = objectBoundingBox.max.y - objectBoundingBox.min.y;
|
||||
|
||||
const objectWidth = objectBoundingBox.max.x - objectBoundingBox.min.x;
|
||||
const objectDepth = objectBoundingBox.max.z - objectBoundingBox.min.z;
|
||||
|
||||
const horizontal = Math.hypot(objectWidth, objectDepth) / camera.aspect;
|
||||
this.orbitRadius = Math.max(horizontal, this.objectHeight) * camera.zoom * 0.625 / Math.tan(camera.fov * 0.5 * (Math.PI / 180));
|
||||
|
||||
scene.add(obj);
|
||||
};
|
||||
|
||||
|
@@ -42,7 +42,8 @@
|
||||
</label>
|
||||
</section>
|
||||
<section>
|
||||
<ui-button primary @click="save()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
|
||||
<ui-button :primary="changed" @click="save()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
|
||||
<ui-button @click="clear()"><fa :icon="faBroom"/> {{ $t('clear') }}</ui-button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,7 +56,7 @@ import { Room } from '../../../scripts/room/room';
|
||||
import parseAcct from '../../../../../../misc/acct/parse';
|
||||
import XPreview from './preview.vue';
|
||||
const storeItems = require('../../../scripts/room/furnitures.json5');
|
||||
import { faBoxOpen, faUndo, faArrowsAlt, faBan } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBoxOpen, faUndo, faArrowsAlt, faBan, faBroom } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSave, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
|
||||
import { query as urlQuery } from '../../../../../../prelude/url';
|
||||
|
||||
@@ -86,11 +87,14 @@ export default Vue.extend({
|
||||
isTranslateMode: false,
|
||||
isRotateMode: false,
|
||||
isMyRoom: false,
|
||||
faBoxOpen, faSave, faTrashAlt, faUndo, faArrowsAlt, faBan,
|
||||
changed: false,
|
||||
faBoxOpen, faSave, faTrashAlt, faUndo, faArrowsAlt, faBan, faBroom,
|
||||
};
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
window.addEventListener('beforeunload', this.beforeunload);
|
||||
|
||||
const user = await this.$root.api('users/show', {
|
||||
...parseAcct(this.acct)
|
||||
});
|
||||
@@ -124,11 +128,37 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.changed) {
|
||||
this.$root.dialog({
|
||||
type: 'warning',
|
||||
text: this.$t('leave-confirm'),
|
||||
showCancelButton: true
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) {
|
||||
next(false);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
room.destroy();
|
||||
window.removeEventListener('beforeunload', this.beforeunload);
|
||||
},
|
||||
|
||||
methods: {
|
||||
beforeunload(e: BeforeUnloadEvent) {
|
||||
if (this.changed) {
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
}
|
||||
},
|
||||
|
||||
async add() {
|
||||
const { canceled, result: id } = await this.$root.dialog({
|
||||
type: null,
|
||||
@@ -142,17 +172,42 @@ export default Vue.extend({
|
||||
});
|
||||
if (canceled) return;
|
||||
room.addFurniture(id);
|
||||
this.changed = true;
|
||||
},
|
||||
|
||||
remove() {
|
||||
this.isTranslateMode = false;
|
||||
this.isRotateMode = false;
|
||||
room.removeFurniture();
|
||||
this.changed = true;
|
||||
},
|
||||
|
||||
save() {
|
||||
this.$root.api('room/update', {
|
||||
room: room.getRoomInfo()
|
||||
}).then(() => {
|
||||
this.changed = false;
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
text: this.$t('saved')
|
||||
});
|
||||
}).catch((e: any) => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e.message
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.$root.dialog({
|
||||
type: 'warning',
|
||||
text: this.$t('clear-confirm'),
|
||||
showCancelButton: true
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
room.removeAllFurnitures();
|
||||
this.changed = true;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -162,22 +217,26 @@ export default Vue.extend({
|
||||
}).then(file => {
|
||||
room.updateProp(key, `/proxy/?${urlQuery({ url: file.thumbnailUrl })}`);
|
||||
this.$refs.preview.selected(room.getSelectedObject());
|
||||
this.changed = true;
|
||||
});
|
||||
},
|
||||
|
||||
updateColor(key, ev) {
|
||||
room.updateProp(key, ev.target.value);
|
||||
this.$refs.preview.selected(room.getSelectedObject());
|
||||
this.changed = true;
|
||||
},
|
||||
|
||||
updateCarpetColor(ev) {
|
||||
room.updateCarpetColor(ev.target.value);
|
||||
this.carpetColor = ev.target.value;
|
||||
this.changed = true;
|
||||
},
|
||||
|
||||
updateRoomType(type) {
|
||||
room.changeRoomType(type);
|
||||
this.roomType = type;
|
||||
this.changed = true;
|
||||
},
|
||||
|
||||
translate() {
|
||||
@@ -188,6 +247,7 @@ export default Vue.extend({
|
||||
this.isTranslateMode = true;
|
||||
room.enterTransformMode('translate');
|
||||
}
|
||||
this.changed = true;
|
||||
},
|
||||
|
||||
rotate() {
|
||||
@@ -198,12 +258,14 @@ export default Vue.extend({
|
||||
this.isRotateMode = true;
|
||||
room.enterTransformMode('rotate');
|
||||
}
|
||||
this.changed = true;
|
||||
},
|
||||
|
||||
exit() {
|
||||
this.isTranslateMode = false;
|
||||
this.isRotateMode = false;
|
||||
room.exitTransformMode();
|
||||
this.changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -25,7 +25,8 @@ export default define({
|
||||
data() {
|
||||
return {
|
||||
text: null,
|
||||
changed: false
|
||||
changed: false,
|
||||
timeoutId: null
|
||||
};
|
||||
},
|
||||
|
||||
@@ -45,6 +46,8 @@ export default define({
|
||||
|
||||
onChange() {
|
||||
this.changed = true;
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = setTimeout(this.saveMemo, 1000);
|
||||
},
|
||||
|
||||
saveMemo() {
|
||||
|
@@ -9,7 +9,6 @@ import VueRouter from 'vue-router';
|
||||
import './style.styl';
|
||||
|
||||
import init from '../init';
|
||||
import fuckAdBlock from '../common/scripts/fuck-ad-block';
|
||||
import composeNotification from '../common/scripts/compose-notification';
|
||||
|
||||
import MkHome from './views/home/home.vue';
|
||||
@@ -185,7 +184,8 @@ init(async (launch, os) => {
|
||||
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
||||
{ path: '/i/drive', component: MkDrive },
|
||||
{ path: '/i/drive/folder/:folder', component: MkDrive },
|
||||
{ path: '/i/settings', component: MkSettings },
|
||||
{ path: '/i/settings', redirect: '/i/settings/profile' },
|
||||
{ path: '/i/settings/:page', component: MkSettings },
|
||||
{ path: '/selectdrive', component: MkSelectDrive },
|
||||
{ path: '/@:acct/room', props: true, component: () => import('../common/views/pages/room/room.vue').then(m => m.default) },
|
||||
{ path: '/share', component: MkShare },
|
||||
@@ -202,13 +202,6 @@ init(async (launch, os) => {
|
||||
// Launch the app
|
||||
const [app, _] = launch(router);
|
||||
|
||||
if (os.store.getters.isSignedIn) {
|
||||
/**
|
||||
* Fuck AD Block
|
||||
*/
|
||||
fuckAdBlock(app);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init Notification
|
||||
*/
|
||||
|
@@ -10,14 +10,18 @@
|
||||
<b>{{ $t('@.post-form.recent-tags') }}:</b>
|
||||
<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('@.post-form.click-to-tagging')">#{{ tag }}</a>
|
||||
</div>
|
||||
<div class="with-quote" v-if="quoteId">{{ $t('@.post-form.quote-attached') }} <a @click="quoteId = null">[x]</a></div>
|
||||
<div v-if="visibility === 'specified'" class="visibleUsers">
|
||||
<span v-for="u in visibleUsers">
|
||||
<mk-user-name :user="u"/><a @click="removeVisibleUser(u)">[x]</a>
|
||||
</span>
|
||||
<a @click="addVisibleUser">{{ $t('@.post-form.add-visible-user') }}</a>
|
||||
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('@.post-form.quote-attached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
|
||||
<div v-if="visibility === 'specified'" class="to-specified">
|
||||
<fa icon="envelope"/> {{ $t('@.post-form.specified-recipient') }}
|
||||
<div class="visibleUsers">
|
||||
<span v-for="u in visibleUsers">
|
||||
<mk-user-name :user="u"/>
|
||||
<button @click="removeVisibleUser(u)"><fa icon="times"/></button>
|
||||
</span>
|
||||
<button @click="addVisibleUser">{{ $t('@.post-form.add-visible-user') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="local-only" v-if="localOnly === true">{{ $t('@.post-form.local-only-message') }}</div>
|
||||
<div class="local-only" v-if="localOnly === true"><fa icon="heart"/> {{ $t('@.post-form.local-only-message') }}</div>
|
||||
<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('@.post-form.cw-placeholder')" v-autocomplete="{ model: 'cw' }">
|
||||
<div class="textarea">
|
||||
<textarea :class="{ with: (files.length != 0 || poll) }"
|
||||
@@ -207,13 +211,37 @@ export default Vue.extend({
|
||||
margin 0 0 8px 0
|
||||
color var(--primary)
|
||||
|
||||
> .visibleUsers
|
||||
margin-bottom 8px
|
||||
font-size 14px
|
||||
> button
|
||||
padding 4px 8px
|
||||
color var(--primaryAlpha04)
|
||||
|
||||
> span
|
||||
margin-right 16px
|
||||
color var(--primary)
|
||||
&:hover
|
||||
color var(--primaryAlpha06)
|
||||
|
||||
&:active
|
||||
color var(--primaryDarken30)
|
||||
|
||||
> .to-specified
|
||||
margin 0 0 8px 0
|
||||
color var(--primary)
|
||||
|
||||
> .visibleUsers
|
||||
display inline
|
||||
top -1px
|
||||
font-size 14px
|
||||
|
||||
> span
|
||||
margin-left 14px
|
||||
|
||||
> button
|
||||
padding 4px 8px
|
||||
color var(--primaryAlpha04)
|
||||
|
||||
&:hover
|
||||
color var(--primaryAlpha06)
|
||||
|
||||
&:active
|
||||
color var(--primaryDarken30)
|
||||
|
||||
> .local-only
|
||||
margin 0 0 8px 0
|
||||
|
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="mk-settings">
|
||||
<div class="nav" :class="{ inWindow }">
|
||||
<p :class="{ active: page == 'profile' }" @mousedown="page = 'profile'"><fa icon="user" fixed-width/>{{ $t('@._settings.profile') }}</p>
|
||||
<p :class="{ active: page == 'appearance' }" @mousedown="page = 'appearance'"><fa icon="palette" fixed-width/>{{ $t('@._settings.appearance') }}</p>
|
||||
<p :class="{ active: page == 'behavior' }" @mousedown="page = 'behavior'"><fa icon="desktop" fixed-width/>{{ $t('@._settings.behavior') }}</p>
|
||||
<p :class="{ active: page == 'notification' }" @mousedown="page = 'notification'"><fa :icon="['far', 'bell']" fixed-width/>{{ $t('@._settings.notification') }}</p>
|
||||
<p :class="{ active: page == 'drive' }" @mousedown="page = 'drive'"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</p>
|
||||
<p :class="{ active: page == 'hashtags' }" @mousedown="page = 'hashtags'"><fa icon="hashtag" fixed-width/>{{ $t('@._settings.tags') }}</p>
|
||||
<p :class="{ active: page == 'muteAndBlock' }" @mousedown="page = 'muteAndBlock'"><fa icon="ban" fixed-width/>{{ $t('@._settings.mute-and-block') }}</p>
|
||||
<p :class="{ active: page == 'apps' }" @mousedown="page = 'apps'"><fa icon="puzzle-piece" fixed-width/>{{ $t('@._settings.apps') }}</p>
|
||||
<p :class="{ active: page == 'security' }" @mousedown="page = 'security'"><fa icon="unlock-alt" fixed-width/>{{ $t('@._settings.security') }}</p>
|
||||
<p :class="{ active: page == 'api' }" @mousedown="page = 'api'"><fa icon="key" fixed-width/>API</p>
|
||||
<p :class="{ active: page == 'other' }" @mousedown="page = 'other'"><fa icon="cogs" fixed-width/>{{ $t('@._settings.other') }}</p>
|
||||
<router-link to="/i/settings/profile" active-class="active"><fa icon="user" fixed-width/>{{ $t('@._settings.profile') }}</router-link>
|
||||
<router-link to="/i/settings/appearance" active-class="active"><fa icon="palette" fixed-width/>{{ $t('@._settings.appearance') }}</router-link>
|
||||
<router-link to="/i/settings/behavior" active-class="active"><fa icon="desktop" fixed-width/>{{ $t('@._settings.behavior') }}</router-link>
|
||||
<router-link to="/i/settings/notification" active-class="active"><fa :icon="['far', 'bell']" fixed-width/>{{ $t('@._settings.notification') }}</router-link>
|
||||
<router-link to="/i/settings/drive" active-class="active"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</router-link>
|
||||
<router-link to="/i/settings/hashtags" active-class="active"><fa icon="hashtag" fixed-width/>{{ $t('@._settings.tags') }}</router-link>
|
||||
<router-link to="/i/settings/muteAndBlock" active-class="active"><fa icon="ban" fixed-width/>{{ $t('@._settings.mute-and-block') }}</router-link>
|
||||
<router-link to="/i/settings/apps" active-class="active"><fa icon="puzzle-piece" fixed-width/>{{ $t('@._settings.apps') }}</router-link>
|
||||
<router-link to="/i/settings/security" active-class="active"><fa icon="unlock-alt" fixed-width/>{{ $t('@._settings.security') }}</router-link>
|
||||
<router-link to="/i/settings/api" active-class="active"><fa icon="key" fixed-width/>API</router-link>
|
||||
<router-link to="/i/settings/other" active-class="active"><fa icon="cogs" fixed-width/>{{ $t('@._settings.other') }}</router-link>
|
||||
</div>
|
||||
<div class="pages">
|
||||
<x-settings :page="page"/>
|
||||
@@ -30,9 +30,9 @@ export default Vue.extend({
|
||||
XSettings,
|
||||
},
|
||||
props: {
|
||||
initialPage: {
|
||||
page: {
|
||||
type: String,
|
||||
required: false
|
||||
required: true,
|
||||
},
|
||||
inWindow: {
|
||||
type: Boolean,
|
||||
@@ -40,11 +40,6 @@ export default Vue.extend({
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: this.initialPage || 'profile',
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -63,7 +58,7 @@ export default Vue.extend({
|
||||
z-index 1
|
||||
font-size 15px
|
||||
|
||||
> p
|
||||
> a
|
||||
display block
|
||||
padding 10px 16px
|
||||
margin 0
|
||||
|
@@ -21,7 +21,7 @@
|
||||
<p>{{ $t('followers') }}</p><span>{{ u.followersCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u" mini/>
|
||||
<mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u" mini/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -156,7 +156,7 @@ export default Vue.extend({
|
||||
font-size 1em
|
||||
color var(--primary)
|
||||
|
||||
> .follow-button
|
||||
> .koudoku-button
|
||||
position absolute
|
||||
top 8px
|
||||
right 8px
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<mk-ui>
|
||||
<main>
|
||||
<x-settings :in-window="false"/>
|
||||
<x-settings :in-window="false" :page="$route.params.page" />
|
||||
</main>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
@@ -140,6 +140,7 @@ init((launch, os) => {
|
||||
]),
|
||||
{ path: '/signup', name: 'signup', component: MkSignup },
|
||||
{ path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) },
|
||||
{ path: '/i/settings/:page', redirect: '/i/settings' },
|
||||
{ path: '/i/favorites', name: 'favorites', component: UI, props: route => ({ component: () => import('../common/views/pages/favorites.vue').then(m => m.default), platform: 'mobile' }) },
|
||||
{ path: '/i/pages', name: 'pages', component: UI, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
|
||||
{ path: '/i/lists', name: 'user-lists', component: UI, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
|
||||
|
@@ -122,7 +122,7 @@ export default Vue.extend({
|
||||
this.$root.api('drive/files/delete', {
|
||||
fileId: this.file.id
|
||||
}).then(() => {
|
||||
this.browser.cd(this.file.folderId, true);
|
||||
this.browser.cd(this.file.folderId);
|
||||
});
|
||||
},
|
||||
|
||||
|
@@ -163,8 +163,6 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
cd(target, silent = false) {
|
||||
this.file = null;
|
||||
|
||||
if (target == null) {
|
||||
this.goRoot(silent);
|
||||
return;
|
||||
@@ -172,6 +170,7 @@ export default Vue.extend({
|
||||
target = target.id;
|
||||
}
|
||||
|
||||
this.file = null;
|
||||
this.fetching = true;
|
||||
|
||||
this.$root.api('drive/folders/show', {
|
||||
@@ -244,13 +243,14 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
goRoot(silent = false) {
|
||||
if (this.folder || this.file) {
|
||||
this.file = null;
|
||||
this.folder = null;
|
||||
this.hierarchyFolders = [];
|
||||
this.$emit('move-root', silent);
|
||||
this.fetch();
|
||||
}
|
||||
// すでにrootにいるなら何もしない
|
||||
if (this.folder == null && this.file == null) return;
|
||||
|
||||
this.file = null;
|
||||
this.folder = null;
|
||||
this.hierarchyFolders = [];
|
||||
this.$emit('move-root', silent);
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
fetch() {
|
||||
|
@@ -17,15 +17,18 @@
|
||||
<div class="form">
|
||||
<mk-note-preview class="preview" v-if="reply" :note="reply"/>
|
||||
<mk-note-preview class="preview" v-if="renote" :note="renote"/>
|
||||
<div class="with-quote" v-if="quoteId">{{ $t('@.post-form.quote-attached') }} <a @click="quoteId = null">[x]</a></div>
|
||||
<div v-if="visibility === 'specified'" class="visibleUsers">
|
||||
<span v-for="u in visibleUsers">
|
||||
<mk-user-name :user="u"/>
|
||||
<a @click="removeVisibleUser(u)">[x]</a>
|
||||
</span>
|
||||
<a @click="addVisibleUser">+{{ $t('@.post-form.add-visible-user') }}</a>
|
||||
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('@.post-form.quote-attached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
|
||||
<div v-if="visibility === 'specified'" class="to-specified">
|
||||
<fa icon="envelope"/> {{ $t('@.post-form.specified-recipient') }}
|
||||
<div class="visibleUsers">
|
||||
<span v-for="u in visibleUsers">
|
||||
<mk-user-name :user="u"/>
|
||||
<button @click="removeVisibleUser(u)"><fa icon="times"/></button>
|
||||
</span>
|
||||
<button @click="addVisibleUser">{{ $t('@.post-form.add-visible-user') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="local-only" v-if="localOnly === true">{{ $t('@.post-form.local-only-message') }}</div>
|
||||
<div class="local-only" v-if="localOnly === true"><fa icon="heart"/> {{ $t('@.post-form.local-only-message') }}</div>
|
||||
<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('@.post-form.cw-placeholder')" v-autocomplete="{ model: 'cw' }">
|
||||
<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }" @paste="onPaste"></textarea>
|
||||
<x-post-form-attaches class="attaches" :files="files"/>
|
||||
@@ -145,13 +148,37 @@ export default Vue.extend({
|
||||
margin 0 0 8px 0
|
||||
color var(--primary)
|
||||
|
||||
> .visibleUsers
|
||||
margin 5px
|
||||
font-size 14px
|
||||
> button
|
||||
padding 4px 8px
|
||||
color var(--primaryAlpha04)
|
||||
|
||||
> span
|
||||
margin-right 16px
|
||||
color var(--text)
|
||||
&:hover
|
||||
color var(--primaryAlpha06)
|
||||
|
||||
&:active
|
||||
color var(--primaryDarken30)
|
||||
|
||||
> .to-specified
|
||||
margin 0 0 8px 0
|
||||
color var(--primary)
|
||||
|
||||
> .visibleUsers
|
||||
display inline
|
||||
top -1px
|
||||
font-size 14px
|
||||
|
||||
> span
|
||||
margin-left 14px
|
||||
|
||||
> button
|
||||
padding 4px 8px
|
||||
color var(--primaryAlpha04)
|
||||
|
||||
&:hover
|
||||
color var(--primaryAlpha06)
|
||||
|
||||
&:active
|
||||
color var(--primaryDarken30)
|
||||
|
||||
> .local-only
|
||||
margin 0 0 8px 0
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
src/client/assets/room/furnitures/cup-noodle/cup-noodle.blend
Normal file
BIN
src/client/assets/room/furnitures/cup-noodle/cup-noodle.blend
Normal file
Binary file not shown.
BIN
src/client/assets/room/furnitures/cup-noodle/cup-noodle.glb
Normal file
BIN
src/client/assets/room/furnitures/cup-noodle/cup-noodle.glb
Normal file
Binary file not shown.
BIN
src/client/assets/room/furnitures/cup-noodle/noodle.png
Normal file
BIN
src/client/assets/room/furnitures/cup-noodle/noodle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 290 KiB |
Binary file not shown.
BIN
src/client/assets/room/furnitures/energy-drink/energy-drink.glb
Normal file
BIN
src/client/assets/room/furnitures/energy-drink/energy-drink.glb
Normal file
Binary file not shown.
BIN
src/client/assets/room/furnitures/energy-drink/texture.afdesign
Normal file
BIN
src/client/assets/room/furnitures/energy-drink/texture.afdesign
Normal file
Binary file not shown.
BIN
src/client/assets/room/furnitures/energy-drink/texture.png
Normal file
BIN
src/client/assets/room/furnitures/energy-drink/texture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
BIN
src/client/assets/room/furnitures/energy-drink/uv.png
Normal file
BIN
src/client/assets/room/furnitures/energy-drink/uv.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
BIN
src/client/assets/room/furnitures/holo-display/holo-display.glb
Normal file
BIN
src/client/assets/room/furnitures/holo-display/holo-display.glb
Normal file
Binary file not shown.
BIN
src/client/assets/room/furnitures/holo-display/ray-uv.png
Normal file
BIN
src/client/assets/room/furnitures/holo-display/ray-uv.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
src/client/assets/room/furnitures/holo-display/ray.png
Normal file
BIN
src/client/assets/room/furnitures/holo-display/ray.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 102 KiB |
BIN
src/client/assets/room/furnitures/pinguin/pinguin.blend
Normal file
BIN
src/client/assets/room/furnitures/pinguin/pinguin.blend
Normal file
Binary file not shown.
Binary file not shown.
@@ -34,6 +34,7 @@ export type Source = {
|
||||
autoAdmin?: boolean;
|
||||
|
||||
proxy?: string;
|
||||
proxySmtp?: string;
|
||||
|
||||
accesslog?: string;
|
||||
|
||||
@@ -42,6 +43,14 @@ export type Source = {
|
||||
id: string;
|
||||
|
||||
outgoingAddressFamily?: 'ipv4' | 'ipv6' | 'dual';
|
||||
|
||||
deliverJobConcurrency?: number;
|
||||
inboxJobConcurrency?: number;
|
||||
|
||||
syslog: {
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
22
src/misc/app-lock.ts
Normal file
22
src/misc/app-lock.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import redis from '../db/redis';
|
||||
import { promisify } from 'util';
|
||||
|
||||
/**
|
||||
* Retry delay (ms) for lock acquisition
|
||||
*/
|
||||
const retryDelay = 100;
|
||||
|
||||
const lock: (key: string, timeout?: number) => Promise<() => void>
|
||||
= redis
|
||||
? promisify(require('redis-lock')(redis, retryDelay))
|
||||
: async () => () => { };
|
||||
|
||||
/**
|
||||
* Get AP Object lock
|
||||
* @param uri AP object ID
|
||||
* @param timeout Lock timeout (ms), The timeout releases previous lock.
|
||||
* @returns Unlock function
|
||||
*/
|
||||
export function getApLock(uri: string, timeout = 30 * 1000) {
|
||||
return lock(`ap-object:${uri}`, timeout);
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
import $ from 'cafy';
|
||||
import { EntityRepository, Repository, In } from 'typeorm';
|
||||
import { EntityRepository, Repository, In, Not } from 'typeorm';
|
||||
import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
||||
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages } from '..';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
@@ -56,6 +56,10 @@ export class UserRepository extends Repository<User> {
|
||||
}
|
||||
|
||||
public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
|
||||
const mute = await Mutings.find({
|
||||
muterId: userId
|
||||
});
|
||||
|
||||
const joinings = await UserGroupJoinings.find({ userId: userId });
|
||||
|
||||
const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
|
||||
@@ -66,11 +70,11 @@ export class UserRepository extends Repository<User> {
|
||||
.getOne().then(x => x != null)));
|
||||
|
||||
const [withUser, withGroups] = await Promise.all([
|
||||
// TODO: ミュートを考慮
|
||||
MessagingMessages.count({
|
||||
where: {
|
||||
recipientId: userId,
|
||||
isRead: false
|
||||
isRead: false,
|
||||
...(mute.length > 0 ? { userId: Not(In(mute.map(x => x.muteeId))) } : {}),
|
||||
},
|
||||
take: 1
|
||||
}).then(count => count > 0),
|
||||
|
@@ -203,8 +203,8 @@ export function createCleanRemoteFilesJob() {
|
||||
|
||||
export default function() {
|
||||
if (!program.onlyServer) {
|
||||
deliverQueue.process(128, processDeliver);
|
||||
inboxQueue.process(128, processInbox);
|
||||
deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
|
||||
inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
|
||||
processDb(dbQueue);
|
||||
procesObjectStorage(objectStorageQueue);
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import { resolvePerson } from '../../models/person';
|
||||
import { apLogger } from '../../logger';
|
||||
import { extractDbHost } from '../../../../misc/convert-host';
|
||||
import { fetchMeta } from '../../../../misc/fetch-meta';
|
||||
import { getApLock } from '../../../../misc/app-lock';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
@@ -25,47 +26,53 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(extractDbHost(uri))) return;
|
||||
|
||||
// 既に同じURIを持つものが登録されていないかチェック
|
||||
const exist = await fetchNote(uri);
|
||||
if (exist) {
|
||||
return;
|
||||
}
|
||||
const unlock = await getApLock(uri);
|
||||
|
||||
// Announce対象をresolve
|
||||
let renote;
|
||||
try {
|
||||
renote = await resolveNote(note);
|
||||
} catch (e) {
|
||||
// 対象が4xxならスキップ
|
||||
if (e.statusCode >= 400 && e.statusCode < 500) {
|
||||
logger.warn(`Ignored announce target ${note.inReplyTo} - ${e.statusCode}`);
|
||||
// 既に同じURIを持つものが登録されていないかチェック
|
||||
const exist = await fetchNote(uri);
|
||||
if (exist) {
|
||||
return;
|
||||
}
|
||||
logger.warn(`Error in announce target ${note.inReplyTo} - ${e.statusCode || e}`);
|
||||
throw e;
|
||||
|
||||
// Announce対象をresolve
|
||||
let renote;
|
||||
try {
|
||||
renote = await resolveNote(note);
|
||||
} catch (e) {
|
||||
// 対象が4xxならスキップ
|
||||
if (e.statusCode >= 400 && e.statusCode < 500) {
|
||||
logger.warn(`Ignored announce target ${note.inReplyTo} - ${e.statusCode}`);
|
||||
return;
|
||||
}
|
||||
logger.warn(`Error in announce target ${note.inReplyTo} - ${e.statusCode || e}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
logger.info(`Creating the (Re)Note: ${uri}`);
|
||||
|
||||
//#region Visibility
|
||||
const to = getApIds(activity.to);
|
||||
const cc = getApIds(activity.cc);
|
||||
|
||||
const visibility = getVisibility(to, cc, actor);
|
||||
|
||||
let visibleUsers: User[] = [];
|
||||
if (visibility == 'specified') {
|
||||
visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri)));
|
||||
}
|
||||
//#endergion
|
||||
|
||||
await post(actor, {
|
||||
createdAt: activity.published ? new Date(activity.published) : null,
|
||||
renote,
|
||||
visibility,
|
||||
visibleUsers,
|
||||
uri
|
||||
});
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
|
||||
logger.info(`Creating the (Re)Note: ${uri}`);
|
||||
|
||||
//#region Visibility
|
||||
const to = getApIds(activity.to);
|
||||
const cc = getApIds(activity.cc);
|
||||
|
||||
const visibility = getVisibility(to, cc, actor);
|
||||
|
||||
let visibleUsers: User[] = [];
|
||||
if (visibility == 'specified') {
|
||||
visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri)));
|
||||
}
|
||||
//#endergion
|
||||
|
||||
await post(actor, {
|
||||
createdAt: activity.published ? new Date(activity.published) : null,
|
||||
renote,
|
||||
visibility,
|
||||
visibleUsers,
|
||||
uri
|
||||
});
|
||||
}
|
||||
|
||||
type visibility = 'public' | 'home' | 'followers' | 'specified';
|
||||
|
@@ -1,13 +1,23 @@
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { createNote, fetchNote } from '../../models/note';
|
||||
import { getApId } from '../../type';
|
||||
import { getApLock } from '../../../../misc/app-lock';
|
||||
|
||||
/**
|
||||
* 投稿作成アクティビティを捌きます
|
||||
*/
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, note: any, silent = false): Promise<void> {
|
||||
const exist = await fetchNote(note);
|
||||
if (exist == null) {
|
||||
await createNote(note);
|
||||
const uri = getApId(note);
|
||||
|
||||
const unlock = await getApLock(uri);
|
||||
|
||||
try {
|
||||
const exist = await fetchNote(note);
|
||||
if (exist == null) {
|
||||
await createNote(note);
|
||||
}
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
16
src/remote/activitypub/kernel/undo/announce.ts
Normal file
16
src/remote/activitypub/kernel/undo/announce.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Notes } from '../../../../models';
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { IAnnounce, getApId } from '../../type';
|
||||
import deleteNote from '../../../../services/note/delete';
|
||||
|
||||
export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<void> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
const note = await Notes.findOne({
|
||||
uri
|
||||
});
|
||||
|
||||
if (!note) return;
|
||||
|
||||
await deleteNote(actor, note);
|
||||
};
|
@@ -1,8 +1,9 @@
|
||||
import { IRemoteUser } from '../../../../models/entities/user';
|
||||
import { IUndo, IFollow, IBlock, ILike } from '../../type';
|
||||
import { IUndo, IFollow, IBlock, ILike, IAnnounce } from '../../type';
|
||||
import unfollow from './follow';
|
||||
import unblock from './block';
|
||||
import undoLike from './like';
|
||||
import { undoAnnounce } from './announce';
|
||||
import Resolver from '../../resolver';
|
||||
import { apLogger } from '../../logger';
|
||||
|
||||
@@ -38,5 +39,8 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
|
||||
case 'Like':
|
||||
undoLike(actor, object as ILike);
|
||||
break;
|
||||
case 'Announce':
|
||||
undoAnnounce(actor, object as IAnnounce);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
@@ -22,6 +22,7 @@ import { Emoji } from '../../../models/entities/emoji';
|
||||
import { genId } from '../../../misc/gen-id';
|
||||
import { fetchMeta } from '../../../misc/fetch-meta';
|
||||
import { ensure } from '../../../prelude/ensure';
|
||||
import { getApLock } from '../../../misc/app-lock';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
@@ -257,30 +258,24 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(extractDbHost(uri))) throw { statusCode: 451 };
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await fetchNote(uri);
|
||||
const unlock = await getApLock(uri);
|
||||
|
||||
if (exist) {
|
||||
return exist;
|
||||
}
|
||||
//#endregion
|
||||
try {
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
const exist = await fetchNote(uri);
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
|
||||
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
||||
return await createNote(uri, resolver, true).catch(e => {
|
||||
if (e.name === 'duplicated') {
|
||||
return fetchNote(uri).then(note => {
|
||||
if (note == null) {
|
||||
throw new Error('something happened');
|
||||
} else {
|
||||
return note;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw e;
|
||||
if (exist) {
|
||||
return exist;
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
|
||||
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
||||
return await createNote(uri, resolver, true);
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> {
|
||||
|
@@ -196,7 +196,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
||||
// ハッシュタグ更新
|
||||
updateUsertags(user!, tags);
|
||||
|
||||
//#region アイコンとヘッダー画像をフェッチ
|
||||
//#region アバターとヘッダー画像をフェッチ
|
||||
const [avatar, banner] = (await Promise.all<DriveFile | null>([
|
||||
person.icon,
|
||||
person.image
|
||||
@@ -285,7 +285,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
||||
|
||||
logger.info(`Updating the Person: ${person.id}`);
|
||||
|
||||
// アイコンとヘッダー画像をフェッチ
|
||||
// アバターとヘッダー画像をフェッチ
|
||||
const [avatar, banner] = (await Promise.all<DriveFile | null>([
|
||||
person.icon,
|
||||
person.image
|
||||
|
@@ -158,7 +158,7 @@ export default async function renderNote(note: Note, dive = true): Promise<any>
|
||||
cc,
|
||||
inReplyTo,
|
||||
attachment: files.map(renderDocument),
|
||||
sensitive: files.some(file => file.isSensitive),
|
||||
sensitive: note.cw != null || files.some(file => file.isSensitive),
|
||||
tag,
|
||||
...asPoll
|
||||
};
|
||||
|
21
src/server/api/endpoints/admin/resync-chart.ts
Normal file
21
src/server/api/endpoints/admin/resync-chart.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import define from '../../define';
|
||||
import { driveChart, notesChart, usersChart, instanceChart } from '../../../../services/chart';
|
||||
import { insertModerationLog } from '../../../../services/insert-moderation-log';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
insertModerationLog(me, 'chartResync');
|
||||
|
||||
driveChart.resync();
|
||||
notesChart.resync();
|
||||
usersChart.resync();
|
||||
instanceChart.resync();
|
||||
|
||||
// TODO: ユーザーごとのチャートもキューに入れて更新する
|
||||
});
|
@@ -66,7 +66,7 @@ export const meta = {
|
||||
avatarId: {
|
||||
validator: $.optional.nullable.type(ID),
|
||||
desc: {
|
||||
'ja-JP': 'アイコンに設定する画像のドライブファイルID'
|
||||
'ja-JP': 'アバターに設定する画像のドライブファイルID'
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -195,10 +195,7 @@ export default define(meta, async (ps, user) => {
|
||||
const mute = await Mutings.find({
|
||||
muterId: recipientUser.id,
|
||||
});
|
||||
const mutedUserIds = mute.map(m => m.muteeId.toString());
|
||||
if (mutedUserIds.indexOf(user.id) != -1) {
|
||||
return;
|
||||
}
|
||||
if (mute.map(m => m.muteeId).includes(user.id)) return;
|
||||
//#endregion
|
||||
|
||||
publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
|
||||
|
@@ -139,7 +139,12 @@ export default define(meta, async (ps, me) => {
|
||||
errorImageUrl: instance.errorImageUrl,
|
||||
iconUrl: instance.iconUrl,
|
||||
maxNoteTextLength: instance.maxNoteTextLength,
|
||||
emojis: emojis,
|
||||
emojis: emojis.map(e => ({
|
||||
id: e.id,
|
||||
aliases: e.aliases,
|
||||
name: e.name,
|
||||
url: e.url,
|
||||
})),
|
||||
enableEmail: instance.enableEmail,
|
||||
|
||||
enableTwitterIntegration: instance.enableTwitterIntegration,
|
||||
|
@@ -130,59 +130,35 @@ export default define(meta, async (ps, user) => {
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
|
||||
/* TODO
|
||||
// MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。
|
||||
// つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
|
||||
// for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
|
||||
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
userId: { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.userId != :meId', { meId: user.id });
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
|
||||
if (ps.includeRenotedMyNotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
'_renote.userId': { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.renoteUserId != :meId', { meId: user.id });
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
|
||||
if (ps.includeLocalRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
'_renote.user.host': { $ne: null }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.renoteUserHost IS NOT NULL');
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
*/
|
||||
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
|
@@ -112,12 +112,8 @@ export default define(meta, async (ps, user) => {
|
||||
}));
|
||||
|
||||
if (ps.excludeNsfw) {
|
||||
// v11 TODO
|
||||
/*
|
||||
query['_files.isSensitive'] = {
|
||||
$ne: true
|
||||
};
|
||||
*/
|
||||
query.andWhere('note.cw IS NULL');
|
||||
query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
@@ -4,6 +4,8 @@ import define from '../../define';
|
||||
import { getNote } from '../../common/getters';
|
||||
import { ApiError } from '../../error';
|
||||
import { NoteReactions } from '../../../../models';
|
||||
import { DeepPartial } from 'typeorm';
|
||||
import { NoteReaction } from '../../../../models/entities/note-reaction';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -24,6 +26,10 @@ export const meta = {
|
||||
}
|
||||
},
|
||||
|
||||
type: {
|
||||
validator: $.optional.nullable.str,
|
||||
},
|
||||
|
||||
limit: {
|
||||
validator: $.optional.num.range(1, 100),
|
||||
default: 10
|
||||
@@ -70,7 +76,11 @@ export default define(meta, async (ps, user) => {
|
||||
|
||||
const query = {
|
||||
noteId: note.id
|
||||
};
|
||||
} as DeepPartial<NoteReaction>;
|
||||
|
||||
if (ps.type) {
|
||||
query.reaction = ps.type;
|
||||
}
|
||||
|
||||
const reactions = await NoteReactions.find({
|
||||
where: query,
|
||||
|
@@ -116,58 +116,35 @@ export default define(meta, async (ps, user) => {
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMuteQuery(query, user);
|
||||
|
||||
/* v11 TODO
|
||||
// MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。
|
||||
// つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
|
||||
// for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
|
||||
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
userId: { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.userId != :meId', { meId: user.id });
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
|
||||
if (ps.includeRenotedMyNotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
'_renote.userId': { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.renoteUserId != :meId', { meId: user.id });
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
|
||||
if (ps.includeLocalRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
'_renote.user.host': { $ne: null }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
}*/
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.renoteUserHost IS NOT NULL');
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
|
@@ -6,6 +6,7 @@ import { UserLists, UserListJoinings, Notes } from '../../../../models';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
|
||||
import { activeUsersChart } from '../../../../services/chart';
|
||||
import { Brackets } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -134,58 +135,35 @@ export default define(meta, async (ps, user) => {
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
|
||||
/* TODO
|
||||
// MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。
|
||||
// つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
|
||||
// for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
|
||||
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
userId: { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.userId != :meId', { meId: user.id });
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
|
||||
if (ps.includeRenotedMyNotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
'_renote.userId': { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.renoteUserId != :meId', { meId: user.id });
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
|
||||
if (ps.includeLocalRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
'_renote.user.host': { $ne: null }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
}*/
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.renoteUserHost IS NOT NULL');
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
|
||||
if (ps.withFiles) {
|
||||
query.andWhere('note.fileIds != \'{}\'');
|
||||
|
@@ -29,7 +29,7 @@ export const meta = {
|
||||
},
|
||||
|
||||
name: {
|
||||
validator: $.str,
|
||||
validator: $.str.min(1),
|
||||
},
|
||||
|
||||
summary: {
|
||||
@@ -76,6 +76,11 @@ export const meta = {
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: 'b7b97489-0f66-4b12-a5ff-b21bd63f6e1c'
|
||||
},
|
||||
nameAlreadyExists: {
|
||||
message: 'Specified name already exists.',
|
||||
code: 'NAME_ALREADY_EXISTS',
|
||||
id: '4650348e-301c-499a-83c9-6aa988c66bc1'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,6 +97,15 @@ export default define(meta, async (ps, user) => {
|
||||
}
|
||||
}
|
||||
|
||||
await Pages.find({
|
||||
userId: user.id,
|
||||
name: ps.name
|
||||
}).then(result => {
|
||||
if (result.length > 0) {
|
||||
throw new ApiError(meta.errors.nameAlreadyExists);
|
||||
}
|
||||
});
|
||||
|
||||
const page = await Pages.save(new Page({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
|
@@ -4,6 +4,7 @@ import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Pages, DriveFiles } from '../../../../models';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import { Not } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -35,7 +36,7 @@ export const meta = {
|
||||
},
|
||||
|
||||
name: {
|
||||
validator: $.optional.str,
|
||||
validator: $.str.min(1),
|
||||
},
|
||||
|
||||
summary: {
|
||||
@@ -85,6 +86,11 @@ export const meta = {
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: 'cfc23c7c-3887-490e-af30-0ed576703c82'
|
||||
},
|
||||
nameAlreadyExists: {
|
||||
message: 'Specified name already exists.',
|
||||
code: 'NAME_ALREADY_EXISTS',
|
||||
id: '2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109,6 +115,16 @@ export default define(meta, async (ps, user) => {
|
||||
}
|
||||
}
|
||||
|
||||
await Pages.find({
|
||||
id: Not(ps.pageId),
|
||||
userId: user.id,
|
||||
name: ps.name
|
||||
}).then(result => {
|
||||
if (result.length > 0) {
|
||||
throw new ApiError(meta.errors.nameAlreadyExists);
|
||||
}
|
||||
});
|
||||
|
||||
await Pages.update(page.id, {
|
||||
updatedAt: new Date(),
|
||||
title: ps.title,
|
||||
|
@@ -74,23 +74,7 @@ export const meta = {
|
||||
validator: $.optional.bool,
|
||||
default: true,
|
||||
desc: {
|
||||
'ja-JP': '自分の行ったRenoteを含めるかどうか'
|
||||
}
|
||||
},
|
||||
|
||||
includeRenotedMyNotes: {
|
||||
validator: $.optional.bool,
|
||||
default: true,
|
||||
desc: {
|
||||
'ja-JP': 'Renoteされた自分の投稿を含めるかどうか'
|
||||
}
|
||||
},
|
||||
|
||||
includeLocalRenotes: {
|
||||
validator: $.optional.bool,
|
||||
default: true,
|
||||
desc: {
|
||||
'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか'
|
||||
'ja-JP': 'Renoteを含めるかどうか'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -166,10 +150,8 @@ export default define(meta, async (ps, me) => {
|
||||
}));
|
||||
|
||||
if (ps.excludeNsfw) {
|
||||
// v11 TODO
|
||||
/*query['_files.isSensitive'] = {
|
||||
$ne: true
|
||||
};*/
|
||||
query.andWhere('note.cw IS NULL');
|
||||
query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,23 +159,15 @@ export default define(meta, async (ps, me) => {
|
||||
query.andWhere('note.replyId IS NULL');
|
||||
}
|
||||
|
||||
/* TODO
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.$and.push({
|
||||
$or: [{
|
||||
userId: { $ne: user.id }
|
||||
}, {
|
||||
renoteId: null
|
||||
}, {
|
||||
text: { $ne: null }
|
||||
}, {
|
||||
fileIds: { $ne: [] }
|
||||
}, {
|
||||
poll: { $ne: null }
|
||||
}]
|
||||
});
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.userId != :userId', { userId: user.id });
|
||||
qb.orWhere('note.renoteId IS NULL');
|
||||
qb.orWhere('note.text IS NOT NULL');
|
||||
qb.orWhere('note.fileIds != \'{}\'');
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
*/
|
||||
|
||||
//#endregion
|
||||
|
||||
|
@@ -6,7 +6,7 @@ import { name, schema } from '../schemas/test';
|
||||
type TestLog = SchemaType<typeof schema>;
|
||||
|
||||
export default class TestChart extends Chart<TestLog> {
|
||||
private total = 0;
|
||||
public total = 0; // publicにするのはテストのため
|
||||
|
||||
constructor() {
|
||||
super(name, schema);
|
||||
|
@@ -65,7 +65,7 @@ export default abstract class Chart<T extends Record<string, any>> {
|
||||
public schema: Schema;
|
||||
protected repository: Repository<Log>;
|
||||
protected abstract genNewLog(latest: T): DeepPartial<T>;
|
||||
protected abstract async fetchActual(group?: string): Promise<DeepPartial<T>>;
|
||||
protected abstract async fetchActual(group: string | null): Promise<DeepPartial<T>>;
|
||||
|
||||
@autobind
|
||||
private static convertSchemaToFlatColumnDefinitions(schema: Schema) {
|
||||
@@ -341,6 +341,24 @@ export default abstract class Chart<T extends Record<string, any>> {
|
||||
]);
|
||||
}
|
||||
|
||||
@autobind
|
||||
public async resync(group: string | null = null): Promise<any> {
|
||||
const data = await this.fetchActual(group);
|
||||
|
||||
const update = async (log: Log) => {
|
||||
await this.repository.createQueryBuilder()
|
||||
.update()
|
||||
.set(Chart.convertObjectToFlattenColumns(data))
|
||||
.where('id = :id', { id: log.id })
|
||||
.execute();
|
||||
};
|
||||
|
||||
return Promise.all([
|
||||
this.getCurrentLog('day', group).then(log => update(log)),
|
||||
this.getCurrentLog('hour', group).then(log => update(log)),
|
||||
]);
|
||||
}
|
||||
|
||||
@autobind
|
||||
protected async inc(inc: DeepPartial<T>, group: string | null = null): Promise<void> {
|
||||
await this.commit(Chart.convertQuery(inc as any), group);
|
||||
|
@@ -6,6 +6,9 @@ import { program } from '../argv';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { Log } from '../models/entities/log';
|
||||
import { genId } from '../misc/gen-id';
|
||||
import config from '../config';
|
||||
|
||||
const SyslogPro = require('syslog-pro');
|
||||
|
||||
type Domain = {
|
||||
name: string;
|
||||
@@ -18,6 +21,7 @@ export default class Logger {
|
||||
private domain: Domain;
|
||||
private parentLogger: Logger | null = null;
|
||||
private store: boolean;
|
||||
private syslogClient: any | null = null;
|
||||
|
||||
constructor(domain: string, color?: string, store = true) {
|
||||
this.domain = {
|
||||
@@ -25,6 +29,20 @@ export default class Logger {
|
||||
color: color,
|
||||
};
|
||||
this.store = store;
|
||||
|
||||
if (config.syslog) {
|
||||
this.syslogClient = new SyslogPro.RFC5424({
|
||||
applacationName: 'Misskey',
|
||||
timestamp: true,
|
||||
encludeStructuredData: true,
|
||||
color: true,
|
||||
extendedColor: true,
|
||||
server: {
|
||||
target: config.syslog.host,
|
||||
port: config.syslog.port,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public createSubLogger(domain: string, color?: string, store = true): Logger {
|
||||
@@ -66,17 +84,29 @@ export default class Logger {
|
||||
console.log(important ? chalk.bold(log) : log);
|
||||
|
||||
if (store) {
|
||||
const Logs = getRepository(Log);
|
||||
Logs.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
machine: os.hostname(),
|
||||
worker: worker.toString(),
|
||||
domain: [this.domain].concat(subDomains).map(d => d.name),
|
||||
level: level,
|
||||
message: message,
|
||||
data: data,
|
||||
} as Log);
|
||||
if (this.syslogClient) {
|
||||
const send =
|
||||
level === 'error' ? this.syslogClient.error :
|
||||
level === 'warning' ? this.syslogClient.warning :
|
||||
level === 'success' ? this.syslogClient.info :
|
||||
level === 'debug' ? this.syslogClient.info :
|
||||
level === 'info' ? this.syslogClient.info :
|
||||
null as never;
|
||||
|
||||
send(message);
|
||||
} else {
|
||||
const Logs = getRepository(Log);
|
||||
Logs.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
machine: os.hostname(),
|
||||
worker: worker.toString(),
|
||||
domain: [this.domain].concat(subDomains).map(d => d.name),
|
||||
level: level,
|
||||
message: message,
|
||||
data: data,
|
||||
} as Log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import { publishNoteStream } from '../stream';
|
||||
import renderDelete from '../../remote/activitypub/renderer/delete';
|
||||
import renderAnnounce from '../../remote/activitypub/renderer/announce';
|
||||
import renderUndo from '../../remote/activitypub/renderer/undo';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import { deliver } from '../../queue';
|
||||
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
|
||||
@@ -35,7 +37,17 @@ export default async function(user: User, note: Note, quiet = false) {
|
||||
|
||||
//#region ローカルの投稿なら削除アクティビティを配送
|
||||
if (Users.isLocalUser(user)) {
|
||||
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user));
|
||||
let renote: Note | undefined;
|
||||
|
||||
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) {
|
||||
renote = await Notes.findOne({
|
||||
id: note.renoteId
|
||||
});
|
||||
}
|
||||
|
||||
const content = renderActivity(renote
|
||||
? renderUndo(renderAnnounce(renote.uri || `${config.url}/notes/${renote.id}`, note), user)
|
||||
: renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user));
|
||||
|
||||
const queue: string[] = [];
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import * as nodemailer from 'nodemailer';
|
||||
import { fetchMeta } from '../misc/fetch-meta';
|
||||
import Logger from './logger';
|
||||
import config from '../config';
|
||||
|
||||
export const logger = new Logger('email');
|
||||
|
||||
@@ -14,6 +15,7 @@ export async function sendEmail(to: string, subject: string, text: string) {
|
||||
port: meta.smtpPort,
|
||||
secure: meta.smtpSecure,
|
||||
ignoreTLS: !enableAuth,
|
||||
proxy: config.proxySmtp,
|
||||
auth: enableAuth ? {
|
||||
user: meta.smtpUser,
|
||||
pass: meta.smtpPass
|
||||
|
@@ -2,10 +2,10 @@
|
||||
* Tests of API (visibility)
|
||||
*
|
||||
* How to run the tests:
|
||||
* > mocha test/api-visibility.ts --require ts-node/register
|
||||
* > npx mocha test/api-visibility.ts --require ts-node/register
|
||||
*
|
||||
* To specify test:
|
||||
* > mocha test/api-visibility.ts --require ts-node/register -g 'test name'
|
||||
* > npx mocha test/api-visibility.ts --require ts-node/register -g 'test name'
|
||||
*
|
||||
* If the tests not start, try set following enviroment variables:
|
||||
* TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true
|
||||
|
@@ -2,10 +2,10 @@
|
||||
* Tests of API
|
||||
*
|
||||
* How to run the tests:
|
||||
* > mocha test/api.ts --require ts-node/register
|
||||
* > npx mocha test/api.ts --require ts-node/register
|
||||
*
|
||||
* To specify test:
|
||||
* > mocha test/api.ts --require ts-node/register -g 'test name'
|
||||
* > npx mocha test/api.ts --require ts-node/register -g 'test name'
|
||||
*
|
||||
* If the tests not start, try set following enviroment variables:
|
||||
* TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user