Merge branch 'develop'
This commit is contained in:
		
						commit
						6fb7721798
					
				
					 110 changed files with 964 additions and 670 deletions
				
			
		| 
						 | 
					@ -88,7 +88,9 @@ redis:
 | 
				
			||||||
#elasticsearch:
 | 
					#elasticsearch:
 | 
				
			||||||
#  host: localhost
 | 
					#  host: localhost
 | 
				
			||||||
#  port: 9200
 | 
					#  port: 9200
 | 
				
			||||||
#  pass: null
 | 
					#  ssl: false
 | 
				
			||||||
 | 
					#  user: 
 | 
				
			||||||
 | 
					#  pass: 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#   ┌───────────────┐
 | 
					#   ┌───────────────┐
 | 
				
			||||||
#───┘ ID generation └───────────────────────────────────────────
 | 
					#───┘ ID generation └───────────────────────────────────────────
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								.github/workflows/nodejs.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/nodejs.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -21,6 +21,7 @@ jobs:
 | 
				
			||||||
          - 5432:5432
 | 
					          - 5432:5432
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          POSTGRES_DB: test-misskey
 | 
					          POSTGRES_DB: test-misskey
 | 
				
			||||||
 | 
					          POSTGRES_HOST_AUTH_METHOD: trust
 | 
				
			||||||
      redis:
 | 
					      redis:
 | 
				
			||||||
        image: redis:alpine
 | 
					        image: redis:alpine
 | 
				
			||||||
        ports:
 | 
					        ports:
 | 
				
			||||||
| 
						 | 
					@ -40,3 +41,13 @@ jobs:
 | 
				
			||||||
      run: yarn build
 | 
					      run: yarn build
 | 
				
			||||||
    - name: Test
 | 
					    - name: Test
 | 
				
			||||||
      run: yarn test
 | 
					      run: yarn test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lint:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - uses: actions/checkout@v2
 | 
				
			||||||
 | 
					    - uses: actions/setup-node@v1
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        node-version: 12.x
 | 
				
			||||||
 | 
					    - run: yarn install
 | 
				
			||||||
 | 
					    - run: yarn lint
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								.mocharc.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.mocharc.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						"timeout": 30000,
 | 
				
			||||||
 | 
						"slow": 1000
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
					@ -129,7 +129,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
				
			||||||
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
 | 
					<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
 | 
				
			||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
 | 
					<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
 | 
				
			||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
 | 
					<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
 | 
				
			||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
 | 
					 | 
				
			||||||
</tr><tr>
 | 
					</tr><tr>
 | 
				
			||||||
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
 | 
					<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
 | 
				
			||||||
<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
 | 
					<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
 | 
				
			||||||
| 
						 | 
					@ -140,7 +139,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
				
			||||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</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/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/hs_sh_net">mewl hayabusa</a></td>
 | 
				
			||||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
 | 
					 | 
				
			||||||
</tr></table>
 | 
					</tr></table>
 | 
				
			||||||
<table><tr>
 | 
					<table><tr>
 | 
				
			||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
 | 
					<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
 | 
				
			||||||
| 
						 | 
					@ -167,6 +165,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
				
			||||||
</tr></table>
 | 
					</tr></table>
 | 
				
			||||||
<table><tr>
 | 
					<table><tr>
 | 
				
			||||||
<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>
 | 
					<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>
 | 
				
			||||||
 | 
					<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28295158/cd2451bfb94a449dbf705ef4718cd355/2.jpeg?token-time=2145916800&token-hash=MRv3BxufHPuCyiBSxU5UYmLGvD6YZlhtSFRfMWg2k4U%3D" alt="012" width="100"></td>
 | 
				
			||||||
 | 
					<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco" 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/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
 | 
				
			||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
 | 
					<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/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/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/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
 | 
				
			||||||
| 
						 | 
					@ -175,6 +175,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
				
			||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
 | 
					<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
 | 
				
			||||||
</tr><tr>
 | 
					</tr><tr>
 | 
				
			||||||
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
 | 
					<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
 | 
				
			||||||
 | 
					<td><a href="https://www.patreon.com/user?u=28295158">012</a></td>
 | 
				
			||||||
 | 
					<td><a href="https://www.patreon.com/nijimiss">nafuchoco</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=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/noellabo">noellabo</a></td>
 | 
					<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
 | 
				
			||||||
| 
						 | 
					@ -183,18 +185,18 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
				
			||||||
<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
 | 
					<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
 | 
				
			||||||
</tr></table>
 | 
					</tr></table>
 | 
				
			||||||
<table><tr>
 | 
					<table><tr>
 | 
				
			||||||
<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.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/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
 | 
				
			||||||
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
 | 
					<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
 | 
				
			||||||
 | 
					<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone" width="100"></td>
 | 
				
			||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
 | 
					<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>
 | 
					</tr><tr>
 | 
				
			||||||
<td><a href="https://www.patreon.com/dansup">dansup</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>
 | 
					<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
 | 
				
			||||||
 | 
					<td><a href="https://www.patreon.com/efertone">Efertone</a></td>
 | 
				
			||||||
<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, 21 Feb 2020 13:39:06 UTC
 | 
					**Last updated:** Tue, 17 Mar 2020 18:57:08 UTC
 | 
				
			||||||
<!-- PATREON_END -->
 | 
					<!-- PATREON_END -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[backer-url]: #backers
 | 
					[backer-url]: #backers
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -84,7 +84,7 @@ clickToShow: "クリックして表示"
 | 
				
			||||||
sensitive: "閲覧注意"
 | 
					sensitive: "閲覧注意"
 | 
				
			||||||
add: "追加"
 | 
					add: "追加"
 | 
				
			||||||
reaction: "リアクション"
 | 
					reaction: "リアクション"
 | 
				
			||||||
reactionSettingDescription: "リアクションピッカーに表示するリアクションを改行で区切って設定します。"
 | 
					reactionSettingDescription: "リアクションピッカーに表示するリアクションを設定します。"
 | 
				
			||||||
rememberNoteVisibility: "公開範囲を記憶する"
 | 
					rememberNoteVisibility: "公開範囲を記憶する"
 | 
				
			||||||
renameFile: "ファイル名を変更"
 | 
					renameFile: "ファイル名を変更"
 | 
				
			||||||
attachCancel: "添付取り消し"
 | 
					attachCancel: "添付取り消し"
 | 
				
			||||||
| 
						 | 
					@ -168,6 +168,7 @@ intro: "Misskeyのインストールが完了しました!管理者アカウ
 | 
				
			||||||
done: "完了"
 | 
					done: "完了"
 | 
				
			||||||
processing: "処理中"
 | 
					processing: "処理中"
 | 
				
			||||||
preview: "プレビュー"
 | 
					preview: "プレビュー"
 | 
				
			||||||
 | 
					default: "デフォルト"
 | 
				
			||||||
noCustomEmojis: "絵文字はありません"
 | 
					noCustomEmojis: "絵文字はありません"
 | 
				
			||||||
customEmojisOfRemote: "リモートの絵文字"
 | 
					customEmojisOfRemote: "リモートの絵文字"
 | 
				
			||||||
noJobs: "ジョブはありません"
 | 
					noJobs: "ジョブはありません"
 | 
				
			||||||
| 
						 | 
					@ -422,16 +423,28 @@ hideThisNote: "このノートを非表示"
 | 
				
			||||||
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示する"
 | 
					showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示する"
 | 
				
			||||||
objectStorage: "オブジェクトストレージ"
 | 
					objectStorage: "オブジェクトストレージ"
 | 
				
			||||||
useObjectStorage: "オブジェクトストレージを使用"
 | 
					useObjectStorage: "オブジェクトストレージを使用"
 | 
				
			||||||
 | 
					objectStorageBaseUrl: "Base URL"
 | 
				
			||||||
 | 
					objectStorageBaseUrlDesc: "参照に使用するURL。CDNやProxyを使用している場合はそのURL、S3: 'https://<bucket>.s3.amazonaws.com'、GCS等: 'https://storage.googleapis.com/<bucket>'。"
 | 
				
			||||||
 | 
					objectStorageBucket: "Bucket"
 | 
				
			||||||
 | 
					objectStorageBucketDesc: "使用サービスのbucket名を指定してください。"
 | 
				
			||||||
 | 
					objectStoragePrefix: "Prefix"
 | 
				
			||||||
 | 
					objectStoragePrefixDesc: "このprefixのディレクトリ下に格納されます。"
 | 
				
			||||||
 | 
					objectStorageEndpoint: "Endpoint"
 | 
				
			||||||
 | 
					objectStorageEndpointDesc: "S3の場合は空、それ以外の場合は各サービスのendpointを指定してください。'<host>'または'<host>:<port>'のように指定します。"
 | 
				
			||||||
 | 
					objectStorageRegion: "Region"
 | 
				
			||||||
 | 
					objectStorageRegionDesc: "'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は、空または'us-east-1'にしてください。"
 | 
				
			||||||
 | 
					objectStorageUseSSL: "SSLを使用する"
 | 
				
			||||||
 | 
					objectStorageUseSSLDesc: "API接続にhttpsを使用しない場合はオフにしてください"
 | 
				
			||||||
serverLogs: "サーバーログ"
 | 
					serverLogs: "サーバーログ"
 | 
				
			||||||
deleteAll: "全て削除"
 | 
					deleteAll: "全て削除"
 | 
				
			||||||
showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
 | 
					showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
 | 
				
			||||||
newNoteRecived: "新しいノートがあります"
 | 
					newNoteRecived: "新しいノートがあります"
 | 
				
			||||||
useNotificationsPopup: "通知一覧をポップアップで表示"
 | 
					 | 
				
			||||||
sounds: "サウンド"
 | 
					sounds: "サウンド"
 | 
				
			||||||
listen: "聴く"
 | 
					listen: "聴く"
 | 
				
			||||||
none: "なし"
 | 
					none: "なし"
 | 
				
			||||||
volume: "音量"
 | 
					volume: "音量"
 | 
				
			||||||
details: "詳細"
 | 
					details: "詳細"
 | 
				
			||||||
 | 
					chooseEmoji: "絵文字を選択"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_sfx:
 | 
					_sfx:
 | 
				
			||||||
  note: "ノート"
 | 
					  note: "ノート"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								migration/1582875306439-note-reaction-length.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1582875306439-note-reaction-length.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					import {MigrationInterface, QueryRunner} from "typeorm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class noteReactionLength1582875306439 implements MigrationInterface {
 | 
				
			||||||
 | 
					    name = 'noteReactionLength1582875306439'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async up(queryRunner: QueryRunner): Promise<any> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(130)`, undefined);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async down(queryRunner: QueryRunner): Promise<any> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(128)`, undefined);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "misskey",
 | 
						"name": "misskey",
 | 
				
			||||||
	"author": "syuilo <syuilotan@yahoo.co.jp>",
 | 
						"author": "syuilo <syuilotan@yahoo.co.jp>",
 | 
				
			||||||
	"version": "12.21.0",
 | 
						"version": "12.22.0",
 | 
				
			||||||
	"codename": "indigo",
 | 
						"codename": "indigo",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
		"type": "git",
 | 
							"type": "git",
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,7 @@
 | 
				
			||||||
		"gulp": "gulp build",
 | 
							"gulp": "gulp build",
 | 
				
			||||||
		"clean": "gulp clean",
 | 
							"clean": "gulp clean",
 | 
				
			||||||
		"cleanall": "gulp cleanall",
 | 
							"cleanall": "gulp cleanall",
 | 
				
			||||||
		"lint": "gulp lint",
 | 
							"lint": "tslint 'src/**/*.ts'",
 | 
				
			||||||
		"test": "cross-env TS_NODE_FILES=true gulp test",
 | 
							"test": "cross-env TS_NODE_FILES=true gulp test",
 | 
				
			||||||
		"format": "gulp format"
 | 
							"format": "gulp format"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/@types/jsrsasign.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								src/@types/jsrsasign.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -171,6 +171,7 @@ declare module 'jsrsasign' {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public static getTLVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): ASN1TLV;
 | 
							public static getTLVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): ASN1TLV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// tslint:disable-next-line:bool-param-default
 | 
				
			||||||
		public static getVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string, removeUnusedbits?: boolean): ASN1V;
 | 
							public static getVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string, removeUnusedbits?: boolean): ASN1V;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public static hextooidstr(hex: ASN1OIDV): OID;
 | 
							public static hextooidstr(hex: ASN1OIDV): OID;
 | 
				
			||||||
| 
						 | 
					@ -620,9 +621,7 @@ declare module 'jsrsasign' {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public encrypt(text: string): HexString | null;
 | 
							public encrypt(text: string): HexString | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public encryptOAEP(text: string, hash?: string, hashLen?: number): HexString | null;
 | 
							public encryptOAEP(text: string, hash?: string | ((s: string) => string), hashLen?: number): HexString | null;
 | 
				
			||||||
 | 
					 | 
				
			||||||
		public encryptOAEP(text: string, hash?: (s: string) => string, hashLen?: number): HexString | null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//// RSA PRIVATE
 | 
							//// RSA PRIVATE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -638,9 +637,7 @@ declare module 'jsrsasign' {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public decrypt(ctext: HexString): string;
 | 
							public decrypt(ctext: HexString): string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public decryptOAEP(ctext: HexString, hash?: string, hashLen?: number): string | null;
 | 
							public decryptOAEP(ctext: HexString, hash?: string | ((s: string) => string), hashLen?: number): string | null;
 | 
				
			||||||
 | 
					 | 
				
			||||||
		public encryptOAEP(ctext: HexString, hash?: (s: string) => string, hashLen?: number): string | null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//// RSA PEM
 | 
							//// RSA PEM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,11 +51,7 @@
 | 
				
			||||||
					<fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
 | 
										<fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
 | 
				
			||||||
				</router-link>
 | 
									</router-link>
 | 
				
			||||||
				<template v-if="$store.getters.isSignedIn">
 | 
									<template v-if="$store.getters.isSignedIn">
 | 
				
			||||||
					<button class="item _button notifications" @click="notificationsOpen = !notificationsOpen" ref="notificationButton" v-if="$store.state.device.useNotificationsPopup">
 | 
										<router-link class="item notifications" active-class="active" to="/my/notifications" ref="notificationButton">
 | 
				
			||||||
						<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
 | 
					 | 
				
			||||||
						<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
 | 
					 | 
				
			||||||
					</button>
 | 
					 | 
				
			||||||
					<router-link class="item notifications" active-class="active" to="/my/notifications" ref="notificationButton" v-else>
 | 
					 | 
				
			||||||
						<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
 | 
											<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
 | 
				
			||||||
						<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
 | 
											<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
 | 
				
			||||||
					</router-link>
 | 
										</router-link>
 | 
				
			||||||
| 
						 | 
					@ -149,17 +145,12 @@
 | 
				
			||||||
		<button class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadSpecifiedNotes || $store.state.i.hasPendingReceivedFollowRequest || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement)"><fa :icon="faCircle"/></i></button>
 | 
							<button class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadSpecifiedNotes || $store.state.i.hasPendingReceivedFollowRequest || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement)"><fa :icon="faCircle"/></i></button>
 | 
				
			||||||
		<button v-if="$route.name === 'index'" class="button home _button" @click="top()"><fa :icon="faHome"/></button>
 | 
							<button v-if="$route.name === 'index'" class="button home _button" @click="top()"><fa :icon="faHome"/></button>
 | 
				
			||||||
		<button v-else class="button home _button" @click="$router.push('/')"><fa :icon="faHome"/></button>
 | 
							<button v-else class="button home _button" @click="$router.push('/')"><fa :icon="faHome"/></button>
 | 
				
			||||||
		<button v-if="$store.getters.isSignedIn && $store.state.device.useNotificationsPopup" class="button notifications _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton2"><fa :icon="notificationsOpen ? faTimes : faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
 | 
							<button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="$router.push('/my/notifications')" ref="notificationButton2"><fa :icon="faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
 | 
				
			||||||
		<button v-if="$store.getters.isSignedIn && !$store.state.device.useNotificationsPopup" class="button notifications _button" @click="$router.push('/my/notifications')" ref="notificationButton2"><fa :icon="faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
 | 
					 | 
				
			||||||
		<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
 | 
							<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
 | 
						<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<transition name="zoom-in-top">
 | 
					 | 
				
			||||||
		<x-notifications v-if="notificationsOpen" class="notifications" ref="notifications"/>
 | 
					 | 
				
			||||||
	</transition>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<stream-indicator v-if="$store.getters.isSignedIn"/>
 | 
						<stream-indicator v-if="$store.getters.isSignedIn"/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -173,7 +164,6 @@ import { v4 as uuid } from 'uuid';
 | 
				
			||||||
import i18n from './i18n';
 | 
					import i18n from './i18n';
 | 
				
			||||||
import { host, instanceName } from './config';
 | 
					import { host, instanceName } from './config';
 | 
				
			||||||
import { search } from './scripts/search';
 | 
					import { search } from './scripts/search';
 | 
				
			||||||
import contains from './scripts/contains';
 | 
					 | 
				
			||||||
import MkToast from './components/toast.vue';
 | 
					import MkToast from './components/toast.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DESKTOP_THRESHOLD = 1100;
 | 
					const DESKTOP_THRESHOLD = 1100;
 | 
				
			||||||
