Compare commits
96 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
6f1048c006 | |||
d686e70f2b | |||
70f524b82d | |||
79c6475028 | |||
7dee5309dc | |||
958ec7b03f | |||
9153434906 | |||
3a08364c24 | |||
a9beeab502 | |||
38c901069a | |||
3f7606060e | |||
777f20e9be | |||
8e39aecffe | |||
9e1ab54097 | |||
0f9e09f4bd | |||
743ebc17b9 | |||
9bc4af76b8 | |||
46fa26426d | |||
58d0dc1795 | |||
bc11702f7d | |||
6288de5813 |
@ -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
|
||||
|
80
CHANGELOG.md
80
CHANGELOG.md
@ -1,6 +1,86 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
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
|
||||
* Room: ソファ追加
|
||||
* Room: 螺旋階段追加
|
||||
* Room: ゴミ箱追加
|
||||
|
||||
### 🐛Fixes
|
||||
* Room: 部屋を離れても裏でレンダリングが続く問題を修正
|
||||
* Room: アバターのレンダリングを修正
|
||||
* Room: ライティングの調整
|
||||
|
||||
11.28.2 (2019/08/18)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
* 他人の部屋なのに部屋編集UIが表示されるのを修正
|
||||
* オブジェクトストレージを使用している場合Roomで画像を読み込めない問題を修正
|
||||
|
||||
11.28.1 (2019/08/18)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
* オブジェクトストレージを使用している場合Roomで画像を読み込めない問題を修正
|
||||
* Roomで家具を移動など確定せずに「しまう」と部屋ごと消える問題を修正
|
||||
|
||||
11.28.0 (2019/08/18)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
|
@ -39,13 +39,14 @@ Misskey uses CircleCI for executing automated tests.
|
||||
Configuration files are located in [`/.circleci`](/.circleci).
|
||||
|
||||
## Adding MisskeyRoom items
|
||||
Currently, we accept only 3D models created with [Blender](https://www.blender.org/).
|
||||
|
||||
* Use English for material, object and texture names
|
||||
* Use meter for unit of length
|
||||
* Your PR must 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).
|
||||
|
||||
## FAQ
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:12.8-alpine AS base
|
||||
FROM node:12.9.1-alpine AS base
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
|
@ -139,7 +139,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<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/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>
|
||||
@ -148,7 +147,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/takimura">takimura</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 +155,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:** Sun, 01 Sep 2019 22:11:05 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
|
@ -122,12 +122,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 +235,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 +283,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 +384,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 +418,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 +427,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 +953,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,5 +1370,7 @@ pages:
|
||||
room:
|
||||
translate: "Přesunout"
|
||||
save: "Uložit"
|
||||
saved: "Uloženo"
|
||||
furnitures:
|
||||
moon: "Po"
|
||||
moon: "Měsíc"
|
||||
bin: "Koš"
|
||||
|
@ -213,7 +213,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 +680,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,5 +1913,7 @@ pages:
|
||||
room:
|
||||
translate: "Flyt"
|
||||
save: "Gem"
|
||||
saved: "Gemt"
|
||||
furnitures:
|
||||
moon: "Man"
|
||||
moon: "Måne"
|
||||
bin: "Skraldespand"
|
||||
|
@ -207,7 +207,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 +605,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,5 +949,7 @@ pages:
|
||||
array: "Listen"
|
||||
room:
|
||||
save: "Speichern"
|
||||
saved: "Gespeichert"
|
||||
furnitures:
|
||||
moon: "Mo"
|
||||
moon: "Mond"
|
||||
bin: "Papierkorb"
|
||||
|
@ -133,6 +133,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 +247,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"
|
||||
@ -296,6 +297,16 @@ common:
|
||||
saved: "Saved"
|
||||
home-profile: "Home profile"
|
||||
deck-profile: "Deck profile"
|
||||
room: "Room"
|
||||
_room:
|
||||
graphicsQuality: "Graphics Quality"
|
||||
_graphicsQuality:
|
||||
ultra: "Ultra"
|
||||
high: "High"
|
||||
medium: "Medium"
|
||||
low: "Low"
|
||||
cheep: "Cheep"
|
||||
useOrthographicCamera: "Use Orthographic Camera"
|
||||
search: "Search"
|
||||
delete: "Delete"
|
||||
loading: "Loading"
|
||||
@ -380,6 +391,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"
|
||||
@ -742,8 +756,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."
|
||||
@ -917,7 +931,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"
|
||||
@ -1135,6 +1149,7 @@ desktop/views/components/ui.header.account.vue:
|
||||
groups: "Groups"
|
||||
follow-requests: "Follow requests"
|
||||
admin: "Admin"
|
||||
room: "Room"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
game: "Games"
|
||||
desktop/views/components/ui.header.notifications.vue:
|
||||
@ -1806,6 +1821,7 @@ 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"
|
||||
are-you-sure-delete: "Do you want to delete this page?"
|
||||
page-deleted: "The page has been deleted"
|
||||
edit-this-page: "Edit this page"
|
||||
@ -2075,7 +2091,63 @@ pages:
|
||||
pageVariables: "Page element"
|
||||
argVariables: "Input slot"
|
||||
room:
|
||||
add-furniture: "Place furniture"
|
||||
translate: "Move"
|
||||
rotate: "Rotate"
|
||||
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"
|
||||
rooms:
|
||||
default: "Default"
|
||||
washitsu: "Japanese-style"
|
||||
furnitures:
|
||||
moon: "M"
|
||||
milk: "Milk carton"
|
||||
bed: "Bed"
|
||||
low-table: "Low Table"
|
||||
desk: "Desk"
|
||||
chair: "Chair"
|
||||
chair2: "Chair 2"
|
||||
fan: "Fan"
|
||||
pc: "Computer"
|
||||
plant: "Houseplant"
|
||||
plant2: "Houseplant 2"
|
||||
eraser: "Eraser"
|
||||
pencil: "Pencil"
|
||||
pudding: "Pudding"
|
||||
cardboard-box: "Cardboard Box"
|
||||
cardboard-box2: "Cardboard Box 2"
|
||||
cardboard-box3: "Cardboard Box 3"
|
||||
book: "Book"
|
||||
book2: "Book 2"
|
||||
piano: "Piano"
|
||||
facial-tissue: "Facial tissue"
|
||||
server: "Servers"
|
||||
moon: "Moon"
|
||||
corkboard: "Cork board"
|
||||
mousepad: "Mousepad"
|
||||
monitor: "Monitor"
|
||||
keyboard: "Keyboard"
|
||||
carpet-stripe: "Carpet (stripe)"
|
||||
mat: "Mat"
|
||||
color-box: "Bookshelf"
|
||||
wall-clock: "Wall clock"
|
||||
photoframe: "Picture frame"
|
||||
cube: "Cube"
|
||||
tv: "TV"
|
||||
pinguin: "Penguin"
|
||||
rubik-cube: "Rubik's Cube"
|
||||
poster-h: "Poster (Horizontal)"
|
||||
poster-v: "Poster (Vertical)"
|
||||
sofa: "Sofa"
|
||||
spiral: "Spiral Staircase"
|
||||
bin: "Waste bin"
|
||||
cup-noodle: "Cup noodle"
|
||||
holo-display: "Holographic display"
|
||||
energy-drink: "Energy drink"
|
||||
|
@ -177,7 +177,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"
|
||||
@ -1135,3 +1135,9 @@ pages:
|
||||
arg1: "Listas"
|
||||
types:
|
||||
array: "Listas"
|
||||
room:
|
||||
save: "Guardar"
|
||||
saved: "Guardado"
|
||||
furnitures:
|
||||
moon: "Luna"
|
||||
bin: "Papelera"
|
||||
|
@ -236,7 +236,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 +284,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 …"
|
||||
@ -1115,6 +1127,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:
|
||||
@ -2000,7 +2013,59 @@ 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"
|
||||
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"
|
||||
|
@ -139,6 +139,7 @@ common:
|
||||
geolocation-alert: "お使いの端末は位置情報に対応していません"
|
||||
error: "エラー"
|
||||
enter-username: "ユーザー名を入力してください"
|
||||
specified-recipient: "宛先"
|
||||
add-visible-user: "ユーザーを追加"
|
||||
cw-placeholder: "内容への注釈 (オプション)"
|
||||
username-prompt: "ユーザー名を入力してください"
|
||||
@ -258,7 +259,7 @@ common:
|
||||
deck-column-width-wide: "広"
|
||||
use-shadow: "UIに影を使用"
|
||||
rounded-corners: "UIの角を丸める"
|
||||
circle-icons: "円形のアイコンを使用"
|
||||
circle-icons: "円形のアバターを使用"
|
||||
contrasted-acct: "ユーザー名にコントラストを付ける"
|
||||
wallpaper: "壁紙"
|
||||
choose-wallpaper: "壁紙を選択"
|
||||
@ -412,6 +413,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 +800,7 @@ common/views/components/profile-editor.vue:
|
||||
you-can-include-hashtags: "ハッシュタグを含めることができます。"
|
||||
language: "言語"
|
||||
birthday: "誕生日"
|
||||
avatar: "アイコン"
|
||||
avatar: "アバター"
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-bot: "このアカウントはBotです"
|
||||
@ -809,7 +814,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 +1004,7 @@ desktop/views/components/drive-window.vue:
|
||||
used: "使用中"
|
||||
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "アイコン"
|
||||
avatar: "アバター"
|
||||
banner: "バナー"
|
||||
nsfw: "閲覧注意"
|
||||
contextmenu:
|
||||
@ -1009,7 +1014,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 +2019,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 +2308,10 @@ room:
|
||||
exit: "戻る"
|
||||
remove: "しまう"
|
||||
save: "保存"
|
||||
saved: "保存しました"
|
||||
clear: "片付け"
|
||||
clear-confirm: "全ての家具をしまいますか?"
|
||||
leave-confirm: "未保存の変更があります、移動しますか?"
|
||||
chooseImage: "画像を選択"
|
||||
room-type: "部屋のタイプ"
|
||||
carpet-color: "床の色"
|
||||
@ -2344,3 +2356,9 @@ room:
|
||||
rubik-cube: "ルービックキューブ"
|
||||
poster-h: "ポスター(横長)"
|
||||
poster-v: "ポスター(縦長)"
|
||||
sofa: "ソファ"
|
||||
spiral: "螺旋階段"
|
||||
bin: "ゴミ箱"
|
||||
cup-noodle: "カップ麺"
|
||||
holo-display: "ホログラフィックディスプレイ"
|
||||
energy-drink: "エナジードリンク"
|
||||
|
@ -467,7 +467,7 @@ common/views/components/profile-editor.vue:
|
||||
description: "自己紹介"
|
||||
language: "言語"
|
||||
birthday: "誕生日"
|
||||
avatar: "アイコン"
|
||||
avatar: "アバター"
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatやで"
|
||||
is-bot: "このアカウントはBotやで"
|
||||
@ -605,7 +605,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 +615,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: "アプリ増やす"
|
||||
@ -1284,3 +1284,10 @@ pages:
|
||||
arg1: "リスト"
|
||||
types:
|
||||
array: "リスト"
|
||||
room:
|
||||
translate: "移動"
|
||||
save: "保存"
|
||||
saved: "保存したで!"
|
||||
furnitures:
|
||||
moon: "月"
|
||||
bin: "ゴミ箱"
|
||||
|
@ -133,6 +133,7 @@ common:
|
||||
geolocation-alert: "사용 중이신 장치에서는 위치 정보를 사용할 수 없습니다"
|
||||
error: "오류"
|
||||
enter-username: "사용자명을 입력해주세요"
|
||||
specified-recipient: "수신인"
|
||||
add-visible-user: "사용자 추가"
|
||||
cw-placeholder: "내용에 대한 주석 (옵션)"
|
||||
username-prompt: "사용자명을 입력해주세요"
|
||||
@ -246,7 +247,7 @@ common:
|
||||
deck-column-width-wide: "넓음"
|
||||
use-shadow: "UI에 그림자 효과 적용"
|
||||
rounded-corners: "UI의 모서리를 둥글게 설정"
|
||||
circle-icons: "원형 아이콘 사용"
|
||||
circle-icons: "원형 아바타를 사용"
|
||||
contrasted-acct: "사용자명에 대비 추가"
|
||||
wallpaper: "배경"
|
||||
choose-wallpaper: "배경 설정"
|
||||
@ -296,6 +297,16 @@ common:
|
||||
saved: "저장하였습니다"
|
||||
home-profile: "홈 프로필"
|
||||
deck-profile: "덱 프로필"
|
||||
room: "룸"
|
||||
_room:
|
||||
graphicsQuality: "그래픽 품질"
|
||||
_graphicsQuality:
|
||||
ultra: "최고"
|
||||
high: "높음"
|
||||
medium: "보통"
|
||||
low: "낮음"
|
||||
cheep: "최저"
|
||||
useOrthographicCamera: "평행 투시 카메라를 사용"
|
||||
search: "검색"
|
||||
delete: "삭제"
|
||||
loading: "로드 중"
|
||||
@ -380,6 +391,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: "플레이어 닫기"
|
||||
@ -1135,6 +1149,7 @@ desktop/views/components/ui.header.account.vue:
|
||||
groups: "그룹"
|
||||
follow-requests: "팔로우 요청"
|
||||
admin: "관리"
|
||||
room: "룸"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
game: "게임"
|
||||
desktop/views/components/ui.header.notifications.vue:
|
||||
@ -1806,6 +1821,7 @@ pages:
|
||||
read-page: "소스 표시중"
|
||||
page-created: "페이지를 만들었습니다"
|
||||
page-updated: "페이지를 수정했습니다"
|
||||
name-already-exists: "지정한 페이지 URL은 이미 존재합니다"
|
||||
are-you-sure-delete: "이 페이지를 삭제하시겠습니까?"
|
||||
page-deleted: "페이지가 삭제되었습니다"
|
||||
edit-this-page: "이 페이지를 편집"
|
||||
@ -2075,7 +2091,63 @@ pages:
|
||||
pageVariables: "페이지 요소"
|
||||
argVariables: "입력 슬롯"
|
||||
room:
|
||||
add-furniture: "가구를 배치"
|
||||
translate: "이동"
|
||||
rotate: "회전"
|
||||
exit: "선택 해제"
|
||||
remove: "치우기"
|
||||
save: "저장"
|
||||
saved: "저장하였습니다"
|
||||
clear: "모두 치우기"
|
||||
clear-confirm: "정말 방 안의 모든 가구를 치우시겠습니까?"
|
||||
leave-confirm: "저장되지 않은 변경 사항이 있습니다. 정말 떠나시겠습니까?"
|
||||
chooseImage: "이미지 선택"
|
||||
room-type: "룸 종류"
|
||||
carpet-color: "바닥 색상"
|
||||
rooms:
|
||||
default: "기본"
|
||||
washitsu: "일본식"
|
||||
furnitures:
|
||||
moon: "월"
|
||||
milk: "우유 팩"
|
||||
bed: "침대"
|
||||
low-table: "낮은 테이블"
|
||||
desk: "책상"
|
||||
chair: "의자"
|
||||
chair2: "의자 2"
|
||||
fan: "환기구"
|
||||
pc: "컴퓨터"
|
||||
plant: "관엽식물"
|
||||
plant2: "관엽식물 2"
|
||||
eraser: "지우개"
|
||||
pencil: "연필"
|
||||
pudding: "푸딩"
|
||||
cardboard-box: "골판지 상자"
|
||||
cardboard-box2: "골판지 상자 2"
|
||||
cardboard-box3: "골판지 상자 3"
|
||||
book: "책"
|
||||
book2: "책 2"
|
||||
piano: "피아노"
|
||||
facial-tissue: "휴지 상자"
|
||||
server: "서버"
|
||||
moon: "달"
|
||||
corkboard: "게시판"
|
||||
mousepad: "마우스 패드"
|
||||
monitor: "모니터"
|
||||
keyboard: "키보드"
|
||||
carpet-stripe: "카페트 (줄무늬)"
|
||||
mat: "매트"
|
||||
color-box: "책장"
|
||||
wall-clock: "벽걸이 시계"
|
||||
photoframe: "액자"
|
||||
cube: "큐브"
|
||||
tv: "TV"
|
||||
pinguin: "펭귄"
|
||||
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"
|
||||
|
@ -520,3 +520,9 @@ pages:
|
||||
arg1: "Lister"
|
||||
types:
|
||||
array: "Lister"
|
||||
room:
|
||||
translate: "Flytt"
|
||||
save: "Lagre"
|
||||
furnitures:
|
||||
moon: "Måne"
|
||||
bin: "Papirkurv"
|
||||
|
@ -98,8 +98,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 +164,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,5 +1262,7 @@ pages:
|
||||
room:
|
||||
translate: "Przenieś"
|
||||
save: "Zapisz"
|
||||
saved: "Zapisano"
|
||||
furnitures:
|
||||
moon: "Pn"
|
||||
moon: "Księżyc"
|
||||
bin: "Kosz"
|
||||
|
@ -284,3 +284,7 @@ pages:
|
||||
blocks:
|
||||
image: "Imagens"
|
||||
post: "Formulário de publicação"
|
||||
room:
|
||||
furnitures:
|
||||
moon: "Lua"
|
||||
bin: "Lixo"
|
||||
|
@ -168,3 +168,7 @@ pages:
|
||||
random: "Случайно"
|
||||
blocks:
|
||||
random: "Случайно"
|
||||
room:
|
||||
furnitures:
|
||||
moon: "Луна"
|
||||
bin: "Мусорное ведро"
|
||||
|
@ -133,6 +133,7 @@ common:
|
||||
geolocation-alert: "您的设备不支持定位服务"
|
||||
error: "错误"
|
||||
enter-username: "输入用户名"
|
||||
specified-recipient: "收件人"
|
||||
add-visible-user: "添加用户"
|
||||
cw-placeholder: "评论帖子(可选)"
|
||||
username-prompt: "输入用户名"
|
||||
@ -246,7 +247,7 @@ common:
|
||||
deck-column-width-wide: "宽"
|
||||
use-shadow: "在UI中使用阴影效果"
|
||||
rounded-corners: "UI界面圆角效果"
|
||||
circle-icons: "使用圆形图标"
|
||||
circle-icons: "使用圆形头像"
|
||||
contrasted-acct: "增加用户名的对比度"
|
||||
wallpaper: "壁纸"
|
||||
choose-wallpaper: "选择壁纸"
|
||||
@ -296,6 +297,16 @@ common:
|
||||
saved: "已保存"
|
||||
home-profile: "定制首页数据"
|
||||
deck-profile: "定制Deck数据"
|
||||
room: "房间"
|
||||
_room:
|
||||
graphicsQuality: "图形质量"
|
||||
_graphicsQuality:
|
||||
ultra: "最高"
|
||||
high: "高"
|
||||
medium: "中"
|
||||
low: "低"
|
||||
cheep: "最低"
|
||||
useOrthographicCamera: "使用正交相机"
|
||||
search: "搜索"
|
||||
delete: "删除"
|
||||
loading: "正在加载中"
|
||||
@ -380,6 +391,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: "关闭播放器"
|
||||
@ -917,7 +931,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: "添加应用"
|
||||
@ -1135,6 +1149,7 @@ desktop/views/components/ui.header.account.vue:
|
||||
groups: "群组"
|
||||
follow-requests: "关注申请"
|
||||
admin: "管理"
|
||||
room: "房间"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
game: "游戏"
|
||||
desktop/views/components/ui.header.notifications.vue:
|
||||
@ -1806,6 +1821,7 @@ pages:
|
||||
read-page: "查看源"
|
||||
page-created: "页面已创建"
|
||||
page-updated: "页面已更新"
|
||||
name-already-exists: "该页面URL已存在"
|
||||
are-you-sure-delete: "是否删除此页面?"
|
||||
page-deleted: "该页面已被删除。"
|
||||
edit-this-page: "编辑此页面"
|
||||
@ -2075,7 +2091,63 @@ pages:
|
||||
pageVariables: "页面元素"
|
||||
argVariables: "输入槽函数"
|
||||
room:
|
||||
add-furniture: "放置家具"
|
||||
translate: "移动"
|
||||
rotate: "旋转"
|
||||
exit: "返回"
|
||||
remove: "移除"
|
||||
save: "保存"
|
||||
saved: "已保存"
|
||||
clear: "清理"
|
||||
clear-confirm: "是否清除所有家具?"
|
||||
leave-confirm: "有尚未保存的修改。是否离开?"
|
||||
chooseImage: "选择图片"
|
||||
room-type: "房间类型"
|
||||
carpet-color: "地板颜色"
|
||||
rooms:
|
||||
default: "默认"
|
||||
washitsu: "和式房间"
|
||||
furnitures:
|
||||
moon: "一"
|
||||
milk: "牛奶纸箱"
|
||||
bed: "床"
|
||||
low-table: "矮桌"
|
||||
desk: "书桌"
|
||||
chair: "椅子"
|
||||
chair2: "椅子2"
|
||||
fan: "换气扇"
|
||||
pc: "电脑"
|
||||
plant: "观叶植物"
|
||||
plant2: "观叶植物2"
|
||||
eraser: "橡皮擦"
|
||||
pencil: "铅笔"
|
||||
pudding: "布丁"
|
||||
cardboard-box: "纸板箱"
|
||||
cardboard-box2: "纸板箱2"
|
||||
cardboard-box3: "纸板箱3"
|
||||
book: "书"
|
||||
book2: "书2"
|
||||
piano: "钢琴"
|
||||
facial-tissue: "纸巾盒"
|
||||
server: "服务器"
|
||||
moon: "月球"
|
||||
corkboard: "软木板"
|
||||
mousepad: "鼠标垫"
|
||||
monitor: "显示器"
|
||||
keyboard: "键盘"
|
||||
carpet-stripe: "地毯(条纹)"
|
||||
mat: "垫子"
|
||||
color-box: "收纳柜"
|
||||
wall-clock: "挂钟"
|
||||
photoframe: "相框"
|
||||
cube: "立方体"
|
||||
tv: "电视"
|
||||
pinguin: "企鹅君"
|
||||
rubik-cube: "魔方"
|
||||
poster-h: "海报(横向)"
|
||||
poster-v: "海报(纵向)"
|
||||
sofa: "沙发"
|
||||
spiral: "螺旋楼梯"
|
||||
bin: "垃圾箱"
|
||||
cup-noodle: "杯面"
|
||||
holo-display: "全息显示器"
|
||||
energy-drink: "能量饮料"
|
||||
|
@ -88,3 +88,6 @@ admin/views/charts.vue:
|
||||
drive: "雲端硬碟"
|
||||
pages:
|
||||
like: "贊"
|
||||
room:
|
||||
furnitures:
|
||||
moon: "月"
|
||||
|
33
package.json
33
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "11.28.0",
|
||||
"version": "11.31.1",
|
||||
"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.5",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "4.0.2",
|
||||
"autwh": "0.1.0",
|
||||
"aws-sdk": "2.512.0",
|
||||
"aws-sdk": "2.520.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",
|
||||
@ -128,12 +128,12 @@
|
||||
"eslint-plugin-vue": "5.2.3",
|
||||
"eventemitter3": "4.0.0",
|
||||
"feed": "3.0.0",
|
||||
"file-type": "12.1.0",
|
||||
"file-type": "12.2.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 +156,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",
|
||||
@ -198,7 +198,7 @@
|
||||
"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",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rename": "1.0.4",
|
||||
@ -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",
|
||||
"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,21 +245,21 @@
|
||||
"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.1",
|
||||
"vue-loader": "15.7.1",
|
||||
"vue-marquee-text-component": "1.1.1",
|
||||
"vue-prism-component": "1.1.1",
|
||||
"vue-router": "3.1.2",
|
||||
"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.17",
|
||||
"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": "4.39.3",
|
||||
"webpack-cli": "3.3.7",
|
||||
"websocket": "1.0.29",
|
||||
"ws": "7.1.2",
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
@ -321,4 +329,69 @@
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "sofa",
|
||||
place: "floor",
|
||||
props: {
|
||||
color: 'color'
|
||||
},
|
||||
color: {
|
||||
Sofa: 'color'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "spiral",
|
||||
place: "floor",
|
||||
props: {
|
||||
color: 'color'
|
||||
},
|
||||
color: {
|
||||
Step: 'color'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "bin",
|
||||
place: "floor",
|
||||
props: {
|
||||
color: 'color'
|
||||
},
|
||||
color: {
|
||||
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",
|
||||
}
|
||||
]
|
||||
|
@ -1,4 +1,5 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
||||
@ -9,7 +10,7 @@ import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js';
|
||||
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';
|
||||
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
|
||||
import { Furniture, RoomInfo } from './furniture';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { query as urlQuery } from '../../../../../prelude/url';
|
||||
const furnitureDefs = require('./furnitures.json5');
|
||||
|
||||
THREE.ImageUtils.crossOrigin = '';
|
||||
@ -20,6 +21,9 @@ type Options = {
|
||||
useOrthographicCamera: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* MisskeyRoom Core Engine
|
||||
*/
|
||||
export class Room {
|
||||
private clock: THREE.Clock;
|
||||
private scene: THREE.Scene;
|
||||
@ -36,7 +40,11 @@ export class Room {
|
||||
private selectedObject: THREE.Object3D = null;
|
||||
private onChangeSelect: Function;
|
||||
private isTransformMode = false;
|
||||
public canvas: HTMLCanvasElement;
|
||||
private renderFrameRequestId: number;
|
||||
|
||||
private get canvas(): HTMLCanvasElement {
|
||||
return this.renderer.domElement;
|
||||
}
|
||||
|
||||
private get furnitures(): Furniture[] {
|
||||
return this.roomInfo.furnitures;
|
||||
@ -94,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 :
|
||||
@ -102,8 +109,7 @@ export class Room {
|
||||
this.graphicsQuality === 'low' ? THREE.BasicShadowMap :
|
||||
THREE.BasicShadowMap; // cheep
|
||||
|
||||
this.canvas = this.renderer.domElement;
|
||||
container.appendChild(this.renderer.domElement);
|
||||
container.appendChild(this.canvas);
|
||||
//#endregion
|
||||
|
||||
//#region Init a camera
|
||||
@ -150,22 +156,29 @@ export class Room {
|
||||
}
|
||||
|
||||
//#region Out light
|
||||
const outLight = new THREE.SpotLight(0xffffff, 0.4);
|
||||
const outLight1 = new THREE.SpotLight(0xffffff, 0.4);
|
||||
outLight1.position.set(9, 3, -2);
|
||||
outLight1.castShadow = this.enableShadow;
|
||||
outLight1.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある
|
||||
outLight1.shadow.mapSize.width = this.shadowQuality;
|
||||
outLight1.shadow.mapSize.height = this.shadowQuality;
|
||||
outLight1.shadow.camera.near = 6;
|
||||
outLight1.shadow.camera.far = 15;
|
||||
outLight1.shadow.camera.fov = 45;
|
||||
this.scene.add(outLight1);
|
||||
|
||||
outLight.position.set(9, 3, -2);
|
||||
outLight.castShadow = this.enableShadow;
|
||||
outLight.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある
|
||||
outLight.shadow.mapSize.width = this.shadowQuality;
|
||||
outLight.shadow.mapSize.height = this.shadowQuality;
|
||||
outLight.shadow.camera.near = 6;
|
||||
outLight.shadow.camera.far = 15;
|
||||
outLight.shadow.camera.fov = 45;
|
||||
|
||||
this.scene.add(outLight);
|
||||
const outLight2 = new THREE.SpotLight(0xffffff, 0.2);
|
||||
outLight2.position.set(-2, 3, 9);
|
||||
outLight2.castShadow = false;
|
||||
outLight2.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある
|
||||
outLight2.shadow.camera.near = 6;
|
||||
outLight2.shadow.camera.far = 15;
|
||||
outLight2.shadow.camera.fov = 45;
|
||||
this.scene.add(outLight2);
|
||||
//#endregion
|
||||
|
||||
//#region Init a controller
|
||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||
this.controls = new OrbitControls(this.camera, this.canvas);
|
||||
|
||||
this.controls.target.set(0, 1, 0);
|
||||
this.controls.enableZoom = true;
|
||||
@ -208,15 +221,17 @@ export class Room {
|
||||
|
||||
//#region Label
|
||||
//#region Avatar
|
||||
const avatarUrl = user.avatarUrl;
|
||||
const avatarUrl = `/proxy/?${urlQuery({ url: user.avatarUrl })}`;
|
||||
|
||||
const iconTexture = new THREE.TextureLoader().load(avatarUrl);
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
textureLoader.crossOrigin = 'anonymous';
|
||||
|
||||
const iconTexture = textureLoader.load(avatarUrl);
|
||||
iconTexture.wrapS = THREE.RepeatWrapping;
|
||||
iconTexture.wrapT = THREE.RepeatWrapping;
|
||||
iconTexture.anisotropy = 16;
|
||||
|
||||
const avatarMaterial = new THREE.MeshLambertMaterial({
|
||||
emissive: 0x111111,
|
||||
const avatarMaterial = new THREE.MeshBasicMaterial({
|
||||
map: iconTexture,
|
||||
side: THREE.DoubleSide,
|
||||
alphaTest: 0.5
|
||||
@ -262,14 +277,14 @@ export class Room {
|
||||
|
||||
//#region Interaction
|
||||
if (isMyRoom) {
|
||||
this.furnitureControl = new TransformControls(this.camera, this.renderer.domElement);
|
||||
this.furnitureControl = new TransformControls(this.camera, this.canvas);
|
||||
this.scene.add(this.furnitureControl);
|
||||
|
||||
// Hover highlight
|
||||
this.renderer.domElement.onmousemove = this.onmousemove;
|
||||
this.canvas.onmousemove = this.onmousemove;
|
||||
|
||||
// Click
|
||||
this.renderer.domElement.onmousedown = this.onmousedown;
|
||||
this.canvas.onmousedown = this.onmousedown;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@ -296,7 +311,8 @@ export class Room {
|
||||
|
||||
@autobind
|
||||
private renderWithoutPostFXs() {
|
||||
requestAnimationFrame(this.renderWithoutPostFXs);
|
||||
this.renderFrameRequestId =
|
||||
window.requestAnimationFrame(this.renderWithoutPostFXs);
|
||||
|
||||
// Update animations
|
||||
const clock = this.clock.getDelta();
|
||||
@ -310,7 +326,8 @@ export class Room {
|
||||
|
||||
@autobind
|
||||
private renderWithPostFXs() {
|
||||
requestAnimationFrame(this.renderWithPostFXs);
|
||||
this.renderFrameRequestId =
|
||||
window.requestAnimationFrame(this.renderWithPostFXs);
|
||||
|
||||
// Update animations
|
||||
const clock = this.clock.getDelta();
|
||||
@ -325,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;
|
||||
|
||||
@ -384,17 +402,13 @@ export class Room {
|
||||
if (!(child instanceof THREE.Mesh)) return;
|
||||
child.castShadow = this.enableShadow;
|
||||
child.receiveShadow = this.enableShadow;
|
||||
child.material = new THREE.MeshLambertMaterial({
|
||||
color: (child.material as THREE.MeshStandardMaterial).color,
|
||||
map: (child.material as THREE.MeshStandardMaterial).map,
|
||||
name: (child.material as THREE.MeshStandardMaterial).name,
|
||||
});
|
||||
(child.material as THREE.MeshStandardMaterial).metalness = 0;
|
||||
|
||||
// 異方性フィルタリング
|
||||
if ((child.material as THREE.MeshLambertMaterial).map && this.graphicsQuality !== 'cheep') {
|
||||
(child.material as THREE.MeshLambertMaterial).map.minFilter = THREE.LinearMipMapLinearFilter;
|
||||
(child.material as THREE.MeshLambertMaterial).map.magFilter = THREE.LinearMipMapLinearFilter;
|
||||
(child.material as THREE.MeshLambertMaterial).map.anisotropy = 8;
|
||||
if ((child.material as THREE.MeshStandardMaterial).map && this.graphicsQuality !== 'cheep') {
|
||||
(child.material as THREE.MeshStandardMaterial).map.minFilter = THREE.LinearMipMapLinearFilter;
|
||||
(child.material as THREE.MeshStandardMaterial).map.magFilter = THREE.LinearMipMapLinearFilter;
|
||||
(child.material as THREE.MeshStandardMaterial).map.anisotropy = 8;
|
||||
}
|
||||
});
|
||||
|
||||
@ -415,37 +429,46 @@ 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;
|
||||
|
||||
model.traverse(child => {
|
||||
if (!(child instanceof THREE.Mesh)) return;
|
||||
for (const t of Object.keys(def.texture)) {
|
||||
@ -467,11 +490,14 @@ export class Room {
|
||||
});
|
||||
|
||||
const img = new Image();
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.onload = () => {
|
||||
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;
|
||||
@ -493,8 +519,8 @@ export class Room {
|
||||
if (this.isTransformMode) return;
|
||||
|
||||
const rect = (ev.target as HTMLElement).getBoundingClientRect();
|
||||
const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.renderer.domElement.width) * 2 - 1;
|
||||
const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.renderer.domElement.height) * 2 + 1;
|
||||
const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.canvas.width) * 2 - 1;
|
||||
const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.canvas.height) * 2 + 1;
|
||||
const pos = new THREE.Vector2(x, y);
|
||||
|
||||
this.camera.updateMatrixWorld();
|
||||
@ -515,24 +541,23 @@ export class Room {
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const intersected = this.getRoot(intersects[0].object);
|
||||
if (!this.isSelectedObject(intersected)) {
|
||||
intersected.traverse(child => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x191919);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.isSelectedObject(intersected)) return;
|
||||
intersected.traverse(child => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x191919);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private onmousedown(ev: MouseEvent) {
|
||||
if (this.isTransformMode) return;
|
||||
if (ev.target !== this.renderer.domElement || ev.button !== 0) return;
|
||||
if (ev.target !== this.canvas || ev.button !== 0) return;
|
||||
|
||||
const rect = (ev.target as HTMLElement).getBoundingClientRect();
|
||||
const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.renderer.domElement.width) * 2 - 1;
|
||||
const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.renderer.domElement.height) * 2 + 1;
|
||||
const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.canvas.width) * 2 - 1;
|
||||
const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.canvas.height) * 2 + 1;
|
||||
const pos = new THREE.Vector2(x, y);
|
||||
|
||||
this.camera.updateMatrixWorld();
|
||||
@ -593,6 +618,10 @@ export class Room {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 家具の移動/回転モードにします
|
||||
* @param type 移動か回転か
|
||||
*/
|
||||
@autobind
|
||||
public enterTransformMode(type: 'translate' | 'rotate') {
|
||||
this.isTransformMode = true;
|
||||
@ -600,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);
|
||||
@ -615,6 +652,10 @@ export class Room {
|
||||
this.applyCustomTexture(this.selectedObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 部屋に家具を追加します
|
||||
* @param type 家具の種類
|
||||
*/
|
||||
@autobind
|
||||
public addFurniture(type: string) {
|
||||
const furniture = {
|
||||
@ -640,8 +681,12 @@ export class Room {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 現在選択されている家具を部屋から削除します
|
||||
*/
|
||||
@autobind
|
||||
public removeFurniture() {
|
||||
this.exitTransformMode();
|
||||
const obj = this.selectedObject;
|
||||
this.scene.remove(obj);
|
||||
this.objects = this.objects.filter(object => object.name !== obj.name);
|
||||
@ -650,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;
|
||||
@ -663,6 +731,9 @@ export class Room {
|
||||
this.loadRoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 部屋データを取得します
|
||||
*/
|
||||
@autobind
|
||||
public getRoomInfo() {
|
||||
for (const obj of this.objects) {
|
||||
@ -678,6 +749,9 @@ export class Room {
|
||||
return this.roomInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 選択されている家具を取得します
|
||||
*/
|
||||
@autobind
|
||||
public getSelectedObject() {
|
||||
return this.selectedObject;
|
||||
@ -687,4 +761,16 @@ export class Room {
|
||||
public findFurnitureById(id: string) {
|
||||
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,110 @@
|
||||
<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">
|
||||
<mk-user-name v-for="u in users" :user="u" :nowrap="false" :key="u.id"/>
|
||||
</span>
|
||||
<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
|
||||
</i18n>
|
||||
<i18n path="many-users" v-if="10 < users.length">
|
||||
<span slot="users">{{ users.slice(0, 10).join(', ') }}</span>
|
||||
<span slot="ommited">{{ users.length - 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,
|
||||
},
|
||||
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 5px 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,46 @@ 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
|
||||
}).then((reactions: any[]) => {
|
||||
const users = reactions.filter(x => x.type === this.reaction)
|
||||
.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,
|
||||
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 +174,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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -25,7 +25,8 @@
|
||||
<ui-button @click="remove()"><fa :icon="faTrashAlt"/> {{ $t('remove') }}</ui-button>
|
||||
</section>
|
||||
</div>
|
||||
<div class="menu">
|
||||
|
||||
<div class="menu" v-if="isMyRoom">
|
||||
<section>
|
||||
<ui-button @click="add()"><fa :icon="faBoxOpen"/> {{ $t('add-furniture') }}</ui-button>
|
||||
</section>
|
||||
@ -41,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>
|
||||
@ -54,8 +56,9 @@ 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';
|
||||
|
||||
let room: Room;
|
||||
|
||||
@ -83,15 +86,21 @@ export default Vue.extend({
|
||||
carpetColor: null,
|
||||
isTranslateMode: false,
|
||||
isRotateMode: false,
|
||||
faBoxOpen, faSave, faTrashAlt, faUndo, faArrowsAlt, faBan,
|
||||
isMyRoom: false,
|
||||
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)
|
||||
});
|
||||
|
||||
this.isMyRoom = this.$store.getters.isSignedIn && this.$store.state.i.id === user.id;
|
||||
|
||||
const roomInfo = await this.$root.api('room/show', {
|
||||
userId: user.id
|
||||
});
|
||||
@ -99,7 +108,7 @@ export default Vue.extend({
|
||||
this.roomType = roomInfo.roomType;
|
||||
this.carpetColor = roomInfo.carpetColor;
|
||||
|
||||
room = new Room(user, this.$store.getters.isSignedIn && this.$store.state.i.id === user.id, roomInfo, this.$el, {
|
||||
room = new Room(user, this.isMyRoom, roomInfo, this.$el, {
|
||||
graphicsQuality: this.$store.state.device.roomGraphicsQuality,
|
||||
onChangeSelect: obj => {
|
||||
this.objectSelected = obj != null;
|
||||
@ -119,7 +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,
|
||||
@ -133,15 +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;
|
||||
});
|
||||
},
|
||||
|
||||
@ -149,24 +215,28 @@ export default Vue.extend({
|
||||
this.$chooseDriveFile({
|
||||
multiple: false
|
||||
}).then(file => {
|
||||
room.updateProp(key, file.thumbnailUrl);
|
||||
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() {
|
||||
@ -177,6 +247,7 @@ export default Vue.extend({
|
||||
this.isTranslateMode = true;
|
||||
room.enterTransformMode('translate');
|
||||
}
|
||||
this.changed = true;
|
||||
},
|
||||
|
||||
rotate() {
|
||||
@ -187,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() {
|
||||
|
@ -185,7 +185,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 },
|
||||
|
@ -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
|
||||
|
@ -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) }) },
|
||||
@ -171,6 +172,7 @@ init((launch, os) => {
|
||||
]},
|
||||
{ path: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) },
|
||||
{ path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) },
|
||||
{ path: '/@:acct/room', props: true, component: () => import('../common/views/pages/room/room.vue').then(m => m.default) },
|
||||
{ path: '/notes/:note', component: MkNote },
|
||||
{ path: '/authorize-follow', component: MkFollow },
|
||||
{ path: '*', component: MkNotFound }
|
||||
|
@ -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
|
||||
|
BIN
src/client/assets/room/furnitures/bin/bin.blend
Normal file
BIN
src/client/assets/room/furnitures/bin/bin.blend
Normal file
Binary file not shown.
BIN
src/client/assets/room/furnitures/bin/bin.glb
Normal file
BIN
src/client/assets/room/furnitures/bin/bin.glb
Normal file
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 |
Binary file not shown.
Binary file not shown.
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.
BIN
src/client/assets/room/furnitures/sofa/sofa.blend
Normal file
BIN
src/client/assets/room/furnitures/sofa/sofa.blend
Normal file
Binary file not shown.
BIN
src/client/assets/room/furnitures/sofa/sofa.glb
Normal file
BIN
src/client/assets/room/furnitures/sofa/sofa.glb
Normal file
Binary file not shown.
BIN
src/client/assets/room/furnitures/spiral/spiral.blend
Normal file
BIN
src/client/assets/room/furnitures/spiral/spiral.blend
Normal file
Binary file not shown.
BIN
src/client/assets/room/furnitures/spiral/spiral.glb
Normal file
BIN
src/client/assets/room/furnitures/spiral/spiral.glb
Normal file
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;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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'
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,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
|
||||
|
@ -2,10 +2,10 @@
|
||||
* Tests of chart engine
|
||||
*
|
||||
* How to run the tests:
|
||||
* > mocha test/chart.ts --require ts-node/register
|
||||
* > npx mocha test/chart.ts --require ts-node/register
|
||||
*
|
||||
* To specify test:
|
||||
* > mocha test/chart.ts --require ts-node/register -g 'test name'
|
||||
* > npx mocha test/chart.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
|
||||
@ -320,4 +320,60 @@ describe('Chart', () => {
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Resync', () => {
|
||||
it('Can resync', async(async () => {
|
||||
testChart.total = 1;
|
||||
|
||||
await testChart.resync();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3);
|
||||
const chartDays = await testChart.getChart('day', 3);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [1, 0, 0]
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [1, 0, 0]
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('Can resync (2)', async(async () => {
|
||||
await testChart.increment();
|
||||
|
||||
clock.tick('01:00:00');
|
||||
|
||||
testChart.total = 100;
|
||||
|
||||
await testChart.resync();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3);
|
||||
const chartDays = await testChart.getChart('day', 3);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 1, 0],
|
||||
total: [100, 1, 0]
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [100, 0, 0]
|
||||
},
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -2,10 +2,10 @@
|
||||
* Tests of MFM
|
||||
*
|
||||
* How to run the tests:
|
||||
* > mocha test/mfm.ts --require ts-node/register
|
||||
* > npx mocha test/mfm.ts --require ts-node/register
|
||||
*
|
||||
* To specify test:
|
||||
* > mocha test/mfm.ts --require ts-node/register -g 'test name'
|
||||
* > npx mocha test/mfm.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 mute
|
||||
*
|
||||
* How to run the tests:
|
||||
* > mocha test/mute.ts --require ts-node/register
|
||||
* > npx mocha test/mute.ts --require ts-node/register
|
||||
*
|
||||
* To specify test:
|
||||
* > mocha test/mute.ts --require ts-node/register -g 'test name'
|
||||
* > npx mocha test/mute.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 Note
|
||||
*
|
||||
* How to run the tests:
|
||||
* > mocha test/note.ts --require ts-node/register
|
||||
* > npx mocha test/note.ts --require ts-node/register
|
||||
*
|
||||
* To specify test:
|
||||
* > mocha test/note.ts --require ts-node/register -g 'test name'
|
||||
* > npx mocha test/note.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 Maybe
|
||||
*
|
||||
* How to run the tests:
|
||||
* > mocha test/prelude/maybe.ts --require ts-node/register
|
||||
* > npx mocha test/prelude/maybe.ts --require ts-node/register
|
||||
*
|
||||
* To specify test:
|
||||
* > mocha test/prelude/maybe.ts --require ts-node/register -g 'test name'
|
||||
* > npx mocha test/prelude/maybe.ts --require ts-node/register -g 'test name'
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
@ -2,10 +2,10 @@
|
||||
* Tests of MFM
|
||||
*
|
||||
* How to run the tests:
|
||||
* > mocha test/reaction-lib.ts --require ts-node/register
|
||||
* > npx mocha test/reaction-lib.ts --require ts-node/register
|
||||
*
|
||||
* To specify test:
|
||||
* > mocha test/reaction-lib.ts --require ts-node/register -g 'test name'
|
||||
* > npx mocha test/reaction-lib.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