Compare commits

...

133 Commits

Author SHA1 Message Date
syuilo ac15c2e71f Fix bug 2018-08-16 08:11:21 +09:00
syuilo 1fc9206c6d 6.0.0 2018-08-16 08:02:50 +09:00
syuilo a8fb0d477f Merge pull request #2251 from syuilo/provide-thumbnails
Provide drive file thumbnails
2018-08-16 07:59:16 +09:00
syuilo 0c372b68d4 Merge pull request #2252 from syuilo/greenkeeper/minio-7.0.0
Update minio to the latest version 🚀
2018-08-16 07:58:56 +09:00
syuilo e7d9018944 ✌️ 2018-08-16 07:56:53 +09:00
syuilo ce16884587 Update example.yml 2018-08-16 07:31:58 +09:00
greenkeeper[bot] 3345733d00 fix(package): update minio to version 7.0.0 2018-08-15 22:26:17 +00:00
greenkeeper[bot] f91cccb6b1 fix(package): update @types/webpack to version 4.4.10 2018-08-16 07:24:19 +09:00
syuilo 6183262037 wip 2018-08-16 07:17:04 +09:00
syuilo 1c7a194950 wip 2018-08-16 06:27:35 +09:00
greenkeeper[bot] 286e15b967 fix(package): update @types/node to version 10.7.1 2018-08-16 06:08:15 +09:00
syuilo 744d366874 Merge pull request #2248 from syuilo/new-kao
Add new kao
2018-08-16 05:17:56 +09:00
Aya Morisawa afa62d3d44 Add new kao 2018-08-16 05:17:00 +09:00
Aya Morisawa 270c7997c6 Remove trailing comma 2018-08-16 05:15:51 +09:00
Aya Morisawa fa95641f88 Use Array.prototype.length to avoid magic number 2018-08-16 05:15:06 +09:00
syuilo aa9fe38c25 Merge pull request #2247 from syuilo/clickable-reaction
Make reactions in reactions-viewer clickable
2018-08-16 05:00:13 +09:00
Aya Morisawa 0d33cbbbbb Make reactions in reactions-viewer clickable 2018-08-16 04:57:09 +09:00
syuilo bf077da72f Trim code 2018-08-16 04:42:44 +09:00
syuilo d8f8e19d06 Merge pull request #2246 from mei23/mei-0816-player
動画プレーヤーの修正
2018-08-16 03:17:41 +09:00
mei23 3c3d3e4c0c Fix url-preview.vue 2018-08-16 03:06:59 +09:00
syuilo 0efbeb36df 5.25.0 2018-08-16 01:29:11 +09:00
syuilo 73c396cb39 2018-08-16 01:25:35 +09:00
syuilo 8e75f8a125 Fix 2018-08-16 01:20:52 +09:00
syuilo e8a7e95c65 Update summaly 2018-08-16 01:17:01 +09:00
syuilo 70a6889fe5 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-16 00:59:54 +09:00
syuilo 6aff7375f6 Update backers 2018-08-16 00:59:48 +09:00
Aya Morisawa d2ef95a8c3 Make text unclickable 2018-08-16 00:49:40 +09:00
syuilo 1ae51df74a Update .travis.yml 2018-08-16 00:42:20 +09:00
syuilo 3098c6a915 Merge pull request #2242 from syuilo/l10n_master
New Crowdin translations
2018-08-16 00:16:31 +09:00
Aya Morisawa ab3f8fd10c Enhance log message 2018-08-16 00:13:24 +09:00
Aya Morisawa 4bdef3720c Update setup.en.md 2018-08-15 23:55:30 +09:00
syuilo c02cecc9e5 New translations ja.yml (English) 2018-08-15 23:30:49 +09:00
syuilo 08b431723a Refactoring 2018-08-15 20:27:49 +09:00
syuilo 83dcfec053 Improve MFM 2018-08-15 20:27:36 +09:00
syuilo 69ac7b739f Improve MFM 2018-08-15 20:23:50 +09:00
syuilo 9ccf9a2496 Fix #2229 2018-08-15 20:20:46 +09:00
syuilo 78563ef9a0 Fix #2228 2018-08-15 20:09:56 +09:00
syuilo 019d157b92 Merge pull request #2198 from syuilo/l10n_master
New Crowdin translations
2018-08-15 19:48:03 +09:00
syuilo 14de35e3f5 Merge pull request #2234 from acid-chicken/patch-player
Update player whitelist
2018-08-15 19:04:31 +09:00
Acid Chicken (硫酸鶏) a860479e88 Update url-preview.vue 2018-08-15 19:00:44 +09:00
Acid Chicken (硫酸鶏) b351b3fae5 Update url-preview.vue 2018-08-15 18:49:05 +09:00
syuilo 89918a9f79 New translations ja.yml (English) 2018-08-15 17:08:24 +09:00
syuilo cadd020915 New translations ja.yml (English) 2018-08-15 16:51:16 +09:00
syuilo e80933b8ae New translations ja.yml (English) 2018-08-15 16:40:52 +09:00
syuilo 4c0832884f New translations ja.yml (English) 2018-08-15 16:30:48 +09:00
syuilo e4ca940979 New translations ja.yml (English) 2018-08-15 16:20:51 +09:00
syuilo 3fd9be3967 New translations ja.yml (English) 2018-08-15 16:13:11 +09:00
syuilo 79ec7aba1d New translations ja.yml (English) 2018-08-15 13:40:56 +09:00
syuilo 42b1f7eddd New translations ja.yml (English) 2018-08-15 11:30:56 +09:00
syuilo 889a73caa4 New translations ja.yml (Catalan) 2018-08-15 11:11:41 +09:00
syuilo c34a89e962 New translations ja.yml (Portuguese) 2018-08-15 11:11:39 +09:00
syuilo 2b4bf681e5 New translations ja.yml (Korean) 2018-08-15 11:11:36 +09:00
syuilo d1d59e3557 New translations ja.yml (Polish) 2018-08-15 11:11:34 +09:00
syuilo 668c21830c New translations ja.yml (Chinese Simplified) 2018-08-15 11:11:31 +09:00
syuilo 7c37ed07f8 New translations ja.yml (Italian) 2018-08-15 11:11:29 +09:00
syuilo a135d8fd59 New translations ja.yml (Russian) 2018-08-15 11:11:27 +09:00
syuilo 0db9aae162 New translations ja.yml (English) 2018-08-15 11:11:25 +09:00
syuilo 1875c362af New translations ja.yml (Spanish) 2018-08-15 11:11:23 +09:00
syuilo 9b36dd9565 New translations ja.yml (German) 2018-08-15 11:11:21 +09:00
syuilo 69452a27de New translations ja.yml (French) 2018-08-15 11:11:18 +09:00
syuilo 6aa5c5895f Merge pull request #2219 from syuilo/patch-2215
Resolve #2215
2018-08-15 11:07:07 +09:00
syuilo 12f20c67b1 New translations ja.yml (English) 2018-08-15 09:21:05 +09:00
syuilo 66b8e5647d New translations ja.yml (English) 2018-08-15 09:11:16 +09:00
syuilo 188a23fec7 New translations ja.yml (English) 2018-08-15 09:01:12 +09:00
syuilo b408b45000 New translations ja.yml (English) 2018-08-15 08:55:29 +09:00
Aya Morisawa c53cb94250 Resolve #2215 2018-08-15 06:50:49 +09:00
syuilo ba2eeabe38 Merge pull request #2216 from syuilo/patch-2201
Resolve #2201
2018-08-15 04:46:52 +09:00
Aya Morisawa 259fac224e Resolve #2201 2018-08-15 04:44:39 +09:00
syuilo aad5440c9e New translations ja.yml (English) 2018-08-15 04:41:14 +09:00
syuilo 5c40f0010f New translations ja.yml (English) 2018-08-15 04:31:06 +09:00
syuilo fb1d181424 New translations ja.yml (English) 2018-08-15 04:21:29 +09:00
syuilo 12cbd8ef5b New translations ja.yml (English) 2018-08-15 04:11:22 +09:00
syuilo cfc4385d1f New translations ja.yml (English) 2018-08-15 04:00:59 +09:00
syuilo 880ef024d0 New translations ja.yml (English) 2018-08-15 03:51:09 +09:00
syuilo c6c6edbc0a New translations ja.yml (English) 2018-08-15 03:41:21 +09:00
syuilo 83ba951bf9 5.24.1 2018-08-15 02:38:47 +09:00
syuilo 9c22b1a68a Fix 2018-08-15 02:35:25 +09:00
syuilo cd6a1d3446 5.24.0 2018-08-15 02:14:57 +09:00
syuilo 4a9fc0c8ed Better query 2018-08-15 02:08:18 +09:00
syuilo fada899b30 Merge pull request #2210 from mei23/mei-0814-ap3
ActivityPub Followers/Following/Outbox の実装
2018-08-15 02:04:08 +09:00
syuilo 22b099fa8a Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-15 02:01:52 +09:00
syuilo e0bc0d2830 Add new kao 2018-08-15 02:01:49 +09:00
greenkeeper[bot] 90768d30aa fix(package): update seedrandom to version 2.4.4 2018-08-15 01:53:48 +09:00
greenkeeper[bot] 177c549493 fix(package): update url-loader to version 1.1.0 2018-08-15 01:53:40 +09:00
syuilo 5e1ee68189 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-15 01:51:46 +09:00
syuilo 175f6303bc Update theme color 2018-08-15 01:51:43 +09:00
syuilo cf9f2a5562 Merge pull request #2211 from skid9000/patch-2
Update doc for twitter integration.
2018-08-14 22:55:02 +09:00
Skid f04526baca Update example.yml 2018-08-14 15:45:36 +02:00
Skid f085ecedb3 Update setup.en.md 2018-08-14 15:41:55 +02:00
mei23 0986301788 Implement ActivityPub Followers/Following/Outbox 2018-08-14 20:13:32 +09:00
greenkeeper[bot] fe418d8d9a fix(package): update @types/ws to version 6.0.0 2018-08-14 17:07:35 +09:00
greenkeeper[bot] 6009be34dc fix(package): update @types/node to version 10.7.0 2018-08-14 17:07:23 +09:00
greenkeeper[bot] 01a0a54a2c fix(package): update @types/mongodb to version 3.1.4 2018-08-14 17:07:15 +09:00
greenkeeper[bot] cfcaf77e21 fix(package): update mongodb to version 3.1.3 2018-08-14 17:07:07 +09:00
greenkeeper[bot] 3b37bdc0b9 fix(package): update vue-style-loader to version 4.1.2 2018-08-14 17:06:54 +09:00
syuilo ec07112f94 Fix bug 2018-08-14 16:53:57 +09:00
syuilo 464faf2673 Merge pull request #2200 from syuilo/use-deque
Use deque instead of linked list
2018-08-14 08:22:23 +09:00
Aya Morisawa bde20a1a65 Use deque instead of linked list 2018-08-14 08:21:25 +09:00
syuilo 86c7276da9 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-14 08:16:24 +09:00
syuilo fec988bb79 Provide isFirstNote flag 2018-08-14 08:16:21 +09:00
syuilo 0702d0974b Merge pull request #2199 from syuilo/patch-2176
Resolve #2176
2018-08-14 07:51:45 +09:00
Aya Morisawa f443d36dbb Resolve #2176 2018-08-14 07:49:59 +09:00
syuilo 5477f0a865 New translations ja.yml (English) 2018-08-14 05:42:07 +09:00
Aya Morisawa dc02168f33 Merge #2182 2018-08-14 05:25:02 +09:00
Aya Morisawa cc5c32b4d2 Clean up 2018-08-14 05:24:51 +09:00
syuilo a35680a838 New translations ja.yml (Catalan) 2018-08-14 04:52:05 +09:00
syuilo 770cba73a6 New translations ja.yml (Portuguese) 2018-08-14 04:52:03 +09:00
syuilo 401fc758fd New translations ja.yml (Korean) 2018-08-14 04:52:01 +09:00
syuilo e8503e6351 New translations ja.yml (Polish) 2018-08-14 04:51:58 +09:00
syuilo de23753409 New translations ja.yml (Chinese Simplified) 2018-08-14 04:51:56 +09:00
syuilo 4857d86cdd New translations ja.yml (Italian) 2018-08-14 04:51:54 +09:00
syuilo dc4a072678 New translations ja.yml (Russian) 2018-08-14 04:51:52 +09:00
syuilo c6ee5ccd88 New translations ja.yml (English) 2018-08-14 04:51:50 +09:00
syuilo 68b630cb37 New translations ja.yml (Spanish) 2018-08-14 04:51:48 +09:00
syuilo 94c0238d3a New translations ja.yml (German) 2018-08-14 04:51:45 +09:00
syuilo dbea387433 New translations ja.yml (French) 2018-08-14 04:51:43 +09:00
syuilo d35f62d0e4 Merge pull request #2195 from syuilo/instance-management-system
管理画面
2018-08-14 04:42:16 +09:00
Aya Morisawa 09b8e81a77 wip 2018-08-14 04:39:37 +09:00
Aya Morisawa 3b38979a34 wip 2018-08-14 04:30:42 +09:00
greenkeeper[bot] 0fd8c86c24 fix(package): update mongodb to version 3.1.2 2018-08-14 03:38:01 +09:00
Acid Chicken (硫酸鶏) 38b75ad977 Update avatar.vue 2018-08-14 02:10:06 +09:00
Aya Morisawa ba08d1aa53 wip 2018-08-14 01:48:11 +09:00
Aya Morisawa fb1e2efbdd wip 2018-08-14 01:37:23 +09:00
Aya Morisawa 92e5cff285 wip 2018-08-14 01:35:36 +09:00
Aya Morisawa ba9340a26b wip 2018-08-14 01:24:46 +09:00
Aya Morisawa 00119328f2 wip 2018-08-14 01:19:05 +09:00
syuilo 9021bb5694 wip 2018-08-14 01:05:58 +09:00
Acid Chicken (硫酸鶏) f15878cc6f Update avatar.vue
refs: https://github.com/syuilo/misskey/pull/2182#discussion_r209609541
2018-08-13 22:49:32 +09:00
Acid Chicken (硫酸鶏) 4edd9efc0b Update avatar.vue
refs: https://github.com/syuilo/misskey/pull/2182#discussion_r209464350
2018-08-13 03:47:56 +09:00
Acid Chicken (硫酸鶏) 2913c7ccfb Update avatar.vue 2018-08-13 03:42:12 +09:00
Acid Chicken (硫酸鶏) e1f460f90f Update user.header.vue 2018-08-13 03:40:50 +09:00
Acid Chicken (硫酸鶏) 70f927ea43 Update avatar.vue 2018-08-13 03:39:32 +09:00
Acid Chicken (硫酸鶏) 80d343bb0b Update avatar.vue 2018-08-13 03:36:42 +09:00
60 changed files with 1255 additions and 251 deletions
+6 -2
View File
@@ -50,8 +50,11 @@ remoteDriveCapacityMb: 8
# If enabled:
# Server will not cache remote files (Using direct link instead).
# You can save your storage.
# Users cannot see remote images when they turn off "Show media from a remote server" setting.
preventCache: false
#
# NOTE:
# * Users cannot see remote images when they turn off "Show media from a remote server" setting.
# * Since thumbnails are not provided, traffic increases.
preventCacheRemoteFiles: false
drive:
storage: 'db'
@@ -123,6 +126,7 @@ drive:
# google_maps_api_key: example-google-maps-api-key
# Twitter integration
# You need to set the oauth callback url as : https://<your-misskey-instance>/api/tw/cb
# twitter:
# consumer_key: example-twitter-consumer-key
# consumer_secret: example-twitter-consumer-secret-key
-1
View File
@@ -22,7 +22,6 @@ addons:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
- graphicsmagick
cache:
directories:
+9
View File
@@ -5,6 +5,15 @@ ChangeLog
This document describes breaking changes only.
6.0.0
-----
### Migration
オブジェクトストレージを使用している場合、設定ファイルの`drive.config.secure``drive.config.useSSL`にリネームしてください。
If you use object storage, please rename `drive.config.secure` to `drive.config.useSSL` in config.
5.0.0
-----
+28 -3
View File
@@ -43,9 +43,34 @@ If you want to...
:heart: Backers & Sponsors
----------------------------------------------------------------
| <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D"> |
|:-:|:-:|:-:|:-:|
| [Gargron](https://www.patreon.com/mastodon) | [39ff](https://www.patreon.com/user/creators?u=12378075) | [dansup](https://www.patreon.com/dansup) | [Takashi Shibuya](https://www.patreon.com/user/creators?u=12531784) |
<table>
<tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12959468/c249e15aebec4424b5c0f427173671b6/1?token-time=2145916800&token-hash=lubpCEdxAkxPlpR2O6bvZ7BIh8Q4nGf-U_mE1qpjVAQ%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D"></td>
<td><img src="https://c8.patreon.com/2/100/12718187"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D"></td>
</tr>
<tr>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/user/creators?u=12378075">39ff</a></td>
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/user/creators?u=12531784">Takashi Shibuya</a></td>
<td><a href="https://www.patreon.com/fujishan">fujishan</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
<td><a href="https://www.patreon.com/user?u=12731202">negao</a></td>
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
</tr>
</table>
:four_leaf_clover: Copyright
----------------------------------------------------------------
+7
View File
@@ -62,6 +62,13 @@ npm install web-push -g
web-push generate-vapid-keys
```
*(optional)* Create a twitter application
----------------------------------------------------------------
If you want to enable the twitter integration, you need to create a twitter app at [https://developer.twitter.com/en/apply/user](https://developer.twitter.com/en/apply/user).
In the app you need to set the oauth callback url as : https://misskey-instance/api/tw/cb
*5.* Make configuration file
----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
+97 -78
View File
@@ -8,8 +8,8 @@ common:
about: "Thank you for finding Misskey. Misskey is a <b>decentralized microblogging platform</b> born on Earth. Since it exists within the Fediverse (a universe where various social media platforms are organized), it is mutually linked with other social media platforms. Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?"
adblock:
detected: "Please disable ad blocker."
warning: "<strong>Misskey is not running ads</strong>, but some features may be unavailable or malfunctioning if ad blocking features are enabled."
application-authorization: "Application authorizations."
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."
got-it: "Got it!"
@@ -17,8 +17,8 @@ common:
title: "Customization tips"
paragraph1: "Home customization allows you to add/delete, drag and drop and rearrange widgets."
paragraph2: "You can change the display by <strong>right clicking</strong> on some widgets."
paragraph3: "To delete a widget, <strong>drag and drop the widget onto the area labeled \"Trash\"</strong> in the header."
paragraph4: "To finish the customization, click \"Finish\" in the upper right."
paragraph3: "To delete a widget, drag and drop the widget onto <strong>the area labeled \"Trash\"</strong> in the header."
paragraph4: "To finish the customization, click \"Finish\" on the upper right."
gotit: "Got it!"
notification:
file-uploaded: "File uploaded!"
@@ -84,7 +84,7 @@ common:
my-token-regenerated: "Your token has been regenerated, so you will be signed out."
i-like-sushi: "I prefer sushi rather than pudding"
show-reversi-board-labels: "Show row and column labels in Reversi"
verified-user: "Verified user"
verified-user: "Authorized User"
disable-animated-mfm: "Disable animated texts in a post"
reversi:
drawn: "Draw"
@@ -151,7 +151,7 @@ auth/views/form.vue:
notification-read: "Read your notifications."
notification-write: "Manage your notifications."
cancel: "Cancel"
accept: "Grant access."
accept: "Allow access."
auth/views/index.vue:
loading: "Loading"
denied: "Application authorization denied."
@@ -179,13 +179,13 @@ common/views/components/games/reversi/reversi.index.vue:
rule: "How to play"
rule-desc: "Reversi is a strategy board game for two players, played on an 8×8 uncheckered board. There are sixty-four identical game pieces called disks (often spelled \"discs\"), which are light on one side and dark on the other. Players take turns placing disks on the board with their assigned color facing up. During a play, any disks of the opponent's color that are in a straight line and bounded by the disk just placed and another disk of the current player's color are turned over to the current player's color. The object of the game is to have the majority of disks turned to display your color when the last playable empty square is filled."
mode-invite: "Invite"
mode-invite-desc: "Invite to the game a user."
invitations: "You received invitation!"
my-games: "My games"
mode-invite-desc: "Game with a specified user."
invitations: "Youve got an invitation!"
my-games: "My game"
all-games: "All games"
enter-username: "Enter username"
enter-username: "Enter a username"
game-state:
ended: "Ended"
ended: "Finished"
playing: "In Progress"
common/views/components/games/reversi/reversi.room.vue:
settings-of-the-game: "Game settings"
@@ -194,14 +194,14 @@ common/views/components/games/reversi/reversi.room.vue:
black-or-white: "Black/White"
black-is: "Black is {}"
rules: "Rules"
is-llotheo: "The lesser one wins"
is-llotheo: "The lesser side wins"
looped-map: "Looped map"
can-put-everywhere: "Can put everywhere"
settings-of-the-bot: "Bot settings"
this-game-is-started-soon: "The game will begin soon"
waiting-for-other: "Waiting for the other party's preparation"
waiting-for-me: "Waiting for the your preparation"
waiting-for-both: "Waiting for yours"
this-game-is-started-soon: "The game will begin in seconds"
waiting-for-other: "Waiting for the opponent"
waiting-for-me: "Waiting for you"
waiting-for-both: "Prepareing"
cancel: "Cancel"
ready: "Ready"
cancel-ready: "Cancel \"Ready\""
@@ -239,13 +239,13 @@ common/views/components/messaging-room.vue:
no-history: "There is no further history"
resize-form: "Drag to resize"
new-message: "New message"
only-one-file-attached: "Only one file can be attached to a message."
only-one-file-attached: "Only ONE file can be attached to a message."
common/views/components/messaging-room.form.vue:
input-message-here: "Enter message here"
send: "Send"
attach-from-local: "Attach files from your device"
attach-from-drive: "Attach files from your Drive"
only-one-file-attached: "Only one file can be attached to a message."
only-one-file-attached: "Only one file can be attached to the message."
common/views/components/messaging-room.message.vue:
is-read: "Read"
deleted: "This message has been deleted"
@@ -260,9 +260,9 @@ common/views/components/nav.vue:
feedback: "Feedback"
common/views/components/note-menu.vue:
favorite: "Favorite this note"
pin: "Pin to your profile page"
pin: "Pin to your profile"
delete: "Delete"
delete-confirm: "Are you sure you want to delete this post?"
delete-confirm: "Delete this post?"
remote: "Show original note"
common/views/components/poll.vue:
vote-to: "Vote for '{}'"
@@ -272,9 +272,9 @@ common/views/components/poll.vue:
show-result: "Show results"
voted: "Voted"
common/views/components/poll-editor.vue:
no-only-one-choice: "At least two choices are required for this survey."
no-only-one-choice: "At least two choices are required"
choice-n: "Choice {}"
remove: "Delete this choice"
remove: "Delete the choice"
add: "+ Add a choice"
destroy: "Discard the poll"
common/views/components/reaction-picker.vue:
@@ -289,15 +289,15 @@ common/views/components/signin.vue:
signin-with-twitter: "Log in with Twitter"
common/views/components/signup.vue:
username: "Username"
checking: "Checking..."
checking: "Confirming..."
available: "Available"
unavailable: "Unavailable"
error: "Network error"
invalid-format: "Only use letters, numbers and -."
too-short: "Please enter at least 1 character!"
too-long: "Please enter up to 20 characters."
invalid-format: "letters, numbers and _ are acceptable."
too-short: "Should not be blank!"
too-long: "Enter within 20 characters."
password: "Password"
password-placeholder: "We recommend more than 8 characters."
password-placeholder: "More than 8 characters are recommended."
weak-password: "Weak password"
normal-password: "Fair password"
strong-password: "Strong password"
@@ -327,9 +327,9 @@ common/views/components/uploader.vue:
common/views/components/visibility-chooser.vue:
public: "Public"
home: "Home"
home-desc: "Post to the home timeline only"
home-desc: "Post to Home only"
followers: "Followers"
followers-desc: "Post to followers only"
followers-desc: "Post to Followers only"
specified: "Direct"
specified-desc: "Post to specified users only"
private: "Private"
@@ -340,8 +340,8 @@ common/views/widgets/broadcast.vue:
next: "Next"
common/views/widgets/calendar.vue:
year: "Year {}"
month: "Month {}"
day: "Day {}"
month: "{},"
day: "{}"
today: "Today: "
this-month: "This month: "
this-year: "This year: "
@@ -400,13 +400,13 @@ desktop:
banner-crop-title: "Crop the part that appears as a banner"
banner: "Banner"
uploading-banner: "Uploading a new banner"
banner-updated: "Updated the banner"
banner-updated: "Successfully updated the banner"
choose-banner: "Choose the banner"
avatar-crop-title: "Crop the part that appears as an avatar"
avatar: "Avatar"
uploading-avatar: "Uploading a new avatar"
avatar-updated: "Updated the avatar"
choose-avatar: "Choose an avatar image"
avatar-updated: "Successfully updated the avatar"
choose-avatar: "Select an image for the avatar"
desktop/views/components/activity.chart.vue:
total: "Black ... Total"
notes: "Blue ... Notes"
@@ -454,7 +454,7 @@ desktop/views/components/drive.file.vue:
rename-file: "Rename file"
input-new-file-name: "Enter new name"
copied: "Copied"
copied-url-to-clipboard: "Copied URL to clipboard"
copied-url-to-clipboard: "URL has been copied to clipboard"
desktop/views/components/drive.folder.vue:
unable-to-process: "The operation could not be completed."
circular-reference-detected: "The destination folder is a subfolder of the folder you wish to move."
@@ -488,7 +488,7 @@ desktop/views/components/drive.vue:
upload: "Upload a file"
url-upload: "Upload from a URL"
desktop/views/components/media-image.vue:
sensitive: "The content is NSFW"
sensitive: "NSFW"
click-to-show: "Click to show"
desktop/views/components/media-video.vue:
sensitive: "The content is NSFW"
@@ -527,8 +527,8 @@ desktop/views/components/messaging-window.vue:
title: "Messaging"
desktop/views/components/note-detail.vue:
more: "Load more conversations"
private: "This post is private"
deleted: "This post has been removed"
private: "Post is private"
deleted: "Post has been removed"
reposted-by: "Reposted by {}"
location: "Location"
renote: "Repost"
@@ -539,8 +539,8 @@ desktop/views/components/notes.note.vue:
renote: "Repost"
add-reaction: "Add a reaction"
detail: "Show details"
private: "This post is private"
deleted: "The post has been deleted"
private: "Post is private"
deleted: "Post has been deleted"
hide: "Hide"
see-more: "See more"
desktop/views/components/notes.vue:
@@ -554,15 +554,15 @@ desktop/views/components/post-form.vue:
add-visible-user: "+Add a user"
attach-location-information: "Attach location information"
hide-contents: "Hide contents"
reply-placeholder: "Reply to this note..."
quote-placeholder: "Quote this note..."
reply-placeholder: "Reply to this Post..."
quote-placeholder: "Quote this Post..."
submit: "Post"
reply: "Reply"
renote: "Repost"
posted: "Posted!"
replied: "Replied!"
reposted: "Reposted!"
note-failed: "Failed to post the note"
note-failed: "Failed to post"
reply-failed: "Failed to reply"
renote-failed: "Failed to repost"
posting: "Posting"
@@ -575,12 +575,12 @@ desktop/views/components/post-form.vue:
recent-tags: "Recent"
click-to-tagging: "Click to tagging"
visibility: "Visibility"
geolocation-alert: "Your device does not support geolocalization."
geolocation-alert: "Your device can not measure location infomation"
error: "Error"
enter-username: "Please enter a username..."
annotations: "内容への注釈 (オプション)"
annotations: "Annotations for the post (optional)"
desktop/views/components/post-form-window.vue:
note: "New note"
note: "New Post"
reply: "Reply"
attaches: "{} media attached"
uploading-media: "Uploading {} media"
@@ -592,9 +592,9 @@ desktop/views/components/renote-form.vue:
renote: "Repost"
reposting: "Reposting..."
success: "Reposted!"
failure: "Repost failed"
failure: "Failed to Repost"
desktop/views/components/renote-form-window.vue:
title: "Are you sure you want to repost this?"
title: "Do you want to Repost it?"
desktop/views/components/settings-window.vue:
settings: "Settings"
desktop/views/components/settings.vue:
@@ -689,11 +689,11 @@ desktop/views/components/settings.2fa.vue:
submit: "Submit"
success: "Settings saved!"
failed: "Failed to setup. Please ensure that the token is correct."
info: "From now on, enter the token that is displayed on your device in addition to your password when signing-in to Misskey."
info: "From the next time you sign in to Misskey, the token displayed on your device will be necessary too, as well as the password."
desktop/views/components/settings.api.vue:
intro: "To access the API, set this token as the key 'i' of request parameters."
caution: "Please do not show this token to third parties (do not enter it somewhere else other than here) otherwise your account could get compromised."
regeneration-of-token: "In the unlikely event that this token leaks out, you can regenerate it."
caution: "Do not enter this token to any apps nor tell this token to others otherwise your account may get compromised."
regeneration-of-token: "In case this token (may) leaks out, you want to regenerate it so that youll be safe."
regenerate-token: "Regenerate the token"
token: "Token:"
enter-password: "Please enter the password"
@@ -713,18 +713,18 @@ desktop/views/components/settings.password.vue:
changed: "Password updated"
desktop/views/components/settings.profile.vue:
avatar: "Avatar"
choice-avatar: "Choose an image"
choice-avatar: "Select an image"
name: "Name"
location: "Location"
description: "Description"
birthday: "Birthday"
save: "Update profile"
locked-account: "Protect your account"
is-locked: "Make a note private"
is-locked: "Make your posts private"
other: "Other"
is-bot: "This account is a Bot"
is-cat: "This account is a Cat"
profile-updated: "Profile updated"
profile-updated: "Profile has successfully updated"
desktop/views/components/sub-note-content.vue:
private: "This post is private"
deleted: "This post has been deleted"
@@ -740,7 +740,7 @@ desktop/views/components/timeline.vue:
list: "Lists"
desktop/views/components/ui.header.vue:
welcome-back: "Welcome back,"
adjective: "Ms."
adjective: "Sir "
desktop/views/components/ui.header.account.vue:
profile: "Your profile"
drive: "Media storage"
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "Pop-out"
close: "Close"
desktop/views/pages/admin/admin.vue:
dashboard: "Dashboard"
drive: "Drive"
users: "Users"
update: "Updates"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "Dashboard"
all-users: "All Users"
original-users: "Users on this instance"
all-notes: "All Posts"
original-notes: "Posts on this instance"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "Suspend a user"
suspend: "Suspend"
suspended: "Successfully suspended."
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "Unsuspend users"
unsuspend: "Unsuspend"
unsuspended: "The user has successfully unsuspended."
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "Only media posts"
is-media-view: "Media view"
@@ -797,7 +816,7 @@ desktop/views/pages/welcome.vue:
gotit: "Got it!"
signin: "Log In"
signup: "Sign up"
signin-button: "Log in"
signin-button: "Logging in..."
signup-button: "Sign up"
timeline: "Timeline"
powered-by-misskey: "Powered by <b>Misskey</b>."
@@ -808,8 +827,8 @@ desktop/views/pages/favorites.vue:
desktop/views/pages/home-customize.vue:
title: "Customize home layout"
desktop/views/pages/note.vue:
prev: "Previous note"
next: "Next note"
prev: "Previous post"
next: "Next post"
desktop/views/pages/selectdrive.vue:
title: "Choose file(s)"
ok: "OK"
@@ -821,22 +840,22 @@ desktop/views/pages/search.vue:
desktop/views/pages/share.vue:
share-with: "Share with {}."
desktop/views/pages/tag.vue:
no-posts-found: "No posts \"{}\" found."
no-posts-found: "No posts contains \"{}\" found."
desktop/views/pages/user-list.users.vue:
users: "User"
add-user: "Add a user"
username: "Username"
desktop/views/pages/user/user.followers-you-know.vue:
title: "Followers you know"
title: "Followers you may know"
loading: "Loading"
no-users: "No followers you know"
desktop/views/pages/user/user.friends.vue:
title: "Frequent mentions"
loading: "Loading"
no-users: "No users"
no-users: "No frequent mentions"
desktop/views/pages/user/user.vue:
is-suspended: "This account has been suspended."
is-remote: "This user is a remote user, so the information about them that you see here is not complete. "
is-remote: "The user is a remote user. Information about them that you see here may not complete."
view-remote: "See their complete profile"
desktop/views/pages/user/user.home.vue:
last-used-at: "Last active:"
@@ -853,7 +872,7 @@ desktop/views/pages/user/user.profile.vue:
muted: "Muting"
unmute: "Unmute"
push-to-a-list: "Add to list"
list-pushed: "You added {user} to {list}."
list-pushed: "Successfully added {user} to {list}."
desktop/views/pages/user/user.header.vue:
posts: "Notes"
following: "Following"
@@ -861,18 +880,18 @@ desktop/views/pages/user/user.header.vue:
is-bot: "This account is a Bot"
desktop/views/pages/user/user.timeline.vue:
default: "Posts"
with-replies: "Notes and replies"
with-replies: "Posts and replies"
with-media: "Media"
empty: "This user doesn't seem to have posted anything yet."
desktop/views/widgets/messaging.vue:
title: "Messaging"
title: "Message"
desktop/views/widgets/notifications.vue:
title: "Notifications"
settings: "Settings"
desktop/views/widgets/polls.vue:
title: "Polls"
refresh: "refresh"
nothing: "Nothing"
nothing: "No polls found!"
desktop/views/widgets/post-form.vue:
title: "Post"
note: "Post"
@@ -882,11 +901,11 @@ desktop/views/widgets/profile.vue:
desktop/views/widgets/trends.vue:
title: "Trend"
refresh: "refresh"
nothing: "Nothing"
nothing: "No trends found!"
desktop/views/widgets/users.vue:
title: "Recommended users"
refresh: "refresh"
no-one: "No one"
no-one: "Anyone!"
mobile/views/components/drive.vue:
drive: "Media storage"
used: "used"
@@ -916,7 +935,7 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)"
exif: "EXIF"
mobile/views/components/media-image.vue:
sensitive: "The content is NSFW"
sensitive: "NSFW"
click-to-show: "Click to show"
mobile/views/components/media-video.vue:
sensitive: "The content is NSFW"
@@ -927,7 +946,7 @@ mobile/views/components/follow-button.vue:
request-pending: "Pending follow request"
follow-request: "Follow request"
mobile/views/components/friends-maker.vue:
title: "Let's follow users"
title: "Let's follow them"
empty: "Featured user was not found."
fetching: "Loading"
refresh: "See more"
@@ -958,7 +977,7 @@ mobile/views/components/notes.vue:
failed: "Failed to load"
retry: "Retry"
mobile/views/components/notifications.vue:
more: "More"
more: "See more"
empty: "No notifications"
mobile/views/components/post-form.vue:
add-visible-user: "Add a user"
@@ -967,7 +986,7 @@ mobile/views/components/post-form.vue:
renote: "Renote"
quote-placeholder: "Quote this post... (optional)"
reply-placeholder: "Reply to this note..."
cw-placeholder: "Comments about content (optional)"
cw-placeholder: "Comments for the post (optional)"
location-alert: "Your device does not provide location services"
error: "Error"
username-prompt: "Enter user name"
@@ -981,7 +1000,7 @@ mobile/views/components/timeline.vue:
load-more: "More"
mobile/views/components/ui.header.vue:
welcome-back: "Welcome back, "
adjective: "Ms."
adjective: "Sir"
mobile/views/components/ui.nav.vue:
timeline: "Timeline"
notifications: "Notifications"
@@ -998,7 +1017,7 @@ mobile/views/components/ui.nav.vue:
about: "About Misskey"
mobile/views/components/user-timeline.vue:
no-notes: "It seems this user hasn't posted anything yet."
no-notes-with-media: "There are no notes with media attachments"
no-notes-with-media: "There are no posts attaching media"
load-more: "More"
mobile/views/components/users-list.vue:
all: "All"
@@ -1013,7 +1032,7 @@ mobile/views/pages/drive.vue:
drive: "Drive"
more: "Load more"
mobile/views/pages/signup.vue:
lets-start: "Let's start! 📦"
lets-start: "Your account is now ready! 📦"
mobile/views/pages/followers.vue:
followers-of: "Followers of {}"
mobile/views/pages/following.vue:
@@ -1082,7 +1101,7 @@ mobile/views/pages/settings.vue:
specify-language: "Select your language"
design: "Design and display"
dark-mode: "Dark Mode"
i-am-under-limited-internet: "I'm under limited internet"
i-am-under-limited-internet: "I'm in limited bandwidth"
circle-icons: "Use circle icons"
timeline: "Timeline"
show-reply-target: "Show reply target"
@@ -1094,7 +1113,7 @@ mobile/views/pages/settings.vue:
behavior: "Behavior"
fetch-on-scroll: "Endless loading on scroll"
disable-via-mobile: "Don't mark the post as 'from mobile'"
load-raw-images: "Show attached images in high-quality"
load-raw-images: "Show attached images in original quality"
load-remote-media: "Show media from a remote server"
twitter: "Twitter integration"
twitter-connect: "Connect to your Twitter account"
@@ -1115,7 +1134,7 @@ mobile/views/pages/user.vue:
follows-you: "Follows you"
following: "Following"
followers: "Followers"
notes: "Notes"
notes: "Posts"
overview: "Overview"
timeline: "Timeline"
media: "Media"
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "Fermer"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "Les publications médias uniquement"
is-media-view: "Vue média"
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
+23
View File
@@ -897,6 +897,29 @@ desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "Pop-out"
close: "Zamknij"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "Tylko wpisy z zawartością multimedialną"
is-media-view: "Widok multimediów"
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
+19
View File
@@ -784,6 +784,25 @@ desktop/views/components/users-list-item.vue:
desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
+13 -11
View File
@@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "5.23.2",
"clientVersion": "1.0.8235",
"version": "6.0.1",
"clientVersion": "1.0.8367",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@@ -31,6 +31,7 @@
"@types/dateformat": "1.0.1",
"@types/debug": "0.0.30",
"@types/deep-equal": "1.0.1",
"@types/double-ended-queue": "2.1.0",
"@types/elasticsearch": "5.0.25",
"@types/file-type": "5.2.1",
"@types/gulp": "3.8.36",
@@ -57,9 +58,9 @@
"@types/minio": "6.0.2",
"@types/mkdirp": "0.5.2",
"@types/mocha": "5.2.3",
"@types/mongodb": "3.1.3",
"@types/mongodb": "3.1.4",
"@types/ms": "0.7.30",
"@types/node": "10.5.8",
"@types/node": "10.7.1",
"@types/portscanner": "2.1.0",
"@types/pug": "2.0.4",
"@types/qrcode": "1.2.0",
@@ -76,10 +77,10 @@
"@types/systeminformation": "3.23.0",
"@types/tmp": "0.0.33",
"@types/uuid": "3.4.3",
"@types/webpack": "4.4.9",
"@types/webpack": "4.4.10",
"@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.39",
"@types/ws": "5.1.2",
"@types/ws": "6.0.0",
"animejs": "2.2.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
@@ -97,6 +98,7 @@
"deepcopy": "0.6.3",
"diskusage": "0.2.4",
"dompurify": "1.0.5",
"double-ended-queue": "2.1.0-0",
"elasticsearch": "15.1.1",
"element-ui": "2.4.6",
"emojilib": "2.3.0",
@@ -147,7 +149,7 @@
"loader-utils": "1.1.0",
"lodash.assign": "4.2.0",
"mecab-async": "0.1.2",
"minio": "6.0.0",
"minio": "7.0.0",
"mkdirp": "0.5.1",
"mocha": "5.2.0",
"moji": "0.5.1",
@@ -178,7 +180,7 @@
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass-loader": "7.1.0",
"seedrandom": "2.4.3",
"seedrandom": "2.4.4",
"sharp": "0.20.5",
"showdown": "1.8.6",
"showdown-highlightjs-extension": "0.1.2",
@@ -188,7 +190,7 @@
"style-loader": "0.22.1",
"stylus": "0.54.5",
"stylus-loader": "3.0.2",
"summaly": "2.1.2",
"summaly": "2.1.3",
"systeminformation": "3.42.9",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
@@ -199,7 +201,7 @@
"typescript": "2.9.2",
"typescript-eslint-parser": "18.0.0",
"uglify-es": "3.3.9",
"url-loader": "1.0.1",
"url-loader": "1.1.0",
"uuid": "3.3.2",
"v-animate-css": "0.0.2",
"vue": "2.5.17",
@@ -208,7 +210,7 @@
"vue-json-tree-view": "2.1.4",
"vue-loader": "15.3.0",
"vue-router": "3.0.1",
"vue-style-loader": "4.1.1",
"vue-style-loader": "4.1.2",
"vue-template-compiler": "2.5.17",
"vuedraggable": "2.16.0",
"vuex": "3.0.1",
+7 -3
View File
@@ -1,5 +1,9 @@
export default () => [
const kaos = [
'(=^・・^=)',
'v(\'ω\')v',
'🐡( \'-\' 🐡 )フグパンチ!!!!'
][Math.floor(Math.random() * 3)];
'🐡( \'-\' 🐡 )フグパンチ!!!!',
'🖕(´・_・`)🖕',
'(。><。)'
];
export default () => kaos[Math.floor(Math.random() * kaos.length)];
@@ -1,8 +1,16 @@
<template>
<span class="mk-avatar" :title="user | acct" :style="style" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick"></span>
<span class="mk-avatar" :title="user | acct" :style="style" v-else-if="disableLink && disablePreview" @click="onClick"></span>
<router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id"></router-link>
<router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-else-if="!disableLink && disablePreview"></router-link>
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
<span class="inner" :style="style"></span>
</span>
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
<span class="inner" :style="style"></span>
</span>
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
<span class="inner" :style="style"></span>
</router-link>
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
<span class="inner" :style="style"></span>
</router-link>
</template>
<script lang="ts">
@@ -30,14 +38,17 @@ export default Vue.extend({
lightmode(): boolean {
return this.$store.state.device.lightmode;
},
cat(): boolean {
return this.user.isCat && this.$store.state.settings.circleIcons;
},
style(): any {
return {
backgroundColor: this.lightmode
? `rgb(${ this.user.avatarColor.slice(0, 3).join(',') })`
? `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`
: this.user.avatarColor && this.user.avatarColor.length == 3
? `rgb(${ this.user.avatarColor.join(',') })`
? `rgb(${this.user.avatarColor.join(',')})`
: null,
backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl })`,
backgroundImage: this.lightmode ? null : `url(${this.user.avatarUrl})`,
borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
};
}
@@ -51,10 +62,47 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
.mk-avatar
root(isDark)
display inline-block
vertical-align bottom
background-size cover
background-position center center
transition border-radius 1s ease
&:not(.cat)
overflow hidden
border-radius 8px
&.cat::before,
&.cat::after
background #df548f
border solid 4px isDark ? #e0eefd : #202224
box-sizing border-box
content ''
display inline-block
height 50%
width 50%
&.cat::before
border-radius 0 75% 75%
transform rotate(37.5deg) skew(30deg)
&.cat::after
border-radius 75% 0 75% 75%
transform rotate(-37.5deg) skew(-30deg)
.inner
background-position center center
background-size cover
bottom 0
left 0
position absolute
right 0
top 0
transition border-radius 1s ease
z-index 1
.mk-avatar[data-darkmode]
root(true)
.mk-avatar:not([data-darkmode])
root(false)
</style>
@@ -2,7 +2,7 @@
<header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu">
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%">%fa:bookmark%</span>
<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%">%fa:star%</span>
<span class="is-admin" v-if="note.user.isAdmin">admin</span>
<span class="is-bot" v-if="note.user.isBot">bot</span>
<span class="is-cat" v-if="note.user.isCat">cat</span>
@@ -1,16 +1,16 @@
<template>
<div class="mk-reactions-viewer">
<template v-if="reactions">
<span v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
<span v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
<span v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
<span v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
<span v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
<span v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
<span v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
<span v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
<span v-if="reactions.rip"><mk-reaction-icon reaction="rip"/><span>{{ reactions.rip }}</span></span>
<span v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
<span :class="{notReacted}" @click="react('like')" v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
<span :class="{notReacted}" @click="react('love')" v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
<span :class="{notReacted}" @click="react('laugh')" v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
<span :class="{notReacted}" @click="react('hmm')" v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
<span :class="{notReacted}" @click="react('surprise')" v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
<span :class="{notReacted}" @click="react('congrats')" v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
<span :class="{notReacted}" @click="react('angry')" v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
<span :class="{notReacted}" @click="react('confused')" v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
<span :class="{notReacted}" @click="react('rip')" v-if="reactions.rip"><mk-reaction-icon reaction="rip"/><span>{{ reactions.rip }}</span></span>
<span :class="{notReacted}" @click="react('pudding')" v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
</template>
</div>
</template>
@@ -22,6 +22,17 @@ export default Vue.extend({
computed: {
reactions(): number {
return this.note.reactionCounts;
},
notReacted(): boolean {
return this.note.myReaction == null;
}
},
methods: {
react(reaction: string) {
(this as any).api('notes/reactions/create', {
noteId: this.note.id,
reaction: reaction
});
}
}
});
@@ -40,6 +51,9 @@ root(isDark)
> span
margin-right 8px
&.notReacted
cursor pointer
> .mk-reaction-icon
font-size 1.4em
@@ -129,6 +129,7 @@ export default Vue.extend({
'newretrowave.com',
'nhk.or.jp',
'nicovideo.jp',
'nico.ms',
'noisetrade.com',
'nood.tv',
'npr.org',
@@ -144,6 +145,7 @@ export default Vue.extend({
'songkick.com',
'soundcloud.com',
'spinninrecords.com',
'spotify.com',
'stitcher.com',
'stream.me',
'switchboard.live',
@@ -158,12 +160,12 @@ export default Vue.extend({
'web.tv',
'youtube.com',
'youtu.be'
].some(x => x == url.hostname || url.hostname.endsWith(`.${x}`))))
].some(x => x == url.hostname || url.hostname.endsWith(`.${x}`)))
this.player = info.player;
}
});
}
}
} // info.url
}) // json
}); // fetch
} // created
});
</script>
+2
View File
@@ -24,6 +24,7 @@ import updateBanner from './api/update-banner';
import MkIndex from './views/pages/index.vue';
import MkDeck from './views/pages/deck/deck.vue';
import MkAdmin from './views/pages/admin/admin.vue';
import MkUser from './views/pages/user/user.vue';
import MkFavorites from './views/pages/favorites.vue';
import MkSelectDrive from './views/pages/selectdrive.vue';
@@ -55,6 +56,7 @@ init(async (launch) => {
routes: [
{ path: '/', name: 'index', component: MkIndex },
{ path: '/deck', name: 'deck', component: MkDeck },
{ path: '/admin', name: 'admin', component: MkAdmin },
{ path: '/i/customize-home', component: MkHomeCustomize },
{ path: '/i/favorites', component: MkFavorites },
{ path: '/i/messaging/:user', component: MkMessagingRoom },
@@ -16,7 +16,7 @@
<p>%i18n:@banner%</p>
</div>
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
<img :src="file.url" alt="" @load="onThumbnailLoaded"/>
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded"/>
</div>
<p class="name">
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
@@ -37,7 +37,7 @@ export default Vue.extend({
style(): any {
return {
'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url})`
'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.thumbnailUrl})`
};
}
},
@@ -10,13 +10,13 @@
<div class="description">{{ u.description }}</div>
<div class="status">
<div>
<p>%i18n:@notes%</p><a>{{ u.notesCount }}</a>
<p>%i18n:@notes%</p><span>{{ u.notesCount }}</span>
</div>
<div>
<p>%i18n:@following%</p><a>{{ u.followingCount }}</a>
<p>%i18n:@following%</p><span>{{ u.followingCount }}</span>
</div>
<div>
<p>%i18n:@followers%</p><a>{{ u.followersCount }}</a>
<p>%i18n:@followers%</p><span>{{ u.followersCount }}</span>
</div>
</div>
<mk-follow-button v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u"/>
@@ -149,7 +149,7 @@ root(isDark)
font-size 0.7em
color #aaa
> a
> span
font-size 1em
color $theme-color
@@ -0,0 +1,37 @@
<template>
<div>
<h1>%i18n:@dashboard%</h1>
<div v-if="stats">
<p><b>%i18n:@all-users%</b>: <span>{{ stats.usersCount | number }}</span></p>
<p><b>%i18n:@original-users%</b>: <span>{{ stats.originalUsersCount | number }}</span></p>
<p><b>%i18n:@all-notes%</b>: <span>{{ stats.notesCount | number }}</span></p>
<p><b>%i18n:@original-notes%</b>: <span>{{ stats.originalNotesCount | number }}</span></p>
</div>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
data() {
return {
stats: null
};
},
created() {
(this as any).api('stats').then(stats => {
this.stats = stats;
});
}
});
</script>
<style lang="stylus" scoped>
h1
margin 0 0 1em 0
padding 0 0 8px 0
font-size 1em
color #555
border-bottom solid 1px #eee
</style>
@@ -0,0 +1,51 @@
<template>
<div>
<header>%i18n:@suspend-user%</header>
<input v-model="username" type="text" class="ui"/>
<button class="ui" @click="suspendUser" :disabled="suspending">%i18n:@suspend%</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import parseAcct from "../../../../../../misc/acct/parse";
export default Vue.extend({
data() {
return {
username: null,
suspending: false
};
},
methods: {
async suspendUser() {
this.suspending = true;
const user = await (this as any).os.api(
"users/show",
parseAcct(this.username)
);
await (this as any).os.api("admin/suspend-user", {
userId: user.id
});
this.suspending = false;
(this as any).os.apis.dialog({ text: "%i18n:@suspended%" });
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
header
margin 10px 0
button
margin 16px 0
</style>
@@ -0,0 +1,51 @@
<template>
<div>
<header>%i18n:@unsuspend-user%</header>
<input v-model="username" type="text" class="ui"/>
<button class="ui" @click="unsuspendUser" :disabled="unsuspending">%i18n:@unsuspend%</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import parseAcct from "../../../../../../misc/acct/parse";
export default Vue.extend({
data() {
return {
username: null,
unsuspending: false
};
},
methods: {
async unsuspendUser() {
this.unsuspending = true;
const user = await (this as any).os.api(
"users/show",
parseAcct(this.username)
);
await (this as any).os.api("admin/unsuspend-user", {
userId: user.id
});
this.unsuspending = false;
(this as any).os.apis.dialog({ text: "%i18n:@unsuspended%" });
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
header
margin 10px 0
button
margin 16px 0
</style>
@@ -0,0 +1,102 @@
<template>
<div class="mk-admin">
<nav>
<ul>
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
</ul>
</nav>
<main>
<div v-if="page == 'dashboard'">
<x-dashboard/>
</div>
<div v-if="page == 'users'">
<x-suspend-user/>
<x-unsuspend-user/>
</div>
<div v-if="page == 'drive'"></div>
<div v-if="page == 'update'"></div>
</main>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import XDashboard from "./admin.dashboard.vue";
import XSuspendUser from "./admin.suspend-user.vue";
import XUnsuspendUser from "./admin.unsuspend-user.vue";
export default Vue.extend({
components: {
XDashboard,
XSuspendUser,
XUnsuspendUser
},
data() {
return {
page: 'dashboard'
};
},
methods: {
nav(page: string) {
this.page = page;
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
.mk-admin
display flex
height 100%
margin 32px
> nav
flex 0 0 250px
width 100%
height 100%
padding 16px 0 0 0
overflow auto
border-right solid 1px #ddd
> ul
list-style none
> li
display block
padding 10px 16px
margin 0
color #666
cursor pointer
user-select none
transition margin-left 0.2s ease
> [data-fa]
margin-right 4px
&:hover
color #555
&.active
margin-left 8px
color $theme-color !important
> main
width 100%
padding 16px 32px
header
margin 10px 0
button
margin 16px 0
position absolute
right 0
</style>
@@ -176,6 +176,10 @@ root(isDark)
height 120px
box-shadow 1px 1px 3px rgba(#000, 0.2)
> &.cat::before,
> &.cat::after
border-width 8px
> .body
padding 16px 16px 16px 154px
color isDark ? #c5ced6 : #555
@@ -43,7 +43,7 @@ export default Vue.extend({
thumbnail(): any {
return {
'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
'background-image': `url(${this.file.url})`
'background-image': `url(${this.file.thumbnailUrl})`
};
}
},
@@ -27,7 +27,7 @@ export default Vue.extend({
},
computed: {
style(): any {
let url = `url(${this.image.url})`;
let url = `url(${this.image.thumbnailUrl})`;
if (this.$store.state.device.loadRemoteMedia || this.$store.state.device.lightmode) {
url = null;
+1 -1
View File
@@ -1,5 +1,5 @@
{
"copyright": "Copyright (c) 2014-2018 syuilo",
"themeColor": "#f66e4f",
"themeColor": "#f6584f",
"themeColorForeground": "#fff"
}
+3 -2
View File
@@ -1,10 +1,11 @@
import * as childProcess from 'child_process';
import * as Deque from 'double-ended-queue';
import Xev from 'xev';
const ev = new Xev();
export default function() {
const log: any[] = [];
const log = new Deque<any>();
const p = childProcess.fork(__dirname + '/notes-stats-child.js');
@@ -15,7 +16,7 @@ export default function() {
});
ev.on('requestNotesStatsLog', id => {
ev.emit('notesStatsLog:' + id, log);
ev.emit('notesStatsLog:' + id, log.toArray());
});
process.on('exit', code => {
+3 -2
View File
@@ -1,6 +1,7 @@
import * as os from 'os';
import * as sysUtils from 'systeminformation';
import * as diskusage from 'diskusage';
import * as Deque from 'double-ended-queue';
import Xev from 'xev';
const osUtils = require('os-utils');
@@ -12,10 +13,10 @@ const interval = 1000;
* Report server stats regularly
*/
export default function() {
const log: any[] = [];
const log = new Deque<any>();
ev.on('requestServerStatsLog', id => {
ev.emit('serverStatsLog:' + id, log);
ev.emit('serverStatsLog:' + id, log.toArray());
});
async function tick() {
+5 -6
View File
@@ -48,7 +48,7 @@ main();
* Init process
*/
function main() {
process.title = `Misskey (${ cluster.isMaster ? 'master' : 'worker' })`;
process.title = `Misskey (${cluster.isMaster ? 'master' : 'worker'})`;
if (cluster.isMaster || program.disableClustering) {
masterMain();
@@ -154,11 +154,10 @@ async function init(): Promise<Config> {
function checkMongoDb(config: Config) {
const mongoDBLogger = new Logger('MongoDB');
mongoDBLogger.info(`Host: ${config.mongodb.host}`);
mongoDBLogger.info(`Port: ${config.mongodb.port}`);
mongoDBLogger.info(`DB: ${config.mongodb.db}`);
if (config.mongodb.user) mongoDBLogger.info(`User: ${config.mongodb.user}`);
if (config.mongodb.pass) mongoDBLogger.info(`Pass: ****`);
const u = config.mongodb.user ? encodeURIComponent(config.mongodb.user) : null;
const p = config.mongodb.pass ? encodeURIComponent(config.mongodb.pass) : null;
const uri = `mongodb://${u && p ? `${u}:****@` : ''}${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`;
mongoDBLogger.info(`Connecting to ${uri}`);
require('./db/mongodb');
mongoDBLogger.succ('Connectivity confirmed');
}
+1 -1
View File
@@ -15,6 +15,6 @@ export default function(text: string) {
return {
type: 'bold',
content: bold,
bold: bold.substr(2, bold.length - 4)
bold: match[1]
} as TextElementBold;
}
+2 -2
View File
@@ -18,7 +18,7 @@ export default function(text: string) {
return {
type: 'code',
content: code,
code: code.substr(3, code.length - 6).trim(),
html: genHtml(code.substr(3, code.length - 6).trim())
code: match[1],
html: genHtml(match[1].trim())
} as TextElementCode;
}
+2 -2
View File
@@ -9,12 +9,12 @@ export type TextElementEmoji = {
};
export default function(text: string) {
const match = text.match(/^:[a-zA-Z0-9+-_]+:/);
const match = text.match(/^:([a-zA-Z0-9+-_]+):/);
if (!match) return null;
const emoji = match[0];
return {
type: 'emoji',
content: emoji,
emoji: emoji.substr(1, emoji.length - 2)
emoji: match[1]
} as TextElementEmoji;
}
+3 -2
View File
@@ -14,11 +14,12 @@ export type TextElementInlineCode = {
export default function(text: string) {
const match = text.match(/^`(.+?)`/);
if (!match) return null;
if (match[1].includes('´')) return null;
const code = match[0];
return {
type: 'inline-code',
content: code,
code: code.substr(1, code.length - 2).trim(),
html: genHtml(code.substr(1, code.length - 2).trim())
code: match[1],
html: genHtml(match[1])
} as TextElementInlineCode;
}
+1 -1
View File
@@ -15,6 +15,6 @@ export default function(text: string) {
return {
type: 'quote',
content: quote,
quote: quote.substr(1, quote.length - 2).trim(),
quote: match[1].trim(),
} as TextElementQuote;
}
+1 -1
View File
@@ -9,7 +9,7 @@ export type TextElementSearch = {
};
export default function(text: string) {
const match = text.match(/^(.+?) (検索|Search)(\n|$)/i);
const match = text.match(/^(.+?)( | )(検索|\[検索\]|Search|\[Search\])(\n|$)/i);
if (!match) return null;
return {
type: 'search',
+2
View File
@@ -31,6 +31,7 @@ export type IMetadata = {
comment: string;
uri?: string;
url?: string;
thumbnailUrl?: string;
src?: string;
deletedAt?: Date;
withoutChunks?: boolean;
@@ -164,6 +165,7 @@ export const pack = (
_target = Object.assign(_target, _file.metadata);
_target.url = _file.metadata.url ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`;
_target.thumbnailUrl = _file.metadata.thumbnailUrl ? _file.metadata.thumbnailUrl : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}?thumbnail`;
_target.isRemote = _file.metadata.isRemote;
if (_target.properties == null) _target.properties = {};
@@ -0,0 +1,16 @@
import config from '../../../config';
import * as mongo from 'mongodb';
import User, { isLocalUser } from '../../../models/user';
/**
* Convert (local|remote)(Follower|Followee)ID to URL
* @param id Follower|Followee ID
*/
export default async function renderFollowUser(id: mongo.ObjectID): Promise<any> {
const user = await User.findOne({
_id: id
});
return isLocalUser(user) ? `${config.url}/users/${user._id}` : user.uri;
}
@@ -0,0 +1,23 @@
/**
* Render OrderedCollectionPage
* @param id URL of self
* @param totalItems Number of total items
* @param orderedItems Items
* @param partOf URL of base
* @param prev URL of prev page (optional)
* @param next URL of next page (optional)
*/
export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev: string, next: string) {
const page = {
id,
partOf,
type: 'OrderedCollectionPage',
totalItems,
orderedItems
} as any;
if (prev) page.prev = prev;
if (next) page.next = next;
return page;
}
@@ -1,6 +1,19 @@
export default (id: string, totalItems: any, orderedItems: any) => ({
id,
type: 'OrderedCollection',
totalItems,
orderedItems
});
/**
* Render OrderedCollection
* @param id URL of self
* @param totalItems Total number of items
* @param first URL of first page (optional)
* @param last URL of last page (optional)
*/
export default function(id: string, totalItems: any, first: string, last: string) {
const page: any = {
id,
type: 'OrderedCollection',
totalItems,
};
if (first) page.first = first;
if (last) page.last = last;
return page;
}
+7 -64
View File
@@ -10,8 +10,9 @@ import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
import renderNote from '../remote/activitypub/renderer/note';
import renderKey from '../remote/activitypub/renderer/key';
import renderPerson from '../remote/activitypub/renderer/person';
import renderOrderedCollection from '../remote/activitypub/renderer/ordered-collection';
import config from '../config';
import Outbox from './activitypub/outbox';
import Followers from './activitypub/followers';
import Following from './activitypub/following';
// Init router
const router = new Router();
@@ -64,72 +65,14 @@ router.get('/notes/:note', async (ctx, next) => {
ctx.body = pack(await renderNote(note));
});
// outbot
router.get('/users/:user/outbox', async ctx => {
const userId = new mongo.ObjectID(ctx.params.user);
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
const notes = await Note.find({ userId: user._id }, {
limit: 10,
sort: { _id: -1 }
});
const renderedNotes = await Promise.all(notes.map(note => renderNote(note)));
const rendered = renderOrderedCollection(`${config.url}/users/${userId}/inbox`, user.notesCount, renderedNotes);
ctx.body = pack(rendered);
});
// outbox
router.get('/users/:user/outbox', Outbox);
// followers
router.get('/users/:user/followers', async ctx => {
const userId = new mongo.ObjectID(ctx.params.user);
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
// TODO: Implement fetch and render
const rendered = renderOrderedCollection(`${config.url}/users/${userId}/followers`, 0, []);
ctx.body = pack(rendered);
});
router.get('/users/:user/followers', Followers);
// following
router.get('/users/:user/following', async ctx => {
const userId = new mongo.ObjectID(ctx.params.user);
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
// TODO: Implement fetch and render
const rendered = renderOrderedCollection(`${config.url}/users/${userId}/following`, 0, []);
ctx.body = pack(rendered);
});
router.get('/users/:user/following', Following);
// publickey
router.get('/users/:user/publickey', async ctx => {
+80
View File
@@ -0,0 +1,80 @@
import * as mongo from 'mongodb';
import * as Koa from 'koa';
import config from '../../config';
import $ from 'cafy'; import ID from '../../misc/cafy-id';
import User from '../../models/user';
import Following from '../../models/following';
import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
import renderFollowUser from '../../remote/activitypub/renderer/follow-user';
export default async (ctx: Koa.Context) => {
const userId = new mongo.ObjectID(ctx.params.user);
// Get 'cursor' parameter
const [cursor = null, cursorErr] = $.type(ID).optional.get(ctx.request.query.cursor);
// Get 'page' parameter
const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page);
const page: boolean = ctx.request.query.page === 'true';
// Validate parameters
if (cursorErr || pageErr) {
ctx.status = 400;
return;
}
// Verify user
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
const limit = 10;
const partOf = `${config.url}/users/${userId}/followers`;
if (page) {
// Construct query
const query = {
followeeId: user._id
} as any;
// カーソルが指定されている場合
if (cursor) {
query._id = {
$lt: cursor
};
}
// Get followers
const followings = await Following
.find(query, {
limit: limit + 1,
sort: { _id: -1 }
});
// 「次のページ」があるかどうか
const inStock = followings.length === limit + 1;
if (inStock) followings.pop();
const renderedFollowers = await Promise.all(followings.map(following => renderFollowUser(following.followerId)));
const rendered = renderOrderedCollectionPage(
`${partOf}?page=true${cursor ? `&cursor=${cursor}` : ''}`,
user.followersCount, renderedFollowers, partOf,
null,
inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null
);
ctx.body = pack(rendered);
} else {
// index page
const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null);
ctx.body = pack(rendered);
}
};
+80
View File
@@ -0,0 +1,80 @@
import * as mongo from 'mongodb';
import * as Koa from 'koa';
import config from '../../config';
import $ from 'cafy'; import ID from '../../misc/cafy-id';
import User from '../../models/user';
import Following from '../../models/following';
import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
import renderFollowUser from '../../remote/activitypub/renderer/follow-user';
export default async (ctx: Koa.Context) => {
const userId = new mongo.ObjectID(ctx.params.user);
// Get 'cursor' parameter
const [cursor = null, cursorErr] = $.type(ID).optional.get(ctx.request.query.cursor);
// Get 'page' parameter
const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page);
const page: boolean = ctx.request.query.page === 'true';
// Validate parameters
if (cursorErr || pageErr) {
ctx.status = 400;
return;
}
// Verify user
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
const limit = 10;
const partOf = `${config.url}/users/${userId}/following`;
if (page) {
// Construct query
const query = {
followerId: user._id
} as any;
// カーソルが指定されている場合
if (cursor) {
query._id = {
$lt: cursor
};
}
// Get followings
const followings = await Following
.find(query, {
limit: limit + 1,
sort: { _id: -1 }
});
// 「次のページ」があるかどうか
const inStock = followings.length === limit + 1;
if (inStock) followings.pop();
const renderedFollowees = await Promise.all(followings.map(following => renderFollowUser(following.followeeId)));
const rendered = renderOrderedCollectionPage(
`${partOf}?page=true${cursor ? `&cursor=${cursor}` : ''}`,
user.followingCount, renderedFollowees, partOf,
null,
inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null
);
ctx.body = pack(rendered);
} else {
// index page
const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null);
ctx.body = pack(rendered);
}
};
+103
View File
@@ -0,0 +1,103 @@
import * as mongo from 'mongodb';
import * as Koa from 'koa';
import config from '../../config';
import $ from 'cafy'; import ID from '../../misc/cafy-id';
import User from '../../models/user';
import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
import Note from '../../models/note';
import renderNote from '../../remote/activitypub/renderer/note';
export default async (ctx: Koa.Context) => {
const userId = new mongo.ObjectID(ctx.params.user);
// Get 'sinceId' parameter
const [sinceId, sinceIdErr] = $.type(ID).optional.get(ctx.request.query.since_id);
// Get 'untilId' parameter
const [untilId, untilIdErr] = $.type(ID).optional.get(ctx.request.query.until_id);
// Get 'page' parameter
const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page);
const page: boolean = ctx.request.query.page === 'true';
// Validate parameters
if (sinceIdErr || untilIdErr || pageErr || [sinceId, untilId].filter(x => x != null).length > 1) {
ctx.status = 400;
return;
}
// Verify user
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
const limit = 20;
const partOf = `${config.url}/users/${userId}/outbox`;
if (page) {
//#region Construct query
const sort = {
_id: -1
};
const query = {
userId: user._id,
$and: [{
$or: [ { visibility: 'public' }, { visibility: 'home' } ]
}, { // exclude renote, but include quote
$or: [{
text: { $ne: null }
}, {
mediaIds: { $ne: [] }
}]
}]
} as any;
if (sinceId) {
sort._id = 1;
query._id = {
$gt: sinceId
};
} else if (untilId) {
query._id = {
$lt: untilId
};
}
//#endregion
// Issue query
const notes = await Note
.find(query, {
limit: limit,
sort: sort
});
if (sinceId) notes.reverse();
const renderedNotes = await Promise.all(notes.map(note => renderNote(note)));
const rendered = renderOrderedCollectionPage(
`${partOf}?page=true${sinceId ? `&since_id=${sinceId}` : ''}${untilId ? `&until_id=${untilId}` : ''}`,
user.notesCount, renderedNotes, partOf,
notes.length > 0 ? `${partOf}?page=true&since_id=${notes[0]._id}` : null,
notes.length > 0 ? `${partOf}?page=true&until_id=${notes[notes.length - 1]._id}` : null
);
ctx.body = pack(rendered);
} else {
// index page
const rendered = renderOrderedCollection(partOf, user.notesCount,
`${partOf}?page=true`,
`${partOf}?page=true&since_id=000000000000000000000000`
);
ctx.body = pack(rendered);
}
};
+6 -2
View File
@@ -1,6 +1,6 @@
import { performance } from 'perf_hooks';
import limitter from './limitter';
import { IUser } from '../../models/user';
import { IUser, isLocalUser } from '../../models/user';
import { IApp } from '../../models/app';
import endpoints from './endpoints';
@@ -21,6 +21,10 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED');
}
if (ep.meta.requireAdmin && !(isLocalUser(user) && user.isAdmin)) {
return rej('YOU_ARE_NOT_ADMIN');
}
if (app && ep.meta.kind) {
if (!app.permission.some(p => p === ep.meta.kind)) {
return rej('PERMISSION_DENIED');
@@ -53,7 +57,7 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
const time = after - before;
if (time > 1000) {
console.warn(`SLOW API CALL DETECTED: ${ep.name} (${ time }ms)`);
console.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`);
}
} catch (e) {
rej(e);
+5
View File
@@ -14,6 +14,11 @@ export interface IEndpointMeta {
*/
requireCredential?: boolean;
/**
* 使
*/
requireAdmin?: boolean;
/**
*
*
@@ -0,0 +1,46 @@
import $ from 'cafy';
import ID from '../../../../misc/cafy-id';
import getParams from '../../get-params';
import User from '../../../../models/user';
export const meta = {
desc: {
ja: '指定したユーザーを凍結します。',
en: 'Suspend a user.'
},
requireCredential: true,
requireAdmin: true,
params: {
userId: $.type(ID).note({
desc: {
ja: '対象のユーザーID',
en: 'The user ID which you want to suspend'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isSuspended: true
}
});
res();
});
@@ -0,0 +1,46 @@
import $ from 'cafy';
import ID from '../../../../misc/cafy-id';
import getParams from '../../get-params';
import User from '../../../../models/user';
export const meta = {
desc: {
ja: '指定したユーザーの凍結を解除します。',
en: 'Unsuspend a user.'
},
requireCredential: true,
requireAdmin: true,
params: {
userId: $.type(ID).note({
desc: {
ja: '対象のユーザーID',
en: 'The user ID which you want to unsuspend'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isSuspended: false
}
});
res();
});
+9 -18
View File
@@ -1,5 +1,3 @@
import * as fs from 'fs';
import * as Koa from 'koa';
import * as send from 'koa-send';
import * as mongodb from 'mongodb';
@@ -51,23 +49,16 @@ export default async function(ctx: Koa.Context) {
};
if ('thumbnail' in ctx.query) {
// 画像以外
if (!file.contentType.startsWith('image/')) {
const readable = fs.createReadStream(`${__dirname}/assets/thumbnail-not-available.png`);
ctx.set('Content-Type', 'image/png');
ctx.body = readable;
} else if (file.contentType == 'image/gif') {
// GIF
await sendRaw();
const thumb = await DriveFileThumbnail.findOne({
'metadata.originalId': fileId
});
if (thumb != null) {
ctx.set('Content-Type', 'image/jpeg');
const bucket = await getDriveFileThumbnailBucket();
ctx.body = bucket.openDownloadStream(thumb._id);
} else {
const thumb = await DriveFileThumbnail.findOne({ 'metadata.originalId': fileId });
if (thumb != null) {
ctx.set('Content-Type', 'image/jpeg');
const bucket = await getDriveFileThumbnailBucket();
ctx.body = bucket.openDownloadStream(thumb._id);
} else {
await sendRaw();
}
await sendRaw();
}
} else {
if ('download' in ctx.query) {
+53 -8
View File
@@ -1,6 +1,5 @@
import { Buffer } from 'buffer';
import * as fs from 'fs';
import * as stream from 'stream';
import * as mongodb from 'mongodb';
import * as crypto from 'crypto';
@@ -17,30 +16,52 @@ import { publishUserStream, publishDriveStream } from '../../stream';
import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
import delFile from './delete-file';
import config from '../../config';
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
const log = debug('misskey:drive:add-file');
async function save(readable: stream.Readable, name: string, type: string, hash: string, size: number, metadata: any): Promise<IDriveFile> {
async function save(path: string, name: string, type: string, hash: string, size: number, metadata: any): Promise<IDriveFile> {
let thumbnail: Buffer;
if (['image/jpeg', 'image/png', 'image/webp'].includes(type)) {
thumbnail = await sharp(path)
.resize(300)
.jpeg({
quality: 50,
progressive: true
})
.toBuffer();
}
if (config.drive && config.drive.storage == 'minio') {
const minio = new Minio.Client(config.drive.config);
const id = uuid.v4();
const obj = `${config.drive.prefix}/${id}`;
const thumbnailObj = `${obj}-thumbnail`;
const baseUrl = config.drive.baseUrl
|| `${ config.drive.config.secure ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }`;
await minio.putObject(config.drive.bucket, obj, readable, size, {
await minio.putObject(config.drive.bucket, obj, fs.createReadStream(path), size, {
'Content-Type': type,
'Cache-Control': 'max-age=31536000, immutable'
});
if (thumbnail) {
await minio.putObject(config.drive.bucket, thumbnailObj, thumbnail, size, {
'Content-Type': 'image/jpeg',
'Cache-Control': 'max-age=31536000, immutable'
});
}
Object.assign(metadata, {
withoutChunks: true,
storage: 'minio',
storageProps: {
id: id
},
url: `${ baseUrl }/${ obj }`
url: `${ baseUrl }/${ obj }`,
thumbnailUrl: thumbnail ? `${ baseUrl }/${ thumbnailObj }` : null
});
const file = await DriveFile.insert({
@@ -57,12 +78,36 @@ async function save(readable: stream.Readable, name: string, type: string, hash:
// Get MongoDB GridFS bucket
const bucket = await getDriveFileBucket();
return new Promise<IDriveFile>((resolve, reject) => {
const writeStream = bucket.openUploadStream(name, { contentType: type, metadata });
const file = await new Promise<IDriveFile>((resolve, reject) => {
const writeStream = bucket.openUploadStream(name, {
contentType: type,
metadata
});
writeStream.once('finish', resolve);
writeStream.on('error', reject);
readable.pipe(writeStream);
fs.createReadStream(path).pipe(writeStream);
});
if (thumbnail) {
const thumbnailBucket = await getDriveFileThumbnailBucket();
await new Promise<IDriveFile>((resolve, reject) => {
const writeStream = thumbnailBucket.openUploadStream(name, {
contentType: 'image/jpeg',
metadata: {
originalId: file._id
}
});
writeStream.once('finish', resolve);
writeStream.on('error', reject);
writeStream.end(thumbnail);
});
}
return file;
}
}
@@ -321,7 +366,7 @@ export default async function(
}
}
} else {
driveFile = await (save(fs.createReadStream(path), detectedName, mime, hash, size, metadata));
driveFile = await (save(path, detectedName, mime, hash, size, metadata));
}
log(`drive file has been created ${driveFile._id}`);
+6
View File
@@ -6,8 +6,14 @@ import config from '../../config';
export default async function(file: IDriveFile, isExpired = false) {
if (file.metadata.storage == 'minio') {
const minio = new Minio.Client(config.drive.config);
const obj = `${config.drive.prefix}/${file.metadata.storageProps.id}`;
await minio.removeObject(config.drive.bucket, obj);
if (file.metadata.thumbnailUrl) {
const thumbnailObj = `${config.drive.prefix}/${file.metadata.storageProps.id}-thumbnail`;
await minio.removeObject(config.drive.bucket, thumbnailObj);
}
}
// チャンクをすべて削除
+6
View File
@@ -95,6 +95,8 @@ type Option = {
};
export default async (user: IUser, data: Option, silent = false) => new Promise<INote>(async (res, rej) => {
const isFirstNote = user.notesCount === 0;
if (data.createdAt == null) data.createdAt = new Date();
if (data.visibility == null) data.visibility = 'public';
if (data.viaMobile == null) data.viaMobile = false;
@@ -164,6 +166,10 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
// Pack the note
const noteObj = await pack(note);
if (isFirstNote) {
noteObj.isFirstNote = true;
}
const nm = new NotificationManager(user, note);
const nmRelatedPromises = [];