| 
						 | 
					@ -183,7 +173,6 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XClock: () => import('./components/header-clock.vue').then(m => m.default),
 | 
							XClock: () => import('./components/header-clock.vue').then(m => m.default),
 | 
				
			||||||
		XNotifications: () => import('./components/notifications.vue').then(m => m.default),
 | 
					 | 
				
			||||||
		MkButton: () => import('./components/ui/button.vue').then(m => m.default),
 | 
							MkButton: () => import('./components/ui/button.vue').then(m => m.default),
 | 
				
			||||||
		XDraggable: () => import('vuedraggable'),
 | 
							XDraggable: () => import('vuedraggable'),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -194,7 +183,6 @@ export default Vue.extend({
 | 
				
			||||||
			pageKey: 0,
 | 
								pageKey: 0,
 | 
				
			||||||
			showNav: false,
 | 
								showNav: false,
 | 
				
			||||||
			searching: false,
 | 
								searching: false,
 | 
				
			||||||
			notificationsOpen: false,
 | 
					 | 
				
			||||||
			accounts: [],
 | 
								accounts: [],
 | 
				
			||||||
			lists: [],
 | 
								lists: [],
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
| 
						 | 
					@ -226,23 +214,10 @@ export default Vue.extend({
 | 
				
			||||||
	watch:{
 | 
						watch:{
 | 
				
			||||||
		$route(to, from) {
 | 
							$route(to, from) {
 | 
				
			||||||
			this.pageKey++;
 | 
								this.pageKey++;
 | 
				
			||||||
			this.notificationsOpen = false;
 | 
					 | 
				
			||||||
			this.showNav = false;
 | 
								this.showNav = false;
 | 
				
			||||||
			this.canBack = (window.history.length > 0 && !['index'].includes(to.name));
 | 
								this.canBack = (window.history.length > 0 && !['index'].includes(to.name));
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		notificationsOpen(open) {
 | 
					 | 
				
			||||||
			if (open) {
 | 
					 | 
				
			||||||
				for (const el of Array.from(document.querySelectorAll('*'))) {
 | 
					 | 
				
			||||||
					el.addEventListener('mousedown', this.onMousedown);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				for (const el of Array.from(document.querySelectorAll('*'))) {
 | 
					 | 
				
			||||||
					el.removeEventListener('mousedown', this.onMousedown);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		isDesktop() {
 | 
							isDesktop() {
 | 
				
			||||||
			if (this.isDesktop) this.adjustWidgetsWidth();
 | 
								if (this.isDesktop) this.adjustWidgetsWidth();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -568,15 +543,6 @@ export default Vue.extend({
 | 
				
			||||||
			this.$root.sound('notification');
 | 
								this.$root.sound('notification');
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onMousedown(e) {
 | 
					 | 
				
			||||||
			e.preventDefault();
 | 
					 | 
				
			||||||
			if (!contains(this.$refs.notifications.$el, e.target) &&
 | 
					 | 
				
			||||||
				!contains(this.$refs.notificationButton, e.target) &&
 | 
					 | 
				
			||||||
				!contains(this.$refs.notificationButton2, e.target)
 | 
					 | 
				
			||||||
				) this.notificationsOpen = false;
 | 
					 | 
				
			||||||
			return false;
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		widgetFunc(id) {
 | 
							widgetFunc(id) {
 | 
				
			||||||
			const w = this.$refs[id][0];
 | 
								const w = this.$refs[id][0];
 | 
				
			||||||
			if (w.func) w.func();
 | 
								if (w.func) w.func();
 | 
				
			||||||
| 
						 | 
					@ -652,7 +618,7 @@ export default Vue.extend({
 | 
				
			||||||
	$header-height: 60px;
 | 
						$header-height: 60px;
 | 
				
			||||||
	$nav-width: 250px;
 | 
						$nav-width: 250px;
 | 
				
			||||||
	$nav-icon-only-width: 74px;
 | 
						$nav-icon-only-width: 74px;
 | 
				
			||||||
	$main-width: 700px;
 | 
						$main-width: 650px;
 | 
				
			||||||
	$ui-font-size: 1em;
 | 
						$ui-font-size: 1em;
 | 
				
			||||||
	$nav-icon-only-threshold: 1300px;
 | 
						$nav-icon-only-threshold: 1300px;
 | 
				
			||||||
	$nav-hide-threshold: 700px;
 | 
						$nav-hide-threshold: 700px;
 | 
				
			||||||
| 
						 | 
					@ -975,17 +941,21 @@ export default Vue.extend({
 | 
				
			||||||
		> main {
 | 
							> main {
 | 
				
			||||||
			width: $main-width;
 | 
								width: $main-width;
 | 
				
			||||||
			min-width: $main-width;
 | 
								min-width: $main-width;
 | 
				
			||||||
 | 
								box-shadow: 1px 0 0 0 var(--divider), -1px 0 0 0 var(--divider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			@media (max-width: $side-hide-threshold) {
 | 
								@media (max-width: $side-hide-threshold) {
 | 
				
			||||||
				min-width: 0;
 | 
									min-width: 0;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> .content {
 | 
								> .content {
 | 
				
			||||||
				padding: 16px;
 | 
									> * {
 | 
				
			||||||
				box-sizing: border-box;
 | 
										&:not(.full) {
 | 
				
			||||||
 | 
											padding: var(--margin) 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				@media (max-width: 500px) {
 | 
										&:not(.naked) {
 | 
				
			||||||
					padding: 8px;
 | 
											background: var(--pageBg);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1023,6 +993,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .widgets {
 | 
							> .widgets {
 | 
				
			||||||
			box-sizing: border-box;
 | 
								box-sizing: border-box;
 | 
				
			||||||
 | 
								margin-left: var(--margin);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			@media (max-width: $side-hide-threshold) {
 | 
								@media (max-width: $side-hide-threshold) {
 | 
				
			||||||
				display: none;
 | 
									display: none;
 | 
				
			||||||
| 
						 | 
					@ -1175,34 +1146,5 @@ export default Vue.extend({
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .notifications {
 | 
					 | 
				
			||||||
		position: fixed;
 | 
					 | 
				
			||||||
		top: 32px;
 | 
					 | 
				
			||||||
		left: 0;
 | 
					 | 
				
			||||||
		right: 0;
 | 
					 | 
				
			||||||
		margin: 0 auto;
 | 
					 | 
				
			||||||
		padding: 8px 8px 0 8px;
 | 
					 | 
				
			||||||
		z-index: 10001;
 | 
					 | 
				
			||||||
		width: 350px;
 | 
					 | 
				
			||||||
		height: 400px;
 | 
					 | 
				
			||||||
		box-sizing: border-box;
 | 
					 | 
				
			||||||
		background: var(--vocsgcxy);
 | 
					 | 
				
			||||||
		-webkit-backdrop-filter: blur(12px);
 | 
					 | 
				
			||||||
		backdrop-filter: blur(12px);
 | 
					 | 
				
			||||||
		border-radius: 6px;
 | 
					 | 
				
			||||||
		box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15);
 | 
					 | 
				
			||||||
		overflow: auto;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		@media (max-width: 800px) {
 | 
					 | 
				
			||||||
			width: 320px;
 | 
					 | 
				
			||||||
			height: 350px;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		@media (max-width: 500px) {
 | 
					 | 
				
			||||||
			width: 290px;
 | 
					 | 
				
			||||||
			height: 310px;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<component :is="$store.state.device.animation ? 'transition-group' : 'div'" class="sqadhkmv" name="list" tag="div" appear :data-direction="direction" :data-reversed="reversed ? 'true' : 'false'">
 | 
					<component :is="$store.state.device.animation ? 'transition-group' : 'div'" class="sqadhkmv" name="list" tag="div" :data-direction="direction" :data-reversed="reversed ? 'true' : 'false'">
 | 
				
			||||||
	<template v-for="(item, i) in items">
 | 
						<template v-for="(item, i) in items">
 | 
				
			||||||
		<slot :item="item" :i="i"></slot>
 | 
							<slot :item="item" :i="i"></slot>
 | 
				
			||||||
		<div class="separator" :key="item.id + '_date'" v-if="showDate(i, item)">
 | 
							<div class="separator" :key="item.id + '_date'" v-if="showDate(i, item)">
 | 
				
			||||||
| 
						 | 
					@ -109,8 +109,6 @@ export default Vue.extend({
 | 
				
			||||||
			line-height: 32px;
 | 
								line-height: 32px;
 | 
				
			||||||
			text-align: center;
 | 
								text-align: center;
 | 
				
			||||||
			font-size: 12px;
 | 
								font-size: 12px;
 | 
				
			||||||
			border-radius: 64px;
 | 
					 | 
				
			||||||
			background: var(--dateLabelBg);
 | 
					 | 
				
			||||||
			color: var(--dateLabelFg);
 | 
								color: var(--dateLabelFg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> span {
 | 
								> span {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,8 +27,6 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.mjndxjcg {
 | 
					.mjndxjcg {
 | 
				
			||||||
	max-width: 350px;
 | 
					 | 
				
			||||||
	margin: 0 auto;
 | 
					 | 
				
			||||||
	padding: 32px;
 | 
						padding: 32px;
 | 
				
			||||||
	text-align: center;
 | 
						text-align: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,13 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mk-google">
 | 
					<div class="mk-google">
 | 
				
			||||||
	<input type="search" v-model="query" :placeholder="q">
 | 
						<input type="search" v-model="query" :placeholder="q">
 | 
				
			||||||
	<button @click="search"><fa icon="search"/> {{ $t('search') }}</button>
 | 
						<button @click="search"><fa :icon="faSearch"/> {{ $t('search') }}</button>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import { faSearch } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import i18n from '../i18n';
 | 
					import i18n from '../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
| 
						 | 
					@ -14,7 +15,8 @@ export default Vue.extend({
 | 
				
			||||||
	props: ['q'],
 | 
						props: ['q'],
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			query: null
 | 
								query: null,
 | 
				
			||||||
 | 
								faSearch
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
| 
						 | 
					@ -42,27 +44,17 @@ export default Vue.extend({
 | 
				
			||||||
		width: 100%;
 | 
							width: 100%;
 | 
				
			||||||
		height: 40px;
 | 
							height: 40px;
 | 
				
			||||||
		font-size: 16px;
 | 
							font-size: 16px;
 | 
				
			||||||
		color: var(--googleSearchFg);
 | 
							border: solid 1px var(--divider);
 | 
				
			||||||
		background: var(--googleSearchBg);
 | 
					 | 
				
			||||||
		border: solid 1px var(--googleSearchBorder);
 | 
					 | 
				
			||||||
		border-radius: 4px 0 0 4px;
 | 
							border-radius: 4px 0 0 4px;
 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover {
 | 
					 | 
				
			||||||
			border-color: var(--googleSearchHoverBorder);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> button {
 | 
						> button {
 | 
				
			||||||
		flex-shrink: 0;
 | 
							flex-shrink: 0;
 | 
				
			||||||
		padding: 0 16px;
 | 
							padding: 0 16px;
 | 
				
			||||||
		border: solid 1px var(--googleSearchBorder);
 | 
							border: solid 1px var(--divider);
 | 
				
			||||||
		border-left: none;
 | 
							border-left: none;
 | 
				
			||||||
		border-radius: 0 4px 4px 0;
 | 
							border-radius: 0 4px 4px 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		&:hover {
 | 
					 | 
				
			||||||
			background-color: var(--googleSearchHoverButton);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:active {
 | 
							&:active {
 | 
				
			||||||
			box-shadow: 0 2px 4px rgba(#000, 0.15) inset;
 | 
								box-shadow: 0 2px 4px rgba(#000, 0.15) inset;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,6 @@ import ellipsis from './ellipsis.vue';
 | 
				
			||||||
import time from './time.vue';
 | 
					import time from './time.vue';
 | 
				
			||||||
import url from './url.vue';
 | 
					import url from './url.vue';
 | 
				
			||||||
import loading from './loading.vue';
 | 
					import loading from './loading.vue';
 | 
				
			||||||
import SequentialEntrance from './sequential-entrance.vue';
 | 
					 | 
				
			||||||
import error from './error.vue';
 | 
					import error from './error.vue';
 | 
				
			||||||
import streamIndicator from './stream-indicator.vue';
 | 
					import streamIndicator from './stream-indicator.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,5 +22,4 @@ Vue.component('mk-time', time);
 | 
				
			||||||
Vue.component('mk-url', url);
 | 
					Vue.component('mk-url', url);
 | 
				
			||||||
Vue.component('mk-loading', loading);
 | 
					Vue.component('mk-loading', loading);
 | 
				
			||||||
Vue.component('mk-error', error);
 | 
					Vue.component('mk-error', error);
 | 
				
			||||||
Vue.component('sequential-entrance', SequentialEntrance);
 | 
					 | 
				
			||||||
Vue.component('stream-indicator', streamIndicator);
 | 
					Vue.component('stream-indicator', streamIndicator);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -90,7 +90,7 @@ export default Vue.extend({
 | 
				
			||||||
	> div {
 | 
						> div {
 | 
				
			||||||
		background-color: var(--fg);
 | 
							background-color: var(--fg);
 | 
				
			||||||
		border-radius: 6px;
 | 
							border-radius: 6px;
 | 
				
			||||||
		color: var(--secondary);
 | 
							color: var(--accentLighten);
 | 
				
			||||||
		display: inline-block;
 | 
							display: inline-block;
 | 
				
			||||||
		font-size: 14px;
 | 
							font-size: 14px;
 | 
				
			||||||
		font-weight: bold;
 | 
							font-weight: bold;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<x-popup :source="source" :no-center="noCenter" :fixed="fixed" :width="width" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
 | 
					<x-popup :source="source" :no-center="noCenter" :fixed="fixed" :width="width" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
 | 
				
			||||||
	<sequential-entrance class="rrevdjwt" :class="{ left: align === 'left' }" :delay="15" :direction="direction" ref="items">
 | 
						<div class="rrevdjwt" :class="{ left: align === 'left' }" ref="items">
 | 
				
			||||||
		<template v-for="(item, i) in items.filter(item => item !== undefined)">
 | 
							<template v-for="(item, i) in items.filter(item => item !== undefined)">
 | 
				
			||||||
			<div v-if="item === null" class="divider" :key="i"></div>
 | 
								<div v-if="item === null" class="divider" :key="i"></div>
 | 
				
			||||||
			<span v-else-if="item.type === 'label'" class="label item" :key="i">
 | 
								<span v-else-if="item.type === 'label'" class="label item" :key="i">
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@
 | 
				
			||||||
				<i v-if="item.indicate"><fa :icon="faCircle"/></i>
 | 
									<i v-if="item.indicate"><fa :icon="faCircle"/></i>
 | 
				
			||||||
			</button>
 | 
								</button>
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
	</sequential-entrance>
 | 
						</div>
 | 
				
			||||||
</x-popup>
 | 
					</x-popup>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -91,7 +91,7 @@ export default Vue.extend({
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		if (this.viaKeyboard) {
 | 
							if (this.viaKeyboard) {
 | 
				
			||||||
			this.$nextTick(() => {
 | 
								this.$nextTick(() => {
 | 
				
			||||||
				focusNext(this.$refs.items.$slots.default[0].elm, true);
 | 
									focusNext(this.$refs.items.children[0], true);
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="zlrxdaqttccpwhpaagdmkawtzklsccam">
 | 
					<div class="wrpstxzv" v-size="[{ max: 450 }]">
 | 
				
			||||||
	<mk-avatar class="avatar" :user="note.user"/>
 | 
						<mk-avatar class="avatar" :user="note.user"/>
 | 
				
			||||||
	<div class="main">
 | 
						<div class="main">
 | 
				
			||||||
		<x-note-header class="header" :note="note" :mini="true"/>
 | 
							<x-note-header class="header" :note="note" :mini="true"/>
 | 
				
			||||||
| 
						 | 
					@ -56,13 +56,12 @@ export default Vue.extend({
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.zlrxdaqttccpwhpaagdmkawtzklsccam {
 | 
					.wrpstxzv {
 | 
				
			||||||
	display: flex;
 | 
						display: flex;
 | 
				
			||||||
	padding: 16px 32px;
 | 
						padding: 16px 32px;
 | 
				
			||||||
	font-size: 0.9em;
 | 
						font-size: 0.9em;
 | 
				
			||||||
	background: rgba(0, 0, 0, 0.03);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@media (max-width: 450px) {
 | 
						&.max-width_450px {
 | 
				
			||||||
		padding: 14px 16px;
 | 
							padding: 14px 16px;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,7 +79,7 @@
 | 
				
			||||||
			<div class="deleted" v-if="appearNote.deletedAt != null">{{ $t('deleted') }}</div>
 | 
								<div class="deleted" v-if="appearNote.deletedAt != null">{{ $t('deleted') }}</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</article>
 | 
						</article>
 | 
				
			||||||
	<x-sub v-for="note in replies" :key="note.id" :note="note"/>
 | 
						<x-sub v-for="note in replies" :key="note.id" :note="note" class="reply"/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -684,6 +684,7 @@ export default Vue.extend({
 | 
				
			||||||
.note {
 | 
					.note {
 | 
				
			||||||
	position: relative;
 | 
						position: relative;
 | 
				
			||||||
	transition: box-shadow 0.1s ease;
 | 
						transition: box-shadow 0.1s ease;
 | 
				
			||||||
 | 
						overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&.max-width_500px {
 | 
						&.max-width_500px {
 | 
				
			||||||
		font-size: 0.9em;
 | 
							font-size: 0.9em;
 | 
				
			||||||
| 
						 | 
					@ -749,14 +750,6 @@ export default Vue.extend({
 | 
				
			||||||
		opacity: 1;
 | 
							opacity: 1;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> *:first-child {
 | 
					 | 
				
			||||||
		border-radius: var(--radius) var(--radius) 0 0;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> *:last-child {
 | 
					 | 
				
			||||||
		border-radius: 0 0 var(--radius) var(--radius);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .info {
 | 
						> .info {
 | 
				
			||||||
		display: flex;
 | 
							display: flex;
 | 
				
			||||||
		align-items: center;
 | 
							align-items: center;
 | 
				
			||||||
| 
						 | 
					@ -784,6 +777,11 @@ export default Vue.extend({
 | 
				
			||||||
		padding-top: 8px;
 | 
							padding-top: 8px;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .reply-to {
 | 
				
			||||||
 | 
							opacity: 0.7;
 | 
				
			||||||
 | 
							padding-bottom: 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .renote {
 | 
						> .renote {
 | 
				
			||||||
		display: flex;
 | 
							display: flex;
 | 
				
			||||||
		align-items: center;
 | 
							align-items: center;
 | 
				
			||||||
| 
						 | 
					@ -937,5 +935,9 @@ export default Vue.extend({
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .reply {
 | 
				
			||||||
 | 
							border-top: solid 1px var(--divider);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,22 +7,22 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<mk-error v-if="error" @retry="init()"/>
 | 
						<mk-error v-if="error" @retry="init()"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="more" v-if="more && reversed" style="margin-bottom: var(--margin);">
 | 
						<div v-if="more && reversed" style="margin-bottom: var(--margin);">
 | 
				
			||||||
		<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
 | 
							<button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()">
 | 
				
			||||||
			<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
 | 
								<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
 | 
				
			||||||
			<template v-if="moreFetching"><mk-loading inline/></template>
 | 
								<template v-if="moreFetching"><mk-loading inline/></template>
 | 
				
			||||||
		</mk-button>
 | 
							</button>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
 | 
						<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
 | 
				
			||||||
		<x-note :note="note" :detail="detail" :key="note._featuredId_ || note._prId_ || note.id"/>
 | 
							<x-note :note="note" :detail="detail" :key="note._featuredId_ || note._prId_ || note.id"/>
 | 
				
			||||||
	</x-list>
 | 
						</x-list>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="more" v-if="more && !reversed" style="margin-top: var(--margin);">
 | 
						<div v-if="more && !reversed" style="margin-top: var(--margin);">
 | 
				
			||||||
		<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
 | 
							<button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()">
 | 
				
			||||||
			<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
 | 
								<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
 | 
				
			||||||
			<template v-if="moreFetching"><mk-loading inline/></template>
 | 
								<template v-if="moreFetching"><mk-loading inline/></template>
 | 
				
			||||||
		</mk-button>
 | 
							</button>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -111,16 +111,10 @@ export default Vue.extend({
 | 
				
			||||||
	&.max-width_500px {
 | 
						&.max-width_500px {
 | 
				
			||||||
		> .notes {
 | 
							> .notes {
 | 
				
			||||||
			> ::v-deep *:not(:last-child) {
 | 
								> ::v-deep *:not(:last-child) {
 | 
				
			||||||
				margin-bottom: var(--marginHalf);
 | 
									//margin-bottom: var(--marginHalf);
 | 
				
			||||||
 | 
									margin-bottom: 0;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .more > .button {
 | 
					 | 
				
			||||||
		margin-left: auto;
 | 
					 | 
				
			||||||
		margin-right: auto;
 | 
					 | 
				
			||||||
		height: 48px;
 | 
					 | 
				
			||||||
		width: 100%;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mk-notifications" :class="{ page }">
 | 
					<div class="mk-notifications">
 | 
				
			||||||
	<x-list class="notifications" :items="items" v-slot="{ item: notification }">
 | 
						<x-list class="notifications" :items="items" v-slot="{ item: notification }">
 | 
				
			||||||
		<x-note v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" :key="notification.id"/>
 | 
							<x-note v-if="['reply', 'quote', 'mention'].includes(notification.type)" :note="notification.note" :key="notification.id"/>
 | 
				
			||||||
		<x-notification v-else :notification="notification" :with-time="true" :full="true" class="notification" :class="{ _panel: page }" :key="notification.id"/>
 | 
							<x-notification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/>
 | 
				
			||||||
	</x-list>
 | 
						</x-list>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
 | 
						<button class="_panel _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
 | 
				
			||||||
		<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
 | 
							<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
 | 
				
			||||||
		<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
 | 
							<template v-if="moreFetching"><mk-loading inline/></template>
 | 
				
			||||||
	</button>
 | 
						</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<p class="empty" v-if="empty">{{ $t('noNotifications') }}</p>
 | 
						<p class="empty" v-if="empty">{{ $t('noNotifications') }}</p>
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
 | 
					 | 
				
			||||||
import i18n from '../i18n';
 | 
					import i18n from '../i18n';
 | 
				
			||||||
import paging from '../scripts/paging';
 | 
					import paging from '../scripts/paging';
 | 
				
			||||||
import XNotification from './notification.vue';
 | 
					import XNotification from './notification.vue';
 | 
				
			||||||
| 
						 | 
					@ -43,11 +42,6 @@ export default Vue.extend({
 | 
				
			||||||
			type: String,
 | 
								type: String,
 | 
				
			||||||
			required: false
 | 
								required: false
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		page: {
 | 
					 | 
				
			||||||
			type: Boolean,
 | 
					 | 
				
			||||||
			required: false,
 | 
					 | 
				
			||||||
			default: false
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
| 
						 | 
					@ -60,7 +54,6 @@ export default Vue.extend({
 | 
				
			||||||
					includeTypes: this.type ? [this.type] : undefined
 | 
										includeTypes: this.type ? [this.type] : undefined
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			faSpinner
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -94,35 +87,10 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.mk-notifications {
 | 
					.mk-notifications {
 | 
				
			||||||
	&.page {
 | 
						> .notifications {
 | 
				
			||||||
		> .notifications {
 | 
							> ::v-deep * {
 | 
				
			||||||
			> ::v-deep * {
 | 
								//margin-bottom: var(--margin);
 | 
				
			||||||
				margin-bottom: var(--margin);
 | 
								margin-bottom: 0;
 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&:not(.page) {
 | 
					 | 
				
			||||||
		> .notifications {
 | 
					 | 
				
			||||||
			> ::v-deep * {
 | 
					 | 
				
			||||||
				margin-bottom: 8px;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			> .notification {
 | 
					 | 
				
			||||||
				background: var(--panel);
 | 
					 | 
				
			||||||
				border-radius: 6px;
 | 
					 | 
				
			||||||
				box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .more {
 | 
					 | 
				
			||||||
		display: block;
 | 
					 | 
				
			||||||
		width: 100%;
 | 
					 | 
				
			||||||
		padding: 16px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> [data-icon] {
 | 
					 | 
				
			||||||
			margin-right: 4px;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,7 +53,7 @@ import Vue from 'vue';
 | 
				
			||||||
import { faExclamationTriangle, faTimes } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faExclamationTriangle, faTimes } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import i18n from '../i18n';
 | 
					import i18n from '../i18n';
 | 
				
			||||||
import { erase } from '../../prelude/array';
 | 
					import { erase } from '../../prelude/array';
 | 
				
			||||||
import { addTimespan } from '../../prelude/time';
 | 
					import { addTime } from '../../prelude/time';
 | 
				
			||||||
import { formatDateTimeString } from '../../misc/format-time-string';
 | 
					import { formatDateTimeString } from '../../misc/format-time-string';
 | 
				
			||||||
import MkInput from './ui/input.vue';
 | 
					import MkInput from './ui/input.vue';
 | 
				
			||||||
import MkSelect from './ui/select.vue';
 | 
					import MkSelect from './ui/select.vue';
 | 
				
			||||||
| 
						 | 
					@ -73,7 +73,7 @@ export default Vue.extend({
 | 
				
			||||||
			choices: ['', ''],
 | 
								choices: ['', ''],
 | 
				
			||||||
			multiple: false,
 | 
								multiple: false,
 | 
				
			||||||
			expiration: 'infinite',
 | 
								expiration: 'infinite',
 | 
				
			||||||
			atDate: formatDateTimeString(addTimespan(new Date(), 1, 'days'), 'yyyy-MM-dd'),
 | 
								atDate: formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'),
 | 
				
			||||||
			atTime: '00:00',
 | 
								atTime: '00:00',
 | 
				
			||||||
			after: 0,
 | 
								after: 0,
 | 
				
			||||||
			unit: 'second',
 | 
								unit: 'second',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,8 @@
 | 
				
			||||||
				:initial-note="initialNote"
 | 
									:initial-note="initialNote"
 | 
				
			||||||
				:instant="instant"
 | 
									:instant="instant"
 | 
				
			||||||
				@posted="onPosted"
 | 
									@posted="onPosted"
 | 
				
			||||||
				@cancel="onCanceled"/>
 | 
									@cancel="onCanceled"
 | 
				
			||||||
 | 
									style="border-radius: var(--radius);"/>
 | 
				
			||||||
		</transition>
 | 
							</transition>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -586,7 +586,6 @@ export default Vue.extend({
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.gafaadew {
 | 
					.gafaadew {
 | 
				
			||||||
	background: var(--panel);
 | 
						background: var(--panel);
 | 
				
			||||||
	border-radius: var(--radius);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> header {
 | 
						> header {
 | 
				
			||||||
		z-index: 1000;
 | 
							z-index: 1000;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/client/components/remote-caution.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/client/components/remote-caution.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="jmgmzlwq _panel"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="href" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import i18n from '../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n,
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							href: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								faExclamationTriangle
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.jmgmzlwq {
 | 
				
			||||||
 | 
						font-size: 0.8em;
 | 
				
			||||||
 | 
						padding: 16px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> a {
 | 
				
			||||||
 | 
							margin-left: 4px;
 | 
				
			||||||
 | 
							color: var(--accent);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,40 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<transition-group v-if="$store.state.device.animation"
 | 
					 | 
				
			||||||
	class="uupnnhew"
 | 
					 | 
				
			||||||
	name="staggered"
 | 
					 | 
				
			||||||
	tag="div"
 | 
					 | 
				
			||||||
	appear
 | 
					 | 
				
			||||||
>
 | 
					 | 
				
			||||||
	<slot></slot>
 | 
					 | 
				
			||||||
</transition-group>
 | 
					 | 
				
			||||||
<div v-else>
 | 
					 | 
				
			||||||
	<slot></slot>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		focus() {
 | 
					 | 
				
			||||||
			this.$slots.default[0].elm.focus();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss">
 | 
					 | 
				
			||||||
.uupnnhew {
 | 
					 | 
				
			||||||
	> .staggered-enter {
 | 
					 | 
				
			||||||
		opacity: 0;
 | 
					 | 
				
			||||||
		transform: translateY(-64px);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@for $i from 1 through 30 {
 | 
					 | 
				
			||||||
		> .staggered-enter-active:nth-child(#{$i}) {
 | 
					 | 
				
			||||||
			transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1)), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) (15ms * ($i - 1));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
							
								
								
									
										0
									
								
								src/client/components/signin.vue
									
										
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								src/client/components/signin.vue
									
										
									
									
									
										
										
										Normal file → Executable file
									
								
							| 
						 | 
					@ -124,7 +124,6 @@ export default Vue.extend({
 | 
				
			||||||
	&.primary {
 | 
						&.primary {
 | 
				
			||||||
		color: #fff;
 | 
							color: #fff;
 | 
				
			||||||
		background: var(--accent);
 | 
							background: var(--accent);
 | 
				
			||||||
		box-shadow: 0 6px 16px var(--accentShadow);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		&:not(:disabled):hover {
 | 
							&:not(:disabled):hover {
 | 
				
			||||||
			background: var(--jkhztclx);
 | 
								background: var(--jkhztclx);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,6 +110,7 @@ export default Vue.extend({
 | 
				
			||||||
	> header {
 | 
						> header {
 | 
				
			||||||
		position: relative;
 | 
							position: relative;
 | 
				
			||||||
		box-shadow: 0 1px 0 0 var(--divider);
 | 
							box-shadow: 0 1px 0 0 var(--divider);
 | 
				
			||||||
 | 
							z-index: 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .title {
 | 
							> .title {
 | 
				
			||||||
			margin: 0;
 | 
								margin: 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<sequential-entrance class="cxiknjgy" :class="{ autoMargin }">
 | 
					<div class="cxiknjgy" :class="{ autoMargin }">
 | 
				
			||||||
	<slot :items="items"></slot>
 | 
						<slot :items="items"></slot>
 | 
				
			||||||
	<div class="empty" v-if="empty" key="_empty_">
 | 
						<div class="empty" v-if="empty" key="_empty_">
 | 
				
			||||||
		<slot name="empty"></slot>
 | 
							<slot name="empty"></slot>
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@
 | 
				
			||||||
			<template v-if="moreFetching"><mk-loading inline/></template>
 | 
								<template v-if="moreFetching"><mk-loading inline/></template>
 | 
				
			||||||
		</mk-button>
 | 
							</mk-button>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</sequential-entrance>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										138
									
								
								src/client/components/ui/range.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/client/components/ui/range.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,138 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="timctyfi" :class="{ focused, disabled }">
 | 
				
			||||||
 | 
						<div class="icon"><slot name="icon"></slot></div>
 | 
				
			||||||
 | 
						<span class="title"><slot name="title"></slot></span>
 | 
				
			||||||
 | 
						<input
 | 
				
			||||||
 | 
							type="range"
 | 
				
			||||||
 | 
							ref="input"
 | 
				
			||||||
 | 
							v-model="v"
 | 
				
			||||||
 | 
							:disabled="disabled"
 | 
				
			||||||
 | 
							:min="min"
 | 
				
			||||||
 | 
							:max="max"
 | 
				
			||||||
 | 
							:step="step"
 | 
				
			||||||
 | 
							:autofocus="autofocus"
 | 
				
			||||||
 | 
							@focus="focused = true"
 | 
				
			||||||
 | 
							@blur="focused = false"
 | 
				
			||||||
 | 
							@input="$emit('input', $event.target.value)"
 | 
				
			||||||
 | 
						/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from "vue";
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							value: {
 | 
				
			||||||
 | 
								type: Number,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: 0
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							disabled: {
 | 
				
			||||||
 | 
								type: Boolean,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							min: {
 | 
				
			||||||
 | 
								type: Number,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: 0
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							max: {
 | 
				
			||||||
 | 
								type: Number,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: 100
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							step: {
 | 
				
			||||||
 | 
								type: Number,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: 1
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							autofocus: {
 | 
				
			||||||
 | 
								type: Boolean,
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								v: this.value,
 | 
				
			||||||
 | 
								focused: false
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						watch: {
 | 
				
			||||||
 | 
							value(v) {
 | 
				
			||||||
 | 
								this.v = parseFloat(v);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							if (this.autofocus) {
 | 
				
			||||||
 | 
								this.$nextTick(() => {
 | 
				
			||||||
 | 
									this.$refs.input.focus();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.timctyfi {
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
 | 
						margin: 8px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .icon {
 | 
				
			||||||
 | 
							display: inline-block;
 | 
				
			||||||
 | 
							width: 24px;
 | 
				
			||||||
 | 
							text-align: center;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .title {
 | 
				
			||||||
 | 
							pointer-events: none;
 | 
				
			||||||
 | 
							font-size: 16px;
 | 
				
			||||||
 | 
							color: var(--inputLabel);
 | 
				
			||||||
 | 
							overflow: hidden;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> input {
 | 
				
			||||||
 | 
							-webkit-appearance: none;
 | 
				
			||||||
 | 
							-moz-appearance: none;
 | 
				
			||||||
 | 
							appearance: none;
 | 
				
			||||||
 | 
							background: var(--xxubwiul);
 | 
				
			||||||
 | 
							height: 7px;
 | 
				
			||||||
 | 
							margin: 0 8px;
 | 
				
			||||||
 | 
							outline: 0;
 | 
				
			||||||
 | 
							border: 0;
 | 
				
			||||||
 | 
							border-radius: 7px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&.disabled {
 | 
				
			||||||
 | 
								opacity: 0.6;
 | 
				
			||||||
 | 
								cursor: not-allowed;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&::-webkit-slider-thumb {
 | 
				
			||||||
 | 
								-webkit-appearance: none;
 | 
				
			||||||
 | 
								appearance: none;
 | 
				
			||||||
 | 
								cursor: pointer;
 | 
				
			||||||
 | 
								width: 20px;
 | 
				
			||||||
 | 
								height: 20px;
 | 
				
			||||||
 | 
								display: block;
 | 
				
			||||||
 | 
								border-radius: 50%;
 | 
				
			||||||
 | 
								border: none;
 | 
				
			||||||
 | 
								background: var(--accent);
 | 
				
			||||||
 | 
								box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
 | 
				
			||||||
 | 
								box-sizing: content-box;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&::-moz-range-thumb {
 | 
				
			||||||
 | 
								-moz-appearance: none;
 | 
				
			||||||
 | 
								appearance: none;
 | 
				
			||||||
 | 
								cursor: pointer;
 | 
				
			||||||
 | 
								width: 20px;
 | 
				
			||||||
 | 
								height: 20px;
 | 
				
			||||||
 | 
								display: block;
 | 
				
			||||||
 | 
								border-radius: 50%;
 | 
				
			||||||
 | 
								border: none;
 | 
				
			||||||
 | 
								background: var(--accent);
 | 
				
			||||||
 | 
								box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -158,6 +158,11 @@ export default Vue.extend({
 | 
				
			||||||
			outline: none;
 | 
								outline: none;
 | 
				
			||||||
			box-shadow: none;
 | 
								box-shadow: none;
 | 
				
			||||||
			color: var(--fg);
 | 
								color: var(--fg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								option,
 | 
				
			||||||
 | 
								optgroup {
 | 
				
			||||||
 | 
									background: var(--bg);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .prefix,
 | 
							> .prefix,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -230,8 +230,8 @@ export default Vue.extend({
 | 
				
			||||||
		position: relative;
 | 
							position: relative;
 | 
				
			||||||
		display: block;
 | 
							display: block;
 | 
				
			||||||
		font-size: 14px;
 | 
							font-size: 14px;
 | 
				
			||||||
		box-shadow: 0 1px 4px var(--tyvedwbe);
 | 
							box-shadow: 0 0 0 1px var(--divider);
 | 
				
			||||||
		border-radius: 4px;
 | 
							border-radius: 6px;
 | 
				
			||||||
		overflow: hidden;
 | 
							overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		&:hover {
 | 
							&:hover {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,15 +6,15 @@
 | 
				
			||||||
			<button class="_button" @click="close()"><fa :icon="faTimes"/></button>
 | 
								<button class="_button" @click="close()"><fa :icon="faTimes"/></button>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<sequential-entrance class="users">
 | 
							<div class="users">
 | 
				
			||||||
			<router-link v-for="(item, i) in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage">
 | 
								<router-link v-for="item in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage">
 | 
				
			||||||
				<mk-avatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
 | 
									<mk-avatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
 | 
				
			||||||
				<div class="body">
 | 
									<div class="body">
 | 
				
			||||||
					<mk-user-name :user="extract ? extract(item) : item" class="name"/>
 | 
										<mk-user-name :user="extract ? extract(item) : item" class="name"/>
 | 
				
			||||||
					<mk-acct :user="extract ? extract(item) : item" class="acct"/>
 | 
										<mk-acct :user="extract ? extract(item) : item" class="acct"/>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</router-link>
 | 
								</router-link>
 | 
				
			||||||
		</sequential-entrance>
 | 
							</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
 | 
							<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
 | 
				
			||||||
			<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
 | 
								<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }">
 | 
					<x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }">
 | 
				
			||||||
	<sequential-entrance class="gqyayizv" :delay="30">
 | 
						<div class="gqyayizv">
 | 
				
			||||||
		<button class="_button" @click="choose('public')" :class="{ active: v == 'public' }" data-index="1" key="public">
 | 
							<button class="_button" @click="choose('public')" :class="{ active: v == 'public' }" data-index="1" key="public">
 | 
				
			||||||
			<div><fa :icon="faGlobe"/></div>
 | 
								<div><fa :icon="faGlobe"/></div>
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,7 @@
 | 
				
			||||||
				<span>{{ $t('_visibility.specifiedDescription') }}</span>
 | 
									<span>{{ $t('_visibility.specifiedDescription') }}</span>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</button>
 | 
							</button>
 | 
				
			||||||
	</sequential-entrance>
 | 
						</div>
 | 
				
			||||||
</x-popup>
 | 
					</x-popup>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,7 +59,7 @@ export default {
 | 
				
			||||||
		const ro = new ResizeObserver((entries, observer) => {
 | 
							const ro = new ResizeObserver((entries, observer) => {
 | 
				
			||||||
			calc();
 | 
								calc();
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
		
 | 
					
 | 
				
			||||||
		ro.observe(el);
 | 
							ro.observe(el);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		el._ro_ = ro;
 | 
							el._ro_ = ro;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,14 +81,14 @@ if (lang == null) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Detect the user agent
 | 
					// Detect the user agent
 | 
				
			||||||
const ua = navigator.userAgent.toLowerCase();
 | 
					const ua = navigator.userAgent.toLowerCase();
 | 
				
			||||||
let isMobile = /mobile|iphone|ipad|android/.test(ua);
 | 
					const isMobile = /mobile|iphone|ipad|android/.test(ua);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Get the <head> element
 | 
					// Get the <head> element
 | 
				
			||||||
const head = document.getElementsByTagName('head')[0];
 | 
					const head = document.getElementsByTagName('head')[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// If mobile, insert the viewport meta tag
 | 
					// If mobile, insert the viewport meta tag
 | 
				
			||||||
if (isMobile || window.innerWidth <= 1024) {
 | 
					if (isMobile || window.innerWidth <= 1024) {
 | 
				
			||||||
	const viewport = document.getElementsByName("viewport").item(0);
 | 
						const viewport = document.getElementsByName('viewport').item(0);
 | 
				
			||||||
	viewport.setAttribute('content',
 | 
						viewport.setAttribute('content',
 | 
				
			||||||
		`${viewport.getAttribute('content')},minimum-scale=1,maximum-scale=1,user-scalable=no`);
 | 
							`${viewport.getAttribute('content')},minimum-scale=1,maximum-scale=1,user-scalable=no`);
 | 
				
			||||||
	head.appendChild(viewport);
 | 
						head.appendChild(viewport);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,8 +123,13 @@ export default class MiOS extends EventEmitter {
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			// Get token from localStorage
 | 
								// Get token from localStorage
 | 
				
			||||||
			const i = localStorage.getItem('i');
 | 
								let i = localStorage.getItem('i');
 | 
				
			||||||
			
 | 
					
 | 
				
			||||||
 | 
								// 連携ログインの場合用にCookieを参照する
 | 
				
			||||||
 | 
								if (i == null || i === 'null') {
 | 
				
			||||||
 | 
									i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1];
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			fetchme(i, me => {
 | 
								fetchme(i, me => {
 | 
				
			||||||
				if (me) {
 | 
									if (me) {
 | 
				
			||||||
					this.store.dispatch('login', me);
 | 
										this.store.dispatch('login', me);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								src/client/pages/auth.vue
									
										
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										5
									
								
								src/client/pages/auth.vue
									
										
									
									
									
										
										
										Normal file → Executable file
									
								
							| 
						 | 
					@ -26,7 +26,7 @@
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
<div class="signin" v-else>
 | 
					<div class="signin" v-else>
 | 
				
			||||||
	<h1>{{ $t('sign-in') }}</h1>
 | 
						<h1>{{ $t('sign-in') }}</h1>
 | 
				
			||||||
	<mk-signin/>
 | 
						<mk-signin @login="onLogin"/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,6 +85,9 @@ export default Vue.extend({
 | 
				
			||||||
			if (this.session.app.callbackUrl) {
 | 
								if (this.session.app.callbackUrl) {
 | 
				
			||||||
				location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`;
 | 
									location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							}, onLogin(res) {
 | 
				
			||||||
 | 
								localStorage.setItem('i', res.i);
 | 
				
			||||||
 | 
								location.reload();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<div class="naked full">
 | 
				
			||||||
	<portal to="header">
 | 
						<portal to="header">
 | 
				
			||||||
		<button @click="menu" class="_button _jmoebdiw_">
 | 
							<button @click="menu" class="_button _jmoebdiw_">
 | 
				
			||||||
			<fa :icon="faCloud" style="margin-right: 8px;"/>
 | 
								<fa :icon="faCloud" style="margin-right: 8px;"/>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,12 +11,12 @@
 | 
				
			||||||
		<canvas ref="chart"></canvas>
 | 
							<canvas ref="chart"></canvas>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div class="_content" style="max-height: 180px; overflow: auto;">
 | 
						<div class="_content" style="max-height: 180px; overflow: auto;">
 | 
				
			||||||
		<sequential-entrance :delay="15" v-if="jobs.length > 0">
 | 
							<div v-if="jobs.length > 0">
 | 
				
			||||||
			<div v-for="(job, i) in jobs" :key="job[0]">
 | 
								<div v-for="job in jobs" :key="job[0]">
 | 
				
			||||||
				<span>{{ job[0] }}</span>
 | 
									<span>{{ job[0] }}</span>
 | 
				
			||||||
				<span style="margin-left: 8px; opacity: 0.7;">({{ job[1] | number }} jobs)</span>
 | 
									<span style="margin-left: 8px; opacity: 0.7;">({{ job[1] | number }} jobs)</span>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</sequential-entrance>
 | 
							</div>
 | 
				
			||||||
		<span v-else style="opacity: 0.5;">{{ $t('noJobs') }}</span>
 | 
							<span v-else style="opacity: 0.5;">{{ $t('noJobs') }}</span>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,21 +102,20 @@
 | 
				
			||||||
		<div class="_content">
 | 
							<div class="_content">
 | 
				
			||||||
			<mk-switch v-model="useObjectStorage">{{ $t('useObjectStorage') }}</mk-switch>
 | 
								<mk-switch v-model="useObjectStorage">{{ $t('useObjectStorage') }}</mk-switch>
 | 
				
			||||||
			<template v-if="useObjectStorage">
 | 
								<template v-if="useObjectStorage">
 | 
				
			||||||
				<mk-input v-model="objectStorageBaseUrl" :disabled="!useObjectStorage">URL</mk-input>
 | 
									<mk-input v-model="objectStorageBaseUrl" :disabled="!useObjectStorage">{{ $t('objectStorageBaseUrl') }}<template #desc>{{ $t('objectStorageBaseUrlDesc') }}</template></mk-input>
 | 
				
			||||||
				<div class="_inputs">
 | 
									<div class="_inputs">
 | 
				
			||||||
					<mk-input v-model="objectStorageBucket" :disabled="!useObjectStorage">Bucket</mk-input>
 | 
										<mk-input v-model="objectStorageBucket" :disabled="!useObjectStorage">{{ $t('objectStorageBucket') }}<template #desc>{{ $t('objectStorageBucketDesc') }}</template></mk-input>
 | 
				
			||||||
					<mk-input v-model="objectStoragePrefix" :disabled="!useObjectStorage">Prefix</mk-input>
 | 
										<mk-input v-model="objectStoragePrefix" :disabled="!useObjectStorage">{{ $t('objectStoragePrefix') }}<template #desc>{{ $t('objectStoragePrefixDesc') }}</template></mk-input>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
				<mk-input v-model="objectStorageEndpoint" :disabled="!useObjectStorage">Endpoint</mk-input>
 | 
									<mk-input v-model="objectStorageEndpoint" :disabled="!useObjectStorage">{{ $t('objectStorageEndpoint') }}<template #desc>{{ $t('objectStorageEndpointDesc') }}</template></mk-input>
 | 
				
			||||||
				<div class="_inputs">
 | 
									<div class="_inputs">
 | 
				
			||||||
					<mk-input v-model="objectStorageRegion" :disabled="!useObjectStorage">Region</mk-input>
 | 
										<mk-input v-model="objectStorageRegion" :disabled="!useObjectStorage">{{ $t('objectStorageRegion') }}<template #desc>{{ $t('objectStorageRegionDesc') }}</template></mk-input>
 | 
				
			||||||
					<mk-input v-model="objectStoragePort" type="number" :disabled="!useObjectStorage">Port</mk-input>
 | 
					 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
				<div class="_inputs">
 | 
									<div class="_inputs">
 | 
				
			||||||
					<mk-input v-model="objectStorageAccessKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Access key</mk-input>
 | 
										<mk-input v-model="objectStorageAccessKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Access key</mk-input>
 | 
				
			||||||
					<mk-input v-model="objectStorageSecretKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Secret key</mk-input>
 | 
										<mk-input v-model="objectStorageSecretKey" :disabled="!useObjectStorage"><template #icon><fa :icon="faKey"/></template>Secret key</mk-input>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
				<mk-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">SSL</mk-switch>
 | 
									<mk-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">{{ $t('objectStorageUseSSL') }}<template #desc>{{ $t('objectStorageUseSSLDesc') }}</template></mk-switch>
 | 
				
			||||||
			</template>
 | 
								</template>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="_footer">
 | 
							<div class="_footer">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
<div class="thvuemwp" :data-is-me="isMe">
 | 
					<div class="thvuemwp" :data-is-me="isMe">
 | 
				
			||||||
	<mk-avatar class="avatar" :user="message.user"/>
 | 
						<mk-avatar class="avatar" :user="message.user"/>
 | 
				
			||||||
	<div class="content">
 | 
						<div class="content">
 | 
				
			||||||
		<div class="balloon _panel" :data-no-text="message.text == null">
 | 
							<div class="balloon" :data-no-text="message.text == null">
 | 
				
			||||||
			<button class="delete-button" v-if="isMe" :title="$t('delete')" @click="del">
 | 
								<button class="delete-button" v-if="isMe" :title="$t('delete')" @click="del">
 | 
				
			||||||
				<img src="/assets/remove.png" alt="Delete"/>
 | 
									<img src="/assets/remove.png" alt="Delete"/>
 | 
				
			||||||
			</button>
 | 
								</button>
 | 
				
			||||||
| 
						 | 
					@ -243,13 +243,14 @@ export default Vue.extend({
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&:not([data-is-me]) {
 | 
						&:not([data-is-me]) {
 | 
				
			||||||
 | 
							padding-left: var(--margin);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .content {
 | 
							> .content {
 | 
				
			||||||
			padding-left: 16px;
 | 
								padding-left: 16px;
 | 
				
			||||||
			padding-right: 32px;
 | 
								padding-right: 32px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> .balloon {
 | 
								> .balloon {
 | 
				
			||||||
				$color: var(--panel);
 | 
									$color: var(--messageBg);
 | 
				
			||||||
				background: $color;
 | 
									background: $color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				&[data-no-text] {
 | 
									&[data-no-text] {
 | 
				
			||||||
| 
						 | 
					@ -279,6 +280,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&[data-is-me] {
 | 
						&[data-is-me] {
 | 
				
			||||||
		flex-direction: row-reverse;
 | 
							flex-direction: row-reverse;
 | 
				
			||||||
 | 
							padding-right: var(--margin);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .content {
 | 
							> .content {
 | 
				
			||||||
			padding-right: 16px;
 | 
								padding-right: 16px;
 | 
				
			||||||
| 
						 | 
					@ -287,7 +289,6 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> .balloon {
 | 
								> .balloon {
 | 
				
			||||||
				background: $me-balloon-color;
 | 
									background: $me-balloon-color;
 | 
				
			||||||
				box-shadow: 0 6px 16px var(--accentShadow);
 | 
					 | 
				
			||||||
				text-align: left;
 | 
									text-align: left;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				&[data-no-text] {
 | 
									&[data-no-text] {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mk-messaging-room"
 | 
					<div class="mk-messaging-room naked"
 | 
				
			||||||
	@dragover.prevent.stop="onDragover"
 | 
						@dragover.prevent.stop="onDragover"
 | 
				
			||||||
	@drop.prevent.stop="onDrop"
 | 
						@drop.prevent.stop="onDrop"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<mk-button @click="start" primary class="start"><fa :icon="faPlus"/> {{ $t('startMessaging') }}</mk-button>
 | 
						<mk-button @click="start" primary class="start"><fa :icon="faPlus"/> {{ $t('startMessaging') }}</mk-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<sequential-entrance class="history" v-if="messages.length > 0" :delay="30">
 | 
						<div class="history" v-if="messages.length > 0">
 | 
				
			||||||
		<router-link v-for="(message, i) in messages"
 | 
							<router-link v-for="(message, i) in messages"
 | 
				
			||||||
			class="message _panel"
 | 
								class="message _panel"
 | 
				
			||||||
			:to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
 | 
								:to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</router-link>
 | 
							</router-link>
 | 
				
			||||||
	</sequential-entrance>
 | 
						</div>
 | 
				
			||||||
	<div class="no-history" v-if="!fetching && messages.length == 0">
 | 
						<div class="no-history" v-if="!fetching && messages.length == 0">
 | 
				
			||||||
		<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
 | 
							<img src="https://xn--931a.moe/assets/info.png" class="_ghost"/>
 | 
				
			||||||
		<div>{{ $t('noHistory') }}</div>
 | 
							<div>{{ $t('noHistory') }}</div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,11 +70,10 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		if (!document.cookie.match(/i=(\w+)/)) {
 | 
							document.cookie = `igi=${this.$store.state.i.token}; path=/;` +
 | 
				
			||||||
			document.cookie = `i=${this.$store.state.i.token}; path=/;` +
 | 
								` max-age=31536000;` +
 | 
				
			||||||
			` domain=${document.location.hostname}; max-age=31536000;` +
 | 
					 | 
				
			||||||
			(document.location.protocol.startsWith('https') ? ' secure' : '');
 | 
								(document.location.protocol.startsWith('https') ? ' secure' : '');
 | 
				
			||||||
		}
 | 
					
 | 
				
			||||||
		this.$watch('integrations', () => {
 | 
							this.$watch('integrations', () => {
 | 
				
			||||||
			if (this.integrations.twitter) {
 | 
								if (this.integrations.twitter) {
 | 
				
			||||||
				if (this.twitterForm) this.twitterForm.close();
 | 
									if (this.twitterForm) this.twitterForm.close();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,10 @@
 | 
				
			||||||
<section class="_card">
 | 
					<section class="_card">
 | 
				
			||||||
	<div class="_title"><fa :icon="faLaugh"/> {{ $t('reaction') }}</div>
 | 
						<div class="_title"><fa :icon="faLaugh"/> {{ $t('reaction') }}</div>
 | 
				
			||||||
	<div class="_content">
 | 
						<div class="_content">
 | 
				
			||||||
		<mk-textarea v-model="reactions">{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }}</template></mk-textarea>
 | 
							<mk-input v-model="reactions" style="font-family: 'Segoe UI Emoji', 'Noto Color Emoji', Roboto, HelveticaNeue, Arial, sans-serif">
 | 
				
			||||||
 | 
								{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></template>
 | 
				
			||||||
 | 
							</mk-input>
 | 
				
			||||||
 | 
							<mk-button inline @click="setDefault"><fa :icon="faUndo"/> {{ $t('default') }}</mk-button>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div class="_footer">
 | 
						<div class="_footer">
 | 
				
			||||||
		<mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
 | 
							<mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
 | 
				
			||||||
| 
						 | 
					@ -14,24 +17,26 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
import MkTextarea from '../../components/ui/textarea.vue';
 | 
					import { faUndo } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import MkInput from '../../components/ui/input.vue';
 | 
				
			||||||
import MkButton from '../../components/ui/button.vue';
 | 
					import MkButton from '../../components/ui/button.vue';
 | 
				
			||||||
import MkReactionPicker from '../../components/reaction-picker.vue';
 | 
					import MkReactionPicker from '../../components/reaction-picker.vue';
 | 
				
			||||||
import i18n from '../../i18n';
 | 
					import i18n from '../../i18n';
 | 
				
			||||||
 | 
					import { emojiRegexWithCustom } from '../../../misc/emoji-regex';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n,
 | 
						i18n,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		MkTextarea,
 | 
							MkInput,
 | 
				
			||||||
		MkButton,
 | 
							MkButton,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			reactions: this.$store.state.settings.reactions.join('\n'),
 | 
								reactions: this.$store.state.settings.reactions.join(''),
 | 
				
			||||||
			changed: false,
 | 
								changed: false,
 | 
				
			||||||
			faLaugh, faSave, faEye
 | 
								faLaugh, faSave, faEye, faUndo
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,21 +46,40 @@ export default Vue.extend({
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						computed: {
 | 
				
			||||||
 | 
							splited(): any {
 | 
				
			||||||
 | 
								return this.reactions.match(emojiRegexWithCustom);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		save() {
 | 
							save() {
 | 
				
			||||||
			this.$store.dispatch('settings/set', { key: 'reactions', value: this.reactions.trim().split('\n') });
 | 
								this.$store.dispatch('settings/set', { key: 'reactions', value: this.splited });
 | 
				
			||||||
			this.changed = false;
 | 
								this.changed = false;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		preview(ev) {
 | 
							preview(ev) {
 | 
				
			||||||
			const picker = this.$root.new(MkReactionPicker, {
 | 
								const picker = this.$root.new(MkReactionPicker, {
 | 
				
			||||||
				source: ev.currentTarget || ev.target,
 | 
									source: ev.currentTarget || ev.target,
 | 
				
			||||||
				reactions: this.reactions.trim().split('\n'),
 | 
									reactions: this.splited,
 | 
				
			||||||
				showFocus: false,
 | 
									showFocus: false,
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			picker.$once('chosen', reaction => {
 | 
								picker.$once('chosen', reaction => {
 | 
				
			||||||
				picker.close();
 | 
									picker.close();
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							setDefault() {
 | 
				
			||||||
 | 
								this.reactions = '👍❤😆🤔😮🎉💢😥😇🍮';
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							async chooseEmoji(ev) {
 | 
				
			||||||
 | 
								const vm = this.$root.new(await import('../../components/emoji-picker.vue').then(m => m.default), {
 | 
				
			||||||
 | 
									source: ev.currentTarget || ev.target
 | 
				
			||||||
 | 
								}).$once('chosen', emoji => {
 | 
				
			||||||
 | 
									this.reactions += emoji;
 | 
				
			||||||
 | 
									vm.close();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,24 +1,28 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mk-note-page">
 | 
					<div class="mk-note-page">
 | 
				
			||||||
	<portal to="avatar" v-if="note"><mk-avatar class="avatar" :user="note.user" :disable-preview="true"/></portal>
 | 
						<portal to="avatar" v-if="note"><mk-avatar class="avatar" :user="note.user" :disable-preview="true"/></portal>
 | 
				
			||||||
	<portal to="title" v-if="note">{{ $t('noteOf', { user: note.user.name }) }}</portal>
 | 
						<portal to="title" v-if="note">
 | 
				
			||||||
 | 
							<mfm 
 | 
				
			||||||
 | 
								:text="$t('noteOf', { user: note.user.name || note.user.username })"
 | 
				
			||||||
 | 
								:plain="true" :nowrap="true" :custom-emojis="note.user.emojis" :is-note="false"
 | 
				
			||||||
 | 
							/>
 | 
				
			||||||
 | 
						</portal>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in">
 | 
						<div v-if="note">
 | 
				
			||||||
		<div v-if="note">
 | 
							<button class="_panel _button" v-if="hasNext && !showNext" @click="showNext = true" style="margin: 0 auto var(--margin) auto;"><fa :icon="faChevronUp"/></button>
 | 
				
			||||||
			<mk-button v-if="hasNext && !showNext" @click="showNext = true" primary style="margin: 0 auto var(--margin) auto;"><fa :icon="faChevronUp"/></mk-button>
 | 
							<x-notes v-if="showNext" ref="next" :pagination="next"/>
 | 
				
			||||||
			<x-notes v-if="showNext" ref="next" :pagination="next"/>
 | 
							<hr v-if="showNext"/>
 | 
				
			||||||
			<hr v-if="showNext"/>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<x-note :note="note" :key="note.id" :detail="true"/>
 | 
							<mk-remote-caution v-if="note.user.host != null" :href="note.uri" style="margin-bottom: var(--margin)"/>
 | 
				
			||||||
			<div v-if="error">
 | 
							<x-note :note="note" :key="note.id" :detail="true"/>
 | 
				
			||||||
				<mk-error @retry="fetch()"/>
 | 
							<div v-if="error">
 | 
				
			||||||
			</div>
 | 
								<mk-error @retry="fetch()"/>
 | 
				
			||||||
 | 
					 | 
				
			||||||
			<mk-button v-if="hasPrev && !showPrev" @click="showPrev = true" primary style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></mk-button>
 | 
					 | 
				
			||||||
			<hr v-if="showPrev"/>
 | 
					 | 
				
			||||||
			<x-notes v-if="showPrev" ref="prev" :pagination="prev" style="margin-top: var(--margin);"/>
 | 
					 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</transition>
 | 
					
 | 
				
			||||||
 | 
							<button class="_panel _button" v-if="hasPrev && !showPrev" @click="showPrev = true" style="margin: var(--margin) auto 0 auto;"><fa :icon="faChevronDown"/></button>
 | 
				
			||||||
 | 
							<hr v-if="showPrev"/>
 | 
				
			||||||
 | 
							<x-notes v-if="showPrev" ref="prev" :pagination="prev" style="margin-top: var(--margin);"/>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,7 +33,7 @@ import i18n from '../i18n';
 | 
				
			||||||
import Progress from '../scripts/loading';
 | 
					import Progress from '../scripts/loading';
 | 
				
			||||||
import XNote from '../components/note.vue';
 | 
					import XNote from '../components/note.vue';
 | 
				
			||||||
import XNotes from '../components/notes.vue';
 | 
					import XNotes from '../components/notes.vue';
 | 
				
			||||||
import MkButton from '../components/ui/button.vue';
 | 
					import MkRemoteCaution from '../components/remote-caution.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n,
 | 
						i18n,
 | 
				
			||||||
| 
						 | 
					@ -41,7 +45,7 @@ export default Vue.extend({
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XNote,
 | 
							XNote,
 | 
				
			||||||
		XNotes,
 | 
							XNotes,
 | 
				
			||||||
		MkButton,
 | 
							MkRemoteCaution,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,8 +8,10 @@
 | 
				
			||||||
	<section class="_card">
 | 
						<section class="_card">
 | 
				
			||||||
		<div class="_title"><fa :icon="faMusic"/> {{ $t('sounds') }}</div>
 | 
							<div class="_title"><fa :icon="faMusic"/> {{ $t('sounds') }}</div>
 | 
				
			||||||
		<div class="_content">
 | 
							<div class="_content">
 | 
				
			||||||
			{{ $t('volume') }}
 | 
								<mk-range v-model="sfxVolume" min="0" max="1" step="0.1">
 | 
				
			||||||
			<input type="range" v-model="sfxVolume" min="0" max="1" step="0.1"/>
 | 
									<fa slot="icon" :icon="volumeIcon"/>
 | 
				
			||||||
 | 
									<span slot="title">{{ $t('volume') }}</span>
 | 
				
			||||||
 | 
								</mk-range>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="_content">
 | 
							<div class="_content">
 | 
				
			||||||
			<mk-select v-model="sfxNote">
 | 
								<mk-select v-model="sfxNote">
 | 
				
			||||||
| 
						 | 
					@ -61,7 +63,6 @@
 | 
				
			||||||
				<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
 | 
									<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
 | 
				
			||||||
			</mk-switch>
 | 
								</mk-switch>
 | 
				
			||||||
			<mk-switch v-model="showFixedPostForm">{{ $t('showFixedPostForm') }}</mk-switch>
 | 
								<mk-switch v-model="showFixedPostForm">{{ $t('showFixedPostForm') }}</mk-switch>
 | 
				
			||||||
			<mk-switch v-model="useNotificationsPopup">{{ $t('useNotificationsPopup') }}</mk-switch>
 | 
					 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="_content">
 | 
							<div class="_content">
 | 
				
			||||||
			<mk-select v-model="lang">
 | 
								<mk-select v-model="lang">
 | 
				
			||||||
| 
						 | 
					@ -85,12 +86,13 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { faImage, faCog, faMusic, faPlay } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faImage, faCog, faMusic, faPlay, faVolumeUp, faVolumeMute } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import MkInput from '../../components/ui/input.vue';
 | 
					import MkInput from '../../components/ui/input.vue';
 | 
				
			||||||
import MkButton from '../../components/ui/button.vue';
 | 
					import MkButton from '../../components/ui/button.vue';
 | 
				
			||||||
import MkSwitch from '../../components/ui/switch.vue';
 | 
					import MkSwitch from '../../components/ui/switch.vue';
 | 
				
			||||||
import MkSelect from '../../components/ui/select.vue';
 | 
					import MkSelect from '../../components/ui/select.vue';
 | 
				
			||||||
import MkRadio from '../../components/ui/radio.vue';
 | 
					import MkRadio from '../../components/ui/radio.vue';
 | 
				
			||||||
 | 
					import MkRange from '../../components/ui/range.vue';
 | 
				
			||||||
import XTheme from './theme.vue';
 | 
					import XTheme from './theme.vue';
 | 
				
			||||||
import i18n from '../../i18n';
 | 
					import i18n from '../../i18n';
 | 
				
			||||||
import { langs } from '../../config';
 | 
					import { langs } from '../../config';
 | 
				
			||||||
| 
						 | 
					@ -128,6 +130,7 @@ export default Vue.extend({
 | 
				
			||||||
		MkSwitch,
 | 
							MkSwitch,
 | 
				
			||||||
		MkSelect,
 | 
							MkSelect,
 | 
				
			||||||
		MkRadio,
 | 
							MkRadio,
 | 
				
			||||||
 | 
							MkRange
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
| 
						 | 
					@ -136,7 +139,7 @@ export default Vue.extend({
 | 
				
			||||||
			lang: localStorage.getItem('lang'),
 | 
								lang: localStorage.getItem('lang'),
 | 
				
			||||||
			fontSize: localStorage.getItem('fontSize'),
 | 
								fontSize: localStorage.getItem('fontSize'),
 | 
				
			||||||
			sounds,
 | 
								sounds,
 | 
				
			||||||
			faImage, faCog, faMusic, faPlay
 | 
								faImage, faCog, faMusic, faPlay, faVolumeUp, faVolumeMute
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -171,14 +174,9 @@ export default Vue.extend({
 | 
				
			||||||
			set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); }
 | 
								set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); }
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		useNotificationsPopup: {
 | 
					 | 
				
			||||||
			get() { return this.$store.state.device.useNotificationsPopup; },
 | 
					 | 
				
			||||||
			set(value) { this.$store.commit('device/set', { key: 'useNotificationsPopup', value }); }
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		sfxVolume: {
 | 
							sfxVolume: {
 | 
				
			||||||
			get() { return this.$store.state.device.sfxVolume; },
 | 
								get() { return this.$store.state.device.sfxVolume; },
 | 
				
			||||||
			set(value) { this.$store.commit('device/set', { key: 'sfxVolume', value }); }
 | 
								set(value) { this.$store.commit('device/set', { key: 'sfxVolume', value: parseFloat(value, 10) }); }
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		sfxNote: {
 | 
							sfxNote: {
 | 
				
			||||||
| 
						 | 
					@ -210,6 +208,12 @@ export default Vue.extend({
 | 
				
			||||||
			get() { return this.$store.state.device.sfxAntenna; },
 | 
								get() { return this.$store.state.device.sfxAntenna; },
 | 
				
			||||||
			set(value) { this.$store.commit('device/set', { key: 'sfxAntenna', value }); }
 | 
								set(value) { this.$store.commit('device/set', { key: 'sfxAntenna', value }); }
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							volumeIcon: {
 | 
				
			||||||
 | 
								get() {
 | 
				
			||||||
 | 
									return this.sfxVolume === 0 ? faVolumeMute : faVolumeUp;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,31 +3,13 @@
 | 
				
			||||||
	<portal to="title" v-if="user"><mk-user-name :user="user" :nowrap="false" class="name"/></portal>
 | 
						<portal to="title" v-if="user"><mk-user-name :user="user" :nowrap="false" class="name"/></portal>
 | 
				
			||||||
	<portal to="avatar" v-if="user"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
 | 
						<portal to="avatar" v-if="user"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	<div class="remote-caution _panel" v-if="user.host != null"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div>
 | 
						<mk-remote-caution v-if="user.host != null" :href="user.url" style="margin-bottom: var(--margin)"/>
 | 
				
			||||||
	<transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in" appear>
 | 
						<div class="profile _panel" :key="user.id">
 | 
				
			||||||
		<div class="profile _panel" :key="user.id">
 | 
							<div class="banner-container" :style="style">
 | 
				
			||||||
			<div class="banner-container" :style="style">
 | 
								<div class="banner" ref="banner" :style="style"></div>
 | 
				
			||||||
				<div class="banner" ref="banner" :style="style"></div>
 | 
								<div class="fade"></div>
 | 
				
			||||||
				<div class="fade"></div>
 | 
					 | 
				
			||||||
				<div class="title">
 | 
					 | 
				
			||||||
					<mk-user-name class="name" :user="user" :nowrap="true"/>
 | 
					 | 
				
			||||||
					<div class="bottom">
 | 
					 | 
				
			||||||
						<span class="username"><mk-acct :user="user" :detail="true" /></span>
 | 
					 | 
				
			||||||
						<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
 | 
					 | 
				
			||||||
						<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span>
 | 
					 | 
				
			||||||
						<span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span>
 | 
					 | 
				
			||||||
						<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
 | 
					 | 
				
			||||||
					</div>
 | 
					 | 
				
			||||||
				</div>
 | 
					 | 
				
			||||||
				<span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span>
 | 
					 | 
				
			||||||
				<div class="actions" v-if="$store.getters.isSignedIn">
 | 
					 | 
				
			||||||
					<button @click="menu" class="menu _button" ref="menu"><fa :icon="faEllipsisH"/></button>
 | 
					 | 
				
			||||||
					<mk-follow-button v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
 | 
					 | 
				
			||||||
				</div>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
			<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
 | 
					 | 
				
			||||||
			<div class="title">
 | 
								<div class="title">
 | 
				
			||||||
				<mk-user-name :user="user" :nowrap="false" class="name"/>
 | 
									<mk-user-name class="name" :user="user" :nowrap="true"/>
 | 
				
			||||||
				<div class="bottom">
 | 
									<div class="bottom">
 | 
				
			||||||
					<span class="username"><mk-acct :user="user" :detail="true" /></span>
 | 
										<span class="username"><mk-acct :user="user" :detail="true" /></span>
 | 
				
			||||||
					<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
 | 
										<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
 | 
				
			||||||
| 
						 | 
					@ -36,55 +18,71 @@
 | 
				
			||||||
					<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
 | 
										<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div class="description">
 | 
								<span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span>
 | 
				
			||||||
				<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
 | 
								<div class="actions" v-if="$store.getters.isSignedIn">
 | 
				
			||||||
				<p v-else class="empty">{{ $t('noAccountDescription') }}</p>
 | 
									<button @click="menu" class="menu _button" ref="menu"><fa :icon="faEllipsisH"/></button>
 | 
				
			||||||
			</div>
 | 
									<mk-follow-button v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
 | 
				
			||||||
			<div class="fields system">
 | 
					 | 
				
			||||||
				<dl class="field" v-if="user.location">
 | 
					 | 
				
			||||||
					<dt class="name"><fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt>
 | 
					 | 
				
			||||||
					<dd class="value">{{ user.location }}</dd>
 | 
					 | 
				
			||||||
				</dl>
 | 
					 | 
				
			||||||
				<dl class="field" v-if="user.birthday">
 | 
					 | 
				
			||||||
					<dt class="name"><fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt>
 | 
					 | 
				
			||||||
					<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
 | 
					 | 
				
			||||||
				</dl>
 | 
					 | 
				
			||||||
				<dl class="field">
 | 
					 | 
				
			||||||
					<dt class="name"><fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt>
 | 
					 | 
				
			||||||
					<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<mk-time :time="user.createdAt"/>)</dd>
 | 
					 | 
				
			||||||
				</dl>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
			<div class="fields" v-if="user.fields.length > 0">
 | 
					 | 
				
			||||||
				<dl class="field" v-for="(field, i) in user.fields" :key="i">
 | 
					 | 
				
			||||||
					<dt class="name">
 | 
					 | 
				
			||||||
						<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
 | 
					 | 
				
			||||||
					</dt>
 | 
					 | 
				
			||||||
					<dd class="value">
 | 
					 | 
				
			||||||
						<mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/>
 | 
					 | 
				
			||||||
					</dd>
 | 
					 | 
				
			||||||
				</dl>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
			<div class="status" v-if="user.host === null">
 | 
					 | 
				
			||||||
				<router-link :to="user | userPage()" :class="{ active: $route.name === 'user' }">
 | 
					 | 
				
			||||||
					<b>{{ user.notesCount | number }}</b>
 | 
					 | 
				
			||||||
					<span>{{ $t('notes') }}</span>
 | 
					 | 
				
			||||||
				</router-link>
 | 
					 | 
				
			||||||
				<router-link :to="user | userPage('following')" :class="{ active: $route.name === 'userFollowing' }">
 | 
					 | 
				
			||||||
					<b>{{ user.followingCount | number }}</b>
 | 
					 | 
				
			||||||
					<span>{{ $t('following') }}</span>
 | 
					 | 
				
			||||||
				</router-link>
 | 
					 | 
				
			||||||
				<router-link :to="user | userPage('followers')" :class="{ active: $route.name === 'userFollowers' }">
 | 
					 | 
				
			||||||
					<b>{{ user.followersCount | number }}</b>
 | 
					 | 
				
			||||||
					<span>{{ $t('followers') }}</span>
 | 
					 | 
				
			||||||
				</router-link>
 | 
					 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</transition>
 | 
							<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
 | 
				
			||||||
 | 
							<div class="title">
 | 
				
			||||||
 | 
								<mk-user-name :user="user" :nowrap="false" class="name"/>
 | 
				
			||||||
 | 
								<div class="bottom">
 | 
				
			||||||
 | 
									<span class="username"><mk-acct :user="user" :detail="true" /></span>
 | 
				
			||||||
 | 
									<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><fa :icon="faBookmark"/></span>
 | 
				
			||||||
 | 
									<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><fa :icon="farBookmark"/></span>
 | 
				
			||||||
 | 
									<span v-if="user.isLocked" :title="$t('isLocked')"><fa :icon="faLock"/></span>
 | 
				
			||||||
 | 
									<span v-if="user.isBot" :title="$t('isBot')"><fa :icon="faRobot"/></span>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="description">
 | 
				
			||||||
 | 
								<mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
 | 
				
			||||||
 | 
								<p v-else class="empty">{{ $t('noAccountDescription') }}</p>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="fields system">
 | 
				
			||||||
 | 
								<dl class="field" v-if="user.location">
 | 
				
			||||||
 | 
									<dt class="name"><fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt>
 | 
				
			||||||
 | 
									<dd class="value">{{ user.location }}</dd>
 | 
				
			||||||
 | 
								</dl>
 | 
				
			||||||
 | 
								<dl class="field" v-if="user.birthday">
 | 
				
			||||||
 | 
									<dt class="name"><fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt>
 | 
				
			||||||
 | 
									<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
 | 
				
			||||||
 | 
								</dl>
 | 
				
			||||||
 | 
								<dl class="field">
 | 
				
			||||||
 | 
									<dt class="name"><fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt>
 | 
				
			||||||
 | 
									<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<mk-time :time="user.createdAt"/>)</dd>
 | 
				
			||||||
 | 
								</dl>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="fields" v-if="user.fields.length > 0">
 | 
				
			||||||
 | 
								<dl class="field" v-for="(field, i) in user.fields" :key="i">
 | 
				
			||||||
 | 
									<dt class="name">
 | 
				
			||||||
 | 
										<mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
 | 
				
			||||||
 | 
									</dt>
 | 
				
			||||||
 | 
									<dd class="value">
 | 
				
			||||||
 | 
										<mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/>
 | 
				
			||||||
 | 
									</dd>
 | 
				
			||||||
 | 
								</dl>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="status">
 | 
				
			||||||
 | 
								<router-link :to="user | userPage()" :class="{ active: $route.name === 'user' }">
 | 
				
			||||||
 | 
									<b>{{ user.notesCount | number }}</b>
 | 
				
			||||||
 | 
									<span>{{ $t('notes') }}</span>
 | 
				
			||||||
 | 
								</router-link>
 | 
				
			||||||
 | 
								<router-link :to="user | userPage('following')" :class="{ active: $route.name === 'userFollowing' }">
 | 
				
			||||||
 | 
									<b>{{ user.followingCount | number }}</b>
 | 
				
			||||||
 | 
									<span>{{ $t('following') }}</span>
 | 
				
			||||||
 | 
								</router-link>
 | 
				
			||||||
 | 
								<router-link :to="user | userPage('followers')" :class="{ active: $route.name === 'userFollowers' }">
 | 
				
			||||||
 | 
									<b>{{ user.followersCount | number }}</b>
 | 
				
			||||||
 | 
									<span>{{ $t('followers') }}</span>
 | 
				
			||||||
 | 
								</router-link>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
	<router-view :user="user"></router-view>
 | 
						<router-view :user="user"></router-view>
 | 
				
			||||||
	<template v-if="$route.name == 'user'">
 | 
						<template v-if="$route.name == 'user'">
 | 
				
			||||||
		<sequential-entrance class="pins">
 | 
							<div class="pins">
 | 
				
			||||||
			<x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
 | 
								<x-note v-for="note in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
 | 
				
			||||||
		</sequential-entrance>
 | 
							</div>
 | 
				
			||||||
		<mk-container :body-togglable="true" class="content">
 | 
							<mk-container :body-togglable="true" class="content">
 | 
				
			||||||
			<template #header><fa :icon="faImage"/>{{ $t('images') }}</template>
 | 
								<template #header><fa :icon="faImage"/>{{ $t('images') }}</template>
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
| 
						 | 
					@ -107,7 +105,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { faEllipsisH, faRobot, faLock, faBookmark, faExclamationTriangle, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faEllipsisH, faRobot, faLock, faBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faCalendarAlt, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faCalendarAlt, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
import * as age from 's-age';
 | 
					import * as age from 's-age';
 | 
				
			||||||
import XUserTimeline from './index.timeline.vue';
 | 
					import XUserTimeline from './index.timeline.vue';
 | 
				
			||||||
| 
						 | 
					@ -115,6 +113,7 @@ import XUserMenu from '../../components/user-menu.vue';
 | 
				
			||||||
import XNote from '../../components/note.vue';
 | 
					import XNote from '../../components/note.vue';
 | 
				
			||||||
import MkFollowButton from '../../components/follow-button.vue';
 | 
					import MkFollowButton from '../../components/follow-button.vue';
 | 
				
			||||||
import MkContainer from '../../components/ui/container.vue';
 | 
					import MkContainer from '../../components/ui/container.vue';
 | 
				
			||||||
 | 
					import MkRemoteCaution from '../../components/remote-caution.vue';
 | 
				
			||||||
import Progress from '../../scripts/loading';
 | 
					import Progress from '../../scripts/loading';
 | 
				
			||||||
import parseAcct from '../../../misc/acct/parse';
 | 
					import parseAcct from '../../../misc/acct/parse';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,6 +123,7 @@ export default Vue.extend({
 | 
				
			||||||
		XNote,
 | 
							XNote,
 | 
				
			||||||
		MkFollowButton,
 | 
							MkFollowButton,
 | 
				
			||||||
		MkContainer,
 | 
							MkContainer,
 | 
				
			||||||
 | 
							MkRemoteCaution,
 | 
				
			||||||
		XPhotos: () => import('./index.photos.vue').then(m => m.default),
 | 
							XPhotos: () => import('./index.photos.vue').then(m => m.default),
 | 
				
			||||||
		XActivity: () => import('./index.activity.vue').then(m => m.default),
 | 
							XActivity: () => import('./index.activity.vue').then(m => m.default),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -139,7 +139,7 @@ export default Vue.extend({
 | 
				
			||||||
			user: null,
 | 
								user: null,
 | 
				
			||||||
			error: null,
 | 
								error: null,
 | 
				
			||||||
			parallaxAnimationId: null,
 | 
								parallaxAnimationId: null,
 | 
				
			||||||
			faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faExclamationTriangle, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt
 | 
								faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -217,17 +217,6 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.mk-user-page {
 | 
					.mk-user-page {
 | 
				
			||||||
	> .remote-caution {
 | 
					 | 
				
			||||||
		font-size: 0.8em;
 | 
					 | 
				
			||||||
		padding: 16px;
 | 
					 | 
				
			||||||
		margin-bottom: var(--margin);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> a {
 | 
					 | 
				
			||||||
			margin-left: 4px;
 | 
					 | 
				
			||||||
			color: var(--accent);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .profile {
 | 
						> .profile {
 | 
				
			||||||
		position: relative;
 | 
							position: relative;
 | 
				
			||||||
		margin-bottom: var(--margin);
 | 
							margin-bottom: var(--margin);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,14 +12,22 @@ type action = {
 | 
				
			||||||
	patterns: pattern[];
 | 
						patterns: pattern[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	callback: Function;
 | 
						callback: Function;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						allowRepeat: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => {
 | 
					const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => {
 | 
				
			||||||
	const result = {
 | 
						const result = {
 | 
				
			||||||
		patterns: [],
 | 
							patterns: [],
 | 
				
			||||||
		callback: callback
 | 
							callback: callback,
 | 
				
			||||||
 | 
							allowRepeat: true
 | 
				
			||||||
	} as action;
 | 
						} as action;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (patterns.match(/^\(.*\)$/) !== null) {
 | 
				
			||||||
 | 
							result.allowRepeat = false;
 | 
				
			||||||
 | 
							patterns = patterns.slice(1, -1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	result.patterns = patterns.split('|').map(part => {
 | 
						result.patterns = patterns.split('|').map(part => {
 | 
				
			||||||
		const pattern = {
 | 
							const pattern = {
 | 
				
			||||||
			which: [],
 | 
								which: [],
 | 
				
			||||||
| 
						 | 
					@ -77,6 +85,7 @@ export default {
 | 
				
			||||||
						const matched = match(e, action.patterns);
 | 
											const matched = match(e, action.patterns);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						if (matched) {
 | 
											if (matched) {
 | 
				
			||||||
 | 
												if (!action.allowRepeat && e.repeat) return;
 | 
				
			||||||
							if (el._hotkey_global && match(e, targetReservedKeys)) return;
 | 
												if (el._hotkey_global && match(e, targetReservedKeys)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							e.preventDefault();
 | 
												e.preventDefault();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,6 @@ const defaultDeviceSettings = {
 | 
				
			||||||
	animatedMfm: true,
 | 
						animatedMfm: true,
 | 
				
			||||||
	imageNewTab: false,
 | 
						imageNewTab: false,
 | 
				
			||||||
	showFixedPostForm: false,
 | 
						showFixedPostForm: false,
 | 
				
			||||||
	useNotificationsPopup: true,
 | 
					 | 
				
			||||||
	sfxVolume: 0.3,
 | 
						sfxVolume: 0.3,
 | 
				
			||||||
	sfxNote: 'syuilo/down',
 | 
						sfxNote: 'syuilo/down',
 | 
				
			||||||
	sfxNoteMy: 'syuilo/up',
 | 
						sfxNoteMy: 'syuilo/up',
 | 
				
			||||||
| 
						 | 
					@ -101,6 +100,7 @@ export default (os: MiOS) => new Vuex.Store({
 | 
				
			||||||
			ctx.commit('settings/init', {});
 | 
								ctx.commit('settings/init', {});
 | 
				
			||||||
			ctx.commit('deviceUser/init', {});
 | 
								ctx.commit('deviceUser/init', {});
 | 
				
			||||||
			localStorage.removeItem('i');
 | 
								localStorage.removeItem('i');
 | 
				
			||||||
 | 
								document.cookie = `igi=; path=/`;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		async switchAccount(ctx, i) {
 | 
							async switchAccount(ctx, i) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
:root {
 | 
					:root {
 | 
				
			||||||
	--radius: 8px;
 | 
						--radius: 8px;
 | 
				
			||||||
	--marginFull: 16px;
 | 
						--marginFull: 16px;
 | 
				
			||||||
	--marginHalf: 8px;
 | 
						--marginHalf: 10px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	--margin: var(--marginFull);
 | 
						--margin: var(--marginFull);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -230,7 +230,6 @@ hr {
 | 
				
			||||||
	@extend ._button;
 | 
						@extend ._button;
 | 
				
			||||||
	color: #fff;
 | 
						color: #fff;
 | 
				
			||||||
	background: var(--accent);
 | 
						background: var(--accent);
 | 
				
			||||||
	box-shadow: 0 6px 16px var(--accentShadow);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&:not(:disabled):hover {
 | 
						&:not(:disabled):hover {
 | 
				
			||||||
		background: var(--jkhztclx);
 | 
							background: var(--jkhztclx);
 | 
				
			||||||
| 
						 | 
					@ -276,23 +275,29 @@ hr {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
._shadow {
 | 
					 | 
				
			||||||
	box-shadow: 0 8px 32px var(--shadow);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@media (max-width: 700px) {
 | 
					 | 
				
			||||||
		box-shadow: 0 4px 16px var(--shadow);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@media (max-width: 500px) {
 | 
					 | 
				
			||||||
		box-shadow: 0 2px 8px var(--shadow);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
._panel {
 | 
					._panel {
 | 
				
			||||||
	@extend ._shadow;
 | 
					 | 
				
			||||||
	position: relative;
 | 
						position: relative;
 | 
				
			||||||
	background: var(--panel);
 | 
						background: var(--panel);
 | 
				
			||||||
	border-radius: var(--radius);
 | 
						border-radius: var(--radius);
 | 
				
			||||||
 | 
						box-shadow: 0 0 0 1px var(--divider);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main ._panel {
 | 
				
			||||||
 | 
						border-radius: 0;
 | 
				
			||||||
 | 
						box-shadow: 0 1px 0 0 var(--divider), 0 -1px 0 0 var(--divider);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					._panel ._panel {
 | 
				
			||||||
 | 
						border-radius: 0;
 | 
				
			||||||
 | 
						box-shadow: 0 1px 0 0 var(--divider), 0 -1px 0 0 var(--divider);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					._panel._button {
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
						min-height: 48px;
 | 
				
			||||||
 | 
						align-items: center;
 | 
				
			||||||
 | 
						justify-content: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
._card {
 | 
					._card {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,9 +17,10 @@
 | 
				
			||||||
		fgHighlighted: ':lighten<3<@fg',
 | 
							fgHighlighted: ':lighten<3<@fg',
 | 
				
			||||||
		html: '@bg',
 | 
							html: '@bg',
 | 
				
			||||||
		indicator: '@accent',
 | 
							indicator: '@accent',
 | 
				
			||||||
		panel: '#111213',
 | 
							panel: '#000',
 | 
				
			||||||
		shadow: 'rgba(0, 0, 0, 0.1)',
 | 
							shadow: 'rgba(0, 0, 0, 0.1)',
 | 
				
			||||||
		header: 'rgba(20, 20, 20, 0.75)',
 | 
							header: 'rgba(20, 20, 20, 0.75)',
 | 
				
			||||||
 | 
							pageBg: ':lighten<5<@bg',
 | 
				
			||||||
		navBg: '@panel',
 | 
							navBg: '@panel',
 | 
				
			||||||
		navFg: '@fg',
 | 
							navFg: '@fg',
 | 
				
			||||||
		navHoverFg: ':lighten<17<@fg',
 | 
							navHoverFg: ':lighten<17<@fg',
 | 
				
			||||||
| 
						 | 
					@ -33,8 +34,7 @@
 | 
				
			||||||
		divider: 'rgba(255, 255, 255, 0.1)',
 | 
							divider: 'rgba(255, 255, 255, 0.1)',
 | 
				
			||||||
		scrollbarHandle: 'rgba(255, 255, 255, 0.2)',
 | 
							scrollbarHandle: 'rgba(255, 255, 255, 0.2)',
 | 
				
			||||||
		scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)',
 | 
							scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)',
 | 
				
			||||||
		dateLabelBg: 'rgba(255, 255, 255, 0.08)',
 | 
							dateLabelFg: '@fg',
 | 
				
			||||||
		dateLabelFg: '#fff',
 | 
					 | 
				
			||||||
		infoBg: '#253142',
 | 
							infoBg: '#253142',
 | 
				
			||||||
		infoFg: '#fff',
 | 
							infoFg: '#fff',
 | 
				
			||||||
		infoWarnBg: '#42321c',
 | 
							infoWarnBg: '#42321c',
 | 
				
			||||||
| 
						 | 
					@ -51,14 +51,13 @@
 | 
				
			||||||
		driveFolderBg: ':alpha<0.3<@accent',
 | 
							driveFolderBg: ':alpha<0.3<@accent',
 | 
				
			||||||
		wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
 | 
							wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
 | 
				
			||||||
		badge: '#31b1ce',
 | 
							badge: '#31b1ce',
 | 
				
			||||||
 | 
							messageBg: ':lighten<5<@bg',
 | 
				
			||||||
		bonzsgfz: ':alpha<0<@bg',
 | 
							bonzsgfz: ':alpha<0<@bg',
 | 
				
			||||||
		pcncwizz: ':darken<2<@panel',
 | 
							pcncwizz: ':darken<2<@panel',
 | 
				
			||||||
		vocsgcxy: 'rgba(0, 0, 0, 0.5)',
 | 
					 | 
				
			||||||
		yrnqrguo: 'rgba(255, 255, 255, 0.05)',
 | 
							yrnqrguo: 'rgba(255, 255, 255, 0.05)',
 | 
				
			||||||
		nwjktjjq: 'rgba(255, 255, 255, 0.1)',
 | 
							nwjktjjq: 'rgba(255, 255, 255, 0.1)',
 | 
				
			||||||
		geavgsxy: 'rgba(255, 255, 255, 0.05)',
 | 
							geavgsxy: 'rgba(255, 255, 255, 0.05)',
 | 
				
			||||||
		nhzhphzx: 'rgba(255, 255, 255, 0.15)',
 | 
							nhzhphzx: 'rgba(255, 255, 255, 0.15)',
 | 
				
			||||||
		tyvedwbe: 'rgba(0, 0, 0, 0.5)',
 | 
					 | 
				
			||||||
		bwqtlupy: 'rgba(255, 255, 255, 0.05)',
 | 
							bwqtlupy: 'rgba(255, 255, 255, 0.05)',
 | 
				
			||||||
		jkhztclx: ':lighten<5<@accent',
 | 
							jkhztclx: ':lighten<5<@accent',
 | 
				
			||||||
		zbqjwygh: ':darken<5<@accent',
 | 
							zbqjwygh: ':darken<5<@accent',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@
 | 
				
			||||||
		panel: '#fff',
 | 
							panel: '#fff',
 | 
				
			||||||
		shadow: 'rgba(0, 0, 0, 0.1)',
 | 
							shadow: 'rgba(0, 0, 0, 0.1)',
 | 
				
			||||||
		header: 'rgba(255, 255, 255, 0.75)',
 | 
							header: 'rgba(255, 255, 255, 0.75)',
 | 
				
			||||||
 | 
							pageBg: '@bg',
 | 
				
			||||||
		navBg: '@panel',
 | 
							navBg: '@panel',
 | 
				
			||||||
		navFg: '@fg',
 | 
							navFg: '@fg',
 | 
				
			||||||
		navHoverFg: ':darken<17<@fg',
 | 
							navHoverFg: ':darken<17<@fg',
 | 
				
			||||||
| 
						 | 
					@ -33,8 +34,7 @@
 | 
				
			||||||
		divider: 'rgba(0, 0, 0, 0.1)',
 | 
							divider: 'rgba(0, 0, 0, 0.1)',
 | 
				
			||||||
		scrollbarHandle: 'rgba(0, 0, 0, 0.2)',
 | 
							scrollbarHandle: 'rgba(0, 0, 0, 0.2)',
 | 
				
			||||||
		scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)',
 | 
							scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)',
 | 
				
			||||||
		dateLabelBg: 'rgba(0, 0, 0, 0.5)',
 | 
							dateLabelFg: '@fg',
 | 
				
			||||||
		dateLabelFg: '#fff',
 | 
					 | 
				
			||||||
		infoBg: '#e5f5ff',
 | 
							infoBg: '#e5f5ff',
 | 
				
			||||||
		infoFg: '#72818a',
 | 
							infoFg: '#72818a',
 | 
				
			||||||
		infoWarnBg: '#fff0db',
 | 
							infoWarnBg: '#fff0db',
 | 
				
			||||||
| 
						 | 
					@ -51,14 +51,13 @@
 | 
				
			||||||
		driveFolderBg: ':alpha<0.3<@accent',
 | 
							driveFolderBg: ':alpha<0.3<@accent',
 | 
				
			||||||
		wallpaperOverlay: 'rgba(255, 255, 255, 0.5)',
 | 
							wallpaperOverlay: 'rgba(255, 255, 255, 0.5)',
 | 
				
			||||||
		badge: '#31b1ce',
 | 
							badge: '#31b1ce',
 | 
				
			||||||
 | 
							messageBg: '@panel',
 | 
				
			||||||
		bonzsgfz: ':alpha<0<@bg',
 | 
							bonzsgfz: ':alpha<0<@bg',
 | 
				
			||||||
		pcncwizz: ':darken<2<@panel',
 | 
							pcncwizz: ':darken<2<@panel',
 | 
				
			||||||
		vocsgcxy: 'rgba(255, 255, 255, 0.5)',
 | 
					 | 
				
			||||||
		yrnqrguo: 'rgba(0, 0, 0, 0.05)',
 | 
							yrnqrguo: 'rgba(0, 0, 0, 0.05)',
 | 
				
			||||||
		nwjktjjq: 'rgba(0, 0, 0, 0.1)',
 | 
							nwjktjjq: 'rgba(0, 0, 0, 0.1)',
 | 
				
			||||||
		geavgsxy: 'rgba(0, 0, 0, 0.05)',
 | 
							geavgsxy: 'rgba(0, 0, 0, 0.05)',
 | 
				
			||||||
		nhzhphzx: 'rgba(0, 0, 0, 0.25)',
 | 
							nhzhphzx: 'rgba(0, 0, 0, 0.25)',
 | 
				
			||||||
		tyvedwbe: 'rgba(0, 0, 0, 0.1)',
 | 
					 | 
				
			||||||
		bwqtlupy: 'rgba(0, 0, 0, 0.05)',
 | 
							bwqtlupy: 'rgba(0, 0, 0, 0.05)',
 | 
				
			||||||
		jkhztclx: ':lighten<5<@accent',
 | 
							jkhztclx: ':lighten<5<@accent',
 | 
				
			||||||
		zbqjwygh: ':darken<5<@accent',
 | 
							zbqjwygh: ':darken<5<@accent',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,5 @@
 | 
				
			||||||
		link: '@accent',
 | 
							link: '@accent',
 | 
				
			||||||
		mention: '@accent',
 | 
							mention: '@accent',
 | 
				
			||||||
		hashtag: '@accent',
 | 
							hashtag: '@accent',
 | 
				
			||||||
		dateLabelBg: 'rgb(204, 186, 188)',
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
	<mk-container :show-header="!props.compact" class="container">
 | 
						<mk-container :show-header="!props.compact" class="container">
 | 
				
			||||||
		<template #header><fa :icon="faBell"/>{{ $t('notifications') }}</template>
 | 
							<template #header><fa :icon="faBell"/>{{ $t('notifications') }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<div class="tl">
 | 
							<div>
 | 
				
			||||||
			<x-notifications/>
 | 
								<x-notifications/>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</mk-container>
 | 
						</mk-container>
 | 
				
			||||||
| 
						 | 
					@ -81,10 +81,5 @@ export default define({
 | 
				
			||||||
			flex-grow: 1;
 | 
								flex-grow: 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	.tl {
 | 
					 | 
				
			||||||
		background: var(--bg);
 | 
					 | 
				
			||||||
		padding: 8px;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@
 | 
				
			||||||
			</button>
 | 
								</button>
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<div class="tl">
 | 
							<div>
 | 
				
			||||||
			<x-timeline :key="props.src === 'list' ? `list:${props.list.id}` : props.src === 'antenna' ? `antenna:${props.antenna.id}` : props.src" :src="props.src" :list="props.list" :antenna="props.antenna"/>
 | 
								<x-timeline :key="props.src === 'list' ? `list:${props.list.id}` : props.src === 'antenna' ? `antenna:${props.antenna.id}` : props.src" :src="props.src" :list="props.list" :antenna="props.antenna"/>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</mk-container>
 | 
						</mk-container>
 | 
				
			||||||
| 
						 | 
					@ -148,11 +148,5 @@ export default define({
 | 
				
			||||||
			flex-grow: 1;
 | 
								flex-grow: 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	.tl {
 | 
					 | 
				
			||||||
		padding: 8px;
 | 
					 | 
				
			||||||
		background: var(--bg);
 | 
					 | 
				
			||||||
		box-sizing: border-box;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,9 +27,10 @@ export type Source = {
 | 
				
			||||||
	elasticsearch: {
 | 
						elasticsearch: {
 | 
				
			||||||
		host: string;
 | 
							host: string;
 | 
				
			||||||
		port: number;
 | 
							port: number;
 | 
				
			||||||
		pass: string;
 | 
					 | 
				
			||||||
		index?: string;
 | 
					 | 
				
			||||||
		ssl?: boolean;
 | 
							ssl?: boolean;
 | 
				
			||||||
 | 
							user?: string;
 | 
				
			||||||
 | 
							pass?: string;
 | 
				
			||||||
 | 
							index?: string;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	proxy?: string;
 | 
						proxy?: string;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,6 +33,10 @@ const index = {
 | 
				
			||||||
// Init ElasticSearch connection
 | 
					// Init ElasticSearch connection
 | 
				
			||||||
const client = config.elasticsearch ? new elasticsearch.Client({
 | 
					const client = config.elasticsearch ? new elasticsearch.Client({
 | 
				
			||||||
	node: `${config.elasticsearch.ssl ? 'https://' : 'http://'}${config.elasticsearch.host}:${config.elasticsearch.port}`,
 | 
						node: `${config.elasticsearch.ssl ? 'https://' : 'http://'}${config.elasticsearch.host}:${config.elasticsearch.port}`,
 | 
				
			||||||
 | 
						auth: (config.elasticsearch.user && config.elasticsearch.pass) ? {
 | 
				
			||||||
 | 
							username: config.elasticsearch.user,
 | 
				
			||||||
 | 
							password: config.elasticsearch.pass
 | 
				
			||||||
 | 
						} : undefined,
 | 
				
			||||||
	pingTimeout: 30000
 | 
						pingTimeout: 30000
 | 
				
			||||||
}) : null;
 | 
					}) : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										112
									
								
								src/mfm/toString.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/mfm/toString.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,112 @@
 | 
				
			||||||
 | 
					import { MfmForest, MfmTree } from './prelude';
 | 
				
			||||||
 | 
					import { nyaize } from '../misc/nyaize';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type RestoreOptions = {
 | 
				
			||||||
 | 
						doNyaize?: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function toString(tokens: MfmForest | null, opts?: RestoreOptions): string {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (tokens === null) return '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function appendChildren(children: MfmForest, opts?: RestoreOptions): string {
 | 
				
			||||||
 | 
							return children.map(t => handlers[t.node.type](t, opts)).join('');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const handlers: { [key: string]: (token: MfmTree, opts?: RestoreOptions) => string } = {
 | 
				
			||||||
 | 
							bold(token, opts) {
 | 
				
			||||||
 | 
								return `**${appendChildren(token.children, opts)}**`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							big(token, opts) {
 | 
				
			||||||
 | 
								return `***${appendChildren(token.children, opts)}***`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							small(token, opts) {
 | 
				
			||||||
 | 
								return `<small>${appendChildren(token.children, opts)}</small>`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							strike(token, opts) {
 | 
				
			||||||
 | 
								return `~~${appendChildren(token.children, opts)}~~`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							italic(token, opts) {
 | 
				
			||||||
 | 
								return `<i>${appendChildren(token.children, opts)}</i>`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							motion(token, opts) {
 | 
				
			||||||
 | 
								return `<motion>${appendChildren(token.children, opts)}</motion>`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							spin(token, opts) {
 | 
				
			||||||
 | 
								return `<spin>${appendChildren(token.children, opts)}</spin>`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							jump(token, opts) {
 | 
				
			||||||
 | 
								return `<jump>${appendChildren(token.children, opts)}</jump>`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							flip(token, opts) {
 | 
				
			||||||
 | 
								return `<flip>${appendChildren(token.children, opts)}</flip>`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							blockCode(token) {
 | 
				
			||||||
 | 
								return `\`\`\`${token.node.props.lang || ''}\n${token.node.props.code}\n\`\`\`\n`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							center(token, opts) {
 | 
				
			||||||
 | 
								return `<center>${appendChildren(token.children, opts)}</center>`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							emoji(token) {
 | 
				
			||||||
 | 
								return (token.node.props.emoji ? token.node.props.emoji : `:${token.node.props.name}:`);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							hashtag(token) {
 | 
				
			||||||
 | 
								return `#${token.node.props.hashtag}`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							inlineCode(token) {
 | 
				
			||||||
 | 
								return `\`${token.node.props.code}\``;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mathInline(token) {
 | 
				
			||||||
 | 
								return `\\(${token.node.props.formula}\\)`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mathBlock(token) {
 | 
				
			||||||
 | 
								return `\\[${token.node.props.formula}\\]`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							link(token, opts) {
 | 
				
			||||||
 | 
								return `[${appendChildren(token.children, opts)}](${token.node.props.url})`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mention(token) {
 | 
				
			||||||
 | 
								return token.node.props.canonical;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							quote(token) {
 | 
				
			||||||
 | 
								return `${appendChildren(token.children, {doNyaize: false}).replace(/^/gm,'>').trim()}\n`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							title(token, opts) {
 | 
				
			||||||
 | 
								return `[${appendChildren(token.children, opts)}]\n`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							text(token, opts) {
 | 
				
			||||||
 | 
								return (opts && opts.doNyaize) ? nyaize(token.node.props.text) : token.node.props.text;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							url(token) {
 | 
				
			||||||
 | 
								return `<${token.node.props.url}>`;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							search(token, opts) {
 | 
				
			||||||
 | 
								const query = token.node.props.query;
 | 
				
			||||||
 | 
								return `${(opts && opts.doNyaize ? nyaize(query) : query)} [search]\n`;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return appendChildren(tokens, { doNyaize: (opts && opts.doNyaize) || false }).trim();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -24,3 +24,7 @@ export function getApLock(uri: string, timeout = 30 * 1000) {
 | 
				
			||||||
export function getNodeinfoLock(host: string, timeout = 30 * 1000) {
 | 
					export function getNodeinfoLock(host: string, timeout = 30 * 1000) {
 | 
				
			||||||
	return lock(`nodeinfo:${host}`, timeout);
 | 
						return lock(`nodeinfo:${host}`, timeout);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getChartInsertLock(lockKey: string, timeout = 30 * 1000) {
 | 
				
			||||||
 | 
						return lock(`chart-insert:${lockKey}`, timeout);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@ export async function checkHitAntenna(antenna: Antenna, note: Note, noteUser: Us
 | 
				
			||||||
					? note.text!.includes(keyword)
 | 
										? note.text!.includes(keyword)
 | 
				
			||||||
					: note.text!.toLowerCase().includes(keyword.toLowerCase())
 | 
										: note.text!.toLowerCase().includes(keyword.toLowerCase())
 | 
				
			||||||
			));
 | 
								));
 | 
				
			||||||
		
 | 
					
 | 
				
			||||||
		if (!matched) return false;
 | 
							if (!matched) return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,7 +61,7 @@ export async function checkHitAntenna(antenna: Antenna, note: Note, noteUser: Us
 | 
				
			||||||
					? note.text!.includes(keyword)
 | 
										? note.text!.includes(keyword)
 | 
				
			||||||
					: note.text!.toLowerCase().includes(keyword.toLowerCase())
 | 
										: note.text!.toLowerCase().includes(keyword.toLowerCase())
 | 
				
			||||||
			));
 | 
								));
 | 
				
			||||||
		
 | 
					
 | 
				
			||||||
		if (matched) return false;
 | 
							if (matched) return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/misc/count-same-renotes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/misc/count-same-renotes.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					import { Notes } from '../models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function countSameRenotes(userId: string, renoteId: string, excludeNoteId: string | undefined): Promise<number> {
 | 
				
			||||||
 | 
						// 指定したユーザーの指定したノートのリノートがいくつあるか数える
 | 
				
			||||||
 | 
						const query = Notes.createQueryBuilder('note')
 | 
				
			||||||
 | 
							.where('note.userId = :userId', { userId })
 | 
				
			||||||
 | 
							.andWhere('note.renoteId = :renoteId', { renoteId })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 指定した投稿を除く
 | 
				
			||||||
 | 
						if (excludeNoteId) {
 | 
				
			||||||
 | 
							query.andWhere('note.id != :excludeNoteId', { excludeNoteId })
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return await query.getCount();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
					@ -1,8 +1,5 @@
 | 
				
			||||||
import rndstr from 'rndstr';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function nyaize(text: string): string {
 | 
					export function nyaize(text: string): string {
 | 
				
			||||||
	const [toNyaize, exclusionMap] = exclude(text);
 | 
						return text
 | 
				
			||||||
	const nyaized = toNyaize
 | 
					 | 
				
			||||||
		// ja-JP
 | 
							// ja-JP
 | 
				
			||||||
		.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ')
 | 
							.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ')
 | 
				
			||||||
		// en-US
 | 
							// en-US
 | 
				
			||||||
| 
						 | 
					@ -13,34 +10,4 @@ export function nyaize(text: string): string {
 | 
				
			||||||
		))
 | 
							))
 | 
				
			||||||
		.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, '다냥')
 | 
							.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, '다냥')
 | 
				
			||||||
		.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, '냥');
 | 
							.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, '냥');
 | 
				
			||||||
	return replaceExceptions(nyaized, exclusionMap);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function exclude(text: string): [string, Record<string, string>] {
 | 
					 | 
				
			||||||
	const map: Record<string, string> = {};
 | 
					 | 
				
			||||||
	function substitute(match: string): string {
 | 
					 | 
				
			||||||
		let randomstr: string;
 | 
					 | 
				
			||||||
		do {
 | 
					 | 
				
			||||||
			randomstr = rndstr({ length: 16, chars: '🀀-🀫' });
 | 
					 | 
				
			||||||
		} while(Object.prototype.hasOwnProperty.call(map, randomstr));
 | 
					 | 
				
			||||||
		map[randomstr] = match;
 | 
					 | 
				
			||||||
		return randomstr;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	const replaced = text
 | 
					 | 
				
			||||||
		.replace(/```(.+?)?\n([\s\S]+?)```(\n|$)/gm, match => substitute(match)) // code block
 | 
					 | 
				
			||||||
		.replace(/`([^`\n]+?)`/g, match => substitute(match)) // inline code
 | 
					 | 
				
			||||||
		.replace(/(https?:\/\/.*?)(?= |$)/gm, match => substitute(match)) // URL
 | 
					 | 
				
			||||||
		.replace(/:([a-z0-9_+-]+):/gim, match => substitute(match)) // emoji
 | 
					 | 
				
			||||||
		.replace(/#([^\s.,!?'"#:\/\[\]【】]+)/gm, match => substitute(match)) // hashtag
 | 
					 | 
				
			||||||
		.replace(/@\w([\w-]*\w)?(?:@[\w.\-]+\w)?/gm, match => substitute(match)); // mention
 | 
					 | 
				
			||||||
	return [replaced, map];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function replaceExceptions(text: string, map: Record<string, string>): string {
 | 
					 | 
				
			||||||
	for (const rule in map) {
 | 
					 | 
				
			||||||
		if (Object.prototype.hasOwnProperty.call(map, rule)) {
 | 
					 | 
				
			||||||
			text = text.replace(rule, map[rule]);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return text;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ import { id } from '../id';
 | 
				
			||||||
export class ClipNote {
 | 
					export class ClipNote {
 | 
				
			||||||
	@PrimaryColumn(id())
 | 
						@PrimaryColumn(id())
 | 
				
			||||||
	public id: string;
 | 
						public id: string;
 | 
				
			||||||
	
 | 
					
 | 
				
			||||||
	@Index()
 | 
						@Index()
 | 
				
			||||||
	@Column({
 | 
						@Column({
 | 
				
			||||||
		...id(),
 | 
							...id(),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@ export class NoteReaction {
 | 
				
			||||||
	public note: Note | null;
 | 
						public note: Note | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column('varchar', {
 | 
						@Column('varchar', {
 | 
				
			||||||
		length: 128
 | 
							length: 130
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public reaction: string;
 | 
						public reaction: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,13 @@
 | 
				
			||||||
import { EntityRepository, Repository, In } from 'typeorm';
 | 
					import { EntityRepository, Repository, In } from 'typeorm';
 | 
				
			||||||
import { Note } from '../entities/note';
 | 
					import { Note } from '../entities/note';
 | 
				
			||||||
import { User } from '../entities/user';
 | 
					import { User } from '../entities/user';
 | 
				
			||||||
import { nyaize } from '../../misc/nyaize';
 | 
					 | 
				
			||||||
import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
 | 
					import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
 | 
				
			||||||
import { ensure } from '../../prelude/ensure';
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
import { SchemaType } from '../../misc/schema';
 | 
					import { SchemaType } from '../../misc/schema';
 | 
				
			||||||
import { awaitAll } from '../../prelude/await-all';
 | 
					import { awaitAll } from '../../prelude/await-all';
 | 
				
			||||||
import { convertLegacyReaction, convertLegacyReactions } from '../../misc/reaction-lib';
 | 
					import { convertLegacyReaction, convertLegacyReactions } from '../../misc/reaction-lib';
 | 
				
			||||||
 | 
					import { toString } from '../../mfm/toString';
 | 
				
			||||||
 | 
					import { parse } from '../../mfm/parse';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type PackedNote = SchemaType<typeof packedNoteSchema>;
 | 
					export type PackedNote = SchemaType<typeof packedNoteSchema>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -217,7 +218,8 @@ export class NoteRepository extends Repository<Note> {
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (packed.user.isCat && packed.text) {
 | 
							if (packed.user.isCat && packed.text) {
 | 
				
			||||||
			packed.text = nyaize(packed.text);
 | 
								const tokens = packed.text ? parse(packed.text) : [];
 | 
				
			||||||
 | 
								packed.text = toString(tokens, { doNyaize: true });
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!opts.skipHide) {
 | 
							if (!opts.skipHide) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -98,7 +98,7 @@ export class UserRepository extends Repository<User> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public async getHasUnreadAntenna(userId: User['id']): Promise<boolean> {
 | 
						public async getHasUnreadAntenna(userId: User['id']): Promise<boolean> {
 | 
				
			||||||
		const antennas = await Antennas.find({ userId });
 | 
							const antennas = await Antennas.find({ userId });
 | 
				
			||||||
		
 | 
					
 | 
				
			||||||
		const unread = antennas.length > 0 ? await AntennaNotes.findOne({
 | 
							const unread = antennas.length > 0 ? await AntennaNotes.findOne({
 | 
				
			||||||
			antennaId: In(antennas.map(x => x.id)),
 | 
								antennaId: In(antennas.map(x => x.id)),
 | 
				
			||||||
			read: false
 | 
								read: false
 | 
				
			||||||
| 
						 | 
					@ -112,7 +112,7 @@ export class UserRepository extends Repository<User> {
 | 
				
			||||||
			muterId: userId
 | 
								muterId: userId
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
		const mutedUserIds = mute.map(m => m.muteeId);
 | 
							const mutedUserIds = mute.map(m => m.muteeId);
 | 
				
			||||||
	
 | 
					
 | 
				
			||||||
		const count = await Notifications.count({
 | 
							const count = await Notifications.count({
 | 
				
			||||||
			where: {
 | 
								where: {
 | 
				
			||||||
				notifieeId: userId,
 | 
									notifieeId: userId,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,15 @@
 | 
				
			||||||
const dateTimeIntervals = {
 | 
					const dateTimeIntervals = {
 | 
				
			||||||
	'days': 86400000,
 | 
						'day': 86400000,
 | 
				
			||||||
	'hours': 3600000,
 | 
						'hour': 3600000,
 | 
				
			||||||
 | 
						'ms': 1,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DateUTC(time: number[]): Date {
 | 
					export function dateUTC(time: number[]): Date {
 | 
				
			||||||
	const r = new Date(0);
 | 
						return new Date(Date.UTC(...time));
 | 
				
			||||||
	r.setUTCFullYear(time[0], time[1], time[2]);
 | 
					 | 
				
			||||||
	if (time[3]) r.setUTCHours(time[3], ...time.slice(4));
 | 
					 | 
				
			||||||
	return r;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function isTimeSame(a: Date, b: Date): boolean {
 | 
					export function isTimeSame(a: Date, b: Date): boolean {
 | 
				
			||||||
	return (a.getTime() - b.getTime()) === 0;
 | 
						return a.getTime() === b.getTime();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function isTimeBefore(a: Date, b: Date): boolean {
 | 
					export function isTimeBefore(a: Date, b: Date): boolean {
 | 
				
			||||||
| 
						 | 
					@ -22,10 +20,10 @@ export function isTimeAfter(a: Date, b: Date): boolean {
 | 
				
			||||||
	return (a.getTime() - b.getTime()) > 0;
 | 
						return (a.getTime() - b.getTime()) > 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function addTimespan(x: Date, value: number, span: keyof typeof dateTimeIntervals): Date {
 | 
					export function addTime(x: Date, value: number, span: keyof typeof dateTimeIntervals = 'ms'): Date {
 | 
				
			||||||
	return new Date(x.getTime() + (value * dateTimeIntervals[span]));
 | 
						return new Date(x.getTime() + (value * dateTimeIntervals[span]));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function subtractTimespan(x: Date, value: number, span: keyof typeof dateTimeIntervals): Date {
 | 
					export function subtractTime(x: Date, value: number, span: keyof typeof dateTimeIntervals = 'ms'): Date {
 | 
				
			||||||
	return new Date(x.getTime() - (value * dateTimeIntervals[span]));
 | 
						return new Date(x.getTime() - (value * dateTimeIntervals[span]));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,10 +7,10 @@ import config from '../../config';
 | 
				
			||||||
import { ILocalUser } from '../../models/entities/user';
 | 
					import { ILocalUser } from '../../models/entities/user';
 | 
				
			||||||
import { UserKeypairs } from '../../models';
 | 
					import { UserKeypairs } from '../../models';
 | 
				
			||||||
import { ensure } from '../../prelude/ensure';
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
import * as httpsProxyAgent from 'https-proxy-agent';
 | 
					import { HttpsProxyAgent } from 'https-proxy-agent';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const agent = config.proxy
 | 
					const agent = config.proxy
 | 
				
			||||||
	? new httpsProxyAgent(config.proxy)
 | 
						? new HttpsProxyAgent(config.proxy)
 | 
				
			||||||
	: new https.Agent({
 | 
						: new https.Agent({
 | 
				
			||||||
			lookup: cache.lookup,
 | 
								lookup: cache.lookup,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,16 +9,12 @@ import { publishMainStream } from '../../../services/stream';
 | 
				
			||||||
export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) {
 | 
					export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) {
 | 
				
			||||||
	if (redirect) {
 | 
						if (redirect) {
 | 
				
			||||||
		//#region Cookie
 | 
							//#region Cookie
 | 
				
			||||||
		const expires = 1000 * 60 * 60 * 24 * 365; // One Year
 | 
							ctx.cookies.set('igi', user.token, {
 | 
				
			||||||
		ctx.cookies.set('i', user.token, {
 | 
					 | 
				
			||||||
			path: '/',
 | 
								path: '/',
 | 
				
			||||||
			domain: config.hostname,
 | 
					 | 
				
			||||||
			// SEE: https://github.com/koajs/koa/issues/974
 | 
								// SEE: https://github.com/koajs/koa/issues/974
 | 
				
			||||||
			// When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header
 | 
								// When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header
 | 
				
			||||||
			secure: config.url.startsWith('https'),
 | 
								secure: config.url.startsWith('https'),
 | 
				
			||||||
			httpOnly: false,
 | 
								httpOnly: false
 | 
				
			||||||
			expires: new Date(Date.now() + expires),
 | 
					 | 
				
			||||||
			maxAge: expires
 | 
					 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
		//#endregion
 | 
							//#endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,7 +82,7 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
			id: ps.userListId,
 | 
								id: ps.userListId,
 | 
				
			||||||
			userId: user.id,
 | 
								userId: user.id,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	
 | 
					
 | 
				
			||||||
		if (userList == null) {
 | 
							if (userList == null) {
 | 
				
			||||||
			throw new ApiError(meta.errors.noSuchUserList);
 | 
								throw new ApiError(meta.errors.noSuchUserList);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -91,7 +91,7 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
			userGroupId: ps.userGroupId,
 | 
								userGroupId: ps.userGroupId,
 | 
				
			||||||
			userId: user.id,
 | 
								userId: user.id,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	
 | 
					
 | 
				
			||||||
		if (userGroupJoining == null) {
 | 
							if (userGroupJoining == null) {
 | 
				
			||||||
			throw new ApiError(meta.errors.noSuchUserGroup);
 | 
								throw new ApiError(meta.errors.noSuchUserGroup);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,7 +101,7 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
			id: ps.userListId,
 | 
								id: ps.userListId,
 | 
				
			||||||
			userId: user.id,
 | 
								userId: user.id,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	
 | 
					
 | 
				
			||||||
		if (userList == null) {
 | 
							if (userList == null) {
 | 
				
			||||||
			throw new ApiError(meta.errors.noSuchUserList);
 | 
								throw new ApiError(meta.errors.noSuchUserList);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -110,7 +110,7 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
			userGroupId: ps.userGroupId,
 | 
								userGroupId: ps.userGroupId,
 | 
				
			||||||
			userId: user.id,
 | 
								userId: user.id,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	
 | 
					
 | 
				
			||||||
		if (userGroupJoining == null) {
 | 
							if (userGroupJoining == null) {
 | 
				
			||||||
			throw new ApiError(meta.errors.noSuchUserGroup);
 | 
								throw new ApiError(meta.errors.noSuchUserGroup);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,11 +25,16 @@ export const meta = {
 | 
				
			||||||
				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
									'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: convertLog(activeUsersChart.schema),
 | 
						res: convertLog(activeUsersChart.schema),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await activeUsersChart.getChart(ps.span as any, ps.limit!);
 | 
						return await activeUsersChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,11 +25,16 @@ export const meta = {
 | 
				
			||||||
				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
									'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: convertLog(driveChart.schema),
 | 
						res: convertLog(driveChart.schema),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await driveChart.getChart(ps.span as any, ps.limit!);
 | 
						return await driveChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,11 +25,16 @@ export const meta = {
 | 
				
			||||||
				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
									'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: convertLog(federationChart.schema),
 | 
						res: convertLog(federationChart.schema),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await federationChart.getChart(ps.span as any, ps.limit!);
 | 
						return await federationChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,11 @@ export const meta = {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		tag: {
 | 
							tag: {
 | 
				
			||||||
			validator: $.str,
 | 
								validator: $.str,
 | 
				
			||||||
			desc: {
 | 
								desc: {
 | 
				
			||||||
| 
						 | 
					@ -38,5 +43,5 @@ export const meta = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await hashtagChart.getChart(ps.span as any, ps.limit!, ps.tag);
 | 
						return await hashtagChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.tag);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,11 @@ export const meta = {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		host: {
 | 
							host: {
 | 
				
			||||||
			validator: $.str,
 | 
								validator: $.str,
 | 
				
			||||||
			desc: {
 | 
								desc: {
 | 
				
			||||||
| 
						 | 
					@ -39,5 +44,5 @@ export const meta = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await instanceChart.getChart(ps.span as any, ps.limit!, ps.host);
 | 
						return await instanceChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.host);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,11 +25,16 @@ export const meta = {
 | 
				
			||||||
				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
									'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: convertLog(networkChart.schema),
 | 
						res: convertLog(networkChart.schema),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await networkChart.getChart(ps.span as any, ps.limit!);
 | 
						return await networkChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,11 +25,16 @@ export const meta = {
 | 
				
			||||||
				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
									'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: convertLog(notesChart.schema),
 | 
						res: convertLog(notesChart.schema),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await notesChart.getChart(ps.span as any, ps.limit!);
 | 
						return await notesChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,11 @@ export const meta = {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		userId: {
 | 
							userId: {
 | 
				
			||||||
			validator: $.type(ID),
 | 
								validator: $.type(ID),
 | 
				
			||||||
			desc: {
 | 
								desc: {
 | 
				
			||||||
| 
						 | 
					@ -40,5 +45,5 @@ export const meta = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await perUserDriveChart.getChart(ps.span as any, ps.limit!, ps.userId);
 | 
						return await perUserDriveChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,11 @@ export const meta = {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		userId: {
 | 
							userId: {
 | 
				
			||||||
			validator: $.type(ID),
 | 
								validator: $.type(ID),
 | 
				
			||||||
			desc: {
 | 
								desc: {
 | 
				
			||||||
| 
						 | 
					@ -40,5 +45,5 @@ export const meta = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await perUserFollowingChart.getChart(ps.span as any, ps.limit!, ps.userId);
 | 
						return await perUserFollowingChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,11 @@ export const meta = {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		userId: {
 | 
							userId: {
 | 
				
			||||||
			validator: $.type(ID),
 | 
								validator: $.type(ID),
 | 
				
			||||||
			desc: {
 | 
								desc: {
 | 
				
			||||||
| 
						 | 
					@ -40,5 +45,5 @@ export const meta = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await perUserNotesChart.getChart(ps.span as any, ps.limit!, ps.userId);
 | 
						return await perUserNotesChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,11 @@ export const meta = {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		userId: {
 | 
							userId: {
 | 
				
			||||||
			validator: $.type(ID),
 | 
								validator: $.type(ID),
 | 
				
			||||||
			desc: {
 | 
								desc: {
 | 
				
			||||||
| 
						 | 
					@ -40,5 +45,5 @@ export const meta = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await perUserReactionsChart.getChart(ps.span as any, ps.limit!, ps.userId);
 | 
						return await perUserReactionsChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,11 +25,16 @@ export const meta = {
 | 
				
			||||||
				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
									'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: convertLog(usersChart.schema),
 | 
						res: convertLog(usersChart.schema),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps) => {
 | 
					export default define(meta, async (ps) => {
 | 
				
			||||||
	return await usersChart.getChart(ps.span as any, ps.limit!);
 | 
						return await usersChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,9 +60,9 @@ export default define(meta, async () => {
 | 
				
			||||||
		Notes.count({ where: { userHost: null }, cache: 3600000 }),
 | 
							Notes.count({ where: { userHost: null }, cache: 3600000 }),
 | 
				
			||||||
		Users.count({ cache: 3600000 }),
 | 
							Users.count({ cache: 3600000 }),
 | 
				
			||||||
		Users.count({ where: { host: null }, cache: 3600000 }),
 | 
							Users.count({ where: { host: null }, cache: 3600000 }),
 | 
				
			||||||
		federationChart.getChart('hour', 1).then(chart => chart.instance.total[0]),
 | 
							federationChart.getChart('hour', 1, null).then(chart => chart.instance.total[0]),
 | 
				
			||||||
		driveChart.getChart('hour', 1).then(chart => chart.local.totalSize[0]),
 | 
							driveChart.getChart('hour', 1, null).then(chart => chart.local.totalSize[0]),
 | 
				
			||||||
		driveChart.getChart('hour', 1).then(chart => chart.remote.totalSize[0]),
 | 
							driveChart.getChart('hour', 1, null).then(chart => chart.remote.totalSize[0]),
 | 
				
			||||||
	]);
 | 
						]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
import $ from 'cafy';
 | 
					import $ from 'cafy';
 | 
				
			||||||
import define from '../../define';
 | 
					import define from '../../define';
 | 
				
			||||||
import { Users } from '../../../../models';
 | 
					import { Users } from '../../../../models';
 | 
				
			||||||
import { User } from '../../../../models/entities/user';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	desc: {
 | 
						desc: {
 | 
				
			||||||
| 
						 | 
					@ -73,14 +72,17 @@ export default define(meta, async (ps, me) => {
 | 
				
			||||||
			q.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
 | 
								q.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							q.orderBy('user.updatedAt', 'DESC');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const users = await q.take(ps.limit!).skip(ps.offset).getMany();
 | 
							const users = await q.take(ps.limit!).skip(ps.offset).getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return await Users.packMany(users, me, { detail: ps.detail });
 | 
							return await Users.packMany(users, me, { detail: ps.detail });
 | 
				
			||||||
	} else {
 | 
						} else if (ps.username) {
 | 
				
			||||||
		let users = await Users.createQueryBuilder('user')
 | 
							let users = await Users.createQueryBuilder('user')
 | 
				
			||||||
			.where('user.host IS NULL')
 | 
								.where('user.host IS NULL')
 | 
				
			||||||
			.andWhere('user.isSuspended = FALSE')
 | 
								.andWhere('user.isSuspended = FALSE')
 | 
				
			||||||
			.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
 | 
								.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
 | 
				
			||||||
 | 
								.orderBy('user.updatedAt', 'DESC')
 | 
				
			||||||
			.take(ps.limit!)
 | 
								.take(ps.limit!)
 | 
				
			||||||
			.skip(ps.offset)
 | 
								.skip(ps.offset)
 | 
				
			||||||
			.getMany();
 | 
								.getMany();
 | 
				
			||||||
| 
						 | 
					@ -90,6 +92,7 @@ export default define(meta, async (ps, me) => {
 | 
				
			||||||
				.where('user.host IS NOT NULL')
 | 
									.where('user.host IS NOT NULL')
 | 
				
			||||||
				.andWhere('user.isSuspended = FALSE')
 | 
									.andWhere('user.isSuspended = FALSE')
 | 
				
			||||||
				.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
 | 
									.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
 | 
				
			||||||
 | 
									.orderBy('user.updatedAt', 'DESC')
 | 
				
			||||||
				.take(ps.limit! - users.length)
 | 
									.take(ps.limit! - users.length)
 | 
				
			||||||
				.getMany();
 | 
									.getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,6 +74,7 @@ export default define(meta, async (ps, me) => {
 | 
				
			||||||
			.where('user.host IS NULL')
 | 
								.where('user.host IS NULL')
 | 
				
			||||||
			.andWhere('user.isSuspended = FALSE')
 | 
								.andWhere('user.isSuspended = FALSE')
 | 
				
			||||||
			.andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
 | 
								.andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
 | 
				
			||||||
 | 
								.orderBy('user.updatedAt', 'DESC')
 | 
				
			||||||
			.take(ps.limit!)
 | 
								.take(ps.limit!)
 | 
				
			||||||
			.skip(ps.offset)
 | 
								.skip(ps.offset)
 | 
				
			||||||
			.getMany();
 | 
								.getMany();
 | 
				
			||||||
| 
						 | 
					@ -83,6 +84,7 @@ export default define(meta, async (ps, me) => {
 | 
				
			||||||
				.where('user.host IS NOT NULL')
 | 
									.where('user.host IS NOT NULL')
 | 
				
			||||||
				.andWhere('user.isSuspended = FALSE')
 | 
									.andWhere('user.isSuspended = FALSE')
 | 
				
			||||||
				.andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
 | 
									.andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
 | 
				
			||||||
 | 
									.orderBy('user.updatedAt', 'DESC')
 | 
				
			||||||
				.take(ps.limit! - users.length)
 | 
									.take(ps.limit! - users.length)
 | 
				
			||||||
				.getMany();
 | 
									.getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ import { ILocalUser } from '../../../models/entities/user';
 | 
				
			||||||
import { ensure } from '../../../prelude/ensure';
 | 
					import { ensure } from '../../../prelude/ensure';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getUserToken(ctx: Koa.Context) {
 | 
					function getUserToken(ctx: Koa.Context) {
 | 
				
			||||||
	return ((ctx.headers['cookie'] || '').match(/i=(\w+)/) || [null, null])[1];
 | 
						return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function compareOrigin(ctx: Koa.Context) {
 | 
					function compareOrigin(ctx: Koa.Context) {
 | 
				
			||||||
| 
						 | 
					@ -113,14 +113,10 @@ router.get('/signin/discord', async ctx => {
 | 
				
			||||||
		response_type: 'code'
 | 
							response_type: 'code'
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const expires = 1000 * 60 * 60; // 1h
 | 
						ctx.cookies.set('signin_with_discord_sid', sessid, {
 | 
				
			||||||
	ctx.cookies.set('signin_with_discord_session_id', sessid, {
 | 
					 | 
				
			||||||
		path: '/',
 | 
							path: '/',
 | 
				
			||||||
		domain: config.host,
 | 
					 | 
				
			||||||
		secure: config.url.startsWith('https'),
 | 
							secure: config.url.startsWith('https'),
 | 
				
			||||||
		httpOnly: true,
 | 
							httpOnly: true
 | 
				
			||||||
		expires: new Date(Date.now() + expires),
 | 
					 | 
				
			||||||
		maxAge: expires
 | 
					 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	redis.set(sessid, JSON.stringify(params));
 | 
						redis.set(sessid, JSON.stringify(params));
 | 
				
			||||||
| 
						 | 
					@ -135,7 +131,7 @@ router.get('/dc/cb', async ctx => {
 | 
				
			||||||
	const oauth2 = await getOAuth2();
 | 
						const oauth2 = await getOAuth2();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!userToken) {
 | 
						if (!userToken) {
 | 
				
			||||||
		const sessid = ctx.cookies.get('signin_with_discord_session_id');
 | 
							const sessid = ctx.cookies.get('signin_with_discord_sid');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!sessid) {
 | 
							if (!sessid) {
 | 
				
			||||||
			ctx.throw(400, 'invalid session');
 | 
								ctx.throw(400, 'invalid session');
 | 
				
			||||||
| 
						 | 
					@ -199,7 +195,7 @@ router.get('/dc/cb', async ctx => {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const profile = await UserProfiles.createQueryBuilder()
 | 
							const profile = await UserProfiles.createQueryBuilder()
 | 
				
			||||||
			.where('"integrations"->"discord"->"id" = :id', { id: id })
 | 
								.where(`"integrations"->'discord'->>'id' = :id`, { id: id })
 | 
				
			||||||
			.andWhere('"userHost" IS NULL')
 | 
								.andWhere('"userHost" IS NULL')
 | 
				
			||||||
			.getOne();
 | 
								.getOne();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -212,6 +208,7 @@ router.get('/dc/cb', async ctx => {
 | 
				
			||||||
			integrations: {
 | 
								integrations: {
 | 
				
			||||||
				...profile.integrations,
 | 
									...profile.integrations,
 | 
				
			||||||
				discord: {
 | 
									discord: {
 | 
				
			||||||
 | 
										id: id,
 | 
				
			||||||
					accessToken: accessToken,
 | 
										accessToken: accessToken,
 | 
				
			||||||
					refreshToken: refreshToken,
 | 
										refreshToken: refreshToken,
 | 
				
			||||||
					expiresDate: expiresDate,
 | 
										expiresDate: expiresDate,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ import { ILocalUser } from '../../../models/entities/user';
 | 
				
			||||||
import { ensure } from '../../../prelude/ensure';
 | 
					import { ensure } from '../../../prelude/ensure';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getUserToken(ctx: Koa.Context) {
 | 
					function getUserToken(ctx: Koa.Context) {
 | 
				
			||||||
	return ((ctx.headers['cookie'] || '').match(/i=(\w+)/) || [null, null])[1];
 | 
						return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function compareOrigin(ctx: Koa.Context) {
 | 
					function compareOrigin(ctx: Koa.Context) {
 | 
				
			||||||
| 
						 | 
					@ -111,14 +111,10 @@ router.get('/signin/github', async ctx => {
 | 
				
			||||||
		state: uuid()
 | 
							state: uuid()
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const expires = 1000 * 60 * 60; // 1h
 | 
						ctx.cookies.set('signin_with_github_sid', sessid, {
 | 
				
			||||||
	ctx.cookies.set('signin_with_github_session_id', sessid, {
 | 
					 | 
				
			||||||
		path: '/',
 | 
							path: '/',
 | 
				
			||||||
		domain: config.host,
 | 
					 | 
				
			||||||
		secure: config.url.startsWith('https'),
 | 
							secure: config.url.startsWith('https'),
 | 
				
			||||||
		httpOnly: true,
 | 
							httpOnly: true
 | 
				
			||||||
		expires: new Date(Date.now() + expires),
 | 
					 | 
				
			||||||
		maxAge: expires
 | 
					 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	redis.set(sessid, JSON.stringify(params));
 | 
						redis.set(sessid, JSON.stringify(params));
 | 
				
			||||||
| 
						 | 
					@ -133,7 +129,7 @@ router.get('/gh/cb', async ctx => {
 | 
				
			||||||
	const oauth2 = await getOath2();
 | 
						const oauth2 = await getOath2();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!userToken) {
 | 
						if (!userToken) {
 | 
				
			||||||
		const sessid = ctx.cookies.get('signin_with_github_session_id');
 | 
							const sessid = ctx.cookies.get('signin_with_github_sid');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!sessid) {
 | 
							if (!sessid) {
 | 
				
			||||||
			ctx.throw(400, 'invalid session');
 | 
								ctx.throw(400, 'invalid session');
 | 
				
			||||||
| 
						 | 
					@ -192,7 +188,7 @@ router.get('/gh/cb', async ctx => {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const link = await UserProfiles.createQueryBuilder()
 | 
							const link = await UserProfiles.createQueryBuilder()
 | 
				
			||||||
			.where('"integrations"->"github"->"id" = :id', { id: id })
 | 
								.where(`"integrations"->'github'->>'id' = :id`, { id: id })
 | 
				
			||||||
			.andWhere('"userHost" IS NULL')
 | 
								.andWhere('"userHost" IS NULL')
 | 
				
			||||||
			.getOne();
 | 
								.getOne();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ import { ILocalUser } from '../../../models/entities/user';
 | 
				
			||||||
import { ensure } from '../../../prelude/ensure';
 | 
					import { ensure } from '../../../prelude/ensure';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getUserToken(ctx: Koa.Context) {
 | 
					function getUserToken(ctx: Koa.Context) {
 | 
				
			||||||
	return ((ctx.headers['cookie'] || '').match(/i=(\w+)/) || [null, null])[1];
 | 
						return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function compareOrigin(ctx: Koa.Context) {
 | 
					function compareOrigin(ctx: Koa.Context) {
 | 
				
			||||||
| 
						 | 
					@ -102,14 +102,10 @@ router.get('/signin/twitter', async ctx => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	redis.set(sessid, JSON.stringify(twCtx));
 | 
						redis.set(sessid, JSON.stringify(twCtx));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const expires = 1000 * 60 * 60; // 1h
 | 
						ctx.cookies.set('signin_with_twitter_sid', sessid, {
 | 
				
			||||||
	ctx.cookies.set('signin_with_twitter_session_id', sessid, {
 | 
					 | 
				
			||||||
		path: '/',
 | 
							path: '/',
 | 
				
			||||||
		domain: config.host,
 | 
					 | 
				
			||||||
		secure: config.url.startsWith('https'),
 | 
							secure: config.url.startsWith('https'),
 | 
				
			||||||
		httpOnly: true,
 | 
							httpOnly: true
 | 
				
			||||||
		expires: new Date(Date.now() + expires),
 | 
					 | 
				
			||||||
		maxAge: expires
 | 
					 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.redirect(twCtx.url);
 | 
						ctx.redirect(twCtx.url);
 | 
				
			||||||
| 
						 | 
					@ -121,7 +117,7 @@ router.get('/tw/cb', async ctx => {
 | 
				
			||||||
	const twAuth = await getTwAuth();
 | 
						const twAuth = await getTwAuth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (userToken == null) {
 | 
						if (userToken == null) {
 | 
				
			||||||
		const sessid = ctx.cookies.get('signin_with_twitter_session_id');
 | 
							const sessid = ctx.cookies.get('signin_with_twitter_sid');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (sessid == null) {
 | 
							if (sessid == null) {
 | 
				
			||||||
			ctx.throw(400, 'invalid session');
 | 
								ctx.throw(400, 'invalid session');
 | 
				
			||||||
| 
						 | 
					@ -139,7 +135,7 @@ router.get('/tw/cb', async ctx => {
 | 
				
			||||||
		const result = await twAuth!.done(JSON.parse(twCtx), ctx.query.oauth_verifier);
 | 
							const result = await twAuth!.done(JSON.parse(twCtx), ctx.query.oauth_verifier);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const link = await UserProfiles.createQueryBuilder()
 | 
							const link = await UserProfiles.createQueryBuilder()
 | 
				
			||||||
			.where('"integrations"->"twitter"->"userId" = :id', { id: result.userId })
 | 
								.where(`"integrations"->'twitter'->>'userId' = :id`, { id: result.userId })
 | 
				
			||||||
			.andWhere('"userHost" IS NULL')
 | 
								.andWhere('"userHost" IS NULL')
 | 
				
			||||||
			.getOne();
 | 
								.getOne();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import autobind from 'autobind-decorator';
 | 
					import autobind from 'autobind-decorator';
 | 
				
			||||||
import Channel from '../channel';
 | 
					import Channel from '../channel';
 | 
				
			||||||
import { Mutings, Notes } from '../../../../models';
 | 
					import { Notes } from '../../../../models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class extends Channel {
 | 
					export default class extends Channel {
 | 
				
			||||||
	public readonly chName = 'main';
 | 
						public readonly chName = 'main';
 | 
				
			||||||
| 
						 | 
					@ -9,15 +9,14 @@ export default class extends Channel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async init(params: any) {
 | 
						public async init(params: any) {
 | 
				
			||||||
		const mute = await Mutings.find({ muterId: this.user!.id });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Subscribe main stream channel
 | 
							// Subscribe main stream channel
 | 
				
			||||||
		this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
 | 
							this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
 | 
				
			||||||
			let { type, body } = data;
 | 
								const { type } = data;
 | 
				
			||||||
 | 
								let { body } = data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			switch (type) {
 | 
								switch (type) {
 | 
				
			||||||
				case 'notification': {
 | 
									case 'notification': {
 | 
				
			||||||
					if (mute.map(m => m.muteeId).includes(body.userId)) return;
 | 
										if (this.muting.includes(body.userId)) return;
 | 
				
			||||||
					if (body.note && body.note.isHidden) {
 | 
										if (body.note && body.note.isHidden) {
 | 
				
			||||||
						body.note = await Notes.pack(body.note.id, this.user, {
 | 
											body.note = await Notes.pack(body.note.id, this.user, {
 | 
				
			||||||
							detail: true
 | 
												detail: true
 | 
				
			||||||
| 
						 | 
					@ -26,7 +25,7 @@ export default class extends Channel {
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				case 'mention': {
 | 
									case 'mention': {
 | 
				
			||||||
					if (mute.map(m => m.muteeId).includes(body.userId)) return;
 | 
										if (this.muting.includes(body.userId)) return;
 | 
				
			||||||
					if (body.isHidden) {
 | 
										if (body.isHidden) {
 | 
				
			||||||
						body = await Notes.pack(body.id, this.user, {
 | 
											body = await Notes.pack(body.id, this.user, {
 | 
				
			||||||
							detail: true
 | 
												detail: true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,7 +38,7 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: U
 | 
				
			||||||
		if (note.renoteId != null) {
 | 
							if (note.renoteId != null) {
 | 
				
			||||||
			_note.renote = await Notes.findOne(note.renoteId).then(ensure);
 | 
								_note.renote = await Notes.findOne(note.renoteId).then(ensure);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
					
 | 
				
			||||||
		if (shouldMuteThisNote(_note, mutings.map(x => x.muteeId))) {
 | 
							if (shouldMuteThisNote(_note, mutings.map(x => x.muteeId))) {
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,9 +8,9 @@ import * as nestedProperty from 'nested-property';
 | 
				
			||||||
import autobind from 'autobind-decorator';
 | 
					import autobind from 'autobind-decorator';
 | 
				
			||||||
import Logger from '../logger';
 | 
					import Logger from '../logger';
 | 
				
			||||||
import { Schema } from '../../misc/schema';
 | 
					import { Schema } from '../../misc/schema';
 | 
				
			||||||
import { EntitySchema, getRepository, Repository, LessThan, MoreThanOrEqual } from 'typeorm';
 | 
					import { EntitySchema, getRepository, Repository, LessThan, Between } from 'typeorm';
 | 
				
			||||||
import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error';
 | 
					import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '../../prelude/time';
 | 
				
			||||||
import { DateUTC, isTimeSame, isTimeBefore, subtractTimespan } from '../../prelude/time';
 | 
					import { getChartInsertLock } from '../../misc/app-lock';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logger = new Logger('chart', 'white', process.env.NODE_ENV !== 'test');
 | 
					const logger = new Logger('chart', 'white', process.env.NODE_ENV !== 'test');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -133,6 +133,24 @@ export default abstract class Chart<T extends Record<string, any>> {
 | 
				
			||||||
		return Math.floor(x.getTime() / 1000);
 | 
							return Math.floor(x.getTime() / 1000);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						private static parseDate(date: Date): [number, number, number, number, number, number, number] {
 | 
				
			||||||
 | 
							const y = date.getUTCFullYear();
 | 
				
			||||||
 | 
							const m = date.getUTCMonth();
 | 
				
			||||||
 | 
							const d = date.getUTCDate();
 | 
				
			||||||
 | 
							const h = date.getUTCHours();
 | 
				
			||||||
 | 
							const _m = date.getUTCMinutes();
 | 
				
			||||||
 | 
							const _s = date.getUTCSeconds();
 | 
				
			||||||
 | 
							const _ms = date.getUTCMilliseconds();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return [y, m, d, h, _m, _s, _ms];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						private static getCurrentDate() {
 | 
				
			||||||
 | 
							return Chart.parseDate(new Date());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public static schemaToEntity(name: string, schema: Schema): EntitySchema {
 | 
						public static schemaToEntity(name: string, schema: Schema): EntitySchema {
 | 
				
			||||||
		return new EntitySchema({
 | 
							return new EntitySchema({
 | 
				
			||||||
| 
						 | 
					@ -211,18 +229,6 @@ export default abstract class Chart<T extends Record<string, any>> {
 | 
				
			||||||
		return log as T;
 | 
							return log as T;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	private getCurrentDate(): [number, number, number, number] {
 | 
					 | 
				
			||||||
		const now = new Date();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const y = now.getUTCFullYear();
 | 
					 | 
				
			||||||
		const m = now.getUTCMonth();
 | 
					 | 
				
			||||||
		const d = now.getUTCDate();
 | 
					 | 
				
			||||||
		const h = now.getUTCHours();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return [y, m, d, h];
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	private getLatestLog(span: Span, group: string | null = null): Promise<Log | null> {
 | 
						private getLatestLog(span: Span, group: string | null = null): Promise<Log | null> {
 | 
				
			||||||
		return this.repository.findOne({
 | 
							return this.repository.findOne({
 | 
				
			||||||
| 
						 | 
					@ -237,11 +243,11 @@ export default abstract class Chart<T extends Record<string, any>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	private async getCurrentLog(span: Span, group: string | null = null): Promise<Log> {
 | 
						private async getCurrentLog(span: Span, group: string | null = null): Promise<Log> {
 | 
				
			||||||
		const [y, m, d, h] = this.getCurrentDate();
 | 
							const [y, m, d, h] = Chart.getCurrentDate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const current =
 | 
							const current =
 | 
				
			||||||
			span == 'day' ? DateUTC([y, m, d]) :
 | 
								span == 'day' ? dateUTC([y, m, d, 0]) :
 | 
				
			||||||
			span == 'hour' ? DateUTC([y, m, d, h]) :
 | 
								span == 'hour' ? dateUTC([y, m, d, h]) :
 | 
				
			||||||
			null as never;
 | 
								null as never;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 現在(今日または今のHour)のログ
 | 
							// 現在(今日または今のHour)のログ
 | 
				
			||||||
| 
						 | 
					@ -283,30 +289,35 @@ export default abstract class Chart<T extends Record<string, any>> {
 | 
				
			||||||
			logger.info(`${this.name + (group ? `:${group}` : '')} (${span}): Initial commit created`);
 | 
								logger.info(`${this.name + (group ? `:${group}` : '')} (${span}): Initial commit created`);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const date = Chart.dateToTimestamp(current);
 | 
				
			||||||
 | 
							const lockKey = `${this.name}:${date}:${group}:${span}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const unlock = await getChartInsertLock(lockKey);
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
 | 
								// ロック内でもう1回チェックする
 | 
				
			||||||
 | 
								const currentLog = await this.repository.findOne({
 | 
				
			||||||
 | 
									span: span,
 | 
				
			||||||
 | 
									date: date,
 | 
				
			||||||
 | 
									...(group ? { group: group } : {})
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// ログがあればそれを返して終了
 | 
				
			||||||
 | 
								if (currentLog != null) return currentLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// 新規ログ挿入
 | 
								// 新規ログ挿入
 | 
				
			||||||
			log = await this.repository.save({
 | 
								log = await this.repository.save({
 | 
				
			||||||
				group: group,
 | 
									group: group,
 | 
				
			||||||
				span: span,
 | 
									span: span,
 | 
				
			||||||
				date: Chart.dateToTimestamp(current),
 | 
									date: date,
 | 
				
			||||||
				...Chart.convertObjectToFlattenColumns(data)
 | 
									...Chart.convertObjectToFlattenColumns(data)
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			logger.info(`${this.name + (group ? `:${group}` : '')} (${span}): New commit created`);
 | 
								logger.info(`${this.name + (group ? `:${group}` : '')} (${span}): New commit created`);
 | 
				
			||||||
		} catch (e) {
 | 
					 | 
				
			||||||
			// duplicate key error
 | 
					 | 
				
			||||||
			// 並列動作している他のチャートエンジンプロセスと処理が重なる場合がある
 | 
					 | 
				
			||||||
			// その場合は再度最も新しいログを持ってくる
 | 
					 | 
				
			||||||
			if (isDuplicateKeyValueError(e)) {
 | 
					 | 
				
			||||||
				log = await this.getLatestLog(span, group) as Log;
 | 
					 | 
				
			||||||
				logger.info(`${this.name + (group ? `:${group}` : '')} (${span}): Commit duplicated`);
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				logger.error(e);
 | 
					 | 
				
			||||||
				throw e;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return log;
 | 
								return log;
 | 
				
			||||||
 | 
							} finally {
 | 
				
			||||||
 | 
								unlock();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
| 
						 | 
					@ -373,12 +384,15 @@ export default abstract class Chart<T extends Record<string, any>> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async getChart(span: Span, range: number, group: string | null = null): Promise<ArrayValue<T>> {
 | 
						public async getChart(span: Span, amount: number, begin: Date | null, group: string | null = null): Promise<ArrayValue<T>> {
 | 
				
			||||||
		const [y, m, d, h] = this.getCurrentDate();
 | 
							const [y, m, d, h, _m, _s, _ms] = begin ? Chart.parseDate(subtractTime(addTime(begin, 1, span), 1)) : Chart.getCurrentDate();
 | 
				
			||||||
 | 
							const [y2, m2, d2, h2] = begin ? Chart.parseDate(addTime(begin, 1, span)) : [] as never;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const lt = dateUTC([y, m, d, h, _m, _s, _ms]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const gt =
 | 
							const gt =
 | 
				
			||||||
			span == 'day' ? subtractTimespan(DateUTC([y, m, d]), range, 'days') :
 | 
								span === 'day' ? subtractTime(begin ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), amount - 1, 'day') :
 | 
				
			||||||
			span == 'hour' ? subtractTimespan(DateUTC([y, m, d, h]), range, 'hours') :
 | 
								span === 'hour' ? subtractTime(begin ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), amount - 1, 'hour') :
 | 
				
			||||||
			null as never;
 | 
								null as never;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// ログ取得
 | 
							// ログ取得
 | 
				
			||||||
| 
						 | 
					@ -386,7 +400,7 @@ export default abstract class Chart<T extends Record<string, any>> {
 | 
				
			||||||
			where: {
 | 
								where: {
 | 
				
			||||||
				group: group,
 | 
									group: group,
 | 
				
			||||||
				span: span,
 | 
									span: span,
 | 
				
			||||||
				date: MoreThanOrEqual(Chart.dateToTimestamp(gt))
 | 
									date: Between(Chart.dateToTimestamp(gt), Chart.dateToTimestamp(lt))
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			order: {
 | 
								order: {
 | 
				
			||||||
				date: -1
 | 
									date: -1
 | 
				
			||||||
| 
						 | 
					@ -432,10 +446,10 @@ export default abstract class Chart<T extends Record<string, any>> {
 | 
				
			||||||
		const chart: T[] = [];
 | 
							const chart: T[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 整形
 | 
							// 整形
 | 
				
			||||||
		for (let i = (range - 1); i >= 0; i--) {
 | 
							for (let i = (amount - 1); i >= 0; i--) {
 | 
				
			||||||
			const current =
 | 
								const current =
 | 
				
			||||||
				span == 'day' ? subtractTimespan(DateUTC([y, m, d]), i, 'days') :
 | 
									span === 'day' ? subtractTime(dateUTC([y, m, d, 0]), i, 'day') :
 | 
				
			||||||
				span == 'hour' ? subtractTimespan(DateUTC([y, m, d, h]), i, 'hours') :
 | 
									span === 'hour' ? subtractTime(dateUTC([y, m, d, h]), i, 'hour') :
 | 
				
			||||||
				null as never;
 | 
									null as never;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const log = logs.find(l => isTimeSame(new Date(l.date * 1000), current));
 | 
								const log = logs.find(l => isTimeSame(new Date(l.date * 1000), current));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -217,7 +217,8 @@ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const upload = s3.upload(params);
 | 
						const upload = s3.upload(params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await upload.promise();
 | 
						const result = await upload.promise();
 | 
				
			||||||
 | 
						if (result) logger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function deleteOldFile(user: IRemoteUser) {
 | 
					async function deleteOldFile(user: IRemoteUser) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +1,23 @@
 | 
				
			||||||
import * as S3 from 'aws-sdk/clients/s3';
 | 
					import * as S3 from 'aws-sdk/clients/s3';
 | 
				
			||||||
import config from '../../config';
 | 
					import config from '../../config';
 | 
				
			||||||
import { Meta } from '../../models/entities/meta';
 | 
					import { Meta } from '../../models/entities/meta';
 | 
				
			||||||
import * as httpsProxyAgent from 'https-proxy-agent';
 | 
					import { HttpsProxyAgent } from 'https-proxy-agent';
 | 
				
			||||||
import * as agentkeepalive from 'agentkeepalive';
 | 
					import * as agentkeepalive from 'agentkeepalive';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const httpsAgent = config.proxy
 | 
					const httpsAgent = config.proxy
 | 
				
			||||||
	? new httpsProxyAgent(config.proxy)
 | 
						? new HttpsProxyAgent(config.proxy)
 | 
				
			||||||
	: new agentkeepalive.HttpsAgent({
 | 
						: new agentkeepalive.HttpsAgent({
 | 
				
			||||||
			freeSocketTimeout: 30 * 1000
 | 
								freeSocketTimeout: 30 * 1000
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getS3(meta: Meta) {
 | 
					export function getS3(meta: Meta) {
 | 
				
			||||||
	const conf = {
 | 
						const conf = {
 | 
				
			||||||
		endpoint: meta.objectStorageEndpoint,
 | 
							endpoint: meta.objectStorageEndpoint || undefined,
 | 
				
			||||||
		accessKeyId: meta.objectStorageAccessKey,
 | 
							accessKeyId: meta.objectStorageAccessKey,
 | 
				
			||||||
		secretAccessKey: meta.objectStorageSecretKey,
 | 
							secretAccessKey: meta.objectStorageSecretKey,
 | 
				
			||||||
		region: meta.objectStorageRegion,
 | 
							region: meta.objectStorageRegion || undefined,
 | 
				
			||||||
		sslEnabled: meta.objectStorageUseSSL,
 | 
							sslEnabled: meta.objectStorageUseSSL,
 | 
				
			||||||
		s3ForcePathStyle: true,
 | 
							s3ForcePathStyle: !!meta.objectStorageEndpoint,
 | 
				
			||||||
		httpOptions: {
 | 
							httpOptions: {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} as S3.ClientConfiguration;
 | 
						} as S3.ClientConfiguration;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,7 @@ import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-erro
 | 
				
			||||||
import { ensure } from '../../prelude/ensure';
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
import { checkHitAntenna } from '../../misc/check-hit-antenna';
 | 
					import { checkHitAntenna } from '../../misc/check-hit-antenna';
 | 
				
			||||||
import { addNoteToAntenna } from '../add-note-to-antenna';
 | 
					import { addNoteToAntenna } from '../add-note-to-antenna';
 | 
				
			||||||
 | 
					import { countSameRenotes } from '../../misc/count-same-renotes';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 | 
					type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -222,7 +223,7 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
 | 
				
			||||||
			.getMany();
 | 
								.getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const followers = followings.map(f => f.followerId);
 | 
							const followers = followings.map(f => f.followerId);
 | 
				
			||||||
		
 | 
					
 | 
				
			||||||
		for (const antenna of antennas) {
 | 
							for (const antenna of antennas) {
 | 
				
			||||||
			checkHitAntenna(antenna, note, user, followers).then(hit => {
 | 
								checkHitAntenna(antenna, note, user, followers).then(hit => {
 | 
				
			||||||
				if (hit) {
 | 
									if (hit) {
 | 
				
			||||||
| 
						 | 
					@ -236,7 +237,8 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
 | 
				
			||||||
		saveReply(data.reply, note);
 | 
							saveReply(data.reply, note);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (data.renote) {
 | 
						// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
 | 
				
			||||||
 | 
						if (data.renote && (await countSameRenotes(user.id, data.renote.id, note.id) === 0)) {
 | 
				
			||||||
		incRenoteCount(data.renote);
 | 
							incRenoteCount(data.renote);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import { Note } from '../../models/entities/note';
 | 
				
			||||||
import { Notes, Users, Instances } from '../../models';
 | 
					import { Notes, Users, Instances } from '../../models';
 | 
				
			||||||
import { notesChart, perUserNotesChart, instanceChart } from '../chart';
 | 
					import { notesChart, perUserNotesChart, instanceChart } from '../chart';
 | 
				
			||||||
import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
 | 
					import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
 | 
				
			||||||
 | 
					import { countSameRenotes } from '../../misc/count-same-renotes';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 投稿を削除します。
 | 
					 * 投稿を削除します。
 | 
				
			||||||
| 
						 | 
					@ -20,7 +21,8 @@ import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
 | 
				
			||||||
export default async function(user: User, note: Note, quiet = false) {
 | 
					export default async function(user: User, note: Note, quiet = false) {
 | 
				
			||||||
	const deletedAt = new Date();
 | 
						const deletedAt = new Date();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (note.renoteId) {
 | 
						// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
 | 
				
			||||||
 | 
						if (note.renoteId && (await countSameRenotes(user.id, note.renoteId, note.id)) === 0) {
 | 
				
			||||||
		Notes.decrement({ id: note.renoteId }, 'renoteCount', 1);
 | 
							Notes.decrement({ id: note.renoteId }, 'renoteCount', 1);
 | 
				
			||||||
		Notes.decrement({ id: note.renoteId }, 'score', 1);
 | 
							Notes.decrement({ id: note.renoteId }, 'score', 1);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,14 +2,10 @@
 | 
				
			||||||
 * Tests of API (visibility)
 | 
					 * Tests of API (visibility)
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * How to run the tests:
 | 
					 * How to run the tests:
 | 
				
			||||||
 * > npx mocha test/api-visibility.ts --require ts-node/register
 | 
					 * > npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true npx mocha test/api-visibility.ts --require ts-node/register
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * To specify test:
 | 
					 * To specify test:
 | 
				
			||||||
 * > npx mocha test/api-visibility.ts --require ts-node/register -g 'test name'
 | 
					 * > npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true npx mocha test/api-visibility.ts --require ts-node/register -g 'test name'
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * If the tests not start, try set following enviroment variables:
 | 
					 | 
				
			||||||
 * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true
 | 
					 | 
				
			||||||
 * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
process.env.NODE_ENV = 'test';
 | 
					process.env.NODE_ENV = 'test';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,14 +2,10 @@
 | 
				
			||||||
 * Tests of API
 | 
					 * Tests of API
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * How to run the tests:
 | 
					 * How to run the tests:
 | 
				
			||||||
 * > npx mocha test/api.ts --require ts-node/register
 | 
					 * > npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true npx mocha test/api.ts --require ts-node/register
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * To specify test:
 | 
					 * To specify test:
 | 
				
			||||||
 * > npx mocha test/api.ts --require ts-node/register -g 'test name'
 | 
					 * > npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true npx mocha test/api.ts --require ts-node/register -g 'test name'
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * If the tests not start, try set following enviroment variables:
 | 
					 | 
				
			||||||
 * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true
 | 
					 | 
				
			||||||
 * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
process.env.NODE_ENV = 'test';
 | 
					process.env.NODE_ENV = 'test';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue