Compare commits
	
		
			125 commits
		
	
	
		
			develop
			...
			acid-chick
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a48336303f | ||
|  | 3e7b342cc6 | ||
|  | 9483bbb386 | ||
|  | 535f3ddf13 | ||
|  | ad9bc299be | ||
|  | b55ddaee09 | ||
|  | 6a7d020f9d | ||
|  | ae672b85c7 | ||
|  | 34f3fdc00e | ||
|  | 43fa4d3016 | ||
|  | bc0df206cd | ||
|  | e52725b11e | ||
|  | 4d989dda48 | ||
|  | acd2ed5c79 | ||
|  | 1fff5f19fe | ||
|  | 3c67be5b37 | ||
|  | 6586e1f792 | ||
|  | 545606ea3c | ||
|  | 97b0987ce1 | ||
|  | 70447e229e | ||
|  | 9190f6e1c8 | ||
|  | 475a3e4a02 | ||
|  | 480a49b2b6 | ||
|  | 6d4cd81556 | ||
|  | ddd62dee76 | ||
|  | a37b8c8750 | ||
|  | 16d7fadb28 | ||
|  | 7c360ae313 | ||
|  | 140c426c90 | ||
|  | 3c6e0403fa | ||
|  | 62b9c4c960 | ||
|  | 97a5f15902 | ||
|  | d9b447bc60 | ||
|  | 0c556e6243 | ||
|  | e5a877e316 | ||
|  | f88dc9605a | ||
|  | e07d11e15f | ||
|  | 6bf888e4e6 | ||
|  | 3b86e5d93a | ||
|  | c7517a5e12 | ||
|  | 2e9b9693fe | ||
|  | 09a540839c | ||
|  | 85806634d7 | ||
|  | 62c3955061 | ||
|  | ec6d250cb8 | ||
|  | 29f57c3de6 | ||
|  | 42e35bd1cd | ||
|  | ca77a1f7f4 | ||
|  | fb3d718310 | ||
|  | fab631a539 | ||
|  | 83e8dea89b | ||
|  | 17bb4ae389 | ||
|  | f343f2dcd6 | ||
|  | 0bf5b4a5a9 | ||
|  | 01960a658b | ||
|  | 362c831b54 | ||
|  | 1a81b4e5d9 | ||
|  | 569ae4a10f | ||
|  | 3821ed3d2b | ||
|  | eeebda7fe6 | ||
|  | bfcd828fc4 | ||
|  | 8f4dd03d3c | ||
|  | 5cac017d93 | ||
|  | 9b90022a72 | ||
|  | f9f1a654c3 | ||
|  | 41430288d3 | ||
|  | 0903704957 | ||
|  | 987f7dc89d | ||
|  | 2c2de1282a | ||
|  | e274c73177 | ||
|  | 69cc97ca83 | ||
|  | ea60565b0d | ||
|  | a26585dcc7 | ||
|  | 008e5bd24c | ||
|  | ebc6564fbb | ||
|  | 06b5078245 | ||
|  | 68337f95ff | ||
|  | 72e148002a | ||
|  | f0a03f71eb | ||
|  | 6049473e46 | ||
|  | f9f2787dfc | ||
|  | b0a068e269 | ||
|  | dd5546690a | ||
|  | d87b6d38ab | ||
|  | 4069bb170a | ||
|  | 600c009549 | ||
|  | c953a28201 | ||
|  | f6541df42a | ||
|  | a57e9460c8 | ||
|  | c7bcf31105 | ||
|  | a397c040fe | ||
|  | e311d73ffc | ||
|  | 2a3599a14d | ||
|  | b85dc8a658 | ||
|  | 7513123052 | ||
|  | 2526b86ec5 | ||
|  | e45aa0532c | ||
|  | 71fc84e224 | ||
|  | 6b726eea39 | ||
|  | acd4b101e1 | ||
|  | 98b8a94f2b | ||
|  | 7d31bd97ff | ||
|  | 828a2a73c9 | ||
|  | bdc7167cf4 | ||
|  | 45b94086ed | ||
|  | e62bb7cdaf | ||
|  | c5f65d9eeb | ||
|  | 4557856104 | ||
|  | 4a23c36740 | ||
|  | 2553b20130 | ||
|  | 6982faf668 | ||
|  | 4db972318f | ||
|  | 81006566a5 | ||
|  | 6abc053a48 | ||
|  | 03a3c56a54 | ||
|  | 83b7010d6a | ||
|  | 71654cbe47 | ||
|  | e8f96e848a | ||
|  | 251abf21d4 | ||
|  | d103427932 | ||
|  | 592cdfa910 | ||
|  | f2ad1a0406 | ||
|  | 82af9320c0 | ||
|  | fceebf7388 | ||
|  | 13caf37991 | 
					 96 changed files with 1042 additions and 599 deletions
				
			
		|  | @ -55,6 +55,7 @@ drive: | ||||||
| 
 | 
 | ||||||
| # OR | # OR | ||||||
| 
 | 
 | ||||||
|  | #drive: | ||||||
| #  storage: 'minio' | #  storage: 'minio' | ||||||
| #  bucket: | #  bucket: | ||||||
| #  prefix: | #  prefix: | ||||||
|  | @ -65,25 +66,38 @@ drive: | ||||||
| #    accessKey: | #    accessKey: | ||||||
| #    secretKey: | #    secretKey: | ||||||
| 
 | 
 | ||||||
|   # S3 example | # S3/GCS example | ||||||
|  | # | ||||||
|  | # * Replace <endpoint> to | ||||||
|  | #     S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region | ||||||
|  | #     GCS: use 'storage.googleapis.com' | ||||||
|  | # | ||||||
|  | # * Replace <region> to | ||||||
|  | #     S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region | ||||||
|  | #     GCS: not needed (just delete the region line) | ||||||
|  | # | ||||||
|  | #drive: | ||||||
| #  storage: 'minio' | #  storage: 'minio' | ||||||
| #  bucket: bucket-name | #  bucket: bucket-name | ||||||
| #  prefix: files | #  prefix: files | ||||||
|  | #  baseUrl: https://bucket-name.<endpoint> | ||||||
| #  config: | #  config: | ||||||
|   #   endPoint: s3-us-west-2.amazonaws.com | #    endPoint: <endpoint> | ||||||
|   #   region: us-west-2 | #    region: <region> | ||||||
| #    useSSL: true | #    useSSL: true | ||||||
| #    accessKey: XXX | #    accessKey: XXX | ||||||
| #    secretKey: YYY | #    secretKey: YYY | ||||||
| 
 | 
 | ||||||
|   # S3 example (with CDN, custom domain) | # S3/GCS example (with CDN, custom domain) | ||||||
|  | # | ||||||
|  | #drive: | ||||||
| #  storage: 'minio' | #  storage: 'minio' | ||||||
| #  bucket: drive.example.com | #  bucket: drive.example.com | ||||||
| #  prefix: files | #  prefix: files | ||||||
| #  baseUrl: https://drive.example.com | #  baseUrl: https://drive.example.com | ||||||
| #  config: | #  config: | ||||||
|   #   endPoint: s3-us-west-2.amazonaws.com | #    endPoint: <endpoint> | ||||||
|   #   region: us-west-2 | #    region: <region> | ||||||
| #    useSSL: true | #    useSSL: true | ||||||
| #    accessKey: XXX | #    accessKey: XXX | ||||||
| #    secretKey: YYY | #    secretKey: YYY | ||||||
|  | @ -113,3 +127,6 @@ autoAdmin: true | ||||||
| 
 | 
 | ||||||
| # Clustering | # Clustering | ||||||
| #clusterLimit: 1 | #clusterLimit: 1 | ||||||
|  | 
 | ||||||
|  | # IP address family used for outgoing request (ipv4, ipv6 or dual) | ||||||
|  | #outgoingAddressFamily: ipv4 | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								.github/CODEOWNERS
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/CODEOWNERS
									
										
									
									
										vendored
									
									
								
							|  | @ -1,7 +1,7 @@ | ||||||
| # PATH                                          OWNERS | # PATH                                          OWNERS | ||||||
| /.autogen/                                      @acid-chicken | /.autogen/                                      @acid-chicken | ||||||
| /.circleci/                                     @syuilo @acid-chicken | /.circleci/                                     @syuilo @acid-chicken | ||||||
| /.config/                                       @syuilo @AyaMorisawa @mei23 @acid-chicken | /.config/                                       @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki | ||||||
| # /.config/mongo_initdb_example.js              @khws4v1 | # /.config/mongo_initdb_example.js              @khws4v1 | ||||||
| /.github/                                       @syuilo @AyaMorisawa @acid-chicken | /.github/                                       @syuilo @AyaMorisawa @acid-chicken | ||||||
| /.vscode/                                       @acid-chicken | /.vscode/                                       @acid-chicken | ||||||
|  | @ -12,7 +12,7 @@ | ||||||
| # /docs/*.fr.md                                 @BoFFire | # /docs/*.fr.md                                 @BoFFire | ||||||
| # /docs/docker.*.md                             @khws4v1 | # /docs/docker.*.md                             @khws4v1 | ||||||
| /locales/                                       @syuilo | /locales/                                       @syuilo | ||||||
| /src/                                           @syuilo @AyaMorisawa @mei23 @acid-chicken | /src/                                           @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki | ||||||
| # /src/crypto_key.cc                            @akihikodaki | # /src/crypto_key.cc                            @akihikodaki | ||||||
| # /src/crypto_key.d.ts                          @akihikodaki | # /src/crypto_key.d.ts                          @akihikodaki | ||||||
| /.dockerignore                                  @syuilo # @khws4v1 | /.dockerignore                                  @syuilo # @khws4v1 | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -19,3 +19,4 @@ api-docs.json | ||||||
| *.code-workspace | *.code-workspace | ||||||
| yarn.lock | yarn.lock | ||||||
| .DS_Store | .DS_Store | ||||||
|  | /files | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										21
									
								
								CHANGELOG.md
									
										
									
									
									
								
							|  | @ -5,6 +5,27 @@ If you encounter any problems with updating, please try the following: | ||||||
| 1. `npm run clean` or `npm run cleanall` | 1. `npm run clean` or `npm run cleanall` | ||||||
| 2. Retry update (Don't forget `npm i`) | 2. Retry update (Don't forget `npm i`) | ||||||
| 
 | 
 | ||||||
|  | 10.102.1 | ||||||
|  | ---------- | ||||||
|  | * 投稿が増殖する問題を修正 | ||||||
|  | * リモートユーザーの修復処理が自動的に実行されない問題を修正 | ||||||
|  | 
 | ||||||
|  | 10.101.0 | ||||||
|  | ---------- | ||||||
|  | * WebFingerリクエストで Proxy, Keep-Alive などをサポート | ||||||
|  | * AP actor Service のサポートが不完全な問題を修正 | ||||||
|  | * Punycodeなインスタンスが重複登録される問題を修正 | ||||||
|  | * ObjectStrage利用時にドライブファイルアイコンが表示されない問題を修正 | ||||||
|  | 
 | ||||||
|  | 10.100.0 | ||||||
|  | ---------- | ||||||
|  | * ユーザーリストでフォローボタンを表示するように | ||||||
|  | * ドライブのファイルのサムネイルを修正 | ||||||
|  | * 投稿ウィジットでローカルのみの公開範囲で投稿できない問題を修正 | ||||||
|  | * TLを遡った時に抜けがある時がある問題を修正 | ||||||
|  | * ユーザータイムラインが投稿日時順ではなくなっているのを修正 | ||||||
|  | * 10.99.0 でチャートのレンダリングがおかしい問題を修正 | ||||||
|  | 
 | ||||||
| 10.99.0 | 10.99.0 | ||||||
| ---------- | ---------- | ||||||
| * manifest.json にインスタンス名を反映させるように | * manifest.json にインスタンス名を反映させるように | ||||||
|  |  | ||||||
							
								
								
									
										106
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										106
									
								
								README.md
									
										
									
									
									
								
							|  | @ -88,12 +88,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). | ||||||
| 		<td><img src="https://avatars0.githubusercontent.com/u/10798641?s=460&v=4" alt="AyaMorisawa" width="100"></td> | 		<td><img src="https://avatars0.githubusercontent.com/u/10798641?s=460&v=4" alt="AyaMorisawa" width="100"></td> | ||||||
| 		<td><img src="https://avatars1.githubusercontent.com/u/30769358?s=460&v=4" alt="mei23" width="100"></td> | 		<td><img src="https://avatars1.githubusercontent.com/u/30769358?s=460&v=4" alt="mei23" width="100"></td> | ||||||
| 		<td><img src="https://avatars2.githubusercontent.com/u/20679825?s=460&v=4" alt="acid-chicken" width="100"></td> | 		<td><img src="https://avatars2.githubusercontent.com/u/20679825?s=460&v=4" alt="acid-chicken" width="100"></td> | ||||||
|  | 		<td><img src="https://avatars2.githubusercontent.com/u/6533808?s=460&v=4" alt="rinsuki" width="100"></td> | ||||||
| 	</tr> | 	</tr> | ||||||
| 	<tr> | 	<tr> | ||||||
| 		<td align="center"><a href="https://github.com/syuilo">@syuilo</a></td> | 		<td align="center"><a href="https://github.com/syuilo">@syuilo</a></td> | ||||||
| 		<td align="center"><a href="https://github.com/AyaMorisawa">@AyaMorisawa</a></td> | 		<td align="center"><a href="https://github.com/AyaMorisawa">@AyaMorisawa</a></td> | ||||||
| 		<td align="center"><a href="https://github.com/mei23">@mei23</a></td> | 		<td align="center"><a href="https://github.com/mei23">@mei23</a></td> | ||||||
| 		<td align="center"><a href="https://github.com/acid-chicken">@acid-chicken</a></td> | 		<td align="center"><a href="https://github.com/acid-chicken">@acid-chicken</a></td> | ||||||
|  | 		<td align="center"><a href="https://github.com/rinsuki">@rinsuki</a></td> | ||||||
| 	</tr> | 	</tr> | ||||||
| </table> | </table> | ||||||
| 
 | 
 | ||||||
|  | @ -101,62 +103,92 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| <!-- PATREON_START --> | <!-- PATREON_START --> | ||||||
| <table><tr> | <table><tr> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1?token-time=2145916800&token-hash=HGkZJ7s4bSaQVoOJ5q30mTWHTxDLiw1LuyaogKPLy24%3D" alt="Hiroshi Seki" width="100"></td> | <td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td> | <td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/12059069" alt="naga_rus" 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/12913507/f7181eacafe8469a93033d85f5969c29/4?token-time=2145916800&token-hash=vZdDTTF-ahiKBjjgppS2ev4rkD8H7TTKkXXoxsucs6Y%3D" alt="Melilot" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/605366/c9dc408fdcbf412fb183ca5b06235f8d/1.jpeg?token-time=2145916800&token-hash=oaqsjLqOFjWN5I9hm2epOaTXaEtKwQUy5OW-EpAz6-g%3D" alt="Jon Leibowitz" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/3.png?token-time=2145916800&token-hash=Zq1TCK2tdY7xudEm_aV70bc_wxmol6pNj3ZWbpFUNbI%3D" alt="Nesakko" width="100"></td> | ||||||
|  | <td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td> | ||||||
| </tr><tr> | </tr><tr> | ||||||
| <td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td> | <td><a href="https://www.patreon.com/user?u=20832595">Roujo</a></td> | ||||||
| <td><a href="https://www.patreon.com/weepjp">weep</a></td> | <td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=12059069">naga_rus</a></td> | <td><a href="https://www.patreon.com/weepjp">weepjp</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | <td><a href="https://www.patreon.com/jonleibowitz">Jon Leibowitz</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td> | <td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td> | ||||||
| </tr></table> | </tr></table> | ||||||
| <table><tr> | <table><tr> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1?token-time=2145916800&token-hash=0xgcpqvFDqRcV_YIEhcPNVH7gs9sLg_BBnTJXCkN4ao%3D" alt="mydarkstar" width="100"></td> | <td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/12718187" alt="Peter G." width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/18833336" alt="itiradi" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=2PsbFNw0tnubZzgSXD01R6hIgncfiElG7H7HX2Y3dyo%3D" alt="nemu" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" 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/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3?token-time=2145916800&token-hash=9JtETp0X8gI280Ne1E8bxn6j4Lw5o2k4mJkICx97V_k%3D" alt="YUKIMOCHI" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" 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/17880724/311738c8a48f4a6b9443c2445a75adde/1?token-time=2145916800&token-hash=95p8VdGX45E8BitZR_eOcDlqCjumjzNLBPQJrJdeCpI%3D" alt="takimura" 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://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4?token-time=2145916800&token-hash=SbdZeN5SmsuT9stD6v0jN1z0hftg0FmRiCTxysU0Ihw%3D" alt="Damillora" width="100"></td> |  | ||||||
| </tr><tr> | </tr><tr> | ||||||
| <td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td> | <td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/user?u=23915207">kabo2468y</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/AureoleArk">AureoleArk</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/osapon">osapon</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/user?u=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/mydarkstar">mydarkstar</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td> | </tr></table> | ||||||
| <td><a href="https://www.patreon.com/user?u=18833336">itiradi</a></td> | <table><tr> | ||||||
| <td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td> | <td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td> | ||||||
|  | <td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro" width="100"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha" width="100"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/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/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td> | ||||||
|  | </tr><tr> | ||||||
|  | <td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td> | <td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td> | ||||||
| <td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td> | <td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td> | <td><a href="https://www.patreon.com/user?u=26340354">totokoro</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/user?u=5827393">motcha</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/takimura">takimura</a></td> | ||||||
| <td><a href="https://www.patreon.com/damillora">Damillora</a></td> | <td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td> | ||||||
| </tr></table> | </tr></table> | ||||||
| <table><tr> | <table><tr> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/935a10339daa4ede8e555903a0707060/1?token-time=2145916800&token-hash=3CrpqH-XtKs_NoIlSsTyVs8wCzP1WFCsG2xwps1IJq0%3D" alt="Atsuko Tominaga" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3?token-time=2145916800&token-hash=-iJszBqgYBhsM5qMdA1knf9wvprhEfESzKfR2oh7mIA%3D" alt="natalie" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=5T8XcaAf9Zyzfg3QubR06s_kJZkArVEM2dwObrBVAU4%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/18072312/98e894d960314fa7bc236a72a39488fe/1?token-time=2145916800&token-hash=D6QK3fPyqiYKJfOzc-QqaSSairUrWdjld-ewp2waj6s%3D" alt="Hekovic" 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/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=Ksk_2l3gjPDbnzMUOCSW1E-hdPJsNs2tSR4_RAakRK8%3D" alt="dansup" 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> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=CXe9AqlZy9AsYfiWd3OBYVOzvODoN47Litz0Tu4BFpU%3D" alt="Gargron" width="100"></td> | <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/5731881/4b6038e6cda34c04b83a5fcce3806a93/1?token-time=2145916800&token-hash=xhR1n6NAAyEb-IUXLD6_dshkFa3mefU5ZZuk1L8qKTs%3D" alt="Nokotaro Takeda" width="100"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=uR-48MQ0A4j0irQSrCAQZJ-sJUSs_Fkihlg3-l59b7c%3D" alt="Takashi Shibuya" width="100"></td> | <td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td> | ||||||
| </tr><tr> | </tr><tr> | ||||||
| <td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td> | <td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> | <td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> | ||||||
| <td><a href="https://www.patreon.com/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> | <td><a href="https://www.patreon.com/hekovic">Hekovic</a></td> | ||||||
| <td><a href="https://www.patreon.com/dansup">dansup</a></td> | <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/takenoko">Nokotaro Takeda</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td> | ||||||
|  | </tr></table> | ||||||
|  | <table><tr> | ||||||
|  | <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/user?u=12531784">Takashi Shibuya</a></td> | <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> | ||||||
| </tr></table> | </tr></table> | ||||||
| 
 | 
 | ||||||
| **Last updated:** Fri, 05 Apr 2019 09:39:06 UTC | **Last updated:** Wed, 29 Jan 2020 21:11:07 UTC | ||||||
| <!-- PATREON_END --> | <!-- PATREON_END --> | ||||||
| 
 | 
 | ||||||
| :four_leaf_clover: Copyright | :four_leaf_clover: Copyright | ||||||
|  |  | ||||||
|  | @ -9,9 +9,23 @@ This guide describes how to install and setup Misskey with Docker. | ||||||
| 
 | 
 | ||||||
| *1.* Download Misskey | *1.* Download Misskey | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `git clone -b master git://github.com/syuilo/misskey.git` Clone Misskey repository's master branch. | 1. Clone Misskey repository's master branch. | ||||||
| 2. `cd misskey` Move to misskey directory. | 
 | ||||||
| 3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) tag. | 	`git clone -b master git://github.com/syuilo/misskey.git` | ||||||
|  | 
 | ||||||
|  | 2. Move to misskey directory. | ||||||
|  | 
 | ||||||
|  | 	`cd misskey` | ||||||
|  | 
 | ||||||
|  | 3. Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) tag. | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
| 
 | 
 | ||||||
| *2.* Configure Misskey | *2.* Configure Misskey | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
|  | @ -39,7 +53,15 @@ Just `docker-compose up -d`. GLHF! | ||||||
| ### How to update your Misskey server to the latest version | ### How to update your Misskey server to the latest version | ||||||
| 1. `git fetch` | 1. `git fetch` | ||||||
| 2. `git stash` | 2. `git stash` | ||||||
| 3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` | 3.  | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
| 4. `git stash pop` | 4. `git stash pop` | ||||||
| 5. `docker-compose build` | 5. `docker-compose build` | ||||||
| 6. Check [ChangeLog](../CHANGELOG.md) for migration information | 6. Check [ChangeLog](../CHANGELOG.md) for migration information | ||||||
|  |  | ||||||
|  | @ -10,9 +10,23 @@ Ce guide explique comment installer et configurer Misskey avec Docker. | ||||||
| 
 | 
 | ||||||
| *1.* Télécharger Misskey | *1.* Télécharger Misskey | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `git clone -b master git://github.com/syuilo/misskey.git` Clone le dépôt de Misskey sur la branche master. | 1. Clone le dépôt de Misskey sur la branche master. | ||||||
| 2. `cd misskey` Naviguez dans le dossier du dépôt. | 
 | ||||||
