Compare commits

..

98 Commits

Author SHA1 Message Date
syuilo
b719d43049 11.32.0 2019-09-10 15:48:37 +09:00
syuilo
f555aa734b Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-09-10 15:46:20 +09:00
syuilo
37fb685016 Update dependencies 🚀 2019-09-10 15:46:00 +09:00
syuilo
09fe1b355a New Crowdin translations (#5385)
* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Danish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)
2019-09-10 15:44:25 +09:00
Aya Morisawa
9d530a5c38 Fix typo: ommited -> omitted (#5406) 2019-09-09 22:47:52 +09:00
MeiMei
827c378ac1 AP Lock (#5410) 2019-09-09 22:46:45 +09:00
syuilo
9b91b92bca Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-09-09 22:28:48 +09:00
syuilo
e7fcca0e06 Fix #5403 2019-09-09 22:28:41 +09:00
syuilo
a36e5b4543 Refactoring 2019-09-09 22:18:32 +09:00
syuilo
ba474648a2 Update CONTRIBUTING.md 2019-09-08 21:21:08 +09:00
MeiMei
a7b710a2f8 広告ブロッカーの警告を削除 (#5408)
* remove f*ckadbloack

* follow-buttonが消されないように

* revert version

* remove locales
2019-09-08 21:19:28 +09:00
MeiMei
c033464b92 AP Undo Announce (#5400)
* AP Undo Announce

* Use activity id
2019-09-08 11:30:44 +09:00
Acid Chicken (硫酸鶏)
3ef6305c76 Update README.md [AUTOGEN] (#5402) 2019-09-07 23:43:35 +09:00
Acid Chicken (硫酸鶏)
511ef20b8a Update README.md [AUTOGEN] (#5396) 2019-09-05 05:05:24 +09:00
syuilo
4db710a4b0 Fix indentation 2019-09-04 14:35:21 +09:00
Acid Chicken (硫酸鶏)
efe92724a4 Update README.md [AUTOGEN] (#5390) 2019-09-04 04:25:22 +09:00
syuilo
aa19767ebc 11.31.4 2019-09-03 08:00:41 +09:00
syuilo
143190ab38 Fix bug 2019-09-03 08:00:21 +09:00
syuilo
baf94f86c4 11.31.3 2019-09-03 07:28:45 +09:00
syuilo
d36b129369 Improve reaction tooltip 2019-09-03 07:25:02 +09:00
syuilo
f36d88246a Fetch more reactins 2019-09-03 07:15:53 +09:00
syuilo
03f87140b3 11.31.2 2019-09-03 06:58:19 +09:00
syuilo
1dc07f6b72 🎨 2019-09-03 06:58:01 +09:00
syuilo
0aa0a9d24b 11.31.1 2019-09-03 06:22:05 +09:00
syuilo
a9a93db2b4 Update reactions-viewer.reaction.vue 2019-09-03 06:20:52 +09:00
syuilo
f187df3933 Update reactions-viewer.reaction.vue 2019-09-03 06:20:04 +09:00
syuilo
8abe8042d7 Fix bug 2019-09-03 06:19:17 +09:00
syuilo
58fd46ff6f 🎨 2019-09-03 06:00:45 +09:00
syuilo
fef8b662c1 🎨 2019-09-03 05:50:01 +09:00
syuilo
8de2f4ce76 Update node to 12.9.1 2019-09-03 05:43:44 +09:00
Acid Chicken (硫酸鶏)
e5e344e1cd Update README.md [AUTOGEN] (#5382) 2019-09-03 01:02:57 +09:00
Aya Morisawa
e70d7edf41 Fix #5380 (#5381) 2019-09-02 07:01:33 +09:00
syuilo
71d4d51fb2 11.31.0 2019-09-02 06:33:07 +09:00
syuilo
aaf38f1cbe Update CHANGELOG.md 2019-09-02 06:23:10 +09:00
syuilo
0e0d6692c0 Fix bug 2019-09-02 06:21:43 +09:00
syuilo
29f927fe72 Update CHANGELOG.md 2019-09-02 06:21:06 +09:00
syuilo
ee39d9594e Improve readability 2019-09-02 06:04:12 +09:00
Oni-Men
cefd2a4c54 ページURLが空の時currentNameを使うように (#5368)
* ページURLが空の時currentNameを使う

* 空の時はページURLにcurrentNameを代入するように

* Update src/client/app/common/views/pages/page-editor/page-editor.vue

Co-Authored-By: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* update validator

* Update src/client/app/common/views/pages/page-editor/page-editor.vue

Co-Authored-By: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* やっぱりuuidは統一

* エラー処理を追加

* some fix

* ちょっとだけ翻訳の追加と改善

* リファクタリング

* Revert "やっぱりuuidは統一"

This reverts commit 965a860504.

* やっぱりuuidをわける

* エラー判定をidからcodeに+リファクタリング
2019-09-02 06:02:35 +09:00
syuilo
a08c20d9af Fix #5353 2019-09-02 05:59:24 +09:00
syuilo
dc11f1afbf Improve readavility 2019-09-02 05:52:38 +09:00
syuilo
b0f2b209a2 Fix error 2019-09-02 05:42:30 +09:00
syuilo
a25fdfd519 Fix #5373 2019-09-02 05:34:25 +09:00
syuilo
c1aa58596d Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-09-02 05:20:17 +09:00
syuilo
b6a3eb2445 Fix #5379 2019-09-02 05:20:06 +09:00
syuilo
310f4d2edb New Crowdin translations (#5337)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Danish)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Danish)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)
2019-09-02 05:04:24 +09:00
Aya Morisawa
701fee3139 Prevent users from opening note menu when already opened (#5367) 2019-09-02 04:45:01 +09:00
MeiMei
593c2b9517 Proxy for SMTP (#5371) 2019-09-02 04:42:52 +09:00
syuilo
96b2267cb8 Chart resyncing (#5372)
* wip

* Add test

* Fix test

* Insert moderation log

* Add todo
2019-09-02 04:41:26 +09:00
Acid Chicken (硫酸鶏)
84730a071a Update README.md [AUTOGEN] (#5377) 2019-09-02 04:06:54 +09:00
syuilo
d0b0cf8dfb Use npx 2019-09-01 04:31:59 +09:00
Aya Morisawa
749200d22b Show users who sent reaction on hover (#5362)
* Show users who sent reaction on hover

* Support i18n

* detail -> details

* Extract methods

* Update on change
2019-08-31 03:04:36 +09:00
syuilo
a47baad943 Update dependencies 🚀 2019-08-31 03:01:50 +09:00
syuilo
50abb51ece Syslog support
Resolve #5355
2019-08-30 08:29:46 +09:00
Aya Morisawa
1f890c5bed Assign URL to each page of admin (#5366)
* Assign URL to each page of admin

* Remove cursor pointer
2019-08-30 04:19:49 +09:00
Aya Morisawa
97f23af86d Assign URL to each page of settings (#5349)
* Assign URL to each page of settings

* Use router-link

* comma

* Use active-class

* Clean up

* space

* comma

* Redirect if mobile

* Redirect to /i/settings/profile

* Clean up
2019-08-29 14:57:28 +09:00
syuilo
d77aa1f26a 不要なプロパティをレスポンスから削除 2019-08-29 07:34:54 +09:00
Acid Chicken (硫酸鶏)
0b075ad4e9 Update README.md [AUTOGEN] (#5356) 2019-08-29 05:33:52 +09:00
Aya Morisawa
423f776ed0 Perform animation only when reaction is added (#5359) 2019-08-29 05:20:27 +09:00
Aya Morisawa
084fd8152b Refactor reactions-viewer.vue (#5358) 2019-08-29 05:15:14 +09:00
syuilo
89d35c2e63 Fix bug 2019-08-29 05:13:03 +09:00
Aya Morisawa
be33581642 Fix animation not being performed on new reaction (#5345)
* Fix animation not being performed on new reaction

* Clean up
2019-08-29 05:11:26 +09:00
Oni-Men
2d6d9f30e1 ページURLが他と重複してたらエラーを投げるように (#5354)
* [Page]nameが重複したときの処理を追加

* page-editor側のerr.idにuuidを適用

* refactor

* uuidをわけた
2019-08-28 08:00:05 +09:00
Aya Morisawa
85721065fd Save memo automatically (#5351)
* Save memo automatically

* Use clearTimeout

* Clean up

* Clean up
2019-08-28 02:26:19 +09:00
Aya Morisawa
9d65768d4d [MFM] Fallback to js if specified lang is not available (#5347) 2019-08-27 19:44:49 +09:00
MeiMei
13f69e4291 excludeNsfwやCWのNSFW扱いなど (#5341)
* NoteにisSensitive

* Revert "NoteにisSensitive"

This reverts commit 3d5bcfbaf0.

* query excludeNsfw

* AP deliverでCW付きはsensitiveにするように

* excludeNsfwでCW付きも除くように
2019-08-27 17:33:07 +09:00
MeiMei
6a0affcec1 Tune worker/job counts (#5346)
* デフォルトのワーカー数を1に

* Tune default job count
2019-08-27 05:33:24 +09:00
MeiMei
ab6a84cd45 未実装のTLのRenoteクエリを実装 (#5343)
* users/notes includeMyRenotes

* other renotes

* fix target user in users/notes

* users/notesからv10から未実装でありえないオプションを削除

* users/notesのincludeMyRenotesの説明を修正

* remove needless anonymous checks
2019-08-27 03:24:35 +09:00
Aya Morisawa
ba93bf7478 Prevent users from changing the disabled option (#5344) 2019-08-27 02:44:01 +09:00
Satsuki Yanagi
1c4e1af7c3 Improve post form (#5326)
* Improve post form

* Remove local icon from button
2019-08-25 16:12:01 +09:00
Satsuki Yanagi
a85f4c4fc4 Resolve #2716 (#5340)
* Resolve #2716

* Update ja-JP.yml
2019-08-25 16:11:20 +09:00
MeiMei
9d6c8806af CWの中のサムネイルのサイズが変なのを修正 (#5339)
* Fix #5338

* Revert "Fix #5338"

This reverts commit 72b32df2b7.

* Fix media-list height

* fix
2019-08-24 19:20:53 +09:00
syuilo
ff52ea2a7c 11.30.0 2019-08-24 02:28:43 +09:00
syuilo
f247ee9dd3 New Crowdin translations (#5322)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Danish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Danish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)
2019-08-24 02:24:24 +09:00
syuilo
f4cec53ba1 [Room] Make Pinguin customizable 2019-08-24 02:18:21 +09:00
syuilo
ad70b50fee [Room] Add energy drink 2019-08-23 06:52:42 +09:00
Acid Chicken (硫酸鶏)
ea7b2b3141 Update README.md [AUTOGEN] (#5335) 2019-08-23 05:06:22 +09:00
syuilo
c2f932e28b Fix #5324 2019-08-22 19:56:27 +09:00
syuilo
c637882578 カップ麺をスムーズシェードするように 2019-08-22 19:42:52 +09:00
syuilo
ef7221e39e Fix holo-display animation glitch 2019-08-22 19:37:01 +09:00
Oni-Men
6b571a7799 Improve furniture preview (#5328)
* Improve furniture preview

* improve calc method

* camera.aspectいらないことに気付いた

* refactor & go camera far away

* 対角線の長さで計算するように

* 対角線の計算にhypot()を使用

Co-Authored-By: Aya Morisawa <AyaMorisawa4869@gmail.com>
2019-08-22 18:34:15 +09:00
MeiMei
0638b6cb69 Roomで未保存警告ダイアログなどを追加 (#5332)
* Room保存時にダイアログを表示するように

* Roomから移動するときに未保存ならば警告するように
2019-08-22 18:33:50 +09:00
syuilo
a39c1706a1 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-08-22 02:40:19 +09:00
syuilo
c741e27057 [Room] Add holo-display 2019-08-22 02:40:01 +09:00
Aya Morisawa
ede854c215 Update button.vue (#5331) 2019-08-22 02:36:19 +09:00
Satsuki Yanagi
6a953b4d94 Update yarn.lock (#5327) 2019-08-21 13:04:51 +09:00
Aya Morisawa
1d763096c7 Update room section of CONTRIBUTING.md 2019-08-20 16:16:27 +09:00
Oni-Men
630d873ec0 [room]Add cup noodle (#5323)
* [Room] Add cup noodles

* remove ,

* Fix Cup-Noodle Texture
2019-08-20 15:57:42 +09:00
syuilo
d427957ea7 New Crowdin translations (#5321)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)
2019-08-20 02:19:06 +09:00
syuilo
2430cc0e2c New translations ja-JP.yml (English) (#5320) 2019-08-20 02:04:55 +09:00
syuilo
e57ee24864 New translations ja-JP.yml (French) (#5319) 2019-08-20 01:55:35 +09:00
syuilo
3bc05ab3f2 New Crowdin translations (#5318)
* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)
2019-08-20 01:44:22 +09:00
syuilo
4e50dcfa93 New Crowdin translations (#5313)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)
2019-08-19 21:24:43 +09:00
syuilo
fcf5531e5b 🎨 2019-08-19 16:44:55 +09:00
syuilo
3bef69ee58 🎨 2019-08-19 16:13:08 +09:00
syuilo
311a4f28b0 Resolve #5298 2019-08-19 16:05:57 +09:00
syuilo
01e692b353 Refactor 2019-08-19 15:59:36 +09:00
syuilo
3b445af6fc Improve readability 2019-08-19 15:51:00 +09:00
syuilo
1e43ece637 [Room] Update model 2019-08-19 14:01:05 +09:00
109 changed files with 2027 additions and 1134 deletions

View File

@@ -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

View File

@@ -1 +1 @@
v12.8.1
v12.9.1

View File

@@ -1,6 +1,88 @@
ChangeLog
=========
11.32.0 (2019/09/10)
--------------------
### ✨Improvements
* APでRenote/Boost取り消しをやりとりするように
* リモートNote存在確認から作成までの間にLockをかけるように
* 広告ブロッカーの警告を削除
### 🐛Fixes
* ミュートしているユーザーからトークメッセージが来たときに通知インジケーターが点灯する問題を修正
* リアクションした人が10人を超えるときツールチップの表示がおかしい問題を修正
11.31.4 (2019/09/03)
--------------------
### 🐛Fixes
* 誰がリアクションしたか見れるやつの表示を改善
11.31.3 (2019/09/03)
--------------------
### 🐛Fixes
* 誰がリアクションしたか見れるやつの表示を改善
11.31.2 (2019/09/03)
--------------------
### 🐛Fixes
* 誰がリアクションしたか見れるやつの表示を改善
11.31.1 (2019/09/03)
--------------------
### 🐛Fixes
* 誰がリアクションしたか見れるやつの表示を改善
11.31.0 (2019/09/02)
--------------------
### ✨Improvements
* Syslogサポート
* チャートの同期機能をAPI経由で使えるように
* SMTPでProxyを使用できるように
* リアクションにホバーすることで誰がリアクションしたか見れるように
* リプライ時、返信元のlocalOnly属性を引き継ぐように
* 引用付き、ローカルのみなどの案内文にアイコン追加
* AP deliver/inbox job の並列度を変更できるように
* clusterLimitの既定値を1に
* AP inbox ジョブの並列度を下げる
* CWが付いた投稿はAP上でNote.sensitiveフラグを付けるように
* メモウィジェットの内容を自動で保存するように
* ページURLが他と重複してたらエラーを投げるように
* ページURLが空の時エラーを投げるように
* リアクションが解除されたときはアニメーションしないように
* 設定の各セクションごとにURLを割り当てるように
* 管理画面の各セクションごとにURLを割り当てるように
### 🐛Fixes
* 未実装のTLのRenoteクエリを実装
* タイムラインAPIのexcludeNsfwオプションを実装
* ユーザーページの投稿一覧の私の投稿にRenoteが表示される問題を修正
* meta APIでemojiプロパティに不要な情報が含まれているのを修正
* モバイル版でドライブのファイルを削除したときの挙動がおかしい問題を修正
* visiblity-chooserにlocalOnly属性が伝わらなかったのを修正
* 言語指定したときコードブロックが表示されない問題を修正
* トークのメッセージがはみ出す問題を修正
* CWの中のサムネイルのサイズが変なのを少し修正
* リアクションが初めて付いた時のエフェクトが消えている問題を修正
* 無効になっているスイッチを操作できる問題を修正
* Mキー連打で画面が真っ暗問題を修正
* AmazonのURLプレビューが出来ない問題を修正
* 表記ゆれを修正
11.30.0 (2019/08/24)
--------------------
### ✨Improvements
* Room: 家具をすべてしまうボタンを追加
* Room: カップ麺追加
* Room: ホログラフィックディスプレイ追加
* Room: エナジードリンク追加
* Room: ピンギンの色を変えられるように
* Room: プレビューの見やすさを向上
* Room保存時にダイアログを表示するように
* Roomから移動するときに未保存ならば警告するように
### 🐛Fixes
* MisskeyRoomからページを戻した時、テキスト入力画面で選択位置変更ができない問題を修正
11.29.0 (2019/08/19)
--------------------
### ✨Improvements

View File

@@ -39,10 +39,12 @@ Misskey uses CircleCI for executing automated tests.
Configuration files are located in [`/.circleci`](/.circleci).
## Adding MisskeyRoom items
* Use English for material, object and texture names
* Use meter for unit of length
* Your PR should include all source files of your models (for later editing)
* Your PR must include the glTF binary files (.glb) of your models
* Use English for material, object and texture names.
* Use meter for unit of length.
* Your PR should include all source files (e.g. `.png`, `.blend`) of your models (for later editing).
* Your PR must include the glTF binary files (`.glb`) of your models.
* Add a locale key `room.furnitures.YOUR_ITEM` at [`/locales/ja-JP.yml`](/locales/ja-JP.yml).
* Add a furniture definition at [`/src/client/app/common/scripts/room/furnitures.json5`](/src/client/app/common/scripts/room/furnitures.json5).
If you have no experience on 3D modeling, we suggest to use the free 3DCG software [Blender](https://www.blender.org/).
You can find information on glTF 2.0 at [glTF 2.0 — Blender Manual]( https://docs.blender.org/manual/en/dev/addons/io_scene_gltf2.html).
@@ -268,3 +270,7 @@ npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前
```
作成されたスクリプトは不必要な変更を含むため除去してください。
## その他
### HTMLのクラス名で follow という単語は使わない
広告ブロッカーで誤ってブロックされる

View File

@@ -1,4 +1,4 @@
FROM node:12.8-alpine AS base
FROM node:12.9.1-alpine AS base
ENV NODE_ENV=production

View File

@@ -104,6 +104,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<!-- PATREON_START -->
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20010324/b8af4bd31ae34fbf8806cc0e6228e400/1.png?token-time=2145916800&token-hash=iyiocfousNIUwASmatsIDq8EOsmLUdrQNkWyktHlmJg%3D" alt="Nemo" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
@@ -111,6 +112,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
<td><a href="https://www.patreon.com/user?u=20010324">Nemo</a></td>
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
@@ -121,34 +123,36 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1.jpe?token-time=2145916800&token-hash=EWxXhVbZYH7KB4IDT3joc8TbIg8zPO40x1r5IDn3R7c%3D" alt="Hiratake" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
<td><a href="https://www.patreon.com/damillora">Damillora</a></td>
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
<td><a href="https://www.patreon.com/Corset">CG</a></td>
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
@@ -157,15 +161,17 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Mon, 05 Aug 2019 20:46:06 UTC
**Last updated:** Sat, 07 Sep 2019 13:23:36 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

View File

@@ -18,9 +18,6 @@ common:
drive: "Disk"
drive-desc: "Chcete sdílet obrázek, který jste již nahráli? Chcete vaše nahrané soubory organizovat, pojmenovávat a vytvářet pro ně složky? Misskey Disk je pro vás nejlepší řešení. Je velmi snadné sdílet vaše soubory online."
outro: "Podívejte se na unikátní vlastnosti Misskey vlastníma očima! Pokud si myslíte, že tato instance není pro vás, zkuste jiné instance, neboť Misskey je decentralizovaná sociální síť, takže můžete snadno najít své přátele. Hodně štěstí a zábavy!"
adblock:
detected: "Prosím vypněte svůj blokovač reklam"
warning: "Některé vlastnosti mohou být nedostupné nebo mohou způsobovat chyby, pokud máte povolený blokovač reklam. <strong>Misskey nepoužívá reklamy</strong>."
application-authorization: "Autorizované aplikace"
close: "Zavřít"
do-not-copy-paste: "Prosím nezadávejte ani nevkládejte sem kód. Váš účet může být kompromitován."
@@ -122,12 +119,15 @@ common:
create-poll: "Vytvořit anketu"
text-remain: "zbývá ještě {} znaků"
recent-tags: "Nejnovější"
click-to-tagging: "Klikni pro otágování"
visibility: "Viditelnost"
geolocation-alert: "Vaše zařízení nedalo k dispozici lokaci"
error: "Chyba"
enter-username: "Zadejte své uživatelské jméno"
specified-recipient: "Pro"
add-visible-user: "Přidat uživatele"
username-prompt: "Zadejte své uživatelské jméno"
enter-file-name: "Upravit název souboru"
weekday-short:
sunday: "Ne"
monday: "Po"
@@ -232,7 +232,7 @@ common:
deck-column-width-wide: "Široké"
use-shadow: "Používat v rozhraní stíny"
rounded-corners: "Zakulatit rohy v rozhraní"
circle-icons: "Používat kulaté ikony"
circle-icons: "Používat kulaté avatary"
contrasted-acct: "Přidat uživatelskému účtu kontrast"
wallpaper: "Obrázek na pozadí"
choose-wallpaper: "Zvolit pozadí"
@@ -280,6 +280,15 @@ common:
sync: "Synchronizace"
save: "Uložit"
saved: "Uloženo"
room: "Místnost"
_room:
graphicsQuality: "Kvalita grafiky"
_graphicsQuality:
ultra: "Nejvyšší"
high: "Vysoká"
medium: "Střední"
low: "Nízká"
cheep: "Nejnižší"
search: "Hledání"
delete: "Odstranit"
loading: "Načítám..."
@@ -372,6 +381,7 @@ common/views/components/games/reversi/reversi.vue:
cancel: "Zrušit"
common/views/components/games/reversi/reversi.game.vue:
surrender: "Vzdát se"
surrendered: "Vzdaním se"
looped-map: "Zacyklená mapa"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
@@ -405,6 +415,7 @@ common/views/components/connect-failed.vue:
title: "Nelze se připojit k serveru"
description: "Nastal problém s Vaším připojením k internetu, nebo server není dostupný nebo zrovna probíhá údržba. Prosím {zkuste to znova} za pár minut."
thanks: "Děkujeme že jste použili Misskey."
troubleshoot: "Odstranění problémů"
common/views/components/connect-failed.troubleshooter.vue:
title: "Poradce při potížích"
network: "Síťové připojení"
@@ -413,6 +424,8 @@ common/views/components/connect-failed.troubleshooter.vue:
checking-internet: "Ověřuji připojení k internetu."
server: "Připojení k serveru"
checking-server: "Spojuji se se serverem"
finding: "Vyšetřování problému"
no-network: "Žádné připojení k síti"
no-network-desc: "Ujistěte se že jste připojeni k Internetu."
no-internet: "Nejste připojeni k internetu"
no-internet-desc: "Jste připojen k síti, ale zdá se že stále chybí připojení k Internetu. Prosím zkontrolujte Vaše připojení k Internetu."
@@ -937,6 +950,7 @@ desktop/views/components/ui.header.account.vue:
lists: "Seznamy"
groups: "Skupiny"
admin: "Administrace"
room: "Místnost"
desktop/views/components/ui.header.nav.vue:
game: "Hry"
desktop/views/components/ui.header.notifications.vue:
@@ -1353,6 +1367,7 @@ pages:
room:
translate: "Přesunout"
save: "Uložit"
saved: "Uloženo"
furnitures:
moon: "Po"
moon: "Měsíc"
bin: "Koš"

View File

@@ -18,9 +18,6 @@ common:
drive: "Drev"
drive-desc: "Vil du poste et billede, som du tidligere har uploadet? Har du brug for at navngive filer og organisere dem i mapper, som du selv har navngivet? Så er Misskey Drev den bedste løsning for dig. Den gør det så let som ingenting at dele dine filer online."
outro: "Tjek Misskey's unikke funktioner ved at se dem med dine egne øjne. Hvis du kommer frem til, at den ene server ikke er noget for dig, så kan du prøve en anden. Misskey er et decentraliseret SNS, så du kan lettere finde frem til brugere, som du klikker med. God fornøjelse!"
adblock:
detected: "Her kan du deaktivere ad blocker."
warning: "Visse funktioner kan forsvinde eller medføre forstyrrelser, hvis ad blocking er aktiveret. <strong>Misskey kører ikke annoncer</strong>."
application-authorization: "Adgangsstyring"
close: "Luk"
do-not-copy-paste: "Undgå venligst at skrive eller klistre kode ind her. I modsat fald kan din konto blive kompromitteret."
@@ -213,7 +210,7 @@ common:
deck-column-width-wide: "Bred"
use-shadow: "Vis skygger"
rounded-corners: "Vis afrundede hjørner"
circle-icons: "Anvend cykliske ikoner"
circle-icons: "Anvend cykliske avatar"
contrasted-acct: "Tilføj kontrast til brugerkontoen"
wallpaper: "Baggrundsbillede"
choose-wallpaper: "Vælg en baggrund"
@@ -680,7 +677,7 @@ common/views/components/profile-editor.vue:
you-can-include-hashtags: "Du må gerne bruge hashtags i din profil beskrivelse"
language: "Sprog"
birthday: "Fødselsdag"
avatar: "Ikon"
avatar: "Avatar"
banner: "Banner"
is-cat: "Denne konto er en Kat"
is-bot: "Denne konto er en Bot"
@@ -1913,6 +1910,7 @@ pages:
room:
translate: "Flyt"
save: "Gem"
saved: "Gemt"
furnitures:
moon: "Man"
moon: "Måne"
bin: "Skraldespand"

View File

@@ -18,9 +18,6 @@ common:
drive: "Drive"
drive-desc: "Du willst ein hochgeladenes Foto nochmal posten? Deine Dateien benennen und in Ordnern sortieren? Misskeys Drive ist der beste Ort dafür. Damit wird das Teilen zum Kinderspiel!"
outro: "Probiere Misskey aus und entdecke Misskeys einzigartige Funktionen. Wenn dir diese Instanz nicht zusagt, nimm einfach eine andere. Dank Misskeys dezentralem System kannst du dich überall mit deinen Freunden verbinden. Also dann, GLHF!"
adblock:
detected: "Bitte deaktiviere den Werbeblocker."
warning: "<strong>Misskey hat keine Werbung.</strong> Allerdings können Werbeblocker für unerwartete Probleme und fehlende Funktionen sorgen."
application-authorization: "Autorisierte Anwendungen"
close: "Schließen"
do-not-copy-paste: "Bitte keinen Code einfügen. Ihr Account könnte gefährdet werden."
@@ -207,7 +204,7 @@ common:
deck-column-width-wide: "Sehr breit"
use-shadow: "Nutze Schatten"
rounded-corners: "Abgerundete Ecken"
circle-icons: "Kreisförmige Icons"
circle-icons: "Kreisförmige Avatar"
contrasted-acct: "Nutzernamen kontrastreicher darstellen"
wallpaper: "Hintergrund"
choose-wallpaper: "Hintergrund auswählen"
@@ -605,6 +602,7 @@ common/views/widgets/memo.vue:
save: "Speichern"
desktop:
banner: "Banner"
avatar: "Avatar"
unable-to-process: "Der Vorgang konnte nicht abgeschlossen werden"
desktop/views/components/activity.chart.vue:
total: "Schwarz ... komplett"
@@ -948,6 +946,7 @@ pages:
array: "Listen"
room:
save: "Speichern"
saved: "Gespeichert"
furnitures:
moon: "Mo"
moon: "Mond"
bin: "Papierkorb"

View File

@@ -18,9 +18,6 @@ common:
drive: "Drive"
drive-desc: "Wanna post a picture you have already uploaded? Wish to organize, name and create a folder for your uploaded files? Misskey Drive is the best solution for you. Very easy to share your files online."
outro: "Check Misskey-unique features by seeing them with your own eyes! If you feel like this instance is not for you, try other instances, as Misskey is a decentralized SNS, so that you can easily find your mates. Then, GLHF!"
adblock:
detected: "Please disable ad blocker."
warning: "Some features may be unavailable or cause malfunctions if ad blocking features are enabled. <strong>Misskey is not running ads</strong>."
application-authorization: "Application authorizations"
close: "Close"
do-not-copy-paste: "Please do not enter or paste the code here. Account may be compromised."
@@ -133,6 +130,7 @@ common:
geolocation-alert: "Your device does not provide location services"
error: "Error"
enter-username: "Please enter username"
specified-recipient: "Recipient"
add-visible-user: "Add a user"
cw-placeholder: "Comments for the post (optional)"
username-prompt: "Please enter username"
@@ -246,7 +244,7 @@ common:
deck-column-width-wide: "Wide"
use-shadow: "Use shadows in the UI"
rounded-corners: "Round the corners of the UI"
circle-icons: "Use circular icons"
circle-icons: "Use circular avatar icon"
contrasted-acct: "Add contrast to user account"
wallpaper: "Background image"
choose-wallpaper: "Choose a background"
@@ -390,6 +388,9 @@ common/views/pages/explore.vue:
federated: "From the fediverse"
explore: "Explore {host}"
users-info: "Currently, {users} users are registered here"
common/views/components/reactions-viewer.details.vue:
few-users: "{users} reacted with {reaction}"
many-users: "{users}, and {omitted} more reacted with {reaction}"
common/views/components/url-preview.vue:
enable-player: "Enable playback"
disable-player: "Close the player"
@@ -752,8 +753,8 @@ common/views/components/profile-editor.vue:
uploading: "Uploading"
upload-failed: "Failed to upload"
unable-to-process: "The operation could not be completed."
avatar-not-an-image: "The file specified as an avatar is not an image"
banner-not-an-image: "The file specified as a banner is not an image"
avatar-not-an-image: "The file you specified as an avatar is not an image"
banner-not-an-image: "The file you specified as a banner is not an image"
email: "Email settings"
email-address: "Email Address"
email-verified: "Your email has been verified."
@@ -927,7 +928,7 @@ desktop/views/components/drive.file.vue:
copy-url: "Copy URL"
download: "Download"
else-files: "Other"
set-as-avatar: "Set as an avatar"
set-as-avatar: "Set as avatar"
set-as-banner: "Set as a banner"
open-in-app: "Open in app"
add-app: "Add app"
@@ -1817,6 +1818,9 @@ pages:
read-page: "Viewing the source"
page-created: "Created the page!"
page-updated: "Updated the page"
name-already-exists: "The specified page name already exists"
title-invalid-name: "The specified page URL is invalid"
text-invalid-name: "Check whether that is not a blank"
are-you-sure-delete: "Do you want to delete this page?"
page-deleted: "The page has been deleted"
edit-this-page: "Edit this page"
@@ -2092,6 +2096,10 @@ room:
exit: "Deselect"
remove: "Remove"
save: "Save"
saved: "Saved"
clear: "Remove All"
clear-confirm: "Are you sure to remove all furnitures in your room?"
leave-confirm: "There are unsaved changes. Do you really want to leave?"
chooseImage: "Select an image"
room-type: "Room type"
carpet-color: "Color of carpet"
@@ -2120,7 +2128,7 @@ room:
piano: "Piano"
facial-tissue: "Facial tissue"
server: "Servers"
moon: "M"
moon: "Moon"
corkboard: "Cork board"
mousepad: "Mousepad"
monitor: "Monitor"
@@ -2136,4 +2144,9 @@ room:
rubik-cube: "Rubik's Cube"
poster-h: "Poster (Horizontal)"
poster-v: "Poster (Vertical)"
bin: "Trash"
sofa: "Sofa"
spiral: "Spiral Staircase"
bin: "Waste bin"
cup-noodle: "Cup noodle"
holo-display: "Holographic display"
energy-drink: "Energy drink"

View File

@@ -16,9 +16,6 @@ common:
ui: "Interfaz"
ui-desc: "No hay ninguna interfaz que le vaya bien a todos. Por eso, Misskey tiene una interfaz altamente personalizable para tus gustos. Puedes hacer tu página principal única editando la interfaz de tu timeline y moviendo varios widgets para conseguir hacer de este lugar uno propio."
drive: "Drive"
adblock:
detected: "Por favor, desactive el bloqueador de publicidad."
warning: "<strong>Misskey no tiene anuncios publicitarios.</strong> Sin embargo, algunas características podrían no estar disponibles si el bloqueador de publicidad está habilitado."
application-authorization: "Autorizaciones de la aplicación."
close: "Cerrar"
do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
@@ -177,7 +174,7 @@ common:
deck-column-width-wide: "Ancho"
use-shadow: "Usar sombras en la Interfaz de Usuario"
rounded-corners: "Esquinas redondeadas en la Interfaz de Usuario"
circle-icons: "Usar iconos circulares"
circle-icons: "Usar avatar circulares"
contrasted-acct: "Añadir contraste al nombre de usuario"
wallpaper: "Fondo de pantalla"
choose-wallpaper: "Escoge un fondo de pantalla"
@@ -1137,6 +1134,7 @@ pages:
array: "Listas"
room:
save: "Guardar"
saved: "Guardado"
furnitures:
moon: "Luna"
bin: "Papelera"

View File

@@ -18,9 +18,6 @@ common:
drive: "Drive"
drive-desc: "Vous voulez poster une photo que vous avez déjà transférée ? Vous souhaitez organiser, nommer et créer un dossier pour vos fichiers téléversés ? Misskey Drive est la meilleure solution pour vous. Très facile de partager vos fichiers en ligne."
outro: "Découvrez vous-même les fonctionnalités de Misskey. Étant donné que Misskey est un réseau social fédéré, vous pouvez essayer dautres instances afin de trouver vos amis si la présente instance ne vous correspond pas. Bonne chance et amusez-vous bien!"
adblock:
detected: "Veuillez désactiver votre bloqueur de publicités"
warning: "<strong>Misskey nutilise pas de publicités</strong>, mais quelques options peuvent être non disponibles ou fonctionneraient mal si un bloqueur de publicités est activé."
application-authorization: "Autorisations de lapplication"
close: "Fermer"
do-not-copy-paste: "Veuillez ne pas entrer ou coller le code ici. Le compte pourrait être compromis."
@@ -37,17 +34,17 @@ common:
unfollow-confirm: "Désirez-vous vous désabonner de {name} ?"
delete-confirm: "Supprimer cette publication ?"
signin-required: "Veuillez vous connecter"
notification-type: "Type de notification"
notification-type: "Type de notifications"
notification-types:
all: "Tout"
pollVote: "Sondage"
pollVote: "Sondages"
follow: "Abonnements"
receiveFollowRequest: "Demandes dabonnements"
reply: "Répondre"
reply: "Réponses"
quote: "Cité par"
renote: "Republier"
renote: "Partages"
mention: "Mentions"
reaction: "Réaction"
reaction: "Réactions"
got-it: "Jai compris !"
customization-tips:
title: "Conseils de personnalisation"
@@ -183,7 +180,7 @@ common:
notification: "Notifications"
apps: "Applications"
tags: "Hashtags"
mute-and-block: "Silencés / Bloqués"
mute-and-block: "Masqués / Bloqués"
blocking: "En cours blocage"
security: "Sécurité"
signin: "Historique des connexions"
@@ -199,6 +196,7 @@ common:
web-search-engine: "Moteur de recherche Web"
web-search-engine-desc: "Exemple: https://www.google.com/?#q={{query}}"
paste: "Coller"
pasted-file-name-desc: "Exemple : \"yyyy-MM-dd HH-mm-ss [{{number}}]\" → \"2018-03-20 21-30-24 1\""
keep-cw: "Maintenir l'avertissement de contenu"
keep-cw-desc: "Lorsque vous répondez à un message, le même avertissement de contenu est reprit par défaut dans la réponse, le même que celui qui a été défini dans le message original."
i-like-sushi: "Je préfère les sushis plutôt que le pudding"
@@ -236,7 +234,7 @@ common:
deck-column-width-wide: "Large"
use-shadow: "Utiliser les ombres dans l'interface utilisateur"
rounded-corners: "Coins arrondis de l'interface utilisateur"
circle-icons: "Utiliser des icônes circulaires"
circle-icons: "Utiliser des avatar circulaires"
contrasted-acct: "Ajouter du contraste au nom de lutilisateur"
wallpaper: "Image du fond d'écran"
choose-wallpaper: "Sélectionner un fond d'écran"
@@ -284,6 +282,18 @@ common:
sync: "Synchroniser"
save: "Enregistrer"
saved: "enregistré"
home-profile: "Profil principal"
deck-profile: "Profil deck"
room: "Pièce"
_room:
graphicsQuality: "Qualité des graphismes"
_graphicsQuality:
ultra: "Très élevée"
high: "Élevée"
medium: "Moyenne"
low: "Basse"
cheep: "Minimale"
useOrthographicCamera: "Utiliser une caméra orthographique"
search: "Recherche"
delete: "Supprimer"
loading: "Chargement en cours…"
@@ -368,6 +378,8 @@ common/views/pages/explore.vue:
federated: "Du Fédiverse"
explore: "Explorer {host}"
users-info: "Actuellement, {users} utilisateur·rice·s se sont inscrit ici"
common/views/components/reactions-viewer.details.vue:
few-users: "{users} ont réagit avec {reaction}"
common/views/components/url-preview.vue:
enable-player: "Activer la lecture"
disable-player: "Fermer le lecteur"
@@ -533,8 +545,9 @@ common/views/components/note-menu.vue:
unpin: "Désépingler"
delete: "Supprimer"
delete-confirm: "Supprimer cette publication ?"
delete-and-edit: "Effacer et éditer"
delete-and-edit: "Supprimer et réécrire"
remote: "Afficher la note originale"
pin-limit-exceeded: "Vous ne pouvez plus épingler davantage de publications."
common/views/components/user-menu.vue:
mention: "Mention"
mute: "Silencier"
@@ -588,7 +601,7 @@ common/views/components/poll-editor.vue:
hour: "Heures"
day: "D"
common/views/components/reaction-picker.vue:
choose-reaction: "Choisissez votre réaction"
choose-reaction: "Envoyer une réaction"
input-reaction-placeholder: "ou insérez un émoji"
common/views/components/emoji-picker.vue:
custom-emoji: "Émoji personnalisé"
@@ -966,8 +979,8 @@ desktop/views/components/note-detail.vue:
deleted: "cette publication a été supprimée"
location: "Géolocalisation"
renote: "Republier"
add-reaction: "Ajouter votre reaction"
undo-reaction: "Inverser la réaction"
add-reaction: "Ajouter une réaction"
undo-reaction: "Annuler la réaction"
desktop/views/components/note.vue:
reply: "Répondre"
renote: "Partager"
@@ -1063,9 +1076,9 @@ common/views/components/drive-settings.vue:
default-upload-folder-name: "Dossier·s"
change-default-upload-folder: "Changer de dossier"
common/views/components/mute-and-block.vue:
mute-and-block: "Silencés / Bloqués"
mute: "Mettre en sourdine"
block: "En cours blocage"
mute-and-block: "Masqués / Bloqués"
mute: "Mis en sourdine"
block: "Vous avez bloqué"
no-muted-users: "Aucun utilisateur nest mis en sourdine"
no-blocked-users: "Aucun utilisateur nest bloqué"
word-mute: "Filtre de mots"
@@ -1115,6 +1128,7 @@ desktop/views/components/ui.header.account.vue:
groups: "Groupes"
follow-requests: "Demandes dabonnement"
admin: "Admin"
room: "Pièce"
desktop/views/components/ui.header.nav.vue:
game: "Jeux"
desktop/views/components/ui.header.notifications.vue:
@@ -1762,6 +1776,8 @@ pages:
read-page: "Voir la source"
page-created: "Page a été créée !"
page-updated: "A mis à jour la page"
name-already-exists: "Une page portant le même nom existe déjà"
title-invalid-name: "LURL de la page spécifiée nest pas valide"
are-you-sure-delete: "Confirmez-vous la suppression de cette page ?"
page-deleted: "La page a bien été supprimée."
edit-this-page: "Éditer cette page"
@@ -1770,6 +1786,7 @@ pages:
view-source: "Afficher la source"
view-page: "Afficher la page"
like: "Bien"
unlike: "Je naime pas"
liked-pages: "Pages favorites"
my-pages: "Mes pages"
inspector: "Inspecteur"
@@ -2000,8 +2017,60 @@ pages:
enviromentVariables: "Variables d'environnement"
pageVariables: "Élément de page"
room:
add-furniture: "Placer des meubles"
translate: "Déplacer"
rotate: "Tourner"
remove: "Enlever"
save: "Enregistrer"
saved: "enregistré"
clear: "Tout enlever"
clear-confirm: "Désirez-vous enlever tout les meubles de votre chambre ?"
leave-confirm: "Vous avez des modifications non-sauvegardées. Voulez-vous vraiment quitter ?"
chooseImage: "Sélectionnez une image"
room-type: "Type de chambre"
carpet-color: "Couleur du tapis"
rooms:
default: "Par défaut"
washitsu: "Style japonnais"
furnitures:
moon: "L"
milk: "Lait en carton"
bed: "Lit"
low-table: "Table basse"
desk: "Bureau"
chair: "Chaise"
chair2: "Chaise 2"
fan: "Ventilateur"
pc: "Ordinateur"
plant: "Plante dintérieur"
plant2: "Plante dintérieur 2"
eraser: "Gomme"
pencil: "Crayon"
cardboard-box: "Boîte en carton"
cardboard-box2: "Boîte en carton 2"
cardboard-box3: "Boîte en carton 3"
book: "Livre"
book2: "Livre 2"
piano: "Piano"
facial-tissue: "Mouchoirs en papier"
server: "Serveurs"
moon: "Lune"
corkboard: "Tableau en liège"
mousepad: "Tapis de souris"
monitor: "Écran"
keyboard: "Clavier"
carpet-stripe: "Tapis (zébré)"
color-box: "Étagère"
wall-clock: "Horloge murale"
photoframe: "Cadre photo"
cube: "Cube"
tv: "Téléviseur"
pinguin: "Pingouin"
rubik-cube: "Cube de Rubik"
poster-h: "Affiche (horizontale)"
poster-v: "Affiche (verticale)"
sofa: "Canapé"
spiral: "Escaliers en spirale"
bin: "Corbeille"
cup-noodle: "Bol de nouilles"
holo-display: "Affichage holographique"
energy-drink: "Boisson énergétique"

View File

@@ -19,9 +19,6 @@ common:
drive: "ドライブ"
drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんかもしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんかMisskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
adblock:
detected: "広告ブロッカーを無効にしてください"
warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
application-authorization: "アプリの連携"
close: "閉じる"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
@@ -139,6 +136,7 @@ common:
geolocation-alert: "お使いの端末は位置情報に対応していません"
error: "エラー"
enter-username: "ユーザー名を入力してください"
specified-recipient: "宛先"
add-visible-user: "ユーザーを追加"
cw-placeholder: "内容への注釈 (オプション)"
username-prompt: "ユーザー名を入力してください"
@@ -258,7 +256,7 @@ common:
deck-column-width-wide: "広"
use-shadow: "UIに影を使用"
rounded-corners: "UIの角を丸める"
circle-icons: "円形のアイコンを使用"
circle-icons: "円形のアバターを使用"
contrasted-acct: "ユーザー名にコントラストを付ける"
wallpaper: "壁紙"
choose-wallpaper: "壁紙を選択"
@@ -412,6 +410,10 @@ common/views/pages/explore.vue:
explore: "{host}を探索"
users-info: "現在{users}ユーザーが登録されています"
common/views/components/reactions-viewer.details.vue:
few-users: "{users}が{reaction}をリアクション"
many-users: "{users}と他{omitted}人が{reaction}をリアクション"
common/views/components/url-preview.vue:
enable-player: "プレイヤーを開く"
disable-player: "プレイヤーを閉じる"
@@ -795,7 +797,7 @@ common/views/components/profile-editor.vue:
you-can-include-hashtags: "ハッシュタグを含めることができます。"
language: "言語"
birthday: "誕生日"
avatar: "アイコン"
avatar: "アバター"
banner: "バナー"
is-cat: "このアカウントはCatです"
is-bot: "このアカウントはBotです"
@@ -809,7 +811,7 @@ common/views/components/profile-editor.vue:
uploading: "アップロード中"
upload-failed: "アップロードに失敗しました"
unable-to-process: "操作を完了できません"
avatar-not-an-image: "アイコンとして指定したファイルは画像ではありません"
avatar-not-an-image: "アバターとして指定したファイルは画像ではありません"
banner-not-an-image: "バナーとして指定したファイルは画像ではありません"
email: "メール設定"
email-address: "メールアドレス"
@@ -999,7 +1001,7 @@ desktop/views/components/drive-window.vue:
used: "使用中"
desktop/views/components/drive.file.vue:
avatar: "アイコン"
avatar: "アバター"
banner: "バナー"
nsfw: "閲覧注意"
contextmenu:
@@ -1009,7 +1011,7 @@ desktop/views/components/drive.file.vue:
copy-url: "URLをコピー"
download: "ダウンロード"
else-files: "その他"
set-as-avatar: "アイコンに設定"
set-as-avatar: "アバターに設定"
set-as-banner: "バナーに設定"
open-in-app: "アプリで開く"
add-app: "アプリを追加"
@@ -2014,6 +2016,9 @@ pages:
read-page: "ソースを表示中"
page-created: "ページを作成しました"
page-updated: "ページを更新しました"
name-already-exists: "指定されたページURLは既に存在しています"
title-invalid-name: "不正なページURLです"
text-invalid-name: "空白でないか確認してください"
are-you-sure-delete: "このページを削除しますか?"
page-deleted: "ページを削除しました"
edit-this-page: "このページを編集"
@@ -2300,6 +2305,10 @@ room:
exit: "戻る"
remove: "しまう"
save: "保存"
saved: "保存しました"
clear: "片付け"
clear-confirm: "全ての家具をしまいますか?"
leave-confirm: "未保存の変更があります、移動しますか?"
chooseImage: "画像を選択"
room-type: "部屋のタイプ"
carpet-color: "床の色"
@@ -2347,3 +2356,6 @@ room:
sofa: "ソファ"
spiral: "螺旋階段"
bin: "ゴミ箱"
cup-noodle: "カップ麺"
holo-display: "ホログラフィックディスプレイ"
energy-drink: "エナジードリンク"

View File

@@ -18,9 +18,6 @@ common:
drive: "ドライブ"
drive-desc: "「こないだの画像、どこやったかな……また投稿したいんやけど……」「さっきのファイルあのフォルダに直しといて」そんなこと言わんとって。Misskeyはもとからドライブ機能持っとるさかい、心配あらへん。ファイルの「わけわけ」したってな。"
outro: "Misskeyの機能は無限大や知らんけど。知らん言うとるやんけ、あんたが見に行けやMisskeyは分散型SNSやから、ここがあかんくても他がある。阪神でもオリックスでもワイは応援するで"
adblock:
detected: "広告ブロッカーを無効にしてや"
warning: "<strong>Misskeyは広告を掲載してへん</strong>けど、広告をブロックしはる機能がおると一部の機能が利用できんくなったり、不具合が発生するかも分からん。知らんけど。"
application-authorization: "アプリの連携"
close: "さいなら"
do-not-copy-paste: "ここにコードを入力したり張り付けたりせんといてください。アカウントが不正利用されるかも分からん。知らんけど。"
@@ -467,7 +464,7 @@ common/views/components/profile-editor.vue:
description: "自己紹介"
language: "言語"
birthday: "誕生日"
avatar: "アイコン"
avatar: "アバター"
banner: "バナー"
is-cat: "このアカウントはCatやで"
is-bot: "このアカウントはBotやで"
@@ -605,7 +602,7 @@ desktop/views/components/crop-window.vue:
desktop/views/components/drive-window.vue:
used: "使うとる"
desktop/views/components/drive.file.vue:
avatar: "アイコン"
avatar: "アバター"
banner: "バナー"
nsfw: "見たらあかんで"
contextmenu:
@@ -615,7 +612,7 @@ desktop/views/components/drive.file.vue:
copy-url: "URLをコピー"
download: "ダウンロード"
else-files: "その他"
set-as-avatar: "アイコンにする"
set-as-avatar: "アバターにする"
set-as-banner: "バナーにする"
open-in-app: "アプリで開く"
add-app: "アプリ増やす"
@@ -1287,6 +1284,7 @@ pages:
room:
translate: "移動"
save: "保存"
saved: "保存したで!"
furnitures:
moon: "月"
bin: "ゴミ箱"

View File

@@ -18,9 +18,6 @@ common:
drive: "드라이브"
drive-desc: "이전에 올렸던 적 있는 이미지를 다시 올리고 싶을 때가 있지 않으신가요? 아니면 업로드했던 파일을 폴더로 정리하고 싶지 않으신가요? Misskey에 기본적으로 내장된 드라이브 기능으로 해결됩니다. 파일 공유도 쉽습니다."
outro: "이외에도 Misskey에만 있는 기능이 아직도 더 있으니 부디 여러분 자신의 눈으로 확인해보시기 바랍니다. Misskey는 분산형 SNS라서 이 인스턴스가 마음에 들지 않으신다면 다른 인스턴스를 시도해보실 수도 있습니다. 그럼, GLHF!"
adblock:
detected: "광고 차단기를 해제하십시오"
warning: "<strong>Misskey는 광고를 게재하지 않습니다</strong>. 하지만 광고 차단 기능을 사용할 경우 일부 기능을 사용할 수 없거나 문제가 발생할 수 있습니다."
application-authorization: "앱 연계"
close: "닫기"
do-not-copy-paste: "여기에 코드를 입력하거나 붙여넣지 마십시오. 계정이 무단으로 사용될 수 있습니다."
@@ -133,6 +130,7 @@ common:
geolocation-alert: "사용 중이신 장치에서는 위치 정보를 사용할 수 없습니다"
error: "오류"
enter-username: "사용자명을 입력해주세요"
specified-recipient: "수신인"
add-visible-user: "사용자 추가"
cw-placeholder: "내용에 대한 주석 (옵션)"
username-prompt: "사용자명을 입력해주세요"
@@ -246,7 +244,7 @@ common:
deck-column-width-wide: "넓음"
use-shadow: "UI에 그림자 효과 적용"
rounded-corners: "UI의 모서리를 둥글게 설정"
circle-icons: "원형 아이콘 사용"
circle-icons: "원형 아바타를 사용"
contrasted-acct: "사용자명에 대비 추가"
wallpaper: "배경"
choose-wallpaper: "배경 설정"
@@ -390,6 +388,9 @@ common/views/pages/explore.vue:
federated: "연합"
explore: "{host}을(를) 탐색"
users-info: "현재 {users} 사용자가 등록되어 있습니다"
common/views/components/reactions-viewer.details.vue:
few-users: "{users}님이 {reaction} 리액션"
many-users: "{users}님 외 {omitted}명이 {reaction} 리액션"
common/views/components/url-preview.vue:
enable-player: "플레이어 열기"
disable-player: "플레이어 닫기"
@@ -1817,6 +1818,9 @@ pages:
read-page: "소스 표시중"
page-created: "페이지를 만들었습니다"
page-updated: "페이지를 수정했습니다"
name-already-exists: "지정한 페이지 URL은 이미 존재합니다"
title-invalid-name: "유효하지 않은 페이지 URL입니다"
text-invalid-name: "비어있지 않은지 확인해주세요"
are-you-sure-delete: "이 페이지를 삭제하시겠습니까?"
page-deleted: "페이지가 삭제되었습니다"
edit-this-page: "이 페이지를 편집"
@@ -2092,6 +2096,10 @@ room:
exit: "선택 해제"
remove: "치우기"
save: "저장"
saved: "저장하였습니다"
clear: "모두 치우기"
clear-confirm: "정말 방 안의 모든 가구를 치우시겠습니까?"
leave-confirm: "저장되지 않은 변경 사항이 있습니다. 정말 떠나시겠습니까?"
chooseImage: "이미지 선택"
room-type: "룸 종류"
carpet-color: "바닥 색상"
@@ -2120,7 +2128,7 @@ room:
piano: "피아노"
facial-tissue: "휴지 상자"
server: "서버"
moon: ""
moon: ""
corkboard: "게시판"
mousepad: "마우스 패드"
monitor: "모니터"
@@ -2136,4 +2144,9 @@ room:
rubik-cube: "루빅스 큐브"
poster-h: "포스터 (가로)"
poster-v: "포스터 (세로)"
sofa: "소파"
spiral: "나선형 계단"
bin: "휴지통"
cup-noodle: "컵라면"
holo-display: "홀로그램"
energy-drink: "에너지 드링크"

View File

@@ -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"

View File

@@ -14,9 +14,6 @@ common:
ui: "Interfejs"
drive: "Dysk"
drive-desc: "Chcesz opublikować zdjęcie, które już przesłałeś? Chcesz uporządkować, nazwać i utworzyć folder dla przesłanych plików? Dysk Misskey to najlepsze rozwiązanie dla Ciebie. Bardzo łatwo udostępniać swoje pliki online."
adblock:
detected: "Spróbuj wyłączyć blokadę reklam."
warning: "<strong>Misskey nie zawiera reklam</strong>, ale część funkcji może nie działać prawidłowo z włączonym blokowaniem reklam."
application-authorization: "Współpraca aplikacji"
close: "Zamknij"
load-more: "Załaduj więcej"
@@ -98,8 +95,10 @@ common:
visibility: "Widoczność"
error: "Błąd"
enter-username: "Wprowadź nazwę użytkownika"
specified-recipient: "Adresat"
add-visible-user: "Dodaj użytkownika"
username-prompt: "Wprowadź nazwę użytkownika"
enter-file-name: "Wprowadź nazwę pliku"
weekday-short:
sunday: "N"
monday: "Pn"
@@ -162,6 +161,7 @@ common:
note-visibility: "Widoczność wpisów"
remember-note-visibility: "Zapamiętaj widoczność wpisów"
web-search-engine: "Wyszukiwarka internetowa"
paste: "Wklej"
line-width: "Szerokości linii"
line-width-thin: "Cienka"
line-width-normal: "Normalna"
@@ -1259,6 +1259,7 @@ pages:
room:
translate: "Przenieś"
save: "Zapisz"
saved: "Zapisano"
furnitures:
moon: "Pn"
moon: "Księżyc"
bin: "Kosz"

View File

@@ -12,9 +12,7 @@ common:
rich-contents: "Post"
rich-contents-desc: "Apenas poste suas ideias, temas do momento e qualquer coisa que você queira compartilhar. Você pode querer decorar suas palavras, anexar suas imagens favoritas, enviar arquivos, inclusive vídeos ou criar uma enquete. Essas são as coisas que você pode fazer em Misskey."
reaction: "Reações"
adblock:
detected: "Por favor, desative o bloqueador de anúncios."
warning: "Alguns recursos podem não estar disponíveis ou apresentar mal funcionamento se o bloqueio de anúncios estiver ativado. <strong>Misskey não está usando anúncios</strong>"
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません"
application-authorization: "Aplicativos autorizados"
close: "Fechar"
do-not-copy-paste: "Por favor, não digite ou copie o código aqui. A conta pode ser comprometida."

View File

@@ -18,9 +18,6 @@ common:
drive: "Хранилище файлов"
drive-desc: "Хотите запостить картинку, которую уже отправляли ранее? Хочется сортировать, переименовать и создать папку для ваших выложенных файлов? Тогда Misskey Drive - это лучшее решение для вас. Очень лёгкий способ делиться своими файлами онлайн."
outro: "Попробуйте будущие, уникальные для Misskey функции своими глазами! Если чувствуете, что это не в вашем вкусе, то попробуйте другие инстанции, ведь Misskey - это децентрализованная социальная сеть, так что ты можешь с лёгкостью найти себе товарищей. И наконец, GLHF!"
adblock:
detected: "Пожалуйста, отключите блокировщик рекламы."
warning: "Некоторые функции перестанут быть доступными или будут работать неправильно, если включена блокировка рекламы. <strong>В Misskey нет рекламы</strong>."
application-authorization: "Авторизация приложений"
close: "Закрыть"
do-not-copy-paste: "Пожалуйста, не вводите и не вставляйте сюда код. Аккаунту может угрожать опасность."

View File

@@ -18,9 +18,6 @@ common:
drive: "网盘"
drive-desc: "想要发布一张您已经上传过的照片吗想要管理文件或为上传的文件创建文件夹吗Misskey的网盘是一个最好的解决方案。"
outro: "Misskey还有其他更多功能请亲身体验一下吧。因为 Misskey 是一个分布式的 SNS如果您感觉某个功能不适合自己试试其他的吧。祝您玩得开心"
adblock:
detected: "请关闭广告拦截器"
warning: "<strong>Misskey不会发布广告</strong>如果您启用广告拦截器, 可能会导致某些功能无法正常使用。"
application-authorization: "应用程序授权"
close: "关闭"
do-not-copy-paste: "请不要在这里输入或粘贴代码。您帐户可能会受到损害。"
@@ -133,6 +130,7 @@ common:
geolocation-alert: "您的设备不支持定位服务"
error: "错误"
enter-username: "输入用户名"
specified-recipient: "收件人"
add-visible-user: "添加用户"
cw-placeholder: "评论帖子(可选)"
username-prompt: "输入用户名"
@@ -246,7 +244,7 @@ common:
deck-column-width-wide: "宽"
use-shadow: "在UI中使用阴影效果"
rounded-corners: "UI界面圆角效果"
circle-icons: "使用圆形图标"
circle-icons: "使用圆形头像"
contrasted-acct: "增加用户名的对比度"
wallpaper: "壁纸"
choose-wallpaper: "选择壁纸"
@@ -390,6 +388,9 @@ common/views/pages/explore.vue:
federated: "联合"
explore: "查找{host}"
users-info: "当前有{users}个注册用户"
common/views/components/reactions-viewer.details.vue:
few-users: "{users}作出了{reaction}的回应"
many-users: "{users}和其他{omitted}人做出了{reaction}的回应"
common/views/components/url-preview.vue:
enable-player: "打开播放器"
disable-player: "关闭播放器"
@@ -927,7 +928,7 @@ desktop/views/components/drive.file.vue:
copy-url: "复制链接"
download: "下载"
else-files: "其他"
set-as-avatar: "设为头像"
set-as-avatar: "设为头像"
set-as-banner: "设置为背景"
open-in-app: "在应用程序中打开"
add-app: "添加应用"
@@ -1817,6 +1818,9 @@ pages:
read-page: "查看源"
page-created: "页面已创建"
page-updated: "页面已更新"
name-already-exists: "该页面URL已存在"
title-invalid-name: "无效的页面URL"
text-invalid-name: "请确认该项不为空"
are-you-sure-delete: "是否删除此页面?"
page-deleted: "该页面已被删除。"
edit-this-page: "编辑此页面"
@@ -2092,6 +2096,10 @@ room:
exit: "返回"
remove: "移除"
save: "保存"
saved: "已保存"
clear: "清理"
clear-confirm: "是否清除所有家具?"
leave-confirm: "有尚未保存的修改。是否离开?"
chooseImage: "选择图片"
room-type: "房间类型"
carpet-color: "地板颜色"
@@ -2120,14 +2128,14 @@ room:
piano: "钢琴"
facial-tissue: "纸巾盒"
server: "服务器"
moon: ""
moon: "月球"
corkboard: "软木板"
mousepad: "鼠标垫"
monitor: "显示器"
keyboard: "键盘"
carpet-stripe: "地毯(条纹)"
mat: "垫子"
color-box: "彩盒"
color-box: "收纳柜"
wall-clock: "挂钟"
photoframe: "相框"
cube: "立方体"
@@ -2136,4 +2144,9 @@ room:
rubik-cube: "魔方"
poster-h: "海报(横向)"
poster-v: "海报(纵向)"
sofa: "沙发"
spiral: "螺旋楼梯"
bin: "垃圾箱"
cup-noodle: "杯面"
holo-display: "全息显示器"
energy-drink: "能量饮料"

View File

@@ -7,8 +7,6 @@ common:
rich-contents: "發佈"
reaction: "回應"
drive: "雲端硬碟"
adblock:
detected: "請禁用廣告封鎖器"
close: "關閉"
enter-password: "請輸入密碼"
2fa: "雙重身份驗證"

View File

@@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "11.29.0",
"version": "11.32.0",
"codename": "daybreak",
"repository": {
"type": "git",
@@ -100,17 +100,17 @@
"@typescript-eslint/parser": "1.11.0",
"agentkeepalive": "4.0.2",
"animejs": "3.1.0",
"apexcharts": "3.8.4",
"apexcharts": "3.8.6",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"aws-sdk": "2.512.0",
"aws-sdk": "2.525.0",
"bcryptjs": "2.4.3",
"bootstrap": "4.3.1",
"bootstrap-vue": "2.0.0-rc.13",
"bull": "3.10.0",
"cafy": "15.1.1",
"cbor": "4.2.1",
"cbor": "4.3.0",
"chai": "4.2.0",
"chalk": "2.4.2",
"cli-highlight": "2.1.1",
@@ -120,7 +120,7 @@
"css-loader": "3.2.0",
"cssnano": "4.1.10",
"dateformat": "3.0.3",
"deep-equal": "1.0.1",
"deep-equal": "1.1.0",
"diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0",
"emojilib": "2.4.0",
@@ -128,12 +128,11 @@
"eslint-plugin-vue": "5.2.3",
"eventemitter3": "4.0.0",
"feed": "3.0.0",
"file-type": "12.1.0",
"file-type": "12.3.0",
"fluent-ffmpeg": "2.1.2",
"fuckadblock": "3.2.1",
"gulp": "4.0.2",
"gulp-cssnano": "2.1.3",
"gulp-imagemin": "6.0.0",
"gulp-imagemin": "6.1.0",
"gulp-mocha": "6.0.0",
"gulp-rename": "1.4.0",
"gulp-replace": "1.0.0",
@@ -156,7 +155,7 @@
"json5-loader": "3.0.0",
"jsrsasign": "8.0.12",
"katex": "0.11.0",
"koa": "2.7.0",
"koa": "2.8.1",
"koa-bodyparser": "4.2.1",
"koa-compress": "3.0.0",
"koa-favicon": "2.0.1",
@@ -167,7 +166,7 @@
"koa-router": "7.4.0",
"koa-send": "5.0.0",
"koa-slow": "2.1.0",
"koa-views": "6.2.0",
"koa-views": "6.2.1",
"langmap": "0.0.16",
"loader-utils": "1.2.3",
"lolex": "4.2.0",
@@ -198,8 +197,9 @@
"randomcolor": "0.5.4",
"ratelimiter": "3.3.1",
"recaptcha-promise": "0.1.3",
"reconnecting-websocket": "4.1.10",
"reconnecting-websocket": "4.2.0",
"redis": "2.8.0",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"rename": "1.0.4",
"request": "2.88.0",
@@ -216,14 +216,15 @@
"speakeasy": "2.0.0",
"stringz": "2.0.0",
"style-loader": "0.23.1",
"stylus": "0.54.5",
"stylus": "0.54.7",
"stylus-loader": "3.0.2",
"summaly": "2.3.0",
"systeminformation": "4.14.4",
"summaly": "2.3.1",
"syslog-pro": "1.0.0",
"systeminformation": "4.14.8",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "1.4.1",
"terser-webpack-plugin": "2.0.1",
"textarea-caret": "3.1.0",
"three": "0.107.0",
"three": "0.108.0",
"tinycolor2": "1.4.1",
"tmp": "0.1.0",
"ts-loader": "5.3.3",
@@ -235,7 +236,7 @@
"uglify-es": "3.3.9",
"ulid": "2.3.0",
"url-loader": "2.1.0",
"uuid": "3.3.2",
"uuid": "3.3.3",
"v-animate-css": "0.0.3",
"v-debounce": "0.1.2",
"vue": "2.6.10",
@@ -244,22 +245,22 @@
"vue-cropperjs": "4.0.0",
"vue-i18n": "8.14.0",
"vue-js-modal": "1.3.31",
"vue-json-pretty": "1.6.0",
"vue-json-pretty": "1.6.2",
"vue-loader": "15.7.1",
"vue-marquee-text-component": "1.1.1",
"vue-prism-component": "1.1.1",
"vue-router": "3.1.2",
"vue-router": "3.1.3",
"vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.2.16",
"vue-svg-inline-loader": "1.2.18",
"vue-template-compiler": "2.6.10",
"vuedraggable": "2.23.0",
"vuewordcloud": "18.7.11",
"vuex": "3.1.1",
"vuex-persistedstate": "2.5.4",
"web-push": "3.3.5",
"webpack": "4.39.2",
"webpack-cli": "3.3.7",
"webpack": "4.39.3",
"webpack-cli": "3.3.8",
"websocket": "1.0.29",
"ws": "7.1.2",
"xev": "2.0.1"

View File

@@ -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));

View File

@@ -19,7 +19,8 @@ init(launch => {
mode: 'history',
base: '/admin/',
routes: [
{ path: '/', component: Index },
{ path: '/:page', component: Index },
{ path: '/', redirect: '/dashboard' },
{ path: '*', component: NotFound }
]
});

View File

@@ -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

View File

@@ -1,18 +0,0 @@
declare const fuckAdBlock: any;
export default ($root: any) => {
require('fuckadblock');
function adBlockDetected() {
$root.dialog({
title: $root.$t('@.adblock.detected'),
text: $root.$t('@.adblock.warning')
});
}
if (fuckAdBlock === undefined) {
adBlockDetected();
} else {
fuckAdBlock.onDetected(adBlockDetected);
}
};

View File

@@ -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() {

View File

@@ -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);

View File

@@ -279,7 +279,15 @@
},
{
id: "pinguin",
place: "floor"
place: "floor",
props: {
body: 'color',
belly: 'color'
},
color: {
Body: 'body',
Belly: 'belly',
}
},
{
id: "rubik-cube",
@@ -351,4 +359,39 @@
Bin: 'color'
}
},
{
id: "cup-noodle",
place: "floor"
},
{
id: "holo-display",
place: "floor",
props: {
image: 'image'
},
texture: {
Image_Front: {
prop: 'image',
uv: {
x: 0,
y: 0,
width: 1024,
height: 1024,
},
},
Image_Back: {
prop: 'image',
uv: {
x: 0,
y: 0,
width: 1024,
height: 1024,
},
},
},
},
{
id: 'energy-drink',
place: "floor",
}
]

View File

@@ -102,7 +102,6 @@ export class Room {
this.renderer.autoClear = false;
this.renderer.setClearColor(new THREE.Color(0x051f2d));
this.renderer.shadowMap.enabled = this.enableShadow;
this.renderer.gammaOutput = true;
this.renderer.shadowMap.type =
this.graphicsQuality === 'ultra' ? THREE.PCFSoftShadowMap :
this.graphicsQuality === 'high' ? THREE.PCFSoftShadowMap :
@@ -343,7 +342,8 @@ export class Room {
@autobind
private loadRoom() {
new GLTFLoader().load(`/assets/room/rooms/${this.roomInfo.roomType}/${this.roomInfo.roomType}.glb`, gltf => {
const type = this.roomInfo.roomType;
new GLTFLoader().load(`/assets/room/rooms/${type}/${type}.glb`, gltf => {
gltf.scene.traverse(child => {
if (!(child instanceof THREE.Mesh)) return;
@@ -429,34 +429,42 @@ export class Room {
private applyCarpetColor() {
this.roomObj.traverse(child => {
if (!(child instanceof THREE.Mesh)) return;
if (child.material && (child.material as THREE.MeshStandardMaterial).name && (child.material as THREE.MeshStandardMaterial).name === 'Carpet') {
(child.material as THREE.MeshStandardMaterial).color.setHex(parseInt(this.roomInfo.carpetColor.substr(1), 16));
if (child.material &&
(child.material as THREE.MeshStandardMaterial).name &&
(child.material as THREE.MeshStandardMaterial).name === 'Carpet'
) {
const colorHex = parseInt(this.roomInfo.carpetColor.substr(1), 16);
(child.material as THREE.MeshStandardMaterial).color.setHex(colorHex);
}
});
}
@autobind
public applyCustomColor(model: THREE.Object3D) {
private applyCustomColor(model: THREE.Object3D) {
const furniture = this.furnitures.find(furniture => furniture.id === model.name);
const def = furnitureDefs.find(d => d.id === furniture.type);
if (def.color == null) return;
model.traverse(child => {
if (!(child instanceof THREE.Mesh)) return;
for (const t of Object.keys(def.color)) {
if (!child.material || !(child.material as THREE.MeshStandardMaterial).name || (child.material as THREE.MeshStandardMaterial).name !== t) continue;
if (!child.material ||
!(child.material as THREE.MeshStandardMaterial).name ||
(child.material as THREE.MeshStandardMaterial).name !== t
) continue;
const prop = def.color[t];
const val = furniture.props ? furniture.props[prop] : undefined;
if (val == null) continue;
(child.material as THREE.MeshStandardMaterial).color.setHex(parseInt(val.substr(1), 16));
const colorHex = parseInt(val.substr(1), 16);
(child.material as THREE.MeshStandardMaterial).color.setHex(colorHex);
}
});
}
@autobind
public applyCustomTexture(model: THREE.Object3D) {
private applyCustomTexture(model: THREE.Object3D) {
const furniture = this.furnitures.find(furniture => furniture.id === model.name);
const def = furnitureDefs.find(d => d.id === furniture.type);
if (def.texture == null) return;
@@ -487,7 +495,9 @@ export class Room {
const uvInfo = def.texture[t].uv;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, img.width, img.height, uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height);
ctx.drawImage(img,
0, 0, img.width, img.height,
uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height);
const texture = new THREE.Texture(canvas);
texture.wrapS = THREE.RepeatWrapping;
@@ -608,6 +618,10 @@ export class Room {
});
}
/**
* 家具の移動/回転モードにします
* @param type 移動か回転か
*/
@autobind
public enterTransformMode(type: 'translate' | 'rotate') {
this.isTransformMode = true;
@@ -615,12 +629,20 @@ export class Room {
this.furnitureControl.attach(this.selectedObject);
}
/**
* 家具の移動/回転モードを終了します
*/
@autobind
public exitTransformMode() {
this.isTransformMode = false;
this.furnitureControl.detach();
}
/**
* 家具プロパティを更新します
* @param key プロパティ名
* @param value 値
*/
@autobind
public updateProp(key: string, value: any) {
const furniture = this.furnitures.find(furniture => furniture.id === this.selectedObject.name);
@@ -630,6 +652,10 @@ export class Room {
this.applyCustomTexture(this.selectedObject);
}
/**
* 部屋に家具を追加します
* @param type 家具の種類
*/
@autobind
public addFurniture(type: string) {
const furniture = {
@@ -655,6 +681,9 @@ export class Room {
});
}
/**
* 現在選択されている家具を部屋から削除します
*/
@autobind
public removeFurniture() {
this.exitTransformMode();
@@ -666,12 +695,35 @@ export class Room {
this.onChangeSelect(null);
}
/**
* 全ての家具を部屋から削除します
*/
@autobind
public removeAllFurnitures() {
this.exitTransformMode();
for (const obj of this.objects) {
this.scene.remove(obj);
}
this.objects = [];
this.furnitures = [];
this.selectedObject = null;
this.onChangeSelect(null);
}
/**
* 部屋の床の色を変更します
* @param color 色
*/
@autobind
public updateCarpetColor(color: string) {
this.roomInfo.carpetColor = color;
this.applyCarpetColor();
}
/**
* 部屋の種類を変更します
* @param type 種類
*/
@autobind
public changeRoomType(type: string) {
this.roomInfo.roomType = type;
@@ -679,6 +731,9 @@ export class Room {
this.loadRoom();
}
/**
* 部屋データを取得します
*/
@autobind
public getRoomInfo() {
for (const obj of this.objects) {
@@ -694,6 +749,9 @@ export class Room {
return this.roomInfo;
}
/**
* 選択されている家具を取得します
*/
@autobind
public getSelectedObject() {
return this.selectedObject;
@@ -704,9 +762,15 @@ export class Room {
return this.furnitures.find(furniture => furniture.id === id);
}
/**
* レンダリングを終了します
*/
@autobind
public destroy() {
// Stop render loop
window.cancelAnimationFrame(this.renderFrameRequestId);
this.controls.dispose();
this.scene.dispose();
}
}

View File

@@ -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>

View File

@@ -57,7 +57,8 @@ export default Vue.extend({
},
fit: {
type: String,
required: true
required: false,
default: 'cover'
},
detail: {
type: Boolean,

View File

@@ -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
},

View File

@@ -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)

View File

@@ -227,6 +227,7 @@ export default Vue.extend({
},
closed() {
this.$emit('closed');
this.$nextTick(() => {
this.destroyDom();
});

View File

@@ -0,0 +1,122 @@
<template>
<transition name="zoom-in-top">
<div class="buebdbiu" ref="popover" v-if="show">
<i18n path="few-users" v-if="users.length <= 10">
<span slot="users">
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
</b>
</span>
<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
</i18n>
<i18n path="many-users" v-if="10 < users.length">
<span slot="users">
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
<mk-avatar :user="u" style="width: 24px; height: 24px; margin-right: 2px;"/>
<mk-user-name :user="u" :nowrap="false" style="line-height: 24px;"/>
</b>
</span>
<span slot="omitted">{{ count - 10 }}</span>
<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" />
</i18n>
</div>
</transition>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
export default Vue.extend({
i18n: i18n('common/views/components/reactions-viewer.details.vue'),
props: {
reaction: {
type: String,
required: true,
},
users: {
type: Array,
required: true,
},
count: {
type: Number,
required: true,
},
source: {
required: true,
}
},
data() {
return {
show: false
};
},
mounted() {
this.show = true;
this.$nextTick(() => {
const popover = this.$refs.popover as any;
if (this.source == null) {
this.destroyDom();
return;
}
const rect = this.source.getBoundingClientRect();
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
const y = rect.top + window.pageYOffset + this.source.offsetHeight;
popover.style.left = (x - 28) + 'px';
popover.style.top = (y + 16) + 'px';
});
}
methods: {
close() {
this.show = false;
setTimeout(this.destroyDom, 300);
}
}
})
</script>
<style lang="stylus" scoped>
.buebdbiu
$bgcolor = var(--popupBg)
z-index 10000
display block
position absolute
max-width 240px
font-size 0.8em
padding 6px 8px
background $bgcolor
text-align center
color var(--text)
border-radius 4px
box-shadow 0 var(--lineWidth) 4px rgba(#000, 0.25)
pointer-events none
transform-origin center -16px
&:before
content ""
pointer-events none
display block
position absolute
top -28px
left 12px
border-top solid 14px transparent
border-right solid 14px transparent
border-bottom solid 14px rgba(#000, 0.1)
border-left solid 14px transparent
&:after
content ""
pointer-events none
display block
position absolute
top -27px
left 12px
border-top solid 14px transparent
border-right solid 14px transparent
border-bottom solid 14px $bgcolor
border-left solid 14px transparent
</style>

View File

@@ -5,6 +5,9 @@
@click="toggleReaction(reaction)"
v-if="count > 0"
v-particle="!isMe"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
ref="reaction"
>
<mk-reaction-icon :reaction="reaction" ref="icon"/>
<span>{{ count }}</span>
@@ -15,6 +18,7 @@
import Vue from 'vue';
import Icon from './reaction-icon.vue';
import anime from 'animejs';
import XDetails from './reactions-viewer.details.vue';
export default Vue.extend({
props: {
@@ -26,6 +30,10 @@ export default Vue.extend({
type: Number,
required: true,
},
isInitial: {
type: Boolean,
required: true,
},
note: {
type: Object,
required: true,
@@ -36,14 +44,25 @@ export default Vue.extend({
default: true,
},
},
data() {
return {
details: null,
detailsTimeoutId: null,
isHovering: false
};
},
computed: {
isMe(): boolean {
return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId;
},
},
mounted() {
if (!this.isInitial) this.anime();
},
watch: {
count() {
this.anime();
count(newCount, oldCount) {
if (oldCount < newCount) this.anime();
if (this.details != null) this.openDetails();
},
},
methods: {
@@ -70,11 +89,49 @@ export default Vue.extend({
});
}
},
onMouseover() {
this.isHovering = true;
this.detailsTimeoutId = setTimeout(this.openDetails, 300);
},
onMouseleave() {
this.isHovering = false;
clearTimeout(this.detailsTimeoutId);
this.closeDetails();
},
openDetails() {
if (this.$root.isMobile) return;
this.$root.api('notes/reactions', {
noteId: this.note.id,
type: this.reaction,
limit: 11
}).then((reactions: any[]) => {
const users = reactions
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
.map(x => x.user);
this.closeDetails();
if (!this.isHovering) return;
this.details = this.$root.new(XDetails, {
reaction: this.reaction,
users,
count: this.count,
source: this.$refs.reaction
});
});
},
closeDetails() {
if (this.details != null) {
this.details.close();
this.details = null;
}
},
anime() {
if (this.$store.state.device.reduceMotion) return;
if (document.hidden) return;
this.$nextTick(() => {
if (this.$refs.icon == null) return;
const rect = this.$refs.icon.$el.getBoundingClientRect();
const x = rect.left;
@@ -120,6 +177,14 @@ export default Vue.extend({
border-radius 4px
cursor pointer
&, *
-webkit-touch-callout none
-webkit-user-select none
-khtml-user-select none
-moz-user-select none
-ms-user-select none
user-select none
*
user-select none
pointer-events none

View File

@@ -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;
},

View File

@@ -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) {

View File

@@ -49,6 +49,7 @@ export default Vue.extend({
},
methods: {
toggle() {
if (this.disabled) return;
this.$emit('change', !this.checked);
}
}

View File

@@ -18,7 +18,7 @@
<div class="description" v-if="user.description" :title="user.description">
<mfm :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :plain="true" :nowrap="true"/>
</div>
<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
<mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
</div>
</div>
<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore()" :disabled="moreFetching">
@@ -134,7 +134,7 @@ export default Vue.extend({
font-size 14px
padding-right 40px
> .follow-button
> .koudoku-button
position absolute
top 8px
right 0

View File

@@ -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);
}
},

View File

@@ -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);
};

View File

@@ -42,7 +42,8 @@
</label>
</section>
<section>
<ui-button primary @click="save()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
<ui-button :primary="changed" @click="save()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
<ui-button @click="clear()"><fa :icon="faBroom"/> {{ $t('clear') }}</ui-button>
</section>
</div>
</div>
@@ -55,7 +56,7 @@ import { Room } from '../../../scripts/room/room';
import parseAcct from '../../../../../../misc/acct/parse';
import XPreview from './preview.vue';
const storeItems = require('../../../scripts/room/furnitures.json5');
import { faBoxOpen, faUndo, faArrowsAlt, faBan } from '@fortawesome/free-solid-svg-icons';
import { faBoxOpen, faUndo, faArrowsAlt, faBan, faBroom } from '@fortawesome/free-solid-svg-icons';
import { faSave, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
import { query as urlQuery } from '../../../../../../prelude/url';
@@ -86,11 +87,14 @@ export default Vue.extend({
isTranslateMode: false,
isRotateMode: false,
isMyRoom: false,
faBoxOpen, faSave, faTrashAlt, faUndo, faArrowsAlt, faBan,
changed: false,
faBoxOpen, faSave, faTrashAlt, faUndo, faArrowsAlt, faBan, faBroom,
};
},
async mounted() {
window.addEventListener('beforeunload', this.beforeunload);
const user = await this.$root.api('users/show', {
...parseAcct(this.acct)
});
@@ -124,11 +128,37 @@ export default Vue.extend({
});
},
beforeRouteLeave(to, from, next) {
if (this.changed) {
this.$root.dialog({
type: 'warning',
text: this.$t('leave-confirm'),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) {
next(false);
} else {
next();
}
});
} else {
next();
}
},
beforeDestroy() {
room.destroy();
window.removeEventListener('beforeunload', this.beforeunload);
},
methods: {
beforeunload(e: BeforeUnloadEvent) {
if (this.changed) {
e.preventDefault();
e.returnValue = '';
}
},
async add() {
const { canceled, result: id } = await this.$root.dialog({
type: null,
@@ -142,17 +172,42 @@ export default Vue.extend({
});
if (canceled) return;
room.addFurniture(id);
this.changed = true;
},
remove() {
this.isTranslateMode = false;
this.isRotateMode = false;
room.removeFurniture();
this.changed = true;
},
save() {
this.$root.api('room/update', {
room: room.getRoomInfo()
}).then(() => {
this.changed = false;
this.$root.dialog({
type: 'success',
text: this.$t('saved')
});
}).catch((e: any) => {
this.$root.dialog({
type: 'error',
text: e.message
});
});
},
clear() {
this.$root.dialog({
type: 'warning',
text: this.$t('clear-confirm'),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) return;
room.removeAllFurnitures();
this.changed = true;
});
},
@@ -162,22 +217,26 @@ export default Vue.extend({
}).then(file => {
room.updateProp(key, `/proxy/?${urlQuery({ url: file.thumbnailUrl })}`);
this.$refs.preview.selected(room.getSelectedObject());
this.changed = true;
});
},
updateColor(key, ev) {
room.updateProp(key, ev.target.value);
this.$refs.preview.selected(room.getSelectedObject());
this.changed = true;
},
updateCarpetColor(ev) {
room.updateCarpetColor(ev.target.value);
this.carpetColor = ev.target.value;
this.changed = true;
},
updateRoomType(type) {
room.changeRoomType(type);
this.roomType = type;
this.changed = true;
},
translate() {
@@ -188,6 +247,7 @@ export default Vue.extend({
this.isTranslateMode = true;
room.enterTransformMode('translate');
}
this.changed = true;
},
rotate() {
@@ -198,12 +258,14 @@ export default Vue.extend({
this.isRotateMode = true;
room.enterTransformMode('rotate');
}
this.changed = true;
},
exit() {
this.isTranslateMode = false;
this.isRotateMode = false;
room.exitTransformMode();
this.changed = true;
}
}
});

View File

@@ -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() {

View File

@@ -9,7 +9,6 @@ import VueRouter from 'vue-router';
import './style.styl';
import init from '../init';
import fuckAdBlock from '../common/scripts/fuck-ad-block';
import composeNotification from '../common/scripts/compose-notification';
import MkHome from './views/home/home.vue';
@@ -185,7 +184,8 @@ init(async (launch, os) => {
{ path: '/i/messaging/:user', component: MkMessagingRoom },
{ path: '/i/drive', component: MkDrive },
{ path: '/i/drive/folder/:folder', component: MkDrive },
{ path: '/i/settings', component: MkSettings },
{ path: '/i/settings', redirect: '/i/settings/profile' },
{ path: '/i/settings/:page', component: MkSettings },
{ path: '/selectdrive', component: MkSelectDrive },
{ path: '/@:acct/room', props: true, component: () => import('../common/views/pages/room/room.vue').then(m => m.default) },
{ path: '/share', component: MkShare },
@@ -202,13 +202,6 @@ init(async (launch, os) => {
// Launch the app
const [app, _] = launch(router);
if (os.store.getters.isSignedIn) {
/**
* Fuck AD Block
*/
fuckAdBlock(app);
}
/**
* Init Notification
*/

View File

@@ -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

View File

@@ -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

View File

@@ -21,7 +21,7 @@
<p>{{ $t('followers') }}</p><span>{{ u.followersCount }}</span>
</div>
</div>
<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u" mini/>
<mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u" mini/>
</template>
</div>
</template>
@@ -156,7 +156,7 @@ export default Vue.extend({
font-size 1em
color var(--primary)
> .follow-button
> .koudoku-button
position absolute
top 8px
right 8px

View File

@@ -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>

View File

@@ -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) }) },

View File

@@ -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);
});
},

View File

@@ -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() {

View File

@@ -17,15 +17,18 @@
<div class="form">
<mk-note-preview class="preview" v-if="reply" :note="reply"/>
<mk-note-preview class="preview" v-if="renote" :note="renote"/>
<div class="with-quote" v-if="quoteId">{{ $t('@.post-form.quote-attached') }} <a @click="quoteId = null">[x]</a></div>
<div v-if="visibility === 'specified'" class="visibleUsers">
<span v-for="u in visibleUsers">
<mk-user-name :user="u"/>
<a @click="removeVisibleUser(u)">[x]</a>
</span>
<a @click="addVisibleUser">+{{ $t('@.post-form.add-visible-user') }}</a>
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('@.post-form.quote-attached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
<div v-if="visibility === 'specified'" class="to-specified">
<fa icon="envelope"/> {{ $t('@.post-form.specified-recipient') }}
<div class="visibleUsers">
<span v-for="u in visibleUsers">
<mk-user-name :user="u"/>
<button @click="removeVisibleUser(u)"><fa icon="times"/></button>
</span>
<button @click="addVisibleUser">{{ $t('@.post-form.add-visible-user') }}</button>
</div>
</div>
<div class="local-only" v-if="localOnly === true">{{ $t('@.post-form.local-only-message') }}</div>
<div class="local-only" v-if="localOnly === true"><fa icon="heart"/> {{ $t('@.post-form.local-only-message') }}</div>
<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('@.post-form.cw-placeholder')" v-autocomplete="{ model: 'cw' }">
<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }" @paste="onPaste"></textarea>
<x-post-form-attaches class="attaches" :files="files"/>
@@ -145,13 +148,37 @@ export default Vue.extend({
margin 0 0 8px 0
color var(--primary)
> .visibleUsers
margin 5px
font-size 14px
> button
padding 4px 8px
color var(--primaryAlpha04)
> span
margin-right 16px
color var(--text)
&:hover
color var(--primaryAlpha06)
&:active
color var(--primaryDarken30)
> .to-specified
margin 0 0 8px 0
color var(--primary)
> .visibleUsers
display inline
top -1px
font-size 14px
> span
margin-left 14px
> button
padding 4px 8px
color var(--primaryAlpha04)
&:hover
color var(--primaryAlpha06)
&:active
color var(--primaryDarken30)
> .local-only
margin 0 0 8px 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -34,6 +34,7 @@ export type Source = {
autoAdmin?: boolean;
proxy?: string;
proxySmtp?: string;
accesslog?: string;
@@ -42,6 +43,14 @@ export type Source = {
id: string;
outgoingAddressFamily?: 'ipv4' | 'ipv6' | 'dual';
deliverJobConcurrency?: number;
inboxJobConcurrency?: number;
syslog: {
host: string;
port: number;
};
};
/**

22
src/misc/app-lock.ts Normal file
View File

@@ -0,0 +1,22 @@
import redis from '../db/redis';
import { promisify } from 'util';
/**
* Retry delay (ms) for lock acquisition
*/
const retryDelay = 100;
const lock: (key: string, timeout?: number) => Promise<() => void>
= redis
? promisify(require('redis-lock')(redis, retryDelay))
: async () => () => { };
/**
* Get AP Object lock
* @param uri AP object ID
* @param timeout Lock timeout (ms), The timeout releases previous lock.
* @returns Unlock function
*/
export function getApLock(uri: string, timeout = 30 * 1000) {
return lock(`ap-object:${uri}`, timeout);
}

View File

@@ -1,5 +1,5 @@
import $ from 'cafy';
import { EntityRepository, Repository, In } from 'typeorm';
import { EntityRepository, Repository, In, Not } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../entities/user';
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages } from '..';
import { ensure } from '../../prelude/ensure';
@@ -56,6 +56,10 @@ export class UserRepository extends Repository<User> {
}
public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
const mute = await Mutings.find({
muterId: userId
});
const joinings = await UserGroupJoinings.find({ userId: userId });
const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
@@ -66,11 +70,11 @@ export class UserRepository extends Repository<User> {
.getOne().then(x => x != null)));
const [withUser, withGroups] = await Promise.all([
// TODO: ミュートを考慮
MessagingMessages.count({
where: {
recipientId: userId,
isRead: false
isRead: false,
...(mute.length > 0 ? { userId: Not(In(mute.map(x => x.muteeId))) } : {}),
},
take: 1
}).then(count => count > 0),

View File

@@ -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);
}

View File

@@ -7,6 +7,7 @@ import { resolvePerson } from '../../models/person';
import { apLogger } from '../../logger';
import { extractDbHost } from '../../../../misc/convert-host';
import { fetchMeta } from '../../../../misc/fetch-meta';
import { getApLock } from '../../../../misc/app-lock';
const logger = apLogger;
@@ -25,47 +26,53 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
const meta = await fetchMeta();
if (meta.blockedHosts.includes(extractDbHost(uri))) return;
// 既に同じURIを持つものが登録されていないかチェック
const exist = await fetchNote(uri);
if (exist) {
return;
}
const unlock = await getApLock(uri);
// Announce対象をresolve
let renote;
try {
renote = await resolveNote(note);
} catch (e) {
// 対象が4xxならスキップ
if (e.statusCode >= 400 && e.statusCode < 500) {
logger.warn(`Ignored announce target ${note.inReplyTo} - ${e.statusCode}`);
// 既に同じURIを持つものが登録されていないかチェック
const exist = await fetchNote(uri);
if (exist) {
return;
}
logger.warn(`Error in announce target ${note.inReplyTo} - ${e.statusCode || e}`);
throw e;
// Announce対象をresolve
let renote;
try {
renote = await resolveNote(note);
} catch (e) {
// 対象が4xxならスキップ
if (e.statusCode >= 400 && e.statusCode < 500) {
logger.warn(`Ignored announce target ${note.inReplyTo} - ${e.statusCode}`);
return;
}
logger.warn(`Error in announce target ${note.inReplyTo} - ${e.statusCode || e}`);
throw e;
}
logger.info(`Creating the (Re)Note: ${uri}`);
//#region Visibility
const to = getApIds(activity.to);
const cc = getApIds(activity.cc);
const visibility = getVisibility(to, cc, actor);
let visibleUsers: User[] = [];
if (visibility == 'specified') {
visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri)));
}
//#endergion
await post(actor, {
createdAt: activity.published ? new Date(activity.published) : null,
renote,
visibility,
visibleUsers,
uri
});
} finally {
unlock();
}
logger.info(`Creating the (Re)Note: ${uri}`);
//#region Visibility
const to = getApIds(activity.to);
const cc = getApIds(activity.cc);
const visibility = getVisibility(to, cc, actor);
let visibleUsers: User[] = [];
if (visibility == 'specified') {
visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri)));
}
//#endergion
await post(actor, {
createdAt: activity.published ? new Date(activity.published) : null,
renote,
visibility,
visibleUsers,
uri
});
}
type visibility = 'public' | 'home' | 'followers' | 'specified';

View File

@@ -1,13 +1,23 @@
import Resolver from '../../resolver';
import { IRemoteUser } from '../../../../models/entities/user';
import { createNote, fetchNote } from '../../models/note';
import { getApId } from '../../type';
import { getApLock } from '../../../../misc/app-lock';
/**
* 投稿作成アクティビティを捌きます
*/
export default async function(resolver: Resolver, actor: IRemoteUser, note: any, silent = false): Promise<void> {
const exist = await fetchNote(note);
if (exist == null) {
await createNote(note);
const uri = getApId(note);
const unlock = await getApLock(uri);
try {
const exist = await fetchNote(note);
if (exist == null) {
await createNote(note);
}
} finally {
unlock();
}
}

View File

@@ -0,0 +1,16 @@
import { Notes } from '../../../../models';
import { IRemoteUser } from '../../../../models/entities/user';
import { IAnnounce, getApId } from '../../type';
import deleteNote from '../../../../services/note/delete';
export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<void> => {
const uri = getApId(activity);
const note = await Notes.findOne({
uri
});
if (!note) return;
await deleteNote(actor, note);
};

View File

@@ -1,8 +1,9 @@
import { IRemoteUser } from '../../../../models/entities/user';
import { IUndo, IFollow, IBlock, ILike } from '../../type';
import { IUndo, IFollow, IBlock, ILike, IAnnounce } from '../../type';
import unfollow from './follow';
import unblock from './block';
import undoLike from './like';
import { undoAnnounce } from './announce';
import Resolver from '../../resolver';
import { apLogger } from '../../logger';
@@ -38,5 +39,8 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
case 'Like':
undoLike(actor, object as ILike);
break;
case 'Announce':
undoAnnounce(actor, object as IAnnounce);
break;
}
};

View File

@@ -22,6 +22,7 @@ import { Emoji } from '../../../models/entities/emoji';
import { genId } from '../../../misc/gen-id';
import { fetchMeta } from '../../../misc/fetch-meta';
import { ensure } from '../../../prelude/ensure';
import { getApLock } from '../../../misc/app-lock';
const logger = apLogger;
@@ -257,30 +258,24 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
const meta = await fetchMeta();
if (meta.blockedHosts.includes(extractDbHost(uri))) throw { statusCode: 451 };
//#region このサーバーに既に登録されていたらそれを返す
const exist = await fetchNote(uri);
const unlock = await getApLock(uri);
if (exist) {
return exist;
}
//#endregion
try {
//#region このサーバーに既に登録されていたらそれを返す
const exist = await fetchNote(uri);
// リモートサーバーからフェッチしてきて登録
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにートが生成されるが
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
return await createNote(uri, resolver, true).catch(e => {
if (e.name === 'duplicated') {
return fetchNote(uri).then(note => {
if (note == null) {
throw new Error('something happened');
} else {
return note;
}
});
} else {
throw e;
if (exist) {
return exist;
}
});
//#endregion
// リモートサーバーからフェッチしてきて登録
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにートが生成されるが
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
return await createNote(uri, resolver, true);
} finally {
unlock();
}
}
export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> {

View File

@@ -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

View File

@@ -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
};

View 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: ユーザーごとのチャートもキューに入れて更新する
});

View File

@@ -66,7 +66,7 @@ export const meta = {
avatarId: {
validator: $.optional.nullable.type(ID),
desc: {
'ja-JP': 'アイコンに設定する画像のドライブファイルID'
'ja-JP': 'アバターに設定する画像のドライブファイルID'
}
},

View File

@@ -195,10 +195,7 @@ export default define(meta, async (ps, user) => {
const mute = await Mutings.find({
muterId: recipientUser.id,
});
const mutedUserIds = mute.map(m => m.muteeId.toString());
if (mutedUserIds.indexOf(user.id) != -1) {
return;
}
if (mute.map(m => m.muteeId).includes(user.id)) return;
//#endregion
publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);

View File

@@ -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,

View File

@@ -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 != \'{}\'');

View File

@@ -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

View File

@@ -4,6 +4,8 @@ import define from '../../define';
import { getNote } from '../../common/getters';
import { ApiError } from '../../error';
import { NoteReactions } from '../../../../models';
import { DeepPartial } from 'typeorm';
import { NoteReaction } from '../../../../models/entities/note-reaction';
export const meta = {
desc: {
@@ -24,6 +26,10 @@ export const meta = {
}
},
type: {
validator: $.optional.nullable.str,
},
limit: {
validator: $.optional.num.range(1, 100),
default: 10
@@ -70,7 +76,11 @@ export default define(meta, async (ps, user) => {
const query = {
noteId: note.id
};
} as DeepPartial<NoteReaction>;
if (ps.type) {
query.reaction = ps.type;
}
const reactions = await NoteReactions.find({
where: query,

View File

@@ -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 != \'{}\'');

View File

@@ -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 != \'{}\'');

View File

@@ -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(),

View File

@@ -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,

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -1,5 +1,7 @@
import { publishNoteStream } from '../stream';
import renderDelete from '../../remote/activitypub/renderer/delete';
import renderAnnounce from '../../remote/activitypub/renderer/announce';
import renderUndo from '../../remote/activitypub/renderer/undo';
import { renderActivity } from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
@@ -35,7 +37,17 @@ export default async function(user: User, note: Note, quiet = false) {
//#region ローカルの投稿なら削除アクティビティを配送
if (Users.isLocalUser(user)) {
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user));
let renote: Note | undefined;
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) {
renote = await Notes.findOne({
id: note.renoteId
});
}
const content = renderActivity(renote
? renderUndo(renderAnnounce(renote.uri || `${config.url}/notes/${renote.id}`, note), user)
: renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user));
const queue: string[] = [];

View File

@@ -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

View File

@@ -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

View File

@@ -2,10 +2,10 @@
* Tests of API
*
* How to run the tests:
* > mocha test/api.ts --require ts-node/register
* > npx mocha test/api.ts --require ts-node/register
*
* To specify test:
* > mocha test/api.ts --require ts-node/register -g 'test name'
* > npx mocha test/api.ts --require ts-node/register -g 'test name'
*
* If the tests not start, try set following enviroment variables:
* TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true

Some files were not shown because too many files have changed in this diff Show More