| 3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout sur le tag de la [dernière version](https://github.com/syuilo/misskey/releases/latest). | 	`git clone -b master git://github.com/syuilo/misskey.git` | ||||||
|  | 
 | ||||||
|  | 2. Naviguez dans le dossier du dépôt. | ||||||
|  | 
 | ||||||
|  | 	`cd misskey` | ||||||
|  | 
 | ||||||
|  | 3. Checkout sur le tag de la [dernière version](https://github.com/syuilo/misskey/releases/latest). | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
| 
 | 
 | ||||||
| *2.* Configuration de Misskey | *2.* Configuration de Misskey | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
|  | @ -40,7 +54,15 @@ Utilisez la commande `docker-compose up -d`. GLHF! | ||||||
| ### How to update your Misskey server to the latest version | ### How to update your Misskey server to the latest version | ||||||
| 1. `git fetch` | 1. `git fetch` | ||||||
| 2. `git stash` | 2. `git stash` | ||||||
| 3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` | 3.  | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
| 4. `git stash pop` | 4. `git stash pop` | ||||||
| 5. `docker-compose build` | 5. `docker-compose build` | ||||||
| 6. Consultez le [ChangeLog](../CHANGELOG.md) pour avoir les éventuelles informations de migration | 6. Consultez le [ChangeLog](../CHANGELOG.md) pour avoir les éventuelles informations de migration | ||||||
|  | @ -52,14 +74,28 @@ Utilisez la commande `docker-compose up -d`. GLHF! | ||||||
| ### Configuration d'ElasticSearch (pour la fonction de recherche) | ### Configuration d'ElasticSearch (pour la fonction de recherche) | ||||||
| *1.* Préparation de l'environnement | *1.* Préparation de l'environnement | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `mkdir elasticsearch && chown 1000:1000 elasticsearch` Permet de créer le dossier d'accueil de la base ElasticSearch aves les bons droits | 1. Permet de créer le dossier d'accueil de la base ElasticSearch aves les bons droits | ||||||
| 2. `sysctl -w vm.max_map_count=262144` Augmente la valeur max du paramètre map_count du système (valeur minimum pour pouvoir lancer ES) | 
 | ||||||
|  | 	`mkdir elasticsearch && chown 1000:1000 elasticsearch` | ||||||
|  | 
 | ||||||
|  | 2. Augmente la valeur max du paramètre map_count du système (valeur minimum pour pouvoir lancer ES) | ||||||
|  | 
 | ||||||
|  | 	`sysctl -w vm.max_map_count=262144` | ||||||
| 
 | 
 | ||||||
| *2.* Après lancement du docker-compose, initialisation de la base ElasticSearch | *2.* Après lancement du docker-compose, initialisation de la base ElasticSearch | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `docker-compose -it web /bin/sh` Connexion dans le conteneur web | 1. Connexion dans le conteneur web | ||||||
| 2. `apk add curl` Ajout du paquet curl | 
 | ||||||
| 3. `curl -X PUT "es:9200/misskey" -H 'Content-Type: application/json' -d'{ "settings" : { "index" : { } }}'` Création de la base ES | 	`docker-compose -it web /bin/sh` | ||||||
|  | 
 | ||||||
|  | 2. Ajout du paquet curl | ||||||
|  | 
 | ||||||
|  | 	`apk add curl` | ||||||
|  | 
 | ||||||
|  | 3. Création de la base ES | ||||||
|  | 
 | ||||||
|  | 	`curl -X PUT "es:9200/misskey" -H 'Content-Type: application/json' -d'{ "settings" : { "index" : { } }}'` | ||||||
|  | 
 | ||||||
| 4. `exit` | 4. `exit` | ||||||
| 
 | 
 | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | @ -9,9 +9,23 @@ Dockerを使ったMisskey構築方法 | ||||||
| 
 | 
 | ||||||
| *1.* Misskeyのダウンロード | *1.* Misskeyのダウンロード | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン | 1. masterブランチからMisskeyレポジトリをクローン | ||||||
| 2. `cd misskey` misskeyディレクトリに移動 | 
 | ||||||
| 3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認 | 	`git clone -b master git://github.com/syuilo/misskey.git` | ||||||
|  | 
 | ||||||
|  | 2. misskeyディレクトリに移動 | ||||||
|  | 
 | ||||||
|  | 	`cd misskey` | ||||||
|  | 
 | ||||||
|  | 3. [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認 | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
| 
 | 
 | ||||||
| *2.* 設定ファイルを作成する | *2.* 設定ファイルを作成する | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
|  | @ -39,7 +53,15 @@ Dockerを使ったMisskey構築方法 | ||||||
| ### Misskeyを最新バージョンにアップデートする方法: | ### Misskeyを最新バージョンにアップデートする方法: | ||||||
| 1. `git fetch` | 1. `git fetch` | ||||||
| 2. `git stash` | 2. `git stash` | ||||||
| 3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` | 3.  | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
| 4. `git stash pop` | 4. `git stash pop` | ||||||
| 5. `docker-compose build` | 5. `docker-compose build` | ||||||
| 6. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する | 6. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する | ||||||
|  |  | ||||||
|  | @ -41,15 +41,38 @@ As root: | ||||||
| 
 | 
 | ||||||
| *4.* Install Misskey | *4.* Install Misskey | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `su - misskey` Connect to misskey user. | 1. Connect to misskey user. | ||||||
| 2. `git clone -b master git://github.com/syuilo/misskey.git` Clone the misskey repo from master branch. | 
 | ||||||
| 3. `cd misskey` Navigate to misskey directory | 	`su - misskey` | ||||||
| 4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) | 
 | ||||||
| 5. `npm install` Install misskey dependencies. | 2. Clone the misskey repo from master branch. | ||||||
|  | 
 | ||||||
|  | 	`git clone -b master git://github.com/syuilo/misskey.git` | ||||||
|  | 
 | ||||||
|  | 3. Navigate to misskey directory | ||||||
|  | 
 | ||||||
|  | 	`cd misskey` | ||||||
|  | 
 | ||||||
|  | 4. Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
|  | 
 | ||||||
|  | 5. Install misskey dependencies. | ||||||
|  | 
 | ||||||
|  | 	`npm install` | ||||||
| 
 | 
 | ||||||
| *5.* Configure Misskey | *5.* Configure Misskey | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`. | 1. Copy the `.config/example.yml` and rename it to `default.yml`. | ||||||
|  | 
 | ||||||
|  | 	`cp .config/example.yml .config/default.yml` | ||||||
|  | 
 | ||||||
| 2. Edit `default.yml` | 2. Edit `default.yml` | ||||||
| 
 | 
 | ||||||
| *6.* Build Misskey | *6.* Build Misskey | ||||||
|  | @ -77,7 +100,10 @@ Just `NODE_ENV=production npm start`. GLHF! | ||||||
| 
 | 
 | ||||||
| ### Launch with systemd | ### Launch with systemd | ||||||
| 
 | 
 | ||||||
| 1. Create a systemd service here: `/etc/systemd/system/misskey.service` | 1. Create a systemd service here | ||||||
|  | 
 | ||||||
|  | 	`/etc/systemd/system/misskey.service` | ||||||
|  | 
 | ||||||
| 2. Edit it, and paste this and save: | 2. Edit it, and paste this and save: | ||||||
| 
 | 
 | ||||||
| 	``` | 	``` | ||||||
|  | @ -100,14 +126,27 @@ Restart=always | ||||||
| 	WantedBy=multi-user.target | 	WantedBy=multi-user.target | ||||||
| 	``` | 	``` | ||||||
| 
 | 
 | ||||||
| 3. `systemctl daemon-reload ; systemctl enable misskey` Reload systemd and enable the misskey service. | 3. Reload systemd and enable the misskey service. | ||||||
| 4. `systemctl start misskey` Start the misskey service. | 
 | ||||||
|  | 	`systemctl daemon-reload ; systemctl enable misskey` | ||||||
|  | 
 | ||||||
|  | 4. Start the misskey service. | ||||||
|  | 
 | ||||||
|  | 	`systemctl start misskey` | ||||||
| 
 | 
 | ||||||
| You can check if the service is running with `systemctl status misskey`. | You can check if the service is running with `systemctl status misskey`. | ||||||
| 
 | 
 | ||||||
| ### How to update your Misskey server to the latest version | ### How to update your Misskey server to the latest version | ||||||
| 1. `git fetch` | 1. `git fetch` | ||||||
| 2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` | 2.   | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
| 3. `npm install` | 3. `npm install` | ||||||
| 4. `NODE_ENV=production npm run build` | 4. `NODE_ENV=production npm run build` | ||||||
| 5. Check [ChangeLog](../CHANGELOG.md) for migration information | 5. Check [ChangeLog](../CHANGELOG.md) for migration information | ||||||
|  |  | ||||||
|  | @ -41,15 +41,38 @@ En root : | ||||||
| 
 | 
 | ||||||
| *4.* Installation de Misskey | *4.* Installation de Misskey | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `su - misskey` Basculez vers l'utilisateur misskey. | 1. Basculez vers l'utilisateur misskey. | ||||||
| 2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey. | 
 | ||||||
| 3. `cd misskey` Accédez au dossier misskey. | 	`su - misskey` | ||||||
| 4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest) | 
 | ||||||
| 5. `npm install` Installez les dépendances de misskey. | 2. Clonez la branche master du dépôt misskey. | ||||||
|  | 
 | ||||||
|  | 	`git clone -b master git://github.com/syuilo/misskey.git` | ||||||
|  | 
 | ||||||
|  | 3. Accédez au dossier misskey. | ||||||
|  | 
 | ||||||
|  | 	`cd misskey` | ||||||
|  | 
 | ||||||
|  | 4. Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest) | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
|  |   | ||||||
|  | 5. Installez les dépendances de misskey. | ||||||
|  | 
 | ||||||
|  | 	`npm install` | ||||||
| 
 | 
 | ||||||
| *5.* Création du fichier de configuration | *5.* Création du fichier de configuration | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le`default.yml`. | 1. Copiez le fichier `.config/example.yml` et renommez-le`default.yml`. | ||||||
|  | 
 | ||||||
|  | 	`cp .config/example.yml .config/default.yml` | ||||||
|  | 
 | ||||||
| 2. Editez le fichier `default.yml` | 2. Editez le fichier `default.yml` | ||||||
| 
 | 
 | ||||||
| *6.* Construction de Misskey | *6.* Construction de Misskey | ||||||
|  | @ -77,7 +100,10 @@ Lancez tout simplement `NODE_ENV=production npm start`. Bonne chance et amusez-v | ||||||
| 
 | 
 | ||||||
| ### Démarrage avec systemd | ### Démarrage avec systemd | ||||||
| 
 | 
 | ||||||
| 1. Créez un service systemd sur : `/etc/systemd/system/misskey.service` | 1. Créez un service systemd sur | ||||||
|  | 
 | ||||||
|  | 	`/etc/systemd/system/misskey.service` | ||||||
|  | 
 | ||||||
| 2. Editez-le puis copiez et coller ceci dans le fichier : | 2. Editez-le puis copiez et coller ceci dans le fichier : | ||||||
| 
 | 
 | ||||||
| 	``` | 	``` | ||||||
|  | @ -100,14 +126,27 @@ Restart=always | ||||||
| 	WantedBy=multi-user.target | 	WantedBy=multi-user.target | ||||||
| 	``` | 	``` | ||||||
| 
 | 
 | ||||||
| 3. `systemctl daemon-reload ; systemctl enable misskey` Redémarre systemd et active le service misskey. | 3. Redémarre systemd et active le service misskey. | ||||||
| 4. `systemctl start misskey` Démarre le service misskey. | 
 | ||||||
|  | 	`systemctl daemon-reload ; systemctl enable misskey` | ||||||
|  | 
 | ||||||
|  | 4. Démarre le service misskey. | ||||||
|  | 
 | ||||||
|  | 	`systemctl start misskey` | ||||||
| 
 | 
 | ||||||
| Vous pouvez vérifier si le service a démarré en utilisant la commande `systemctl status misskey`. | Vous pouvez vérifier si le service a démarré en utilisant la commande `systemctl status misskey`. | ||||||
| 
 | 
 | ||||||
| ### Méthode de mise à jour vers la plus récente version de Misskey | ### Méthode de mise à jour vers la plus récente version de Misskey | ||||||
| 1. `git fetch` | 1. `git fetch` | ||||||
| 2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` | 2.   | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
| 3. `npm install` | 3. `npm install` | ||||||
| 4. `NODE_ENV=production npm run build` | 4. `NODE_ENV=production npm run build` | ||||||
| 5. Consultez [ChangeLog](../CHANGELOG.md) pour les information de migration. | 5. Consultez [ChangeLog](../CHANGELOG.md) pour les information de migration. | ||||||
|  |  | ||||||
|  | @ -48,15 +48,38 @@ adduser --disabled-password --disabled-login misskey | ||||||
| 
 | 
 | ||||||
| *4.* Misskeyのインストール | *4.* Misskeyのインストール | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `su - misskey` misskeyユーザーを使用 | 1. misskeyユーザーを使用 | ||||||
| 2. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン | 
 | ||||||
| 3. `cd misskey` misskeyディレクトリに移動 | 	`su - misskey` | ||||||
| 4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認 | 
 | ||||||
| 5. `npm install` Misskeyの依存パッケージをインストール | 2. masterブランチからMisskeyレポジトリをクローン | ||||||
|  | 
 | ||||||
|  | 	`git clone -b master git://github.com/syuilo/misskey.git` | ||||||
|  | 
 | ||||||
|  | 3. misskeyディレクトリに移動 | ||||||
|  | 
 | ||||||
|  | 	`cd misskey` | ||||||
|  | 
 | ||||||
|  | 4. [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認 | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
|  | 
 | ||||||
|  | 5. Misskeyの依存パッケージをインストール | ||||||
|  | 
 | ||||||
|  | 	`npm install` | ||||||
| 
 | 
 | ||||||
| *5.* 設定ファイルを作成する | *5.* 設定ファイルを作成する | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| 1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする。 | 1. `.config/example.yml`をコピーし名前を`default.yml`にする。 | ||||||
|  | 
 | ||||||
|  | 	`cp .config/example.yml .config/default.yml` | ||||||
|  | 
 | ||||||
| 2. `default.yml` を編集する。 | 2. `default.yml` を編集する。 | ||||||
| 
 | 
 | ||||||
| *6.* Misskeyのビルド | *6.* Misskeyのビルド | ||||||
|  | @ -82,7 +105,10 @@ Debianをお使いであれば、`build-essential`パッケージをインスト | ||||||
| `NODE_ENV=production npm start`するだけです。GLHF! | `NODE_ENV=production npm start`するだけです。GLHF! | ||||||
| 
 | 
 | ||||||
| ### systemdを用いた起動 | ### systemdを用いた起動 | ||||||
| 1. systemdサービスのファイルを作成: `/etc/systemd/system/misskey.service` | 1. systemdサービスのファイルを作成 | ||||||
|  | 
 | ||||||
|  | 	`/etc/systemd/system/misskey.service` | ||||||
|  | 
 | ||||||
| 2. エディタで開き、以下のコードを貼り付けて保存: | 2. エディタで開き、以下のコードを貼り付けて保存: | ||||||
| 
 | 
 | ||||||
| 	``` | 	``` | ||||||
|  | @ -104,16 +130,31 @@ Restart=always | ||||||
| 	[Install] | 	[Install] | ||||||
| 	WantedBy=multi-user.target | 	WantedBy=multi-user.target | ||||||
| 	``` | 	``` | ||||||
|  | 
 | ||||||
| 	CentOSで1024以下のポートを使用してMisskeyを使用する場合は`ExecStart=/usr/bin/sudo /usr/bin/npm start`に変更する必要があります。 | 	CentOSで1024以下のポートを使用してMisskeyを使用する場合は`ExecStart=/usr/bin/sudo /usr/bin/npm start`に変更する必要があります。 | ||||||
| 
 | 
 | ||||||
| 3. `systemctl daemon-reload ; systemctl enable misskey` systemdを再読み込みしmisskeyサービスを有効化 | 3. systemdを再読み込みしmisskeyサービスを有効化 | ||||||
| 4. `systemctl start misskey` misskeyサービスの起動 | 
 | ||||||
|  | 	`systemctl daemon-reload ; systemctl enable misskey` | ||||||
|  | 
 | ||||||
|  | 4. misskeyサービスの起動 | ||||||
|  | 
 | ||||||
|  | 	`systemctl start misskey` | ||||||
| 
 | 
 | ||||||
| `systemctl status misskey`と入力すると、サービスの状態を調べることができます。 | `systemctl status misskey`と入力すると、サービスの状態を調べることができます。 | ||||||
| 
 | 
 | ||||||
| ### Misskeyを最新バージョンにアップデートする方法: | ### Misskeyを最新バージョンにアップデートする方法: | ||||||
| 1. `git fetch` | 1. `git fetch` | ||||||
| 2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` | 2.   | ||||||
|  | 
 | ||||||
|  | 	```bash | ||||||
|  | 	git tag | grep '^10\.' | sort -V --reverse | \ | ||||||
|  | 	while read tag_name; do \ | ||||||
|  | 	if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \ | ||||||
|  | 	| grep -qE '"(draft|prerelease)": true'; \ | ||||||
|  | 	then git checkout $tag_name; break; fi ; done | ||||||
|  | 	``` | ||||||
|  | 
 | ||||||
| 3. `npm install` | 3. `npm install` | ||||||
| 4. `NODE_ENV=production npm run build` | 4. `NODE_ENV=production npm run build` | ||||||
| 5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する | 5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する | ||||||
|  |  | ||||||
|  | @ -877,7 +877,6 @@ desktop/views/components/post-form.vue: | ||||||
|   posting: "Posting" |   posting: "Posting" | ||||||
|   attach-media-from-local: "Attach media from your device" |   attach-media-from-local: "Attach media from your device" | ||||||
|   attach-media-from-drive: "Attach media from your Drive" |   attach-media-from-drive: "Attach media from your Drive" | ||||||
|   attach-cancel: "Cancel attachment" |  | ||||||
|   insert-a-kao: "v('ω')v" |   insert-a-kao: "v('ω')v" | ||||||
|   create-poll: "Create a poll" |   create-poll: "Create a poll" | ||||||
|   text-remain: "{} characters remaining" |   text-remain: "{} characters remaining" | ||||||
|  | @ -970,6 +969,10 @@ common/views/components/password-settings.vue: | ||||||
|   not-match: "The new passwords do not match" |   not-match: "The new passwords do not match" | ||||||
|   changed: "Password changed" |   changed: "Password changed" | ||||||
|   failed: "Failed to change password" |   failed: "Failed to change password" | ||||||
|  | common/views/components/post-form-attaches.vue: | ||||||
|  |   attach-cancel: "Remove Attachment" | ||||||
|  |   mark-as-sensitive: "Mark as 'sensitive'" | ||||||
|  |   unmark-as-sensitive: "Unmark as 'sensitive'" | ||||||
| desktop/views/components/sub-note-content.vue: | desktop/views/components/sub-note-content.vue: | ||||||
|   private: "This post is private" |   private: "This post is private" | ||||||
|   deleted: "This post has been deleted" |   deleted: "This post has been deleted" | ||||||
|  |  | ||||||
|  | @ -513,8 +513,12 @@ common/views/components/user-menu.vue: | ||||||
|   mention: "メンション" |   mention: "メンション" | ||||||
|   mute: "ミュート" |   mute: "ミュート" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   mute-confirm: "このユーザーをミュートしますか?" | ||||||
|  |   unmute-confirm: "このユーザーをミュート解除しますか?" | ||||||
|   block: "ブロック" |   block: "ブロック" | ||||||
|   unblock: "ブロック解除" |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|  |   unblock-confirm: "このユーザーをブロック解除しますか?" | ||||||
|   push-to-list: "リストに追加" |   push-to-list: "リストに追加" | ||||||
|   select-list: "リストを選択してください" |   select-list: "リストを選択してください" | ||||||
|   report-abuse: "スパムを報告" |   report-abuse: "スパムを報告" | ||||||
|  | @ -522,8 +526,12 @@ common/views/components/user-menu.vue: | ||||||
|   report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。" |   report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。" | ||||||
|   silence: "サイレンス" |   silence: "サイレンス" | ||||||
|   unsilence: "サイレンス解除" |   unsilence: "サイレンス解除" | ||||||
|  |   silence-confirm: "このユーザーをサイレンスしますか?" | ||||||
|  |   unsilence-confirm: "このユーザーをサイレンス解除しますか?" | ||||||
|   suspend: "凍結" |   suspend: "凍結" | ||||||
|   unsuspend: "凍結解除" |   unsuspend: "凍結解除" | ||||||
|  |   suspend-confirm: "このユーザーを凍結しますか?" | ||||||
|  |   unsuspend-confirm: "このユーザーを凍結解除しますか?" | ||||||
| 
 | 
 | ||||||
| common/views/components/poll.vue: | common/views/components/poll.vue: | ||||||
|   vote-to: "「{}」に投票する" |   vote-to: "「{}」に投票する" | ||||||
|  | @ -967,7 +975,6 @@ desktop/views/components/post-form.vue: | ||||||
|   posting: "投稿中" |   posting: "投稿中" | ||||||
|   attach-media-from-local: "PCからメディアを添付" |   attach-media-from-local: "PCからメディアを添付" | ||||||
|   attach-media-from-drive: "ドライブからメディアを添付" |   attach-media-from-drive: "ドライブからメディアを添付" | ||||||
|   attach-cancel: "添付取り消し" |  | ||||||
|   insert-a-kao: "v('ω')v" |   insert-a-kao: "v('ω')v" | ||||||
|   create-poll: "アンケートを作成" |   create-poll: "アンケートを作成" | ||||||
|   text-remain: "残り{}文字" |   text-remain: "残り{}文字" | ||||||
|  | @ -1073,6 +1080,11 @@ common/views/components/password-settings.vue: | ||||||
|   changed: "パスワードを変更しました" |   changed: "パスワードを変更しました" | ||||||
|   failed: "パスワード変更に失敗しました" |   failed: "パスワード変更に失敗しました" | ||||||
| 
 | 
 | ||||||
|  | common/views/components/post-form-attaches.vue: | ||||||
|  |   attach-cancel: "添付取り消し" | ||||||
|  |   mark-as-sensitive: "閲覧注意に設定" | ||||||
|  |   unmark-as-sensitive: "閲覧注意を解除" | ||||||
|  | 
 | ||||||
| desktop/views/components/sub-note-content.vue: | desktop/views/components/sub-note-content.vue: | ||||||
|   private: "この投稿は非公開です" |   private: "この投稿は非公開です" | ||||||
|   deleted: "この投稿は削除されました" |   deleted: "この投稿は削除されました" | ||||||
|  | @ -1326,7 +1338,9 @@ admin/views/users.vue: | ||||||
|   unsuspend-confirm: "凍結を解除しますか?" |   unsuspend-confirm: "凍結を解除しますか?" | ||||||
|   unsuspended: "凍結を解除しました" |   unsuspended: "凍結を解除しました" | ||||||
|   make-silence: "サイレンス" |   make-silence: "サイレンス" | ||||||
|  |   silence-confirm: "サイレンスしますか?" | ||||||
|   unmake-silence: "サイレンスの解除" |   unmake-silence: "サイレンスの解除" | ||||||
|  |   unsilence-confirm: "サイレンスを解除しますか?" | ||||||
|   verify: "公式アカウントにする" |   verify: "公式アカウントにする" | ||||||
|   verify-confirm: "公式アカウントにしますか?" |   verify-confirm: "公式アカウントにしますか?" | ||||||
|   verified: "公式アカウントにしました" |   verified: "公式アカウントにしました" | ||||||
|  |  | ||||||
							
								
								
									
										130
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										130
									
								
								package.json
									
										
									
									
									
								
							|  | @ -1,7 +1,7 @@ | ||||||
| { | { | ||||||
| 	"name": "misskey", | 	"name": "misskey", | ||||||
| 	"author": "syuilo <i@syuilo.com>", | 	"author": "syuilo <i@syuilo.com>", | ||||||
| 	"version": "10.99.0", | 	"version": "10.102.4", | ||||||
| 	"codename": "nighthike", | 	"codename": "nighthike", | ||||||
| 	"repository": { | 	"repository": { | ||||||
| 		"type": "git", | 		"type": "git", | ||||||
|  | @ -22,24 +22,27 @@ | ||||||
| 		"test": "gulp test", | 		"test": "gulp test", | ||||||
| 		"format": "gulp format" | 		"format": "gulp format" | ||||||
| 	}, | 	}, | ||||||
|  | 	"resolutions": { | ||||||
|  | 		"gulp-cssnano/cssnano/postcss-svgo/svgo/js-yaml": "^3.13.1", | ||||||
|  | 		"video-thumbnail-generator/lodash": "^4.17.11" | ||||||
|  | 	}, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@fortawesome/fontawesome-svg-core": "1.2.15", | 		"@fortawesome/fontawesome-svg-core": "1.2.19", | ||||||
| 		"@fortawesome/free-brands-svg-icons": "5.7.2", | 		"@fortawesome/free-brands-svg-icons": "5.9.0", | ||||||
| 		"@fortawesome/free-regular-svg-icons": "5.7.2", | 		"@fortawesome/free-regular-svg-icons": "5.9.0", | ||||||
| 		"@fortawesome/free-solid-svg-icons": "5.7.2", | 		"@fortawesome/free-solid-svg-icons": "5.9.0", | ||||||
| 		"@fortawesome/vue-fontawesome": "0.1.5", | 		"@fortawesome/vue-fontawesome": "0.1.6", | ||||||
| 		"@koa/cors": "2.2.3", | 		"@koa/cors": "2.2.3", | ||||||
| 		"@prezzemolo/rap": "0.1.2", | 		"@prezzemolo/rap": "0.1.2", | ||||||
| 		"@prezzemolo/zip": "0.0.3", | 		"@prezzemolo/zip": "0.0.3", | ||||||
| 		"@types/bcryptjs": "2.4.2", | 		"@types/bcryptjs": "2.4.2", | ||||||
| 		"@types/bull": "3.5.8", | 		"@types/bull": "3.5.14", | ||||||
| 		"@types/chai-http": "3.0.5", | 		"@types/chai-http": "3.0.5", | ||||||
| 		"@types/dateformat": "3.0.0", | 		"@types/dateformat": "3.0.0", | ||||||
| 		"@types/deep-equal": "1.0.1", | 		"@types/deep-equal": "1.0.1", | ||||||
| 		"@types/double-ended-queue": "2.1.0", | 		"@types/double-ended-queue": "2.1.0", | ||||||
| 		"@types/elasticsearch": "5.0.30", | 		"@types/elasticsearch": "5.0.34", | ||||||
| 		"@types/file-type": "10.6.0", | 		"@types/gulp": "4.0.6", | ||||||
| 		"@types/gulp": "4.0.5", |  | ||||||
| 		"@types/gulp-mocha": "0.0.32", | 		"@types/gulp-mocha": "0.0.32", | ||||||
| 		"@types/gulp-rename": "0.0.33", | 		"@types/gulp-rename": "0.0.33", | ||||||
| 		"@types/gulp-replace": "0.0.31", | 		"@types/gulp-replace": "0.0.31", | ||||||
|  | @ -47,65 +50,65 @@ | ||||||
| 		"@types/gulp-util": "3.0.34", | 		"@types/gulp-util": "3.0.34", | ||||||
| 		"@types/is-root": "1.0.0", | 		"@types/is-root": "1.0.0", | ||||||
| 		"@types/is-url": "1.2.28", | 		"@types/is-url": "1.2.28", | ||||||
| 		"@types/js-yaml": "3.12.0", | 		"@types/js-yaml": "3.12.1", | ||||||
| 		"@types/jsdom": "12.2.3", | 		"@types/jsdom": "12.2.3", | ||||||
| 		"@types/katex": "0.10.1", | 		"@types/katex": "0.10.1", | ||||||
| 		"@types/koa": "2.0.48", | 		"@types/koa": "2.0.48", | ||||||
| 		"@types/koa-bodyparser": "5.0.2", | 		"@types/koa-bodyparser": "5.0.2", | ||||||
| 		"@types/koa-compress": "2.0.8", | 		"@types/koa-compress": "2.0.9", | ||||||
| 		"@types/koa-cors": "0.0.0", | 		"@types/koa-cors": "0.0.0", | ||||||
| 		"@types/koa-favicon": "2.0.19", | 		"@types/koa-favicon": "2.0.19", | ||||||
| 		"@types/koa-logger": "3.1.1", | 		"@types/koa-logger": "3.1.1", | ||||||
| 		"@types/koa-mount": "3.0.1", | 		"@types/koa-mount": "3.0.1", | ||||||
| 		"@types/koa-multer": "1.0.0", | 		"@types/koa-multer": "1.0.0", | ||||||
| 		"@types/koa-router": "7.0.40", | 		"@types/koa-router": "7.0.40", | ||||||
| 		"@types/koa-send": "4.1.1", | 		"@types/koa-send": "4.1.2", | ||||||
| 		"@types/koa-views": "2.0.3", | 		"@types/koa-views": "2.0.3", | ||||||
| 		"@types/koa__cors": "2.2.3", | 		"@types/koa__cors": "2.2.3", | ||||||
| 		"@types/minio": "7.0.1", | 		"@types/minio": "7.0.2", | ||||||
| 		"@types/mkdirp": "0.5.2", | 		"@types/mkdirp": "0.5.2", | ||||||
| 		"@types/mocha": "5.2.5", | 		"@types/mocha": "5.2.7", | ||||||
| 		"@types/mongodb": "3.1.20", | 		"@types/mongodb": "3.1.28", | ||||||
| 		"@types/node": "11.10.4", | 		"@types/node": "11.10.4", | ||||||
| 		"@types/nodemailer": "4.6.6", | 		"@types/nodemailer": "6.2.0", | ||||||
| 		"@types/nprogress": "0.0.29", | 		"@types/nprogress": "0.2.0", | ||||||
| 		"@types/oauth": "0.9.1", | 		"@types/oauth": "0.9.1", | ||||||
| 		"@types/parse5": "5.0.0", | 		"@types/parse5": "5.0.0", | ||||||
| 		"@types/parsimmon": "1.10.0", | 		"@types/parsimmon": "1.10.0", | ||||||
| 		"@types/portscanner": "2.1.0", | 		"@types/portscanner": "2.1.0", | ||||||
| 		"@types/pug": "2.0.4", | 		"@types/pug": "2.0.4", | ||||||
| 		"@types/qrcode": "1.3.0", | 		"@types/qrcode": "1.3.3", | ||||||
| 		"@types/ratelimiter": "2.1.28", | 		"@types/ratelimiter": "2.1.28", | ||||||
| 		"@types/redis": "2.8.10", | 		"@types/redis": "2.8.13", | ||||||
| 		"@types/rename": "1.0.1", | 		"@types/rename": "1.0.1", | ||||||
| 		"@types/request": "2.48.1", | 		"@types/request": "2.48.1", | ||||||
| 		"@types/request-promise-native": "1.0.15", | 		"@types/request-promise-native": "1.0.16", | ||||||
| 		"@types/request-stats": "3.0.0", | 		"@types/request-stats": "3.0.0", | ||||||
| 		"@types/rimraf": "2.0.2", | 		"@types/rimraf": "2.0.2", | ||||||
| 		"@types/seedrandom": "2.4.27", | 		"@types/seedrandom": "2.4.28", | ||||||
| 		"@types/sharp": "0.21.2", | 		"@types/sharp": "0.22.2", | ||||||
| 		"@types/showdown": "1.9.2", | 		"@types/showdown": "1.9.2", | ||||||
| 		"@types/speakeasy": "2.0.4", | 		"@types/speakeasy": "2.0.4", | ||||||
| 		"@types/systeminformation": "3.23.1", | 		"@types/systeminformation": "3.23.1", | ||||||
| 		"@types/tinycolor2": "1.4.1", | 		"@types/tinycolor2": "1.4.2", | ||||||
| 		"@types/tmp": "0.0.33", | 		"@types/tmp": "0.1.0", | ||||||
| 		"@types/uuid": "3.4.4", | 		"@types/uuid": "3.4.4", | ||||||
| 		"@types/web-push": "3.3.0", | 		"@types/web-push": "3.3.0", | ||||||
| 		"@types/webpack": "4.4.24", | 		"@types/webpack": "4.4.32", | ||||||
| 		"@types/webpack-stream": "3.2.10", | 		"@types/webpack-stream": "3.2.10", | ||||||
| 		"@types/websocket": "0.0.40", | 		"@types/websocket": "0.0.40", | ||||||
| 		"@types/ws": "6.0.1", | 		"@types/ws": "6.0.1", | ||||||
| 		"animejs": "3.0.1", | 		"animejs": "3.0.1", | ||||||
| 		"apexcharts": "3.6.5", | 		"apexcharts": "3.8.0", | ||||||
| 		"autobind-decorator": "2.4.0", | 		"autobind-decorator": "2.4.0", | ||||||
| 		"autosize": "4.0.2", | 		"autosize": "4.0.2", | ||||||
| 		"autwh": "0.1.0", | 		"autwh": "0.1.0", | ||||||
| 		"bcryptjs": "2.4.3", | 		"bcryptjs": "2.4.3", | ||||||
| 		"bootstrap-vue": "2.0.0-rc.13", | 		"bootstrap-vue": "2.0.0-rc.22", | ||||||
| 		"bull": "3.7.0", | 		"bull": "3.10.0", | ||||||
| 		"cafy": "15.1.0", | 		"cafy": "15.1.1", | ||||||
| 		"chai": "4.2.0", | 		"chai": "4.2.0", | ||||||
| 		"chai-http": "4.2.1", | 		"chai-http": "4.3.0", | ||||||
| 		"chalk": "2.4.2", | 		"chalk": "2.4.2", | ||||||
| 		"commander": "2.20.0", | 		"commander": "2.20.0", | ||||||
| 		"content-disposition": "0.5.3", | 		"content-disposition": "0.5.3", | ||||||
|  | @ -115,18 +118,18 @@ | ||||||
| 		"dateformat": "3.0.3", | 		"dateformat": "3.0.3", | ||||||
| 		"deep-equal": "1.0.1", | 		"deep-equal": "1.0.1", | ||||||
| 		"deepcopy": "0.6.3", | 		"deepcopy": "0.6.3", | ||||||
| 		"diskusage": "1.0.0", | 		"diskusage": "1.1.1", | ||||||
| 		"double-ended-queue": "2.1.0-0", | 		"double-ended-queue": "2.1.0-0", | ||||||
| 		"elasticsearch": "15.4.1", | 		"elasticsearch": "15.4.1", | ||||||
| 		"emojilib": "2.4.0", | 		"emojilib": "2.4.0", | ||||||
| 		"escape-regexp": "0.0.1", | 		"escape-regexp": "0.0.1", | ||||||
| 		"eslint": "5.15.1", | 		"eslint": "5.16.0", | ||||||
| 		"eslint-plugin-vue": "5.2.2", | 		"eslint-plugin-vue": "5.2.2", | ||||||
| 		"eventemitter3": "3.1.0", | 		"eventemitter3": "3.1.2", | ||||||
| 		"feed": "2.0.4", | 		"feed": "2.0.4", | ||||||
| 		"file-type": "10.10.0", | 		"file-type": "10.10.0", | ||||||
| 		"fuckadblock": "3.2.1", | 		"fuckadblock": "3.2.1", | ||||||
| 		"gulp": "4.0.0", | 		"gulp": "4.0.2", | ||||||
| 		"gulp-cssnano": "2.1.3", | 		"gulp-cssnano": "2.1.3", | ||||||
| 		"gulp-imagemin": "5.0.3", | 		"gulp-imagemin": "5.0.3", | ||||||
| 		"gulp-mocha": "6.0.0", | 		"gulp-mocha": "6.0.0", | ||||||
|  | @ -135,20 +138,20 @@ | ||||||
| 		"gulp-sourcemaps": "2.6.5", | 		"gulp-sourcemaps": "2.6.5", | ||||||
| 		"gulp-stylus": "2.7.0", | 		"gulp-stylus": "2.7.0", | ||||||
| 		"gulp-tslint": "8.1.4", | 		"gulp-tslint": "8.1.4", | ||||||
| 		"gulp-typescript": "5.0.0", | 		"gulp-typescript": "5.0.1", | ||||||
| 		"gulp-uglify": "3.0.2", | 		"gulp-uglify": "3.0.2", | ||||||
| 		"gulp-util": "3.0.8", | 		"gulp-util": "3.0.8", | ||||||
| 		"hard-source-webpack-plugin": "0.13.1", | 		"hard-source-webpack-plugin": "0.13.1", | ||||||
| 		"html-minifier": "3.5.21", | 		"html-minifier": "4.0.0", | ||||||
| 		"http-signature": "1.2.0", | 		"http-signature": "1.2.0", | ||||||
| 		"insert-text-at-cursor": "0.1.2", | 		"insert-text-at-cursor": "0.2.0", | ||||||
| 		"is-root": "2.0.0", | 		"is-root": "2.1.0", | ||||||
| 		"is-svg": "4.0.0", | 		"is-svg": "4.2.0", | ||||||
| 		"js-yaml": "3.13.0", | 		"js-yaml": "3.13.1", | ||||||
| 		"jsdom": "14.0.0", | 		"jsdom": "15.1.1", | ||||||
| 		"json5": "2.1.0", | 		"json5": "2.1.0", | ||||||
| 		"json5-loader": "1.0.1", | 		"json5-loader": "2.0.0", | ||||||
| 		"katex": "0.10.1", | 		"katex": "0.10.2", | ||||||
| 		"koa": "2.7.0", | 		"koa": "2.7.0", | ||||||
| 		"koa-bodyparser": "4.2.1", | 		"koa-bodyparser": "4.2.1", | ||||||
| 		"koa-compress": "3.0.0", | 		"koa-compress": "3.0.0", | ||||||
|  | @ -164,15 +167,15 @@ | ||||||
| 		"langmap": "0.0.16", | 		"langmap": "0.0.16", | ||||||
| 		"loader-utils": "1.2.3", | 		"loader-utils": "1.2.3", | ||||||
| 		"lookup-dns-cache": "2.1.0", | 		"lookup-dns-cache": "2.1.0", | ||||||
| 		"minio": "7.0.5", | 		"minio": "7.0.8", | ||||||
| 		"mkdirp": "0.5.1", | 		"mkdirp": "0.5.1", | ||||||
| 		"mocha": "5.2.0", | 		"mocha": "5.2.0", | ||||||
| 		"moji": "0.5.1", | 		"moji": "0.5.1", | ||||||
| 		"moment": "2.24.0", | 		"moment": "2.24.0", | ||||||
| 		"mongodb": "3.2.2", | 		"mongodb": "3.2.7", | ||||||
| 		"monk": "6.0.6", | 		"monk": "6.0.6", | ||||||
| 		"ms": "2.1.1", | 		"ms": "2.1.2", | ||||||
| 		"nan": "2.12.1", | 		"nan": "2.14.0", | ||||||
| 		"nested-property": "0.0.7", | 		"nested-property": "0.0.7", | ||||||
| 		"nodemailer": "5.1.1", | 		"nodemailer": "5.1.1", | ||||||
| 		"nprogress": "0.2.0", | 		"nprogress": "0.2.0", | ||||||
|  | @ -203,7 +206,7 @@ | ||||||
| 		"rndstr": "1.0.0", | 		"rndstr": "1.0.0", | ||||||
| 		"s-age": "1.1.2", | 		"s-age": "1.1.2", | ||||||
| 		"seedrandom": "2.4.4", | 		"seedrandom": "2.4.4", | ||||||
| 		"sharp": "0.22.0", | 		"sharp": "0.22.1", | ||||||
| 		"showdown": "1.9.0", | 		"showdown": "1.9.0", | ||||||
| 		"showdown-highlightjs-extension": "0.1.2", | 		"showdown-highlightjs-extension": "0.1.2", | ||||||
| 		"speakeasy": "2.0.0", | 		"speakeasy": "2.0.0", | ||||||
|  | @ -212,17 +215,17 @@ | ||||||
| 		"stylus": "0.54.5", | 		"stylus": "0.54.5", | ||||||
| 		"stylus-loader": "3.0.2", | 		"stylus-loader": "3.0.2", | ||||||
| 		"summaly": "2.2.0", | 		"summaly": "2.2.0", | ||||||
| 		"systeminformation": "4.0.16", | 		"systeminformation": "4.9.0", | ||||||
| 		"syuilo-password-strength": "0.0.1", | 		"syuilo-password-strength": "0.0.1", | ||||||
| 		"terser-webpack-plugin": "1.2.3", | 		"terser-webpack-plugin": "1.3.0", | ||||||
| 		"textarea-caret": "3.1.0", | 		"textarea-caret": "3.1.0", | ||||||
| 		"tinycolor2": "1.4.1", | 		"tinycolor2": "1.4.1", | ||||||
| 		"tmp": "0.0.33", | 		"tmp": "0.1.0", | ||||||
| 		"ts-loader": "5.3.3", | 		"ts-loader": "5.3.3", | ||||||
| 		"ts-node": "8.0.3", | 		"ts-node": "8.0.3", | ||||||
| 		"tslint": "5.13.1", | 		"tslint": "5.17.0", | ||||||
| 		"tslint-sonarts": "1.9.0", | 		"tslint-sonarts": "1.9.0", | ||||||
| 		"typescript": "3.3.3333", | 		"typescript": "3.5.1", | ||||||
| 		"typescript-eslint-parser": "22.0.0", | 		"typescript-eslint-parser": "22.0.0", | ||||||
| 		"uglify-es": "3.3.9", | 		"uglify-es": "3.3.9", | ||||||
| 		"url-loader": "1.1.2", | 		"url-loader": "1.1.2", | ||||||
|  | @ -232,27 +235,26 @@ | ||||||
| 		"video-thumbnail-generator": "1.1.3", | 		"video-thumbnail-generator": "1.1.3", | ||||||
| 		"vue": "2.6.10", | 		"vue": "2.6.10", | ||||||
| 		"vue-color": "2.7.0", | 		"vue-color": "2.7.0", | ||||||
| 		"vue-content-loading": "1.5.3", | 		"vue-content-loading": "1.6.0", | ||||||
| 		"vue-cropperjs": "3.0.0", | 		"vue-cropperjs": "3.0.0", | ||||||
| 		"vue-i18n": "8.10.0", | 		"vue-i18n": "8.11.2", | ||||||
| 		"vue-js-modal": "1.3.28", | 		"vue-js-modal": "1.3.31", | ||||||
| 		"vue-json-pretty": "1.6.0", | 		"vue-json-pretty": "1.6.0", | ||||||
| 		"vue-loader": "15.7.0", | 		"vue-loader": "15.7.0", | ||||||
| 		"vue-marquee-text-component": "1.1.1", | 		"vue-marquee-text-component": "1.1.1", | ||||||
| 		"vue-prism-component": "1.1.1", | 		"vue-prism-component": "1.1.1", | ||||||
| 		"vue-router": "3.0.2", | 		"vue-router": "3.0.6", | ||||||
| 		"vue-sequential-entrance": "1.1.3", | 		"vue-sequential-entrance": "1.1.3", | ||||||
| 		"vue-style-loader": "4.1.2", | 		"vue-style-loader": "4.1.2", | ||||||
| 		"vue-svg-inline-loader": "1.2.15", | 		"vue-svg-inline-loader": "1.2.15", | ||||||
| 		"vue-template-compiler": "2.6.10", | 		"vue-template-compiler": "2.6.10", | ||||||
| 		"vuedraggable": "2.20.0", | 		"vuedraggable": "2.21.0", | ||||||
| 		"vuewordcloud": "18.7.11", | 		"vuewordcloud": "18.7.11", | ||||||
| 		"vuex": "3.1.0", | 		"vuex": "3.1.1", | ||||||
| 		"vuex-persistedstate": "2.5.4", | 		"vuex-persistedstate": "2.5.4", | ||||||
| 		"web-push": "3.3.3", | 		"web-push": "3.3.5", | ||||||
| 		"webfinger.js": "2.7.0", | 		"webpack": "4.33.0", | ||||||
| 		"webpack": "4.28.4", | 		"webpack-cli": "3.3.3", | ||||||
| 		"webpack-cli": "3.2.3", |  | ||||||
| 		"websocket": "1.0.28", | 		"websocket": "1.0.28", | ||||||
| 		"ws": "6.2.1", | 		"ws": "6.2.1", | ||||||
| 		"xev": "2.0.1" | 		"xev": "2.0.1" | ||||||
|  |  | ||||||
							
								
								
									
										65
									
								
								src/@types/webfinger.js.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										65
									
								
								src/@types/webfinger.js.d.ts
									
										
									
									
										vendored
									
									
								
							|  | @ -1,65 +0,0 @@ | ||||||
| declare module 'webfinger.js' { |  | ||||||
| 	interface IWebFingerConstructorConfig { |  | ||||||
| 		tls_only?: boolean; |  | ||||||
| 		webfist_fallback?: boolean; |  | ||||||
| 		uri_fallback?: boolean; |  | ||||||
| 		request_timeout?: number; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	type JRDProperties = { [type: string]: string }; |  | ||||||
| 
 |  | ||||||
| 	interface IJRDLink { |  | ||||||
| 		rel: string; |  | ||||||
| 		type?: string; |  | ||||||
| 		href?: string; |  | ||||||
| 		template?: string; |  | ||||||
| 		titles?: { [lang: string]: string }; |  | ||||||
| 		properties?: JRDProperties; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	interface IJRD { |  | ||||||
| 		subject?: string; |  | ||||||
| 		expires?: Date; |  | ||||||
| 		aliases?: string[]; |  | ||||||
| 		properties?: JRDProperties; |  | ||||||
| 		links?: IJRDLink[]; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	interface IIDXLinks { |  | ||||||
| 		'avatar': IJRDLink[]; |  | ||||||
| 		'remotestorage': IJRDLink[]; |  | ||||||
| 		'blog': IJRDLink[]; |  | ||||||
| 		'vcard': IJRDLink[]; |  | ||||||
| 		'updates': IJRDLink[]; |  | ||||||
| 		'share': IJRDLink[]; |  | ||||||
| 		'profile': IJRDLink[]; |  | ||||||
| 		'webfist': IJRDLink[]; |  | ||||||
| 		'camlistore': IJRDLink[]; |  | ||||||
| 		[type: string]: IJRDLink[]; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	interface IIDXProperties { |  | ||||||
| 		'name': string; |  | ||||||
| 		[type: string]: string; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	interface IIDX { |  | ||||||
| 		links: IIDXLinks; |  | ||||||
| 		properties: IIDXProperties; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	interface ILookupCallbackResult { |  | ||||||
| 		object: IJRD; |  | ||||||
| 		json: string; |  | ||||||
| 		idx: IIDX; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	type LookupCallback = (err: Error | string, result?: ILookupCallbackResult) => void; |  | ||||||
| 
 |  | ||||||
| 	export class WebFinger { |  | ||||||
| 		constructor(config?: IWebFingerConstructorConfig); |  | ||||||
| 
 |  | ||||||
| 		public lookup(address: string, cb: LookupCallback): NodeJS.Timeout; |  | ||||||
| 		public lookupLink(address: string, rel: string, cb: IJRDLink): void; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -38,7 +38,7 @@ | ||||||
| 				<div class="kidvdlkg" v-for="file in files"> | 				<div class="kidvdlkg" v-for="file in files"> | ||||||
| 					<div @click="file._open = !file._open"> | 					<div @click="file._open = !file._open"> | ||||||
| 						<div> | 						<div> | ||||||
| 							<div class="thumbnail" :style="thumbnail(file)"></div> | 							<x-file-thumbnail class="thumbnail" :file="file" fit="contain" @click="showFileMenu(file)"/> | ||||||
| 						</div> | 						</div> | ||||||
| 						<div> | 						<div> | ||||||
| 							<header> | 							<header> | ||||||
|  | @ -75,10 +75,15 @@ import Vue from 'vue'; | ||||||
| import i18n from '../../i18n'; | import i18n from '../../i18n'; | ||||||
| import { faCloud, faTerminal, faSearch } from '@fortawesome/free-solid-svg-icons'; | import { faCloud, faTerminal, faSearch } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; | import { faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  | import XFileThumbnail from '../../common/views/components/drive-file-thumbnail.vue'; | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('admin/views/drive.vue'), | 	i18n: i18n('admin/views/drive.vue'), | ||||||
| 
 | 
 | ||||||
|  | 	components: { | ||||||
|  | 		XFileThumbnail | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			file: null, | 			file: null, | ||||||
|  | @ -151,13 +156,6 @@ export default Vue.extend({ | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		thumbnail(file: any): any { |  | ||||||
| 			return { |  | ||||||
| 				'background-color': file.properties.avgColor && file.properties.avgColor.length == 3 ? `rgb(${file.properties.avgColor.join(',')})` : 'transparent', |  | ||||||
| 				'background-image': `url(${file.thumbnailUrl})` |  | ||||||
| 			}; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		async del(file: any) { | 		async del(file: any) { | ||||||
| 			const process = async () => { | 			const process = async () => { | ||||||
| 				await this.$root.api('drive/files/delete', { fileId: file.id }); | 				await this.$root.api('drive/files/delete', { fileId: file.id }); | ||||||
|  | @ -179,9 +177,9 @@ export default Vue.extend({ | ||||||
| 			this.$root.api('drive/files/update', { | 			this.$root.api('drive/files/update', { | ||||||
| 				fileId: file.id, | 				fileId: file.id, | ||||||
| 				isSensitive: !file.isSensitive | 				isSensitive: !file.isSensitive | ||||||
| 			}); | 			}).then(() => { | ||||||
| 
 |  | ||||||
| 				file.isSensitive = !file.isSensitive; | 				file.isSensitive = !file.isSensitive; | ||||||
|  | 			}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		async show() { | 		async show() { | ||||||
|  | @ -244,7 +242,7 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 		> div:nth-child(1) | 		> div:nth-child(1) | ||||||
| 			> .thumbnail | 			> .thumbnail | ||||||
| 				display block | 				display flex | ||||||
| 				width 64px | 				width 64px | ||||||
| 				height 64px | 				height 64px | ||||||
| 				background-size cover | 				background-size cover | ||||||
|  |  | ||||||
|  | @ -130,7 +130,7 @@ | ||||||
| 					<span>{{ $t('status') }}</span> | 					<span>{{ $t('status') }}</span> | ||||||
| 				</header> | 				</header> | ||||||
| 				<div v-for="instance in instances" :style="{ opacity: instance.isNotResponding ? 0.5 : 1 }"> | 				<div v-for="instance in instances" :style="{ opacity: instance.isNotResponding ? 0.5 : 1 }"> | ||||||
| 					<a @click.prevent="showInstance(instance.host)" target="_blank" :href="`https://${instance.host}`" :style="{ textDecoration: instance.isMarkedAsClosed ? 'line-through' : 'none' }">{{ instance.host }}</a> | 					<a @click.prevent="showInstance(instance.host)" rel="nofollow noopener" target="_blank" :href="`https://${instance.host}`" :style="{ textDecoration: instance.isMarkedAsClosed ? 'line-through' : 'none' }">{{ instance.host }}</a> | ||||||
| 					<span>{{ instance.notesCount | number }}</span> | 					<span>{{ instance.notesCount | number }}</span> | ||||||
| 					<span>{{ instance.usersCount | number }}</span> | 					<span>{{ instance.usersCount | number }}</span> | ||||||
| 					<span>{{ instance.followingCount | number }}</span> | 					<span>{{ instance.followingCount | number }}</span> | ||||||
|  |  | ||||||
|  | @ -232,6 +232,8 @@ export default Vue.extend({ | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		async silenceUser() { | 		async silenceUser() { | ||||||
|  | 			if (!await this.getConfirmed(this.$t('silence-confirm'))) return; | ||||||
|  | 
 | ||||||
| 			const process = async () => { | 			const process = async () => { | ||||||
| 				await this.$root.api('admin/silence-user', { userId: this.user._id }); | 				await this.$root.api('admin/silence-user', { userId: this.user._id }); | ||||||
| 				this.$root.dialog({ | 				this.$root.dialog({ | ||||||
|  | @ -251,6 +253,8 @@ export default Vue.extend({ | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		async unsilenceUser() { | 		async unsilenceUser() { | ||||||
|  | 			if (!await this.getConfirmed(this.$t('unsilence-confirm'))) return; | ||||||
|  | 
 | ||||||
| 			const process = async () => { | 			const process = async () => { | ||||||
| 				await this.$root.api('admin/unsilence-user', { userId: this.user._id }); | 				await this.$root.api('admin/unsilence-user', { userId: this.user._id }); | ||||||
| 				this.$root.dialog({ | 				this.$root.dialog({ | ||||||
|  |  | ||||||
|  | @ -16,10 +16,10 @@ | ||||||
| 	</ol> | 	</ol> | ||||||
| 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | ||||||
| 		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> | 		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> | ||||||
| 			<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="emoji.url" :alt="emoji.emoji"/></span> | 			<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> | ||||||
| 			<span class="emoji" v-else-if="!useOsDefaultEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span> | 			<span class="emoji" v-else-if="!useOsDefaultEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||||
| 			<span class="emoji" v-else>{{ emoji.emoji }}</span> | 			<span class="emoji" v-else>{{ emoji.emoji }}</span> | ||||||
| 			<span class="name">{{ beforeQ }}<b>{{ q }}</b>{{ afterQ }}</span> | 			<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> | ||||||
| 			<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span> | 			<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span> | ||||||
| 		</li> | 		</li> | ||||||
| 	</ol> | 	</ol> | ||||||
|  | @ -31,6 +31,7 @@ import Vue from 'vue'; | ||||||
| import * as emojilib from 'emojilib'; | import * as emojilib from 'emojilib'; | ||||||
| import contains from '../../../common/scripts/contains'; | import contains from '../../../common/scripts/contains'; | ||||||
| import { twemojiBase } from '../../../../../misc/twemoji-base'; | import { twemojiBase } from '../../../../../misc/twemoji-base'; | ||||||
|  | import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url'; | ||||||
| 
 | 
 | ||||||
| type EmojiDef = { | type EmojiDef = { | ||||||
| 	emoji: string; | 	emoji: string; | ||||||
|  | @ -78,6 +79,7 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
|  | 			getStaticImageUrl, | ||||||
| 			fetching: true, | 			fetching: true, | ||||||
| 			users: [], | 			users: [], | ||||||
| 			hashtags: [], | 			hashtags: [], | ||||||
|  | @ -89,14 +91,6 @@ export default Vue.extend({ | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	computed: { | 	computed: { | ||||||
| 		beforeQ(): string { |  | ||||||
| 			return this.emoji.name.split(this.q)[0]; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		afterQ(): string { |  | ||||||
| 			return this.emoji.name.split(this.q)[1] || ''; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		items(): HTMLCollection { | 		items(): HTMLCollection { | ||||||
| 			return (this.$refs.suggests as Element).children; | 			return (this.$refs.suggests as Element).children; | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -183,9 +183,6 @@ export default Vue.extend({ | ||||||
| 	height 100% | 	height 100% | ||||||
| 
 | 
 | ||||||
| 	&.splash | 	&.splash | ||||||
| 		&, * |  | ||||||
| 			pointer-events none !important |  | ||||||
| 
 |  | ||||||
| 		> .main | 		> .main | ||||||
| 			min-width 0 | 			min-width 0 | ||||||
| 			width initial | 			width initial | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <template> | <template> | ||||||
| <a class="a" :href="repositoryUrl" target="_blank" title="View source on GitHub"> | <a class="a" :href="repositoryUrl" rel="noopener" target="_blank" title="View source on GitHub"> | ||||||
| 	<svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="aria-hidden"> | 	<svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="aria-hidden"> | ||||||
| 		<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> | 		<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> | ||||||
| 		<path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor"></path> | 		<path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor"></path> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <template> | <template> | ||||||
| <a class="zxrjzpcj" :href="url" :class="service" target="_blank"> | <a class="zxrjzpcj" :href="url" :class="service" rel="noopener" target="_blank"> | ||||||
| 	<fa :icon="icon" size="lg" fixed-width /><span>{{ text }}</span> | 	<fa :icon="icon" size="lg" fixed-width /><span>{{ text }}</span> | ||||||
| </a> | </a> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
| 			<div class="content" v-if="!message.isDeleted"> | 			<div class="content" v-if="!message.isDeleted"> | ||||||
| 				<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/> | 				<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/> | ||||||
| 				<div class="file" v-if="message.file"> | 				<div class="file" v-if="message.file"> | ||||||
| 					<a :href="message.file.url" target="_blank" :title="message.file.name"> | 					<a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name"> | ||||||
| 						<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name" | 						<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name" | ||||||
| 							:style="{ backgroundColor: message.file.properties.avgColor && message.file.properties.avgColor.length == 3 ? `rgb(${message.file.properties.avgColor.join(',')})` : 'transparent' }"/> | 							:style="{ backgroundColor: message.file.properties.avgColor && message.file.properties.avgColor.length == 3 ? `rgb(${message.file.properties.avgColor.join(',')})` : 'transparent' }"/> | ||||||
| 						<p v-else>{{ message.file.name }}</p> | 						<p v-else>{{ message.file.name }}</p> | ||||||
|  |  | ||||||
|  | @ -270,17 +270,13 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| <style lang="stylus" scoped> | <style lang="stylus" scoped> | ||||||
| .mk-messaging-room | .mk-messaging-room | ||||||
| 	display flex |  | ||||||
| 	flex 1 |  | ||||||
| 	flex-direction column |  | ||||||
| 	height 100% |  | ||||||
| 	background var(--messagingRoomBg) | 	background var(--messagingRoomBg) | ||||||
| 
 | 
 | ||||||
| 	> .body | 	> .body | ||||||
| 		width 100% | 		width 100% | ||||||
| 		max-width 600px | 		max-width 600px | ||||||
| 		margin 0 auto | 		margin 0 auto | ||||||
| 		flex 1 | 		min-height calc(100% - 103px) | ||||||
| 
 | 
 | ||||||
| 		> .init, | 		> .init, | ||||||
| 		> .empty | 		> .empty | ||||||
|  |  | ||||||
|  | @ -174,6 +174,7 @@ export default Vue.component('misskey-flavored-markdown', { | ||||||
| 						key: Math.random(), | 						key: Math.random(), | ||||||
| 						props: { | 						props: { | ||||||
| 							url: token.node.props.url, | 							url: token.node.props.url, | ||||||
|  | 							rel: 'nofollow noopener', | ||||||
| 							target: '_blank' | 							target: '_blank' | ||||||
| 						}, | 						}, | ||||||
| 						attrs: { | 						attrs: { | ||||||
|  | @ -187,6 +188,7 @@ export default Vue.component('misskey-flavored-markdown', { | ||||||
| 						attrs: { | 						attrs: { | ||||||
| 							class: 'link', | 							class: 'link', | ||||||
| 							href: token.node.props.url, | 							href: token.node.props.url, | ||||||
|  | 							rel: 'nofollow noopener', | ||||||
| 							target: '_blank', | 							target: '_blank', | ||||||
| 							title: token.node.props.url, | 							title: token.node.props.url, | ||||||
| 							style: 'color:var(--mfmLink);' | 							style: 'color:var(--mfmLink);' | ||||||
|  |  | ||||||
|  | @ -2,9 +2,9 @@ | ||||||
| <span class="mk-nav"> | <span class="mk-nav"> | ||||||
| 	<a :href="aboutUrl">{{ $t('about') }}</a> | 	<a :href="aboutUrl">{{ $t('about') }}</a> | ||||||
| 	<i>・</i> | 	<i>・</i> | ||||||
| 	<a :href="repositoryUrl">{{ $t('repository') }}</a> | 	<a :href="repositoryUrl" rel="noopener" target="_blank">{{ $t('repository') }}</a> | ||||||
| 	<i>・</i> | 	<i>・</i> | ||||||
| 	<a :href="feedbackUrl" target="_blank">{{ $t('feedback') }}</a> | 	<a :href="feedbackUrl" rel="noopener" target="_blank">{{ $t('feedback') }}</a> | ||||||
| 	<i>・</i> | 	<i>・</i> | ||||||
| 	<a href="/dev">{{ $t('develop') }}</a> | 	<a href="/dev">{{ $t('develop') }}</a> | ||||||
| </span> | </span> | ||||||
|  |  | ||||||
|  | @ -89,9 +89,7 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 		get() { | 		get() { | ||||||
| 			const at = () => { | 			const at = () => { | ||||||
| 				const [date] = moment(this.atDate).toISOString().split('T'); | 				return moment(`${this.atDate} ${this.atTime}`).valueOf(); | ||||||
| 				const [hour, minute] = this.atTime.split(':'); |  | ||||||
| 				return moment(`${date}T${hour}:${minute}Z`).valueOf(); |  | ||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
| 			const after = () => { | 			const after = () => { | ||||||
|  |  | ||||||
							
								
								
									
										140
									
								
								src/client/app/common/views/components/post-form-attaches.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/client/app/common/views/components/post-form-attaches.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,140 @@ | ||||||
|  | <template> | ||||||
|  | <div class="skeikyzd" v-show="files.length != 0"> | ||||||
|  | 	<x-draggable class="files" :list="files" :options="{ animation: 150 }"> | ||||||
|  | 		<div v-for="file in files" :key="file.id" @click="showFileMenu(file, $event)" @contextmenu.prevent="showFileMenu(file, $event)"> | ||||||
|  | 			<x-file-thumbnail :data-id="file.id" class="thumbnail" :file="file" fit="cover"/> | ||||||
|  | 			<img class="remove" @click.stop="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/> | ||||||
|  | 			<div class="sensitive" v-if="file.isSensitive"> | ||||||
|  | 				<fa class="icon" :icon="faExclamationTriangle"/> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</x-draggable> | ||||||
|  | 	<p class="remain">{{ 4 - files.length }}/4</p> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import i18n from '../../../i18n'; | ||||||
|  | import * as XDraggable from 'vuedraggable'; | ||||||
|  | import XMenu from '../../../common/views/components/menu.vue'; | ||||||
|  | import { faTimesCircle, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  | import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; | ||||||
|  | import XFileThumbnail from './drive-file-thumbnail.vue' | ||||||
|  | 
 | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	i18n: i18n('common/views/components/post-form-attaches.vue'), | ||||||
|  | 
 | ||||||
|  | 	components: { | ||||||
|  | 		XDraggable, | ||||||
|  | 		XFileThumbnail | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	props: { | ||||||
|  | 		files: { | ||||||
|  | 			type: Object, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 		detachMediaFn: { | ||||||
|  | 			type: Object, | ||||||
|  | 			required: false | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			faExclamationTriangle | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	methods: { | ||||||
|  | 		detachMedia(id) { | ||||||
|  | 			if (this.detachMediaFn) this.detachMediaFn(id) | ||||||
|  | 			else if (this.$parent.detachMedia) this.$parent.detachMedia(id) | ||||||
|  | 		}, | ||||||
|  | 		toggleSensitive(file) { | ||||||
|  | 			this.$root.api('drive/files/update', { | ||||||
|  | 				fileId: file.id, | ||||||
|  | 				isSensitive: !file.isSensitive | ||||||
|  | 			}).then(() => { | ||||||
|  | 				file.isSensitive = !file.isSensitive; | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 		showFileMenu(file, ev: MouseEvent) { | ||||||
|  | 			this.$root.new(XMenu, { | ||||||
|  | 				items: [{ | ||||||
|  | 					type: 'item', | ||||||
|  | 					text: file.isSensitive ? this.$t('unmark-as-sensitive') : this.$t('mark-as-sensitive'), | ||||||
|  | 					icon: file.isSensitive ? faEyeSlash : faEye, | ||||||
|  | 					action: () => { this.toggleSensitive(file) } | ||||||
|  | 				}, { | ||||||
|  | 					type: 'item', | ||||||
|  | 					text: this.$t('attach-cancel'), | ||||||
|  | 					icon: faTimesCircle, | ||||||
|  | 					action: () => { this.detachMedia(file.id) } | ||||||
|  | 				}], | ||||||
|  | 				source: ev.currentTarget || ev.target | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .skeikyzd | ||||||
|  | 	padding 4px | ||||||
|  | 
 | ||||||
|  | 	> .files | ||||||
|  | 		display flex | ||||||
|  | 		flex-wrap wrap | ||||||
|  | 
 | ||||||
|  | 		> div | ||||||
|  | 			width 64px | ||||||
|  | 			height 64px | ||||||
|  | 			margin 4px | ||||||
|  | 			cursor move | ||||||
|  | 
 | ||||||
|  | 			&:hover > .remove | ||||||
|  | 				display block | ||||||
|  | 
 | ||||||
|  | 			> .thumbnail | ||||||
|  | 				width 100% | ||||||
|  | 				height 100% | ||||||
|  | 				z-index 1 | ||||||
|  | 				color var(--text) | ||||||
|  | 				background-color: rgba(128, 128, 128, 0.3) | ||||||
|  | 
 | ||||||
|  | 			> .remove | ||||||
|  | 				display none | ||||||
|  | 				position absolute | ||||||
|  | 				top -6px | ||||||
|  | 				right -6px | ||||||
|  | 				width 16px | ||||||
|  | 				height 16px | ||||||
|  | 				cursor pointer | ||||||
|  | 				z-index 1000 | ||||||
|  | 
 | ||||||
|  | 			> .sensitive | ||||||
|  | 				display flex | ||||||
|  | 				position absolute | ||||||
|  | 				width 64px | ||||||
|  | 				height 64px | ||||||
|  | 				top 0 | ||||||
|  | 				left 0 | ||||||
|  | 				z-index 2 | ||||||
|  | 				background rgba(17, 17, 17, .7) | ||||||
|  | 				color #fff | ||||||
|  | 
 | ||||||
|  | 				> .icon | ||||||
|  | 					margin auto | ||||||
|  | 
 | ||||||
|  | 	> .remain | ||||||
|  | 		display block | ||||||
|  | 		position absolute | ||||||
|  | 		top 8px | ||||||
|  | 		right 8px | ||||||
|  | 		margin 0 | ||||||
|  | 		padding 0 | ||||||
|  | 		color var(--primaryAlpha04) | ||||||
|  | 
 | ||||||
|  | </style> | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
| 	</template> | 	</template> | ||||||
| 	<div v-if="data && !$store.state.i.twoFactorEnabled"> | 	<div v-if="data && !$store.state.i.twoFactorEnabled"> | ||||||
| 		<ol> | 		<ol> | ||||||
| 			<li>{{ $t('authenticator') }}<a href="https://support.google.com/accounts/answer/1066447" target="_blank">{{ $t('howtoinstall') }}</a></li> | 			<li>{{ $t('authenticator') }}<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank">{{ $t('howtoinstall') }}</a></li> | ||||||
| 			<li>{{ $t('scan') }}<br><img :src="data.qr"></li> | 			<li>{{ $t('scan') }}<br><img :src="data.qr"></li> | ||||||
| 			<li>{{ $t('done') }}<br> | 			<li>{{ $t('done') }}<br> | ||||||
| 				<ui-input v-model="token">{{ $t('token') }}</ui-input> | 				<ui-input v-model="token">{{ $t('token') }}</ui-input> | ||||||
|  |  | ||||||
|  | @ -4,21 +4,21 @@ | ||||||
| 
 | 
 | ||||||
| 	<section v-if="enableTwitterIntegration"> | 	<section v-if="enableTwitterIntegration"> | ||||||
| 		<header><fa :icon="['fab', 'twitter']"/> Twitter</header> | 		<header><fa :icon="['fab', 'twitter']"/> Twitter</header> | ||||||
| 		<p v-if="$store.state.i.twitter">{{ $t('connected-to') }}: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p> | 		<p v-if="$store.state.i.twitter">{{ $t('connected-to') }}: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p> | ||||||
| 		<ui-button v-if="$store.state.i.twitter" @click="disconnectTwitter">{{ $t('disconnect') }}</ui-button> | 		<ui-button v-if="$store.state.i.twitter" @click="disconnectTwitter">{{ $t('disconnect') }}</ui-button> | ||||||
| 		<ui-button v-else @click="connectTwitter">{{ $t('connect') }}</ui-button> | 		<ui-button v-else @click="connectTwitter">{{ $t('connect') }}</ui-button> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section v-if="enableDiscordIntegration"> | 	<section v-if="enableDiscordIntegration"> | ||||||
| 		<header><fa :icon="['fab', 'discord']"/> Discord</header> | 		<header><fa :icon="['fab', 'discord']"/> Discord</header> | ||||||
| 		<p v-if="$store.state.i.discord">{{ $t('connected-to') }}: <a :href="`https://discordapp.com/users/${$store.state.i.discord.id}`" target="_blank">@{{ $store.state.i.discord.username }}#{{ $store.state.i.discord.discriminator }}</a></p> | 		<p v-if="$store.state.i.discord">{{ $t('connected-to') }}: <a :href="`https://discordapp.com/users/${$store.state.i.discord.id}`" rel="nofollow noopener" target="_blank">@{{ $store.state.i.discord.username }}#{{ $store.state.i.discord.discriminator }}</a></p> | ||||||
| 		<ui-button v-if="$store.state.i.discord" @click="disconnectDiscord">{{ $t('disconnect') }}</ui-button> | 		<ui-button v-if="$store.state.i.discord" @click="disconnectDiscord">{{ $t('disconnect') }}</ui-button> | ||||||
| 		<ui-button v-else @click="connectDiscord">{{ $t('connect') }}</ui-button> | 		<ui-button v-else @click="connectDiscord">{{ $t('connect') }}</ui-button> | ||||||
| 	</section> | 	</section> | ||||||
| 
 | 
 | ||||||
| 	<section v-if="enableGithubIntegration"> | 	<section v-if="enableGithubIntegration"> | ||||||
| 		<header><fa :icon="['fab', 'github']"/> GitHub</header> | 		<header><fa :icon="['fab', 'github']"/> GitHub</header> | ||||||
| 		<p v-if="$store.state.i.github">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p> | 		<p v-if="$store.state.i.github">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" rel="nofollow noopener" target="_blank">@{{ $store.state.i.github.login }}</a></p> | ||||||
| 		<ui-button v-if="$store.state.i.github" @click="disconnectGithub">{{ $t('disconnect') }}</ui-button> | 		<ui-button v-if="$store.state.i.github" @click="disconnectGithub">{{ $t('disconnect') }}</ui-button> | ||||||
| 		<ui-button v-else @click="connectGithub">{{ $t('connect') }}</ui-button> | 		<ui-button v-else @click="connectGithub">{{ $t('connect') }}</ui-button> | ||||||
| 	</section> | 	</section> | ||||||
|  |  | ||||||
|  | @ -290,12 +290,17 @@ export default Vue.extend({ | ||||||
| 				this.exportTarget == 'mute' ? 'i/export-mute' : | 				this.exportTarget == 'mute' ? 'i/export-mute' : | ||||||
| 				this.exportTarget == 'blocking' ? 'i/export-blocking' : | 				this.exportTarget == 'blocking' ? 'i/export-blocking' : | ||||||
| 				this.exportTarget == 'user-lists' ? 'i/export-user-lists' : | 				this.exportTarget == 'user-lists' ? 'i/export-user-lists' : | ||||||
| 				null, {}); | 				null, {}).then(() => { | ||||||
| 
 |  | ||||||
| 					this.$root.dialog({ | 					this.$root.dialog({ | ||||||
| 						type: 'info', | 						type: 'info', | ||||||
| 						text: this.$t('export-requested') | 						text: this.$t('export-requested') | ||||||
| 					}); | 					}); | ||||||
|  | 				}).catch((e: any) => { | ||||||
|  | 					this.$root.dialog({ | ||||||
|  | 						type: 'error', | ||||||
|  | 						text: e.message | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		doImport() { | 		doImport() { | ||||||
|  |  | ||||||
|  | @ -45,7 +45,7 @@ | ||||||
| 			</ui-select> | 			</ui-select> | ||||||
| 		</label> | 		</label> | ||||||
| 
 | 
 | ||||||
| 		<a href="https://assets.msky.cafe/theme/list" target="_blank">{{ $t('find-more-theme') }}</a> | 		<a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank">{{ $t('find-more-theme') }}</a> | ||||||
| 
 | 
 | ||||||
| 		<details class="creator"> | 		<details class="creator"> | ||||||
| 			<summary><fa icon="palette"/> {{ $t('create-a-theme') }}</summary> | 			<summary><fa icon="palette"/> {{ $t('create-a-theme') }}</summary> | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
| 	</blockquote> | 	</blockquote> | ||||||
| </div> | </div> | ||||||
| <div v-else class="mk-url-preview"> | <div v-else class="mk-url-preview"> | ||||||
| 	<a :class="{ mini: narrow, compact }" :href="url" target="_blank" :title="url" v-if="!fetching"> | 	<a :class="{ mini: narrow, compact }" :href="url" rel="nofollow noopener" target="_blank" :title="url" v-if="!fetching"> | ||||||
| 		<div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`"> | 		<div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`"> | ||||||
| 			<button v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enable-player')"><fa :icon="['far', 'play-circle']"/></button> | 			<button v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enable-player')"><fa :icon="['far', 'play-circle']"/></button> | ||||||
| 		</div> | 		</div> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <template> | <template> | ||||||
| <a class="mk-url" :href="url" :target="target"> | <a class="mk-url" :href="url" :rel="rel" :target="target"> | ||||||
| 	<span class="schema">{{ schema }}//</span> | 	<span class="schema">{{ schema }}//</span> | ||||||
| 	<span class="hostname">{{ hostname }}</span> | 	<span class="hostname">{{ hostname }}</span> | ||||||
| 	<span class="port" v-if="port != ''">:{{ port }}</span> | 	<span class="port" v-if="port != ''">:{{ port }}</span> | ||||||
|  | @ -15,7 +15,7 @@ import Vue from 'vue'; | ||||||
| import { toUnicode as decodePunycode } from 'punycode'; | import { toUnicode as decodePunycode } from 'punycode'; | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	props: ['url', 'target'], | 	props: ['url', 'rel', 'target'], | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			schema: null, | 			schema: null, | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
| 		<div class="no-users" v-if="inited && us.length == 0"> | 		<div class="no-users" v-if="inited && us.length == 0"> | ||||||
| 			<p>{{ $t('no-users') }}</p> | 			<p>{{ $t('no-users') }}</p> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="user" v-for="user in us"> | 		<div class="user" v-for="user in us" :key="user.id"> | ||||||
| 			<mk-avatar class="avatar" :user="user"/> | 			<mk-avatar class="avatar" :user="user"/> | ||||||
| 			<div class="body" v-if="!iconOnly"> | 			<div class="body" v-if="!iconOnly"> | ||||||
| 				<div class="name"> | 				<div class="name"> | ||||||
|  | @ -18,6 +18,7 @@ | ||||||
| 				<div class="description" v-if="user.description" :title="user.description"> | 				<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" :should-break="false" :plain-text="true"/> | 					<mfm :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :should-break="false" :plain-text="true"/> | ||||||
| 				</div> | 				</div> | ||||||
|  | 				<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<button class="more" :class="{ fetching: fetchingMoreUsers }" v-if="cursor != null" @click="fetchMoreUsers()" :disabled="fetchingMoreUsers"> | 		<button class="more" :class="{ fetching: fetchingMoreUsers }" v-if="cursor != null" @click="fetchMoreUsers()" :disabled="fetchingMoreUsers"> | ||||||
|  | @ -160,6 +161,12 @@ export default Vue.extend({ | ||||||
| 				text-overflow ellipsis | 				text-overflow ellipsis | ||||||
| 				opacity 0.7 | 				opacity 0.7 | ||||||
| 				font-size 14px | 				font-size 14px | ||||||
|  | 				padding-right 40px | ||||||
|  | 
 | ||||||
|  | 			> .follow-button | ||||||
|  | 				position absolute | ||||||
|  | 				top 8px | ||||||
|  | 				right 0px | ||||||
| 
 | 
 | ||||||
| 	> .more | 	> .more | ||||||
| 		display block | 		display block | ||||||
|  |  | ||||||
|  | @ -7,7 +7,6 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import copyToClipboard from '../../../common/scripts/copy-to-clipboard'; |  | ||||||
| import { faExclamationCircle, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons'; | import { faExclamationCircle, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; | import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; | ||||||
| 
 | 
 | ||||||
|  | @ -27,7 +26,10 @@ export default Vue.extend({ | ||||||
| 			icon: ['fas', 'list'], | 			icon: ['fas', 'list'], | ||||||
| 			text: this.$t('push-to-list'), | 			text: this.$t('push-to-list'), | ||||||
| 			action: this.pushList | 			action: this.pushList | ||||||
| 		}, null, { | 		}] as any; | ||||||
|  | 		 | ||||||
|  | 		if (this.$store.getters.isSignedIn && this.$store.state.i.id != this.user.id) { | ||||||
|  | 			menu = menu.concat([null, { | ||||||
| 				icon: this.user.isMuted ? ['fas', 'eye'] : ['far', 'eye-slash'], | 				icon: this.user.isMuted ? ['fas', 'eye'] : ['far', 'eye-slash'], | ||||||
| 				text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'), | 				text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'), | ||||||
| 				action: this.toggleMute | 				action: this.toggleMute | ||||||
|  | @ -39,7 +41,8 @@ export default Vue.extend({ | ||||||
| 				icon: faExclamationCircle, | 				icon: faExclamationCircle, | ||||||
| 				text: this.$t('report-abuse'), | 				text: this.$t('report-abuse'), | ||||||
| 				action: this.reportAbuse | 				action: this.reportAbuse | ||||||
| 		}]; | 			}]); | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		if (this.$store.getters.isSignedIn && (this.$store.state.i.isAdmin || this.$store.state.i.isModerator)) { | 		if (this.$store.getters.isSignedIn && (this.$store.state.i.isAdmin || this.$store.state.i.isModerator)) { | ||||||
| 			menu = menu.concat([null, { | 			menu = menu.concat([null, { | ||||||
|  | @ -89,8 +92,10 @@ export default Vue.extend({ | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		toggleMute() { | 		async toggleMute() { | ||||||
| 			if (this.user.isMuted) { | 			if (this.user.isMuted) { | ||||||
|  | 				if (!await this.getConfirmed(this.$t('unmute-confirm'))) return; | ||||||
|  | 
 | ||||||
| 				this.$root.api('mute/delete', { | 				this.$root.api('mute/delete', { | ||||||
| 					userId: this.user.id | 					userId: this.user.id | ||||||
| 				}).then(() => { | 				}).then(() => { | ||||||
|  | @ -102,6 +107,8 @@ export default Vue.extend({ | ||||||
| 					}); | 					}); | ||||||
| 				}); | 				}); | ||||||
| 			} else { | 			} else { | ||||||
|  | 				if (!await this.getConfirmed(this.$t('mute-confirm'))) return; | ||||||
|  | 
 | ||||||
| 				this.$root.api('mute/create', { | 				this.$root.api('mute/create', { | ||||||
| 					userId: this.user.id | 					userId: this.user.id | ||||||
| 				}).then(() => { | 				}).then(() => { | ||||||
|  | @ -115,8 +122,10 @@ export default Vue.extend({ | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		toggleBlock() { | 		async toggleBlock() { | ||||||
| 			if (this.user.isBlocking) { | 			if (this.user.isBlocking) { | ||||||
|  | 				if (!await this.getConfirmed(this.$t('unblock-confirm'))) return; | ||||||
|  | 
 | ||||||
| 				this.$root.api('blocking/delete', { | 				this.$root.api('blocking/delete', { | ||||||
| 					userId: this.user.id | 					userId: this.user.id | ||||||
| 				}).then(() => { | 				}).then(() => { | ||||||
|  | @ -128,6 +137,8 @@ export default Vue.extend({ | ||||||
| 					}); | 					}); | ||||||
| 				}); | 				}); | ||||||
| 			} else { | 			} else { | ||||||
|  | 				if (!await this.getConfirmed(this.$t('block-confirm'))) return; | ||||||
|  | 
 | ||||||
| 				this.$root.api('blocking/create', { | 				this.$root.api('blocking/create', { | ||||||
| 					userId: this.user.id | 					userId: this.user.id | ||||||
| 				}).then(() => { | 				}).then(() => { | ||||||
|  | @ -164,7 +175,9 @@ export default Vue.extend({ | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		toggleSilence() { | 		async toggleSilence() { | ||||||
|  | 			if (!await this.getConfirmed(this.$t(this.user.isSilenced ? 'unsilence-confirm' : 'silence-confirm'))) return; | ||||||
|  | 
 | ||||||
| 			this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', { | 			this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', { | ||||||
| 				userId: this.user.id | 				userId: this.user.id | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
|  | @ -181,7 +194,9 @@ export default Vue.extend({ | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		toggleSuspend() { | 		async toggleSuspend() { | ||||||
|  | 			if (!await this.getConfirmed(this.$t(this.user.isSuspended ? 'unsuspend-confirm' : 'suspend-confirm'))) return; | ||||||
|  | 
 | ||||||
| 			this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', { | 			this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', { | ||||||
| 				userId: this.user.id | 				userId: this.user.id | ||||||
| 			}).then(() => { | 			}).then(() => { | ||||||
|  | @ -196,7 +211,18 @@ export default Vue.extend({ | ||||||
| 					text: e | 					text: e | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
| 		} | 		}, | ||||||
|  | 
 | ||||||
|  | 		async getConfirmed(text: string): Promise<Boolean> { | ||||||
|  | 			const confirm = await this.$root.dialog({ | ||||||
|  | 				type: 'warning', | ||||||
|  | 				showCancelButton: true, | ||||||
|  | 				title: 'confirm', | ||||||
|  | 				text, | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
|  | 			return !confirm.canceled; | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
| 		<div class="is-remote" v-if="note.user.host != null"> | 		<div class="is-remote" v-if="note.user.host != null"> | ||||||
| 			<details> | 			<details> | ||||||
| 				<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-post') }}</summary> | 				<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-post') }}</summary> | ||||||
| 				<a :href="note.url || note.uri" target="_blank">{{ $t('@.view-on-remote') }}</a> | 				<a :href="note.url || note.uri" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a> | ||||||
| 			</details> | 			</details> | ||||||
| 		</div> | 		</div> | ||||||
| 		<mk-note :note="note" :detail="true" :key="note.id"/> | 		<mk-note :note="note" :detail="true" :key="note.id"/> | ||||||
|  |  | ||||||
|  | @ -157,6 +157,7 @@ export default Vue.extend({ | ||||||
| 				// オーバーフローしたら古い投稿は捨てる | 				// オーバーフローしたら古い投稿は捨てる | ||||||
| 				if (this.notes.length >= displayLimit) { | 				if (this.notes.length >= displayLimit) { | ||||||
| 					this.notes = this.notes.slice(0, displayLimit); | 					this.notes = this.notes.slice(0, displayLimit); | ||||||
|  | 					this.cursor = this.notes[this.notes.length - 1].id | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				this.queue.push(note); | 				this.queue.push(note); | ||||||
|  | @ -165,6 +166,7 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 		append(note) { | 		append(note) { | ||||||
| 			this.notes.push(note); | 			this.notes.push(note); | ||||||
|  | 			this.cursor = this.notes[this.notes.length - 1].id | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		releaseQueue() { | 		releaseQueue() { | ||||||
|  |  | ||||||
|  | @ -85,7 +85,7 @@ export default Vue.extend({ | ||||||
| 			this.makePromise = cursor => this.$root.api('users/notes', { | 			this.makePromise = cursor => this.$root.api('users/notes', { | ||||||
| 				userId: this.user.id, | 				userId: this.user.id, | ||||||
| 				limit: fetchLimit + 1, | 				limit: fetchLimit + 1, | ||||||
| 				untilId: cursor ? cursor : undefined, | 				untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365, | ||||||
| 				withFiles: this.withFiles, | 				withFiles: this.withFiles, | ||||||
| 				includeMyRenotes: this.$store.state.settings.showMyRenotes, | 				includeMyRenotes: this.$store.state.settings.showMyRenotes, | ||||||
| 				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, | 				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, | ||||||
|  | @ -95,7 +95,7 @@ export default Vue.extend({ | ||||||
| 					notes.pop(); | 					notes.pop(); | ||||||
| 					return { | 					return { | ||||||
| 						notes: notes, | 						notes: notes, | ||||||
| 						cursor: notes[notes.length - 1].id | 						cursor: new Date(notes[notes.length - 1].createdAt).getTime() | ||||||
| 					}; | 					}; | ||||||
| 				} else { | 				} else { | ||||||
| 					return { | 					return { | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
| 		<div class="is-remote" v-if="user.host != null"> | 		<div class="is-remote" v-if="user.host != null"> | ||||||
| 			<details> | 			<details> | ||||||
| 				<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary> | 				<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary> | ||||||
| 				<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a> | 				<a :href="user.url || user.uri" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a> | ||||||
| 			</details> | 			</details> | ||||||
| 		</div> | 		</div> | ||||||
| 		<header :style="bannerStyle"> | 		<header :style="bannerStyle"> | ||||||
|  |  | ||||||
|  | @ -21,14 +21,7 @@ | ||||||
| 					<fa :icon="['far', 'laugh']"/> | 					<fa :icon="['far', 'laugh']"/> | ||||||
| 				</button> | 				</button> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div class="files" v-show="files.length != 0"> | 			<x-post-form-attaches class="files" :files="files" :detachMediaFn="detachMedia"/> | ||||||
| 				<x-draggable :list="files" :options="{ animation: 150 }"> |  | ||||||
| 					<div v-for="file in files" :key="file.id"> |  | ||||||
| 						<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div> |  | ||||||
| 						<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/> |  | ||||||
| 					</div> |  | ||||||
| 				</x-draggable> |  | ||||||
| 			</div> |  | ||||||
| 			<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/> | 			<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/> | ||||||
| 			<mk-uploader ref="uploader" @uploaded="attachMedia"/> | 			<mk-uploader ref="uploader" @uploaded="attachMedia"/> | ||||||
| 			<footer> | 			<footer> | ||||||
|  | @ -45,7 +38,7 @@ | ||||||
| import define from '../../../common/define-widget'; | import define from '../../../common/define-widget'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import insertTextAtCursor from 'insert-text-at-cursor'; | import insertTextAtCursor from 'insert-text-at-cursor'; | ||||||
| import * as XDraggable from 'vuedraggable'; | import XPostFormAttaches from '../components/post-form-attaches.vue'; | ||||||
| 
 | 
 | ||||||
| export default define({ | export default define({ | ||||||
| 	name: 'post-form', | 	name: 'post-form', | ||||||
|  | @ -56,7 +49,7 @@ export default define({ | ||||||
| 	i18n: i18n('desktop/views/widgets/post-form.vue'), | 	i18n: i18n('desktop/views/widgets/post-form.vue'), | ||||||
| 
 | 
 | ||||||
| 	components: { | 	components: { | ||||||
| 		XDraggable | 		XPostFormAttaches | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	data() { | 	data() { | ||||||
|  | @ -176,10 +169,22 @@ export default define({ | ||||||
| 		post() { | 		post() { | ||||||
| 			this.posting = true; | 			this.posting = true; | ||||||
| 
 | 
 | ||||||
|  | 			let visibility = 'public'; | ||||||
|  | 			let localOnly = false; | ||||||
|  | 
 | ||||||
|  | 			const m = this.$store.state.settings.defaultNoteVisibility.match(/^local-(.+)/); | ||||||
|  | 			if (m) { | ||||||
|  | 				visibility = m[1]; | ||||||
|  | 				localOnly = true; | ||||||
|  | 			} else { | ||||||
|  | 				visibility = this.$store.state.settings.defaultNoteVisibility; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
| 			this.$root.api('notes/create', { | 			this.$root.api('notes/create', { | ||||||
| 				text: this.text == '' ? undefined : this.text, | 				text: this.text == '' ? undefined : this.text, | ||||||
| 				fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, | 				fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, | ||||||
| 				visibility: this.$store.state.settings.defaultNoteVisibility | 				visibility, | ||||||
|  | 				localOnly, | ||||||
| 			}).then(data => { | 			}).then(data => { | ||||||
| 				this.clear(); | 				this.clear(); | ||||||
| 			}).catch(err => { | 			}).catch(err => { | ||||||
|  | @ -237,38 +242,6 @@ export default define({ | ||||||
| 				& + .emoji | 				& + .emoji | ||||||
| 					opacity 0.7 | 					opacity 0.7 | ||||||
| 
 | 
 | ||||||
| 	> .files |  | ||||||
| 		> div |  | ||||||
| 			padding 4px |  | ||||||
| 
 |  | ||||||
| 			&:after |  | ||||||
| 				content "" |  | ||||||
| 				display block |  | ||||||
| 				clear both |  | ||||||
| 
 |  | ||||||
| 			> div |  | ||||||
| 				float left |  | ||||||
| 				border solid 4px transparent |  | ||||||
| 				cursor move |  | ||||||
| 
 |  | ||||||
| 				&:hover > .remove |  | ||||||
| 					display block |  | ||||||
| 
 |  | ||||||
| 				> .img |  | ||||||
| 					width 64px |  | ||||||
| 					height 64px |  | ||||||
| 					background-size cover |  | ||||||
| 					background-position center center |  | ||||||
| 
 |  | ||||||
| 				> .remove |  | ||||||
| 					display none |  | ||||||
| 					position absolute |  | ||||||
| 					top -6px |  | ||||||
| 					right -6px |  | ||||||
| 					width 16px |  | ||||||
| 					height 16px |  | ||||||
| 					cursor pointer |  | ||||||
| 
 |  | ||||||
| 	> input[type=file] | 	> input[type=file] | ||||||
| 		display none | 		display none | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
| 		<div class="mkw-rss--body" :data-mobile="platform == 'mobile'"> | 		<div class="mkw-rss--body" :data-mobile="platform == 'mobile'"> | ||||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | 			<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||||
| 			<div class="feed" v-else> | 			<div class="feed" v-else> | ||||||
| 				<a v-for="item in items" :href="item.link" target="_blank" :title="item.title">{{ item.title }}</a> | 				<a v-for="item in items" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</ui-container> | 	</ui-container> | ||||||
|  |  | ||||||
|  | @ -769,7 +769,6 @@ export default Vue.extend({ | ||||||
| 	> .mk-uploader | 	> .mk-uploader | ||||||
| 		height 100px | 		height 100px | ||||||
| 		padding 16px | 		padding 16px | ||||||
| 		background #fff |  | ||||||
| 
 | 
 | ||||||
| 	> input | 	> input | ||||||
| 		display none | 		display none | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ | ||||||
| 				</div> | 				</div> | ||||||
| 				<mk-poll v-if="appearNote.poll" :note="appearNote"/> | 				<mk-poll v-if="appearNote.poll" :note="appearNote"/> | ||||||
| 				<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> | 				<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> | ||||||
| 				<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> | 				<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> | ||||||
| 				<div class="map" v-if="appearNote.geo" ref="map"></div> | 				<div class="map" v-if="appearNote.geo" ref="map"></div> | ||||||
| 				<div class="renote" v-if="appearNote.renote"> | 				<div class="renote" v-if="appearNote.renote"> | ||||||
| 					<mk-note-preview :note="appearNote.renote"/> | 					<mk-note-preview :note="appearNote.renote"/> | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ | ||||||
| 						<mk-media-list :media-list="appearNote.files"/> | 						<mk-media-list :media-list="appearNote.files"/> | ||||||
| 					</div> | 					</div> | ||||||
| 					<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> | 					<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> | ||||||
| 					<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> 位置情報</a> | 					<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> 位置情報</a> | ||||||
| 					<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> | 					<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> | ||||||
| 					<mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="compact"/> | 					<mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="compact"/> | ||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
|  | @ -157,6 +157,7 @@ export default Vue.extend({ | ||||||
| 				// オーバーフローしたら古い投稿は捨てる | 				// オーバーフローしたら古い投稿は捨てる | ||||||
| 				if (this.notes.length >= displayLimit) { | 				if (this.notes.length >= displayLimit) { | ||||||
| 					this.notes = this.notes.slice(0, displayLimit); | 					this.notes = this.notes.slice(0, displayLimit); | ||||||
|  | 					this.cursor = this.notes[this.notes.length - 1].id | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				this.queue.push(note); | 				this.queue.push(note); | ||||||
|  | @ -165,6 +166,7 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 		append(note) { | 		append(note) { | ||||||
| 			this.notes.push(note); | 			this.notes.push(note); | ||||||
|  | 			this.cursor = this.notes[this.notes.length - 1].id | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		releaseQueue() { | 		releaseQueue() { | ||||||
|  |  | ||||||
|  | @ -27,15 +27,7 @@ | ||||||
| 			<button class="emoji" @click="emoji" ref="emoji"> | 			<button class="emoji" @click="emoji" ref="emoji"> | ||||||
| 				<fa :icon="['far', 'laugh']"/> | 				<fa :icon="['far', 'laugh']"/> | ||||||
| 			</button> | 			</button> | ||||||
| 			<div class="files" :class="{ with: poll }" v-show="files.length != 0"> | 			<x-post-form-attaches class="files" :files="files" :detachMediaFn="detachMedia"/> | ||||||
| 				<x-draggable :list="files" :options="{ animation: 150 }"> |  | ||||||
| 					<div v-for="file in files" :key="file.id"> |  | ||||||
| 						<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div> |  | ||||||
| 						<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/> |  | ||||||
| 					</div> |  | ||||||
| 				</x-draggable> |  | ||||||
| 				<p class="remain">{{ 4 - files.length }}/4</p> |  | ||||||
| 			</div> |  | ||||||
| 			<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/> | 			<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
|  | @ -65,7 +57,6 @@ | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import insertTextAtCursor from 'insert-text-at-cursor'; | import insertTextAtCursor from 'insert-text-at-cursor'; | ||||||
| import * as XDraggable from 'vuedraggable'; |  | ||||||
| import getFace from '../../../common/scripts/get-face'; | import getFace from '../../../common/scripts/get-face'; | ||||||
| import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; | import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; | ||||||
| import { parse } from '../../../../../mfm/parse'; | import { parse } from '../../../../../mfm/parse'; | ||||||
|  | @ -74,13 +65,14 @@ import { erase, unique } from '../../../../../prelude/array'; | ||||||
| import { length } from 'stringz'; | import { length } from 'stringz'; | ||||||
| import { toASCII } from 'punycode'; | import { toASCII } from 'punycode'; | ||||||
| import extractMentions from '../../../../../misc/extract-mentions'; | import extractMentions from '../../../../../misc/extract-mentions'; | ||||||
|  | import XPostFormAttaches from '../../../common/views/components/post-form-attaches.vue'; | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('desktop/views/components/post-form.vue'), | 	i18n: i18n('desktop/views/components/post-form.vue'), | ||||||
| 
 | 
 | ||||||
| 	components: { | 	components: { | ||||||
| 		XDraggable, | 		MkVisibilityChooser, | ||||||
| 		MkVisibilityChooser | 		XPostFormAttaches | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	props: { | 	props: { | ||||||
|  | @ -640,17 +632,14 @@ export default Vue.extend({ | ||||||
| 						border solid 4px transparent | 						border solid 4px transparent | ||||||
| 						cursor move | 						cursor move | ||||||
| 
 | 
 | ||||||
| 						&:hover > .remove |  | ||||||
| 							display block |  | ||||||
| 
 |  | ||||||
| 						> .img | 						> .img | ||||||
| 							width 64px | 							width 64px | ||||||
| 							height 64px | 							height 64px | ||||||
| 							background-size cover | 							background-size cover | ||||||
| 							background-position center center | 							background-position center center | ||||||
|  | 							background-color: rgba(128, 128, 128, 0.3) | ||||||
| 
 | 
 | ||||||
| 						> .remove | 						> .remove | ||||||
| 							display none |  | ||||||
| 							position absolute | 							position absolute | ||||||
| 							top -6px | 							top -6px | ||||||
| 							right -6px | 							right -6px | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| 		<fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }} | 		<fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }} | ||||||
| 	</div> | 	</div> | ||||||
| 	<div class="is-remote" v-if="user.host != null" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> | 	<div class="is-remote" v-if="user.host != null" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> | ||||||
| 		<fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a> | 		<fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div class="main"> | 	<div class="main"> | ||||||
| 		<x-header class="header" :user="user"/> | 		<x-header class="header" :user="user"/> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| <template> | <template> | ||||||
| <div class="header" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> | <div class="header" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> | ||||||
| 	<div class="banner-container" :style="style"> | 	<div class="banner-container" :style="style"> | ||||||
| 		<div class="banner" ref="banner" :style="style" @click="onBannerClick"></div> | 		<div class="banner" ref="banner" :style="style"></div> | ||||||
| 		<div class="fade"></div> | 		<div class="fade"></div> | ||||||
| 		<div class="title"> | 		<div class="title"> | ||||||
| 			<p class="name"> | 			<p class="name"> | ||||||
|  | @ -105,14 +105,6 @@ export default Vue.extend({ | ||||||
| 			if (blur <= 10) banner.style.filter = `blur(${blur}px)`; | 			if (blur <= 10) banner.style.filter = `blur(${blur}px)`; | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		onBannerClick() { |  | ||||||
| 			if (!this.$store.getters.isSignedIn || this.$store.state.i.id != this.user.id) return; |  | ||||||
| 
 |  | ||||||
| 			this.$updateBanner().then(i => { |  | ||||||
| 				this.user.bannerUrl = i.bannerUrl; |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		menu() { | 		menu() { | ||||||
| 			this.$root.new(XUserMenu, { | 			this.$root.new(XUserMenu, { | ||||||
| 				source: this.$refs.menu, | 				source: this.$refs.menu, | ||||||
|  | @ -171,9 +163,6 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 			> .menu | 			> .menu | ||||||
| 				height 100% | 				height 100% | ||||||
| 				display block |  | ||||||
| 				position absolute |  | ||||||
| 				left -42px |  | ||||||
| 				padding 0 14px | 				padding 0 14px | ||||||
| 				color #fff | 				color #fff | ||||||
| 				text-shadow 0 0 8px #000 | 				text-shadow 0 0 8px #000 | ||||||
|  |  | ||||||
|  | @ -36,13 +36,13 @@ export default Vue.extend({ | ||||||
| 				includeReplies: this.mode == 'with-replies', | 				includeReplies: this.mode == 'with-replies', | ||||||
| 				includeMyRenotes: this.mode != 'my-posts', | 				includeMyRenotes: this.mode != 'my-posts', | ||||||
| 				withFiles: this.mode == 'with-media', | 				withFiles: this.mode == 'with-media', | ||||||
| 				untilId: cursor ? cursor : undefined | 				untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365 | ||||||
| 			}).then(notes => { | 			}).then(notes => { | ||||||
| 				if (notes.length == fetchLimit + 1) { | 				if (notes.length == fetchLimit + 1) { | ||||||
| 					notes.pop(); | 					notes.pop(); | ||||||
| 					return { | 					return { | ||||||
| 						notes: notes, | 						notes: notes, | ||||||
| 						cursor: notes[notes.length - 1].id | 						cursor: new Date(notes[notes.length - 1].createdAt).getTime() | ||||||
| 					}; | 					}; | ||||||
| 				} else { | 				} else { | ||||||
| 					return { | 					return { | ||||||
|  |  | ||||||
|  | @ -11,7 +11,9 @@ | ||||||
| 
 | 
 | ||||||
| 		<div class="mkw-polls--body"> | 		<div class="mkw-polls--body"> | ||||||
| 			<div class="poll" v-if="!fetching && poll != null"> | 			<div class="poll" v-if="!fetching && poll != null"> | ||||||
| 				<p v-if="poll.text"><router-link :to="poll | notePage">{{ poll.text }}</router-link></p> | 				<p v-if="poll.text"><router-link :to="poll | notePage"> | ||||||
|  | 					<mfm :text="poll.text" :author="poll.user" :custom-emojis="poll.emojis"/> | ||||||
|  | 				</router-link></p> | ||||||
| 				<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p> | 				<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p> | ||||||
| 				<mk-poll :note="poll"/> | 				<mk-poll :note="poll"/> | ||||||
| 			</div> | 			</div> | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ | ||||||
| </div> | </div> | ||||||
| <a class="kkjnbbplepmiyuadieoenjgutgcmtsvu" v-else | <a class="kkjnbbplepmiyuadieoenjgutgcmtsvu" v-else | ||||||
| 	:href="video.url" | 	:href="video.url" | ||||||
|  | 	rel="nofollow noopener" | ||||||
| 	target="_blank" | 	target="_blank" | ||||||
| 	:style="imageStyle" | 	:style="imageStyle" | ||||||
| 	:title="video.name" | 	:title="video.name" | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ | ||||||
| 				</div> | 				</div> | ||||||
| 				<mk-poll v-if="appearNote.poll" :note="appearNote"/> | 				<mk-poll v-if="appearNote.poll" :note="appearNote"/> | ||||||
| 				<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> | 				<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> | ||||||
| 				<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> | 				<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> | ||||||
| 				<div class="map" v-if="appearNote.geo" ref="map"></div> | 				<div class="map" v-if="appearNote.geo" ref="map"></div> | ||||||
| 				<div class="renote" v-if="appearNote.renote"> | 				<div class="renote" v-if="appearNote.renote"> | ||||||
| 					<mk-note-preview :note="appearNote.renote"/> | 					<mk-note-preview :note="appearNote.renote"/> | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ | ||||||
| 					</div> | 					</div> | ||||||
| 					<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> | 					<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> | ||||||
| 					<mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="true"/> | 					<mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="true"/> | ||||||
| 					<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> | 					<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> | ||||||
| 					<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> | 					<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> | ||||||
| 				</div> | 				</div> | ||||||
| 				<span class="app" v-if="appearNote.app && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span> | 				<span class="app" v-if="appearNote.app && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span> | ||||||
|  |  | ||||||
|  | @ -151,6 +151,7 @@ export default Vue.extend({ | ||||||
| 				// オーバーフローしたら古い投稿は捨てる | 				// オーバーフローしたら古い投稿は捨てる | ||||||
| 				if (this.notes.length >= displayLimit) { | 				if (this.notes.length >= displayLimit) { | ||||||
| 					this.notes = this.notes.slice(0, displayLimit); | 					this.notes = this.notes.slice(0, displayLimit); | ||||||
|  | 					this.cursor = this.notes[this.notes.length - 1].id | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				this.queue.push(note); | 				this.queue.push(note); | ||||||
|  | @ -159,6 +160,7 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 		append(note) { | 		append(note) { | ||||||
| 			this.notes.push(note); | 			this.notes.push(note); | ||||||
|  | 			this.cursor = this.notes[this.notes.length - 1].id | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		releaseQueue() { | 		releaseQueue() { | ||||||
|  |  | ||||||
|  | @ -21,13 +21,7 @@ | ||||||
| 			</div> | 			</div> | ||||||
| 			<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="{ model: 'cw' }"> | 			<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="{ model: 'cw' }"> | ||||||
| 			<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }"></textarea> | 			<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }"></textarea> | ||||||
| 			<div class="attaches" v-show="files.length != 0"> | 			<x-post-form-attaches class="attaches" :files="files"/> | ||||||
| 				<x-draggable class="files" :list="files" :options="{ animation: 150 }"> |  | ||||||
| 					<div class="file" v-for="file in files" :key="file.id"> |  | ||||||
| 						<div class="img" :style="`background-image: url(${file.thumbnailUrl})`" @click="detachMedia(file)"></div> |  | ||||||
| 					</div> |  | ||||||
| 				</x-draggable> |  | ||||||
| 			</div> |  | ||||||
| 			<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/> | 			<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/> | ||||||
| 			<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/> | 			<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/> | ||||||
| 			<footer> | 			<footer> | ||||||
|  | @ -57,7 +51,6 @@ | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../i18n'; | import i18n from '../../../i18n'; | ||||||
| import insertTextAtCursor from 'insert-text-at-cursor'; | import insertTextAtCursor from 'insert-text-at-cursor'; | ||||||
| import * as XDraggable from 'vuedraggable'; |  | ||||||
| import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; | import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; | ||||||
| import getFace from '../../../common/scripts/get-face'; | import getFace from '../../../common/scripts/get-face'; | ||||||
| import { parse } from '../../../../../mfm/parse'; | import { parse } from '../../../../../mfm/parse'; | ||||||
|  | @ -66,11 +59,12 @@ import { erase, unique } from '../../../../../prelude/array'; | ||||||
| import { length } from 'stringz'; | import { length } from 'stringz'; | ||||||
| import { toASCII } from 'punycode'; | import { toASCII } from 'punycode'; | ||||||
| import extractMentions from '../../../../../misc/extract-mentions'; | import extractMentions from '../../../../../misc/extract-mentions'; | ||||||
|  | import XPostFormAttaches from '../../../common/views/components/post-form-attaches.vue'; | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('mobile/views/components/post-form.vue'), | 	i18n: i18n('mobile/views/components/post-form.vue'), | ||||||
| 	components: { | 	components: { | ||||||
| 		XDraggable | 		XPostFormAttaches | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	props: { | 	props: { | ||||||
|  | @ -264,8 +258,8 @@ export default Vue.extend({ | ||||||
| 			this.$emit('change-attached-files', this.files); | 			this.$emit('change-attached-files', this.files); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		detachMedia(file) { | 		detachMedia(id) { | ||||||
| 			this.files = this.files.filter(x => x.id != file.id); | 			this.files = this.files.filter(x => x.id != id); | ||||||
| 			this.$emit('change-attached-files', this.files); | 			this.$emit('change-attached-files', this.files); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
|  | @ -481,32 +475,6 @@ export default Vue.extend({ | ||||||
| 				min-width 100% | 				min-width 100% | ||||||
| 				min-height 80px | 				min-height 80px | ||||||
| 
 | 
 | ||||||
| 			> .attaches |  | ||||||
| 
 |  | ||||||
| 				> .files |  | ||||||
| 					display block |  | ||||||
| 					margin 0 |  | ||||||
| 					padding 4px |  | ||||||
| 					list-style none |  | ||||||
| 
 |  | ||||||
| 					&:after |  | ||||||
| 						content "" |  | ||||||
| 						display block |  | ||||||
| 						clear both |  | ||||||
| 
 |  | ||||||
| 					> .file |  | ||||||
| 						display block |  | ||||||
| 						float left |  | ||||||
| 						margin 0 |  | ||||||
| 						padding 0 |  | ||||||
| 						border solid 4px transparent |  | ||||||
| 
 |  | ||||||
| 						> .img |  | ||||||
| 							width 64px |  | ||||||
| 							height 64px |  | ||||||
| 							background-size cover |  | ||||||
| 							background-position center center |  | ||||||
| 
 |  | ||||||
| 			> .mk-uploader | 			> .mk-uploader | ||||||
| 				margin 8px 0 0 0 | 				margin 8px 0 0 0 | ||||||
| 				padding 8px | 				padding 8px | ||||||
|  |  | ||||||
|  | @ -21,13 +21,13 @@ export default Vue.extend({ | ||||||
| 				userId: this.user.id, | 				userId: this.user.id, | ||||||
| 				limit: fetchLimit + 1, | 				limit: fetchLimit + 1, | ||||||
| 				withFiles: this.withMedia, | 				withFiles: this.withMedia, | ||||||
| 				untilId: cursor ? cursor : undefined | 				untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365 | ||||||
| 			}).then(notes => { | 			}).then(notes => { | ||||||
| 				if (notes.length == fetchLimit + 1) { | 				if (notes.length == fetchLimit + 1) { | ||||||
| 					notes.pop(); | 					notes.pop(); | ||||||
| 					return { | 					return { | ||||||
| 						notes: notes, | 						notes: notes, | ||||||
| 						cursor: notes[notes.length - 1].id | 						cursor: new Date(notes[notes.length - 1].createdAt).getTime() | ||||||
| 					}; | 					}; | ||||||
| 				} else { | 				} else { | ||||||
| 					return { | 					return { | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ | ||||||
| 	</template> | 	</template> | ||||||
| 	<div class="wwtwuxyh" v-if="!fetching"> | 	<div class="wwtwuxyh" v-if="!fetching"> | ||||||
| 		<div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div> | 		<div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div> | ||||||
| 		<div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div> | 		<div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div> | ||||||
| 		<header> | 		<header> | ||||||
| 			<div class="banner" :style="style"></div> | 			<div class="banner" :style="style"></div> | ||||||
| 			<div class="body"> | 			<div class="body"> | ||||||
|  |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								src/client/assets/thumbnail-not-available.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/client/assets/thumbnail-not-available.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.6 KiB | 
|  | @ -42,6 +42,8 @@ export type Source = { | ||||||
| 	accesslog?: string; | 	accesslog?: string; | ||||||
| 
 | 
 | ||||||
| 	clusterLimit?: number; | 	clusterLimit?: number; | ||||||
|  | 
 | ||||||
|  | 	outgoingAddressFamily?: 'ipv4' | 'ipv6' | 'dual'; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  |  | ||||||
|  | @ -56,20 +56,12 @@ function cpuUsage() { | ||||||
| 
 | 
 | ||||||
| // MEMORY(excl buffer + cache) STAT
 | // MEMORY(excl buffer + cache) STAT
 | ||||||
| async function usedMem() { | async function usedMem() { | ||||||
| 	try { |  | ||||||
| 	const data = await sysUtils.mem(); | 	const data = await sysUtils.mem(); | ||||||
| 	return data.active; | 	return data.active; | ||||||
| 	} catch (error) { |  | ||||||
| 		throw error; |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TOTAL MEMORY STAT
 | // TOTAL MEMORY STAT
 | ||||||
| async function totalMem() { | async function totalMem() { | ||||||
| 	try { |  | ||||||
| 	const data = await sysUtils.mem(); | 	const data = await sysUtils.mem(); | ||||||
| 	return data.total; | 	return data.total; | ||||||
| 	} catch (error) { |  | ||||||
| 		throw error; |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,4 +6,4 @@ block main | ||||||
| block footer | block footer | ||||||
| 	p | 	p | ||||||
| 		= i18n('docs.edit-this-page-on-github') | 		= i18n('docs.edit-this-page-on-github') | ||||||
| 		a(href=src target="_blank")= i18n('docs.edit-this-page-on-github-link') | 		a(href=src rel="noopener" target="_blank")= i18n('docs.edit-this-page-on-github-link') | ||||||
|  |  | ||||||
|  | @ -71,7 +71,7 @@ function greet() { | ||||||
| 		console.log(' ' + chalk.gray(v) + ('                        |___|\n'.substr(v.length))); | 		console.log(' ' + chalk.gray(v) + ('                        |___|\n'.substr(v.length))); | ||||||
| 		//#endregion
 | 		//#endregion
 | ||||||
| 
 | 
 | ||||||
| 		console.log(' Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, and @acid-chicken.'); | 		console.log(' Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, @acid-chicken, and @rinsuki.'); | ||||||
| 		console.log(chalk.keyword('orange')(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo')); | 		console.log(chalk.keyword('orange')(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo')); | ||||||
| 
 | 
 | ||||||
| 		console.log(''); | 		console.log(''); | ||||||
|  |  | ||||||
|  | @ -16,6 +16,11 @@ export function extractDbHost(uri: string) { | ||||||
| 	return toDbHost(url.hostname); | 	return toDbHost(url.hostname); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function extractApHost(uri: string) { | ||||||
|  | 	const url = new URL(uri); | ||||||
|  | 	return toApHost(url.hostname); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function toDbHost(host: string) { | export function toDbHost(host: string) { | ||||||
| 	if (host == null) return null; | 	if (host == null) return null; | ||||||
| 	return toUnicode(host.toLowerCase()); | 	return toUnicode(host.toLowerCase()); | ||||||
|  |  | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										12
									
								
								src/misc/gen-id.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/misc/gen-id.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | import { genMeid7 } from './id/meid7'; | ||||||
|  | 
 | ||||||
|  | const method = 'meid7'; | ||||||
|  | 
 | ||||||
|  | export function genId(date?: Date): string { | ||||||
|  | 	if (!date || (date > new Date())) date = new Date(); | ||||||
|  | 
 | ||||||
|  | 	switch (method) { | ||||||
|  | 		case 'meid7': return genMeid7(date); | ||||||
|  | 		default: throw new Error('unknown id generation method'); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								src/misc/id/meid7.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/misc/id/meid7.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | ||||||
|  | const CHARS = '0123456789abcdef'; | ||||||
|  | 
 | ||||||
|  | //  4bit Fixed hex value '7'
 | ||||||
|  | // 44bit UNIX Time ms in Hex
 | ||||||
|  | // 48bit Random value in Hex
 | ||||||
|  | 
 | ||||||
|  | function getTime(time: number) { | ||||||
|  | 	if (time < 0) time = 0; | ||||||
|  | 	if (time === 0) { | ||||||
|  | 		return CHARS[0]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return time.toString(16).padStart(11, CHARS[0]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getRandom() { | ||||||
|  | 	let str = ''; | ||||||
|  | 
 | ||||||
|  | 	for (let i = 0; i < 12; i++) { | ||||||
|  | 		str += CHARS[Math.floor(Math.random() * CHARS.length)]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function genMeid7(date: Date): string { | ||||||
|  | 	return '7' + getTime(date.getTime()) + getRandom(); | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| export const twemojiBase = 'https://cdn.jsdelivr.net/npm/twemoji@11.3.0'; | export const twemojiBase = 'https://cdn.jsdelivr.net/npm/twemoji@12.0.1'; | ||||||
| // https://cdn.jsdelivr.net/npm/twemoji@11.3.0
 | // https://cdn.jsdelivr.net/npm/twemoji@12.0.1
 | ||||||
| // https://cdnjs.cloudflare.com/ajax/libs/twemoji/11.3.0
 | // https://cdnjs.cloudflare.com/ajax/libs/twemoji/12.0.1
 | ||||||
| // https://twemoji.maxcdn.com
 | // https://twemoji.maxcdn.com
 | ||||||
|  |  | ||||||
|  | @ -1,19 +1,19 @@ | ||||||
| export interface Maybe<T> { | export interface IMaybe<T> { | ||||||
| 	isJust(): this is Just<T>; | 	isJust(): this is IJust<T>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type Just<T> = Maybe<T> & { | export interface IJust<T> extends IMaybe<T> { | ||||||
| 	get(): T | 	get(): T; | ||||||
| }; | } | ||||||
| 
 | 
 | ||||||
| export function just<T>(value: T): Just<T> { | export function just<T>(value: T): IJust<T> { | ||||||
| 	return { | 	return { | ||||||
| 		isJust: () => true, | 		isJust: () => true, | ||||||
| 		get: () => value | 		get: () => value | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function nothing<T>(): Maybe<T> { | export function nothing<T>(): IMaybe<T> { | ||||||
| 	return { | 	return { | ||||||
| 		isJust: () => false, | 		isJust: () => false, | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|  | @ -16,10 +16,9 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void> | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	let deletedCount = 0; | 	let deletedCount = 0; | ||||||
| 	let ended = false; |  | ||||||
| 	let cursor: any = null; | 	let cursor: any = null; | ||||||
| 
 | 
 | ||||||
| 	while (!ended) { | 	while (true) { | ||||||
| 		const files = await DriveFile.find({ | 		const files = await DriveFile.find({ | ||||||
| 			userId: user._id, | 			userId: user._id, | ||||||
| 			...(cursor ? { _id: { $gt: cursor } } : {}) | 			...(cursor ? { _id: { $gt: cursor } } : {}) | ||||||
|  | @ -31,7 +30,6 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void> | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		if (files.length === 0) { | 		if (files.length === 0) { | ||||||
| 			ended = true; |  | ||||||
| 			job.progress(100); | 			job.progress(100); | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -32,10 +32,9 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> { | ||||||
| 	const stream = fs.createWriteStream(path, { flags: 'a' }); | 	const stream = fs.createWriteStream(path, { flags: 'a' }); | ||||||
| 
 | 
 | ||||||
| 	let exportedCount = 0; | 	let exportedCount = 0; | ||||||
| 	let ended = false; |  | ||||||
| 	let cursor: any = null; | 	let cursor: any = null; | ||||||
| 
 | 
 | ||||||
| 	while (!ended) { | 	while (true) { | ||||||
| 		const blockings = await Blocking.find({ | 		const blockings = await Blocking.find({ | ||||||
| 			blockerId: user._id, | 			blockerId: user._id, | ||||||
| 			...(cursor ? { _id: { $gt: cursor } } : {}) | 			...(cursor ? { _id: { $gt: cursor } } : {}) | ||||||
|  | @ -47,7 +46,6 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> { | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		if (blockings.length === 0) { | 		if (blockings.length === 0) { | ||||||
| 			ended = true; |  | ||||||
| 			job.progress(100); | 			job.progress(100); | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
|  | @ -81,7 +79,7 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> { | ||||||
| 	logger.succ(`Exported to: ${path}`); | 	logger.succ(`Exported to: ${path}`); | ||||||
| 
 | 
 | ||||||
| 	const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; | 	const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; | ||||||
| 	const driveFile = await addFile(user, path, fileName); | 	const driveFile = await addFile(user, path, fileName, null, null, true); | ||||||
| 
 | 
 | ||||||
| 	logger.succ(`Exported to: ${driveFile._id}`); | 	logger.succ(`Exported to: ${driveFile._id}`); | ||||||
| 	cleanup(); | 	cleanup(); | ||||||
|  |  | ||||||
|  | @ -32,10 +32,9 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> { | ||||||
| 	const stream = fs.createWriteStream(path, { flags: 'a' }); | 	const stream = fs.createWriteStream(path, { flags: 'a' }); | ||||||
| 
 | 
 | ||||||
| 	let exportedCount = 0; | 	let exportedCount = 0; | ||||||
| 	let ended = false; |  | ||||||
| 	let cursor: any = null; | 	let cursor: any = null; | ||||||
| 
 | 
 | ||||||
| 	while (!ended) { | 	while (true) { | ||||||
| 		const followings = await Following.find({ | 		const followings = await Following.find({ | ||||||
| 			followerId: user._id, | 			followerId: user._id, | ||||||
| 			...(cursor ? { _id: { $gt: cursor } } : {}) | 			...(cursor ? { _id: { $gt: cursor } } : {}) | ||||||
|  | @ -47,7 +46,6 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> { | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		if (followings.length === 0) { | 		if (followings.length === 0) { | ||||||
| 			ended = true; |  | ||||||
| 			job.progress(100); | 			job.progress(100); | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
|  | @ -81,7 +79,7 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> { | ||||||
| 	logger.succ(`Exported to: ${path}`); | 	logger.succ(`Exported to: ${path}`); | ||||||
| 
 | 
 | ||||||
| 	const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; | 	const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; | ||||||
| 	const driveFile = await addFile(user, path, fileName); | 	const driveFile = await addFile(user, path, fileName, null, null, true); | ||||||
| 
 | 
 | ||||||
| 	logger.succ(`Exported to: ${driveFile._id}`); | 	logger.succ(`Exported to: ${driveFile._id}`); | ||||||
| 	cleanup(); | 	cleanup(); | ||||||
|  |  | ||||||
|  | @ -32,10 +32,9 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> { | ||||||
| 	const stream = fs.createWriteStream(path, { flags: 'a' }); | 	const stream = fs.createWriteStream(path, { flags: 'a' }); | ||||||
| 
 | 
 | ||||||
| 	let exportedCount = 0; | 	let exportedCount = 0; | ||||||
| 	let ended = false; |  | ||||||
| 	let cursor: any = null; | 	let cursor: any = null; | ||||||
| 
 | 
 | ||||||
| 	while (!ended) { | 	while (true) { | ||||||
| 		const mutes = await Mute.find({ | 		const mutes = await Mute.find({ | ||||||
| 			muterId: user._id, | 			muterId: user._id, | ||||||
| 			...(cursor ? { _id: { $gt: cursor } } : {}) | 			...(cursor ? { _id: { $gt: cursor } } : {}) | ||||||
|  | @ -47,7 +46,6 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> { | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		if (mutes.length === 0) { | 		if (mutes.length === 0) { | ||||||
| 			ended = true; |  | ||||||
| 			job.progress(100); | 			job.progress(100); | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
|  | @ -81,7 +79,7 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> { | ||||||
| 	logger.succ(`Exported to: ${path}`); | 	logger.succ(`Exported to: ${path}`); | ||||||
| 
 | 
 | ||||||
| 	const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; | 	const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; | ||||||
| 	const driveFile = await addFile(user, path, fileName); | 	const driveFile = await addFile(user, path, fileName, null, null, true); | ||||||
| 
 | 
 | ||||||
| 	logger.succ(`Exported to: ${driveFile._id}`); | 	logger.succ(`Exported to: ${driveFile._id}`); | ||||||
| 	cleanup(); | 	cleanup(); | ||||||
|  |  | ||||||
|  | @ -42,10 +42,9 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> { | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	let exportedNotesCount = 0; | 	let exportedNotesCount = 0; | ||||||
| 	let ended = false; |  | ||||||
| 	let cursor: any = null; | 	let cursor: any = null; | ||||||
| 
 | 
 | ||||||
| 	while (!ended) { | 	while (true) { | ||||||
| 		const notes = await Note.find({ | 		const notes = await Note.find({ | ||||||
| 			userId: user._id, | 			userId: user._id, | ||||||
| 			...(cursor ? { _id: { $gt: cursor } } : {}) | 			...(cursor ? { _id: { $gt: cursor } } : {}) | ||||||
|  | @ -57,7 +56,6 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> { | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		if (notes.length === 0) { | 		if (notes.length === 0) { | ||||||
| 			ended = true; |  | ||||||
| 			job.progress(100); | 			job.progress(100); | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
|  | @ -101,7 +99,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> { | ||||||
| 	logger.succ(`Exported to: ${path}`); | 	logger.succ(`Exported to: ${path}`); | ||||||
| 
 | 
 | ||||||
| 	const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json'; | 	const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json'; | ||||||
| 	const driveFile = await addFile(user, path, fileName); | 	const driveFile = await addFile(user, path, fileName, null, null, true); | ||||||
| 
 | 
 | ||||||
| 	logger.succ(`Exported to: ${driveFile._id}`); | 	logger.succ(`Exported to: ${driveFile._id}`); | ||||||
| 	cleanup(); | 	cleanup(); | ||||||
|  |  | ||||||
|  | @ -65,7 +65,7 @@ export async function exportUserLists(job: Bull.Job, done: any): Promise<void> { | ||||||
| 	logger.succ(`Exported to: ${path}`); | 	logger.succ(`Exported to: ${path}`); | ||||||
| 
 | 
 | ||||||
| 	const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; | 	const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; | ||||||
| 	const driveFile = await addFile(user, path, fileName); | 	const driveFile = await addFile(user, path, fileName, null, null, true); | ||||||
| 
 | 
 | ||||||
| 	logger.succ(`Exported to: ${driveFile._id}`); | 	logger.succ(`Exported to: ${driveFile._id}`); | ||||||
| 	cleanup(); | 	cleanup(); | ||||||
|  |  | ||||||
|  | @ -34,7 +34,8 @@ export async function importFollowing(job: Bull.Job, done: any): Promise<void> { | ||||||
| 		linenum++; | 		linenum++; | ||||||
| 
 | 
 | ||||||
| 		try { | 		try { | ||||||
| 			const { username, host } = parseAcct(line.trim()); | 			const acct = line.split(',')[0].trim(); | ||||||
|  | 			const { username, host } = parseAcct(acct); | ||||||
| 
 | 
 | ||||||
| 			let target = isSelfHost(host) ? await User.findOne({ | 			let target = isSelfHost(host) ? await User.findOne({ | ||||||
| 				host: null, | 				host: null, | ||||||
|  |  | ||||||
|  | @ -11,6 +11,8 @@ import Logger from '../../services/logger'; | ||||||
| import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc'; | import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc'; | ||||||
| import Instance from '../../models/instance'; | import Instance from '../../models/instance'; | ||||||
| import instanceChart from '../../services/chart/instance'; | import instanceChart from '../../services/chart/instance'; | ||||||
|  | import { validActor } from '../../remote/activitypub/type'; | ||||||
|  | import { toDbHost } from '../../misc/convert-host'; | ||||||
| 
 | 
 | ||||||
| const logger = new Logger('inbox'); | const logger = new Logger('inbox'); | ||||||
| 
 | 
 | ||||||
|  | @ -46,7 +48,7 @@ export default async (job: Bull.Job): Promise<void> => { | ||||||
| 
 | 
 | ||||||
| 		// ブロックしてたら中断
 | 		// ブロックしてたら中断
 | ||||||
| 		// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
 | 		// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
 | ||||||
| 		const instance = await Instance.findOne({ host: host.toLowerCase() }); | 		const instance = await Instance.findOne({ host: toDbHost(host) }); | ||||||
| 		if (instance && instance.isBlocked) { | 		if (instance && instance.isBlocked) { | ||||||
| 			logger.info(`Blocked request: ${host}`); | 			logger.info(`Blocked request: ${host}`); | ||||||
| 			return; | 			return; | ||||||
|  | @ -65,7 +67,7 @@ export default async (job: Bull.Job): Promise<void> => { | ||||||
| 
 | 
 | ||||||
| 		// ブロックしてたら中断
 | 		// ブロックしてたら中断
 | ||||||
| 		// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
 | 		// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
 | ||||||
| 		const instance = await Instance.findOne({ host: host.toLowerCase() }); | 		const instance = await Instance.findOne({ host: toDbHost(host) }); | ||||||
| 		if (instance && instance.isBlocked) { | 		if (instance && instance.isBlocked) { | ||||||
| 			logger.warn(`Blocked request: ${host}`); | 			logger.warn(`Blocked request: ${host}`); | ||||||
| 			return; | 			return; | ||||||
|  | @ -79,7 +81,7 @@ export default async (job: Bull.Job): Promise<void> => { | ||||||
| 
 | 
 | ||||||
| 	// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
 | 	// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
 | ||||||
| 	if (activity.type === 'Update') { | 	if (activity.type === 'Update') { | ||||||
| 		if (activity.object && activity.object.type === 'Person') { | 		if (activity.object && validActor.includes(activity.object.type)) { | ||||||
| 			if (user == null) { | 			if (user == null) { | ||||||
| 				logger.warn('Update activity received, but user not registed.'); | 				logger.warn('Update activity received, but user not registed.'); | ||||||
| 			} else if (!httpSignature.verifySignature(signature, user.publicKey.publicKeyPem)) { | 			} else if (!httpSignature.verifySignature(signature, user.publicKey.publicKeyPem)) { | ||||||
|  |  | ||||||
|  | @ -15,6 +15,8 @@ import block from './block'; | ||||||
| import { apLogger } from '../logger'; | import { apLogger } from '../logger'; | ||||||
| 
 | 
 | ||||||
| const self = async (actor: IRemoteUser, activity: Object): Promise<void> => { | const self = async (actor: IRemoteUser, activity: Object): Promise<void> => { | ||||||
|  | 	if (actor.isSuspended) return; | ||||||
|  | 
 | ||||||
| 	switch (activity.type) { | 	switch (activity.type) { | ||||||
| 	case 'Create': | 	case 'Create': | ||||||
| 		await create(actor, activity); | 		await create(actor, activity); | ||||||
|  |  | ||||||
|  | @ -20,10 +20,32 @@ import { apLogger } from '../logger'; | ||||||
| import { IDriveFile } from '../../../models/drive-file'; | import { IDriveFile } from '../../../models/drive-file'; | ||||||
| import { deliverQuestionUpdate } from '../../../services/note/polls/update'; | import { deliverQuestionUpdate } from '../../../services/note/polls/update'; | ||||||
| import Instance from '../../../models/instance'; | import Instance from '../../../models/instance'; | ||||||
| import { extractDbHost } from '../../../misc/convert-host'; | import { extractDbHost, extractApHost } from '../../../misc/convert-host'; | ||||||
| 
 | 
 | ||||||
| const logger = apLogger; | const logger = apLogger; | ||||||
| 
 | 
 | ||||||
|  | export function validateNote(object: any, uri: string) { | ||||||
|  | 	const expectHost = extractApHost(uri); | ||||||
|  | 
 | ||||||
|  | 	if (object == null) { | ||||||
|  | 		return new Error('invalid Note: object is null'); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!['Note', 'Question', 'Article'].includes(object.type)) { | ||||||
|  | 		return new Error(`invalid Note: invalied object type ${object.type}`); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (object.id && extractApHost(object.id) !== expectHost) { | ||||||
|  | 		return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${extractApHost(object.id)}`); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (object.attributedTo && extractApHost(object.attributedTo) !== expectHost) { | ||||||
|  | 		return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractApHost(object.attributedTo)}`); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Noteをフェッチします。 |  * Noteをフェッチします。 | ||||||
|  * |  * | ||||||
|  | @ -57,8 +79,10 @@ export async function createNote(value: any, resolver?: Resolver, silent = false | ||||||
| 
 | 
 | ||||||
| 	const object: any = await resolver.resolve(value); | 	const object: any = await resolver.resolve(value); | ||||||
| 
 | 
 | ||||||
| 	if (!object || !['Note', 'Question', 'Article'].includes(object.type)) { | 	const entryUri = value.id || value; | ||||||
| 		logger.error(`invalid note: ${value}`, { | 	const err = validateNote(object, entryUri); | ||||||
|  | 	if (err) { | ||||||
|  | 		logger.error(`${err.message}`, { | ||||||
| 			resolver: { | 			resolver: { | ||||||
| 				history: resolver.getHistory() | 				history: resolver.getHistory() | ||||||
| 			}, | 			}, | ||||||
|  | @ -241,7 +265,7 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): | ||||||
| 	// リモートサーバーからフェッチしてきて登録
 | 	// リモートサーバーからフェッチしてきて登録
 | ||||||
| 	// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
 | 	// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
 | ||||||
| 	// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
 | 	// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
 | ||||||
| 	return await createNote(uri, resolver); | 	return await createNote(uri, resolver, true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function extractEmojis(tags: ITag[], host_: string) { | export async function extractEmojis(tags: ITag[], host_: string) { | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ import config from '../../../config'; | ||||||
| import User, { validateUsername, isValidName, IUser, IRemoteUser, isRemoteUser } from '../../../models/user'; | import User, { validateUsername, isValidName, IUser, IRemoteUser, isRemoteUser } from '../../../models/user'; | ||||||
| import Resolver from '../resolver'; | import Resolver from '../resolver'; | ||||||
| import { resolveImage } from './image'; | import { resolveImage } from './image'; | ||||||
| import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type'; | import { isCollectionOrOrderedCollection, isCollection, IPerson, validActor } from '../type'; | ||||||
| import { IDriveFile } from '../../../models/drive-file'; | import { IDriveFile } from '../../../models/drive-file'; | ||||||
| import Meta from '../../../models/meta'; | import Meta from '../../../models/meta'; | ||||||
| import { fromHtml } from '../../../mfm/fromHtml'; | import { fromHtml } from '../../../mfm/fromHtml'; | ||||||
|  | @ -38,7 +38,7 @@ function validatePerson(x: any, uri: string) { | ||||||
| 		return new Error('invalid person: object is null'); | 		return new Error('invalid person: object is null'); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (x.type != 'Person' && x.type != 'Service') { | 	if (!validActor.includes(x.type)) { | ||||||
| 		return new Error(`invalid person: object is not a person or service '${x.type}'`); | 		return new Error(`invalid person: object is not a person or service '${x.type}'`); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -294,13 +294,6 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje | ||||||
| 	} | 	} | ||||||
| 	//#endregion
 | 	//#endregion
 | ||||||
| 
 | 
 | ||||||
| 	// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
 |  | ||||||
| 	await User.update({ _id: exist._id }, { |  | ||||||
| 		$set: { |  | ||||||
| 			lastFetchedAt: new Date(), |  | ||||||
| 		}, |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	if (resolver == null) resolver = new Resolver(); | 	if (resolver == null) resolver = new Resolver(); | ||||||
| 
 | 
 | ||||||
| 	const object = hint || await resolver.resolve(uri) as any; | 	const object = hint || await resolver.resolve(uri) as any; | ||||||
|  |  | ||||||
|  | @ -4,13 +4,13 @@ import { URL } from 'url'; | ||||||
| import * as crypto from 'crypto'; | import * as crypto from 'crypto'; | ||||||
| import { lookup, IRunOptions } from 'lookup-dns-cache'; | import { lookup, IRunOptions } from 'lookup-dns-cache'; | ||||||
| import * as promiseAny from 'promise-any'; | import * as promiseAny from 'promise-any'; | ||||||
| import { toUnicode } from 'punycode'; |  | ||||||
| 
 | 
 | ||||||
| import config from '../../config'; | import config from '../../config'; | ||||||
| import { ILocalUser } from '../../models/user'; | import { ILocalUser } from '../../models/user'; | ||||||
| import { publishApLogStream } from '../../services/stream'; | import { publishApLogStream } from '../../services/stream'; | ||||||
| import { apLogger } from './logger'; | import { apLogger } from './logger'; | ||||||
| import Instance from '../../models/instance'; | import Instance from '../../models/instance'; | ||||||
|  | import { toDbHost } from '../../misc/convert-host'; | ||||||
| 
 | 
 | ||||||
| export const logger = apLogger.createSubLogger('deliver'); | export const logger = apLogger.createSubLogger('deliver'); | ||||||
| 
 | 
 | ||||||
|  | @ -23,7 +23,7 @@ export default async (user: ILocalUser, url: string, object: any) => { | ||||||
| 
 | 
 | ||||||
| 	// ブロックしてたら中断
 | 	// ブロックしてたら中断
 | ||||||
| 	// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
 | 	// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
 | ||||||
| 	const instance = await Instance.findOne({ host: toUnicode(host) }); | 	const instance = await Instance.findOne({ host: toDbHost(host) }); | ||||||
| 	if (instance && instance.isBlocked) return; | 	if (instance && instance.isBlocked) return; | ||||||
| 
 | 
 | ||||||
| 	const data = JSON.stringify(object); | 	const data = JSON.stringify(object); | ||||||
|  | @ -35,7 +35,7 @@ export default async (user: ILocalUser, url: string, object: any) => { | ||||||
| 	const addr = await resolveAddr(hostname); | 	const addr = await resolveAddr(hostname); | ||||||
| 	if (!addr) return; | 	if (!addr) return; | ||||||
| 
 | 
 | ||||||
| 	const _ = new Promise((resolve, reject) => { | 	await new  Promise((resolve, reject) => { | ||||||
| 		const req = request({ | 		const req = request({ | ||||||
| 			protocol, | 			protocol, | ||||||
| 			hostname: addr, | 			hostname: addr, | ||||||
|  | @ -82,8 +82,6 @@ export default async (user: ILocalUser, url: string, object: any) => { | ||||||
| 		req.end(data); | 		req.end(data); | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	await _; |  | ||||||
| 
 |  | ||||||
| 	//#region Log
 | 	//#region Log
 | ||||||
| 	publishApLogStream({ | 	publishApLogStream({ | ||||||
| 		direction: 'out', | 		direction: 'out', | ||||||
|  | @ -98,11 +96,18 @@ export default async (user: ILocalUser, url: string, object: any) => { | ||||||
|  * Resolve host (with cached, asynchrony) |  * Resolve host (with cached, asynchrony) | ||||||
|  */ |  */ | ||||||
| async function resolveAddr(domain: string) { | async function resolveAddr(domain: string) { | ||||||
|  | 	const af = config.outgoingAddressFamily || 'ipv4'; | ||||||
|  | 	const useV4 = af == 'ipv4' || af == 'dual'; | ||||||
|  | 	const useV6 = af == 'ipv6' || af == 'dual'; | ||||||
|  | 
 | ||||||
|  | 	const promises = []; | ||||||
|  | 
 | ||||||
|  | 	if (!useV4 && !useV6) throw 'No usable address family available'; | ||||||
|  | 	if (useV4) promises.push(resolveAddrInner(domain, { family: 4 })); | ||||||
|  | 	if (useV6) promises.push(resolveAddrInner(domain, { family: 6 })); | ||||||
|  | 
 | ||||||
| 	// v4/v6で先に取得できた方を採用する
 | 	// v4/v6で先に取得できた方を採用する
 | ||||||
| 	return await promiseAny([ | 	return await promiseAny(promises); | ||||||
| 		resolveAddrInner(domain, { family: 4 }), |  | ||||||
| 		resolveAddrInner(domain, { family: 6 }) |  | ||||||
| 	]); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise<string> { | function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise<string> { | ||||||
|  |  | ||||||
|  | @ -65,6 +65,8 @@ interface IQuestionChoice { | ||||||
| 	_misskey_votes?: number; | 	_misskey_votes?: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export const validActor = ['Person', 'Service']; | ||||||
|  | 
 | ||||||
| export interface IPerson extends IObject { | export interface IPerson extends IObject { | ||||||
| 	type: 'Person'; | 	type: 'Person'; | ||||||
| 	name: string; | 	name: string; | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ import chalk from 'chalk'; | ||||||
| 
 | 
 | ||||||
| const logger = remoteLogger.createSubLogger('resolve-user'); | const logger = remoteLogger.createSubLogger('resolve-user'); | ||||||
| 
 | 
 | ||||||
| export default async (username: string, _host: string, option?: any, resync?: boolean): Promise<IUser> => { | export default async (username: string, _host: string, option?: any, resync = false): Promise<IUser> => { | ||||||
| 	const usernameLower = username.toLowerCase(); | 	const usernameLower = username.toLowerCase(); | ||||||
| 
 | 
 | ||||||
| 	if (_host == null) { | 	if (_host == null) { | ||||||
|  | @ -28,7 +28,7 @@ export default async (username: string, _host: string, option?: any, resync?: bo | ||||||
| 		return await User.findOne({ usernameLower, host: null }); | 		return await User.findOne({ usernameLower, host: null }); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	const user = await User.findOne({ usernameLower, host }, option); | 	const user = await User.findOne({ usernameLower, host }, option) as IRemoteUser; | ||||||
| 
 | 
 | ||||||
| 	const acctLower = `${usernameLower}@${hostAscii}`; | 	const acctLower = `${usernameLower}@${hostAscii}`; | ||||||
| 
 | 
 | ||||||
|  | @ -39,14 +39,22 @@ export default async (username: string, _host: string, option?: any, resync?: bo | ||||||
| 		return await createPerson(self.href); | 		return await createPerson(self.href); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (resync) { | 	// resyncオプション OR ユーザー情報が古い場合は、WebFilgerからやりなおして返す
 | ||||||
|  | 	if (resync || user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { | ||||||
|  | 		// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
 | ||||||
|  | 		await User.update({ _id: user._id }, { | ||||||
|  | 			$set: { | ||||||
|  | 				lastFetchedAt: new Date(), | ||||||
|  | 			}, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
| 		logger.info(`try resync: ${acctLower}`); | 		logger.info(`try resync: ${acctLower}`); | ||||||
| 		const self = await resolveSelf(acctLower); | 		const self = await resolveSelf(acctLower); | ||||||
| 
 | 
 | ||||||
| 		if ((user as IRemoteUser).uri !== self.href) { | 		if (user.uri !== self.href) { | ||||||
| 			// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
 | 			// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
 | ||||||
| 			logger.info(`uri missmatch: ${acctLower}`); | 			logger.info(`uri missmatch: ${acctLower}`); | ||||||
| 			logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${(user as IRemoteUser).uri} to ${self.href}`); | 			logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); | ||||||
| 
 | 
 | ||||||
| 			// validate uri
 | 			// validate uri
 | ||||||
| 			const uri = new URL(self.href); | 			const uri = new URL(self.href); | ||||||
|  | @ -79,8 +87,8 @@ export default async (username: string, _host: string, option?: any, resync?: bo | ||||||
| async function resolveSelf(acctLower: string) { | async function resolveSelf(acctLower: string) { | ||||||
| 	logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); | 	logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); | ||||||
| 	const finger = await webFinger(acctLower).catch(e => { | 	const finger = await webFinger(acctLower).catch(e => { | ||||||
| 		logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${e.message} (${e.status})`); | 		logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`); | ||||||
| 		throw e; | 		throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`); | ||||||
| 	}); | 	}); | ||||||
| 	const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); | 	const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); | ||||||
| 	if (!self) { | 	if (!self) { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { WebFinger } from 'webfinger.js'; | import config from '../config'; | ||||||
| 
 | import * as request from 'request-promise-native'; | ||||||
| const webFinger = new WebFinger({ }); | import { URL } from 'url'; | ||||||
|  | import { query as urlQuery } from '../prelude/url'; | ||||||
| 
 | 
 | ||||||
| type ILink = { | type ILink = { | ||||||
| 	href: string; | 	href: string; | ||||||
|  | @ -12,12 +13,33 @@ type IWebFinger = { | ||||||
| 	subject: string; | 	subject: string; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default async function resolve(query: any): Promise<IWebFinger> { | export default async function(query: string): Promise<IWebFinger> { | ||||||
| 	return await new Promise((res, rej) => webFinger.lookup(query, (error: Error | string, result: any) => { | 	const url = genUrl(query); | ||||||
| 		if (error) { | 
 | ||||||
| 			return rej(error); | 	return await request({ | ||||||
|  | 		url, | ||||||
|  | 		proxy: config.proxy, | ||||||
|  | 		timeout: 10 * 1000, | ||||||
|  | 		forever: true, | ||||||
|  | 		headers: { | ||||||
|  | 			'User-Agent': config.userAgent, | ||||||
|  | 			Accept: 'application/jrd+json, application/json' | ||||||
|  | 		}, | ||||||
|  | 		json: true | ||||||
|  | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 		res(result.object); | function genUrl(query: string) { | ||||||
| 	})) as IWebFinger; | 	if (query.match(/^https?:\/\//)) { | ||||||
|  | 		const u = new URL(query); | ||||||
|  | 		return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query }); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const m = query.match(/^([^@]+)@(.*)/); | ||||||
|  | 	if (m) { | ||||||
|  | 		const hostname = m[2]; | ||||||
|  | 		return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` }); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	throw new Error(`Invalied query (${query})`); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -22,6 +22,9 @@ export default define(meta, async (ps) => { | ||||||
| 			createdAt: { | 			createdAt: { | ||||||
| 				$gt: new Date(Date.now() - span) | 				$gt: new Date(Date.now() - span) | ||||||
| 			}, | 			}, | ||||||
|  | 			visibility: { | ||||||
|  | 				$in: ['public', 'home'] | ||||||
|  | 			}, | ||||||
| 			tagsLower: { | 			tagsLower: { | ||||||
| 				$exists: true, | 				$exists: true, | ||||||
| 				$ne: [] | 				$ne: [] | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import Resolver from '../../../../remote/activitypub/resolver'; | ||||||
| import { ApiError } from '../../error'; | import { ApiError } from '../../error'; | ||||||
| import Instance from '../../../../models/instance'; | import Instance from '../../../../models/instance'; | ||||||
| import { extractDbHost } from '../../../../misc/convert-host'; | import { extractDbHost } from '../../../../misc/convert-host'; | ||||||
|  | import { validActor } from '../../../../remote/activitypub/type'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['federation'], | 	tags: ['federation'], | ||||||
|  | @ -85,6 +86,17 @@ async function fetchAny(uri: string) { | ||||||
| 	// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
 | 	// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
 | ||||||
| 	// これはDBに存在する可能性があるため再度DB検索
 | 	// これはDBに存在する可能性があるため再度DB検索
 | ||||||
| 	if (uri !== object.id) { | 	if (uri !== object.id) { | ||||||
|  | 		if (object.id.startsWith(config.url + '/')) { | ||||||
|  | 			const id = new mongo.ObjectID(object.id.split('/').pop()); | ||||||
|  | 			const [user, note] = await Promise.all([ | ||||||
|  | 				User.findOne({ _id: id }), | ||||||
|  | 				Note.findOne({ _id: id }) | ||||||
|  | 			]); | ||||||
|  | 
 | ||||||
|  | 			const packed = await mergePack(user, note); | ||||||
|  | 			if (packed !== null) return packed; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		const [user, note] = await Promise.all([ | 		const [user, note] = await Promise.all([ | ||||||
| 			User.findOne({ uri: object.id }), | 			User.findOne({ uri: object.id }), | ||||||
| 			Note.findOne({ uri: object.id }) | 			Note.findOne({ uri: object.id }) | ||||||
|  | @ -95,7 +107,7 @@ async function fetchAny(uri: string) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// それでもみつからなければ新規であるため登録
 | 	// それでもみつからなければ新規であるため登録
 | ||||||
| 	if (object.type === 'Person') { | 	if (validActor.includes(object.type)) { | ||||||
| 		const user = await createPerson(object.id); | 		const user = await createPerson(object.id); | ||||||
| 		return { | 		return { | ||||||
| 			type: 'User', | 			type: 'User', | ||||||
|  | @ -104,7 +116,7 @@ async function fetchAny(uri: string) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (['Note', 'Question', 'Article'].includes(object.type)) { | 	if (['Note', 'Question', 'Article'].includes(object.type)) { | ||||||
| 		const note = await createNote(object.id); | 		const note = await createNote(object.id, null, true); | ||||||
| 		return { | 		return { | ||||||
| 			type: 'Note', | 			type: 'Note', | ||||||
| 			object: await packNote(note, null, { detail: true }) | 			object: await packNote(note, null, { detail: true }) | ||||||
|  |  | ||||||
|  | @ -31,6 +31,9 @@ export default define(meta, async () => { | ||||||
| 			createdAt: { | 			createdAt: { | ||||||
| 				$gt: new Date(Date.now() - rangeA) | 				$gt: new Date(Date.now() - rangeA) | ||||||
| 			}, | 			}, | ||||||
|  | 			visibility: { | ||||||
|  | 				$in: ['public', 'home'] | ||||||
|  | 			}, | ||||||
| 			tagsLower: { | 			tagsLower: { | ||||||
| 				$exists: true, | 				$exists: true, | ||||||
| 				$ne: [] | 				$ne: [] | ||||||
|  | @ -79,6 +82,9 @@ export default define(meta, async () => { | ||||||
| 	const hotsPromises = limitedTags.map(async tag => { | 	const hotsPromises = limitedTags.map(async tag => { | ||||||
| 		const passedCount = (await Note.distinct('userId', { | 		const passedCount = (await Note.distinct('userId', { | ||||||
| 			tagsLower: tag.name, | 			tagsLower: tag.name, | ||||||
|  | 			visibility: { | ||||||
|  | 				$in: ['public', 'home'] | ||||||
|  | 			}, | ||||||
| 			createdAt: { | 			createdAt: { | ||||||
| 				$lt: new Date(Date.now() - rangeA), | 				$lt: new Date(Date.now() - rangeA), | ||||||
| 				$gt: new Date(Date.now() - rangeB) | 				$gt: new Date(Date.now() - rangeB) | ||||||
|  | @ -120,6 +126,9 @@ export default define(meta, async () => { | ||||||
| 	for (let i = 0; i < range; i++) { | 	for (let i = 0; i < range; i++) { | ||||||
| 		countPromises.push(Promise.all(hots.map(tag => Note.distinct('userId', { | 		countPromises.push(Promise.all(hots.map(tag => Note.distinct('userId', { | ||||||
| 			tagsLower: tag, | 			tagsLower: tag, | ||||||
|  | 			visibility: { | ||||||
|  | 				$in: ['public', 'home'] | ||||||
|  | 			}, | ||||||
| 			createdAt: { | 			createdAt: { | ||||||
| 				$lt: new Date(Date.now() - (interval * i)), | 				$lt: new Date(Date.now() - (interval * i)), | ||||||
| 				$gt: new Date(Date.now() - (interval * (i + 1))) | 				$gt: new Date(Date.now() - (interval * (i + 1))) | ||||||
|  | @ -131,6 +140,9 @@ export default define(meta, async () => { | ||||||
| 
 | 
 | ||||||
| 	const totalCounts: any = await Promise.all(hots.map(tag => Note.distinct('userId', { | 	const totalCounts: any = await Promise.all(hots.map(tag => Note.distinct('userId', { | ||||||
| 		tagsLower: tag, | 		tagsLower: tag, | ||||||
|  | 		visibility: { | ||||||
|  | 			$in: ['public', 'home'] | ||||||
|  | 		}, | ||||||
| 		createdAt: { | 		createdAt: { | ||||||
| 			$gt: new Date(Date.now() - (interval * range)) | 			$gt: new Date(Date.now() - (interval * range)) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import $ from 'cafy'; | import $ from 'cafy'; | ||||||
| import ID, { transform, transformMany } from '../../../../misc/cafy-id'; | import ID, { transform, transformMany } from '../../../../misc/cafy-id'; | ||||||
| import User, { pack, isRemoteUser } from '../../../../models/user'; | import User, { pack } from '../../../../models/user'; | ||||||
| import resolveRemoteUser from '../../../../remote/resolve-user'; | import resolveRemoteUser from '../../../../remote/resolve-user'; | ||||||
| import define from '../../define'; | import define from '../../define'; | ||||||
| import { apiLogger } from '../../logger'; | import { apiLogger } from '../../logger'; | ||||||
|  | @ -96,13 +96,6 @@ export default define(meta, async (ps, me) => { | ||||||
| 			throw new ApiError(meta.errors.noSuchUser); | 			throw new ApiError(meta.errors.noSuchUser); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// ユーザー情報更新
 |  | ||||||
| 		if (isRemoteUser(user)) { |  | ||||||
| 			if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { |  | ||||||
| 				resolveRemoteUser(ps.username, ps.host, { }, true); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return await pack(user, me, { | 		return await pack(user, me, { | ||||||
| 			detail: true | 			detail: true | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | @ -16,7 +16,8 @@ import discord from './service/discord'; | ||||||
| import github from './service/github'; | import github from './service/github'; | ||||||
| import twitter from './service/twitter'; | import twitter from './service/twitter'; | ||||||
| import Instance from '../../models/instance'; | import Instance from '../../models/instance'; | ||||||
| import { toASCII } from 'punycode'; | import { toApHost } from '../../misc/convert-host'; | ||||||
|  | import { unique } from '../../prelude/array'; | ||||||
| 
 | 
 | ||||||
| // Init app
 | // Init app
 | ||||||
| const app = new Koa(); | const app = new Koa(); | ||||||
|  | @ -72,7 +73,7 @@ router.get('/v1/instance/peers', async ctx => { | ||||||
| 			host: 1 | 			host: 1 | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 	const punyCodes = instances.map(instance => toASCII(instance.host)); | 	const punyCodes = unique(instances.map(instance => toApHost(instance.host))); | ||||||
| 
 | 
 | ||||||
| 	ctx.body = punyCodes; | 	ctx.body = punyCodes; | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ export default class extends Channel { | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
| 				case 'mention': { | 				case 'mention': { | ||||||
|  | 					if (mutedUserIds.includes(body.userId)) return; | ||||||
| 					if (body.isHidden) return; | 					if (body.isHidden) return; | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -73,7 +73,7 @@ export default async function(ctx: Koa.BaseContext) { | ||||||
| 				await sendRaw(); | 				await sendRaw(); | ||||||
| 			} else { | 			} else { | ||||||
| 				ctx.status = 404; | 				ctx.status = 404; | ||||||
| 				await send(ctx as any, '/dummy.png', { root: assets }); | 				await send(ctx as any, '/thumbnail-not-available.png', { root: assets }); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} else if ('web' in ctx.query) { | 	} else if ('web' in ctx.query) { | ||||||
|  |  | ||||||
|  | @ -17,6 +17,8 @@ export async function proxyMedia(ctx: Koa.BaseContext) { | ||||||
| 
 | 
 | ||||||
| 		const [type, ext] = await detectMine(path); | 		const [type, ext] = await detectMine(path); | ||||||
| 
 | 
 | ||||||
|  | 		if (!type.startsWith('image/')) throw 403; | ||||||
|  | 
 | ||||||
| 		let image: IImage; | 		let image: IImage; | ||||||
| 
 | 
 | ||||||
| 		if ('static' in ctx.query && ['image/png', 'image/gif'].includes(type)) { | 		if ('static' in ctx.query && ['image/png', 'image/gif'].includes(type)) { | ||||||
|  |  | ||||||
|  | @ -25,6 +25,9 @@ block meta | ||||||
| 
 | 
 | ||||||
| 	meta(name='twitter:card' content='summary') | 	meta(name='twitter:card' content='summary') | ||||||
| 
 | 
 | ||||||
|  | 	if user.host | ||||||
|  | 		meta(name='robots' content='noindex') | ||||||
|  | 
 | ||||||
| 	if user.twitter | 	if user.twitter | ||||||
| 		meta(name='twitter:creator' content=`@${user.twitter.screenName}`) | 		meta(name='twitter:creator' content=`@${user.twitter.screenName}`) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,6 +24,9 @@ block meta | ||||||
| 
 | 
 | ||||||
| 	meta(name='twitter:card' content='summary') | 	meta(name='twitter:card' content='summary') | ||||||
| 
 | 
 | ||||||
|  | 	if user.host | ||||||
|  | 		meta(name='robots' content='noindex') | ||||||
|  | 
 | ||||||
| 	if user.twitter | 	if user.twitter | ||||||
| 		meta(name='twitter:creator' content=`@${user.twitter.screenName}`) | 		meta(name='twitter:creator' content=`@${user.twitter.screenName}`) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -34,6 +34,7 @@ import Instance from '../../models/instance'; | ||||||
| import extractMentions from '../../misc/extract-mentions'; | import extractMentions from '../../misc/extract-mentions'; | ||||||
| import extractEmojis from '../../misc/extract-emojis'; | import extractEmojis from '../../misc/extract-emojis'; | ||||||
| import extractHashtags from '../../misc/extract-hashtags'; | import extractHashtags from '../../misc/extract-hashtags'; | ||||||
|  | import { genId } from '../../misc/gen-id'; | ||||||
| 
 | 
 | ||||||
| type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; | type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; | ||||||
| 
 | 
 | ||||||
|  | @ -237,7 +238,9 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// ハッシュタグ更新
 | 	// ハッシュタグ更新
 | ||||||
|  | 	if (data.visibility === 'public' || data.visibility === 'home') { | ||||||
| 		for (const tag of tags) updateHashtag(user, tag); | 		for (const tag of tags) updateHashtag(user, tag); | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// ファイルが添付されていた場合ドライブのファイルの「このファイルが添付された投稿一覧」プロパティにこの投稿を追加
 | 	// ファイルが添付されていた場合ドライブのファイルの「このファイルが添付された投稿一覧」プロパティにこの投稿を追加
 | ||||||
| 	if (data.files) { | 	if (data.files) { | ||||||
|  | @ -434,6 +437,7 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren | ||||||
| 
 | 
 | ||||||
| async function insertNote(user: IUser, data: Option, tags: string[], emojis: string[], mentionedUsers: IUser[]) { | async function insertNote(user: IUser, data: Option, tags: string[], emojis: string[], mentionedUsers: IUser[]) { | ||||||
| 	const insert: any = { | 	const insert: any = { | ||||||
|  | 		_id: genId(data.createdAt), | ||||||
| 		createdAt: data.createdAt, | 		createdAt: data.createdAt, | ||||||
| 		fileIds: data.files ? data.files.map(file => file._id) : [], | 		fileIds: data.files ? data.files.map(file => file._id) : [], | ||||||
| 		replyId: data.reply ? data.reply._id : null, | 		replyId: data.reply ? data.reply._id : null, | ||||||
|  |  | ||||||
|  | @ -1,9 +1,12 @@ | ||||||
| import Instance, { IInstance } from '../models/instance'; | import Instance, { IInstance } from '../models/instance'; | ||||||
| import federationChart from '../services/chart/federation'; | import federationChart from '../services/chart/federation'; | ||||||
|  | import { toDbHost } from '../misc/convert-host'; | ||||||
| 
 | 
 | ||||||
| export async function registerOrFetchInstanceDoc(host: string): Promise<IInstance> { | export async function registerOrFetchInstanceDoc(host: string): Promise<IInstance> { | ||||||
| 	if (host == null) return null; | 	if (host == null) return null; | ||||||
| 
 | 
 | ||||||
|  | 	host = toDbHost(host); | ||||||
|  | 
 | ||||||
| 	const index = await Instance.findOne({ host }); | 	const index = await Instance.findOne({ host }); | ||||||
| 
 | 
 | ||||||
| 	if (index == null) { | 	if (index == null) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue