Merge remote-tracking branch 'misskey/develop' into future-2024-04-10
This commit is contained in:
		
						commit
						a3b4ca782a
					
				
					 78 changed files with 3068 additions and 2243 deletions
				
			
		
							
								
								
									
										14
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
					@ -18,6 +18,13 @@
 | 
				
			||||||
  - 実装の都合により、プラグインは1つエラーを起こした時に即時停止するようになりました
 | 
					  - 実装の都合により、プラグインは1つエラーを起こした時に即時停止するようになりました
 | 
				
			||||||
- Enhance: ページのデザインを変更
 | 
					- Enhance: ページのデザインを変更
 | 
				
			||||||
- Enhance: 2要素認証(ワンタイムパスワード)の入力欄を改善
 | 
					- Enhance: 2要素認証(ワンタイムパスワード)の入力欄を改善
 | 
				
			||||||
 | 
					- Enhance: 「今日誕生日のフォロー中ユーザー」ウィジェットを手動でリロードできるように
 | 
				
			||||||
 | 
					- Enhance: 映像・音声の再生にブラウザのネイティブプレイヤーを使用できるように
 | 
				
			||||||
 | 
					- Enhance: 映像・音声の再生メニューに「再生速度」「ループ再生」「ピクチャインピクチャ」を追加
 | 
				
			||||||
 | 
					- Enhance: 映像・音声の再生にキーボードショートカットが使えるように
 | 
				
			||||||
 | 
					- Enhance: ノートについているリアクションの「もっと!」から、リアクションの一覧を表示できるように
 | 
				
			||||||
 | 
					- Enhance: リプライにて引用がある場合テキストが空でもノートできるように
 | 
				
			||||||
 | 
					  - 引用したいノートのURLをコピーしリプライ投稿画面にペーストして添付することで達成できます
 | 
				
			||||||
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
 | 
					- Fix: 一部のページ内リンクが正しく動作しない問題を修正
 | 
				
			||||||
- Fix: 周年の実績が閏年を考慮しない問題を修正
 | 
					- Fix: 周年の実績が閏年を考慮しない問題を修正
 | 
				
			||||||
- Fix: ローカルURLのプレビューポップアップが左上に表示される
 | 
					- Fix: ローカルURLのプレビューポップアップが左上に表示される
 | 
				
			||||||
| 
						 | 
					@ -27,12 +34,19 @@
 | 
				
			||||||
  (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/528)
 | 
					  (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/528)
 | 
				
			||||||
- Fix: コードブロックのシンタックスハイライトで使用される定義ファイルをCDNから取得するように #13177
 | 
					- Fix: コードブロックのシンタックスハイライトで使用される定義ファイルをCDNから取得するように #13177
 | 
				
			||||||
  - CDNから取得せずMisskey本体にバンドルする場合は`pacakges/frontend/vite.config.ts`を修正してください。
 | 
					  - CDNから取得せずMisskey本体にバンドルする場合は`pacakges/frontend/vite.config.ts`を修正してください。
 | 
				
			||||||
 | 
					- Fix: タイムゾーンによっては、「今日誕生日のフォロー中ユーザー」ウィジェットが正しく動作しない問題を修正
 | 
				
			||||||
 | 
					- Fix: CWのみの引用リノートが詳細ページで純粋なリノートとして誤って扱われてしまう問題を修正
 | 
				
			||||||
 | 
					- Fix: ノート詳細ページにおいてCW付き引用リノートのCWボタンのラベルに「引用」が含まれていない問題を修正
 | 
				
			||||||
 | 
					- Fix: ダイアログの入力で字数制限に違反していてもEnterキーが押せてしまう問題を修正
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Server
 | 
					### Server
 | 
				
			||||||
- Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに
 | 
					- Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに
 | 
				
			||||||
- Enhance: misskey-dev/summaly@5.1.0の取り込み(プレビュー生成処理の効率化)
 | 
					- Enhance: misskey-dev/summaly@5.1.0の取り込み(プレビュー生成処理の効率化)
 | 
				
			||||||
- Fix: フォローリクエストを作成する際に既存のものは削除するように  
 | 
					- Fix: フォローリクエストを作成する際に既存のものは削除するように  
 | 
				
			||||||
  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/440)
 | 
					  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/440)
 | 
				
			||||||
 | 
					- Fix: エンドポイント`notes/translate`のエラーを改善
 | 
				
			||||||
 | 
					- Fix: CleanRemoteFilesProcessorService report progress from 100% (#13632)
 | 
				
			||||||
 | 
					- Fix: 一部の音声ファイルが映像ファイルとして扱われる問題を修正
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024.3.1
 | 
					## 2024.3.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										40
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -5125,6 +5125,14 @@ export interface Locale extends ILocale {
 | 
				
			||||||
     * バックアップコードを使う
 | 
					     * バックアップコードを使う
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    "useBackupCode": string;
 | 
					    "useBackupCode": string;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * アプリを起動
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    "launchApp": string;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 動画・音声の再生にブラウザのUIを使用する
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    "useNativeUIForVideoAudioPlayer": string;
 | 
				
			||||||
    "_bubbleGame": {
 | 
					    "_bubbleGame": {
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * 遊び方
 | 
					         * 遊び方
 | 
				
			||||||
| 
						 | 
					@ -7767,13 +7775,9 @@ export interface Locale extends ILocale {
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        "step1": ParameterizedString<"a" | "b">;
 | 
					        "step1": ParameterizedString<"a" | "b">;
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * 次に、表示されているQRコードをアプリでスキャンします。
 | 
					         * 次に、表示されているQRコードをアプリでスキャンするか、ボタンをクリックして端末上でアプリを開きます。
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        "step2": string;
 | 
					        "step2": string;
 | 
				
			||||||
        /**
 | 
					 | 
				
			||||||
         * QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。
 | 
					 | 
				
			||||||
         */
 | 
					 | 
				
			||||||
        "step2Click": string;
 | 
					 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * デスクトップアプリを使用する場合は次のURIを入力します
 | 
					         * デスクトップアプリを使用する場合は次のURIを入力します
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
| 
						 | 
					@ -7866,6 +7870,10 @@ export interface Locale extends ILocale {
 | 
				
			||||||
         * バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。
 | 
					         * バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        "backupCodesExhaustedWarning": string;
 | 
					        "backupCodesExhaustedWarning": string;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * 詳細なガイドはこちら
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        "moreDetailedGuideHere": string;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    "_permissions": {
 | 
					    "_permissions": {
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
| 
						 | 
					@ -9079,6 +9087,14 @@ export interface Locale extends ILocale {
 | 
				
			||||||
             * ボタン
 | 
					             * ボタン
 | 
				
			||||||
             */
 | 
					             */
 | 
				
			||||||
            "button": string;
 | 
					            "button": string;
 | 
				
			||||||
 | 
					            /**
 | 
				
			||||||
 | 
					             * 動的ブロック
 | 
				
			||||||
 | 
					             */
 | 
				
			||||||
 | 
					            "dynamic": string;
 | 
				
			||||||
 | 
					            /**
 | 
				
			||||||
 | 
					             * このブロックは廃止されています。今後は{play}を利用してください。
 | 
				
			||||||
 | 
					             */
 | 
				
			||||||
 | 
					            "dynamicDescription": ParameterizedString<"play">;
 | 
				
			||||||
            /**
 | 
					            /**
 | 
				
			||||||
             * ノート埋め込み
 | 
					             * ノート埋め込み
 | 
				
			||||||
             */
 | 
					             */
 | 
				
			||||||
| 
						 | 
					@ -10133,6 +10149,20 @@ export interface Locale extends ILocale {
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        "summaryProxyDescription2": string;
 | 
					        "summaryProxyDescription2": string;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    "_mediaControls": {
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * ピクチャインピクチャ
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        "pip": string;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * 再生速度
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        "playbackRate": string;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * ループ再生
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        "loop": string;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
declare const locales: {
 | 
					declare const locales: {
 | 
				
			||||||
    [lang: string]: Locale;
 | 
					    [lang: string]: Locale;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1277,6 +1277,8 @@ gameRetry: "リトライ"
 | 
				
			||||||
notUsePleaseLeaveBlank: "使用しない場合は空欄にしてください"
 | 
					notUsePleaseLeaveBlank: "使用しない場合は空欄にしてください"
 | 
				
			||||||
useTotp: "ワンタイムパスワードを使う"
 | 
					useTotp: "ワンタイムパスワードを使う"
 | 
				
			||||||
useBackupCode: "バックアップコードを使う"
 | 
					useBackupCode: "バックアップコードを使う"
 | 
				
			||||||
 | 
					launchApp: "アプリを起動"
 | 
				
			||||||
 | 
					useNativeUIForVideoAudioPlayer: "動画・音声の再生にブラウザのUIを使用する"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_bubbleGame:
 | 
					_bubbleGame:
 | 
				
			||||||
  howToPlay: "遊び方"
 | 
					  howToPlay: "遊び方"
 | 
				
			||||||
| 
						 | 
					@ -2039,8 +2041,7 @@ _2fa:
 | 
				
			||||||
  alreadyRegistered: "既に設定は完了しています。"
 | 
					  alreadyRegistered: "既に設定は完了しています。"
 | 
				
			||||||
  registerTOTP: "認証アプリの設定を開始"
 | 
					  registerTOTP: "認証アプリの設定を開始"
 | 
				
			||||||
  step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
 | 
					  step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
 | 
				
			||||||
  step2: "次に、表示されているQRコードをアプリでスキャンします。"
 | 
					  step2: "次に、表示されているQRコードをアプリでスキャンするか、ボタンをクリックして端末上でアプリを開きます。"
 | 
				
			||||||
  step2Click: "QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。"
 | 
					 | 
				
			||||||
  step2Uri: "デスクトップアプリを使用する場合は次のURIを入力します"
 | 
					  step2Uri: "デスクトップアプリを使用する場合は次のURIを入力します"
 | 
				
			||||||
  step3Title: "確認コードを入力"
 | 
					  step3Title: "確認コードを入力"
 | 
				
			||||||
  step3: "アプリに表示されている確認コード(トークン)を入力します。"
 | 
					  step3: "アプリに表示されている確認コード(トークン)を入力します。"
 | 
				
			||||||
| 
						 | 
					@ -2064,6 +2065,7 @@ _2fa:
 | 
				
			||||||
  backupCodesDescription: "認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。"
 | 
					  backupCodesDescription: "認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。"
 | 
				
			||||||
  backupCodeUsedWarning: "バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。"
 | 
					  backupCodeUsedWarning: "バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。"
 | 
				
			||||||
  backupCodesExhaustedWarning: "バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。"
 | 
					  backupCodesExhaustedWarning: "バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。"
 | 
				
			||||||
 | 
					  moreDetailedGuideHere: "詳細なガイドはこちら"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_permissions:
 | 
					_permissions:
 | 
				
			||||||
  "read:account": "アカウントの情報を見る"
 | 
					  "read:account": "アカウントの情報を見る"
 | 
				
			||||||
| 
						 | 
					@ -2393,6 +2395,8 @@ _pages:
 | 
				
			||||||
    section: "セクション"
 | 
					    section: "セクション"
 | 
				
			||||||
    image: "画像"
 | 
					    image: "画像"
 | 
				
			||||||
    button: "ボタン"
 | 
					    button: "ボタン"
 | 
				
			||||||
 | 
					    dynamic: "動的ブロック"
 | 
				
			||||||
 | 
					    dynamicDescription: "このブロックは廃止されています。今後は{play}を利用してください。"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    note: "ノート埋め込み"
 | 
					    note: "ノート埋め込み"
 | 
				
			||||||
    _note:
 | 
					    _note:
 | 
				
			||||||
| 
						 | 
					@ -2697,3 +2701,8 @@ _urlPreviewSetting:
 | 
				
			||||||
  summaryProxy: "プレビューを生成するプロキシのエンドポイント"
 | 
					  summaryProxy: "プレビューを生成するプロキシのエンドポイント"
 | 
				
			||||||
  summaryProxyDescription: "Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。"
 | 
					  summaryProxyDescription: "Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。"
 | 
				
			||||||
  summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。"
 | 
					  summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_mediaControls:
 | 
				
			||||||
 | 
					  pip: "ピクチャインピクチャ"
 | 
				
			||||||
 | 
					  playbackRate: "再生速度"
 | 
				
			||||||
 | 
					  loop: "ループ再生"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,9 @@
 | 
				
			||||||
		"postcss": "8.4.35",
 | 
							"postcss": "8.4.35",
 | 
				
			||||||
		"tar": "6.2.0",
 | 
							"tar": "6.2.0",
 | 
				
			||||||
		"terser": "5.28.1",
 | 
							"terser": "5.28.1",
 | 
				
			||||||
		"typescript": "5.3.3"
 | 
							"typescript": "5.3.3",
 | 
				
			||||||
 | 
							"esbuild": "0.19.11",
 | 
				
			||||||
 | 
							"glob": "10.3.10"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"devDependencies": {
 | 
						"devDependencies": {
 | 
				
			||||||
		"@types/node": "^20.11.28",
 | 
							"@types/node": "^20.11.28",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,5 +19,6 @@
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"target": "es2022"
 | 
							"target": "es2022"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"minify": false
 | 
						"minify": false,
 | 
				
			||||||
 | 
						"sourceMaps": "inline"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,15 +11,15 @@
 | 
				
			||||||
		"start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js",
 | 
							"start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js",
 | 
				
			||||||
		"migrate": "pnpm typeorm migration:run -d ormconfig.js",
 | 
							"migrate": "pnpm typeorm migration:run -d ormconfig.js",
 | 
				
			||||||
		"revert": "pnpm typeorm migration:revert -d ormconfig.js",
 | 
							"revert": "pnpm typeorm migration:revert -d ormconfig.js",
 | 
				
			||||||
		"check:connect": "node ./check_connect.js",
 | 
							"check:connect": "node ./scripts/check_connect.js",
 | 
				
			||||||
		"build": "swc src -d built -D",
 | 
							"build": "swc src -d built -D",
 | 
				
			||||||
		"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc",
 | 
							"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc",
 | 
				
			||||||
		"watch:swc": "swc src -d built -D -w",
 | 
							"watch:swc": "swc src -d built -D -w",
 | 
				
			||||||
		"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
 | 
							"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
 | 
				
			||||||
		"watch": "node watch.mjs",
 | 
							"watch": "node ./scripts/watch.mjs",
 | 
				
			||||||
		"restart": "pnpm build && pnpm start",
 | 
							"restart": "pnpm build && pnpm start",
 | 
				
			||||||
		"dev": "nodemon -w src -e ts,js,mjs,cjs,json --exec \"cross-env NODE_ENV=development pnpm run restart\"",
 | 
							"dev": "node ./scripts/dev.mjs",
 | 
				
			||||||
		"typecheck": "pnpm --filter megalodon build && tsc --noEmit",
 | 
							"typecheck": "pnpm --filter megalodon build && tsc --noEmit && tsc -p test --noEmit",
 | 
				
			||||||
		"eslint": "eslint --quiet \"src/**/*.ts\"",
 | 
							"eslint": "eslint --quiet \"src/**/*.ts\"",
 | 
				
			||||||
		"lint": "pnpm typecheck && pnpm eslint",
 | 
							"lint": "pnpm typecheck && pnpm eslint",
 | 
				
			||||||
		"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
 | 
							"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@
 | 
				
			||||||
		"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
 | 
							"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
 | 
				
			||||||
		"test-and-coverage": "pnpm jest-and-coverage",
 | 
							"test-and-coverage": "pnpm jest-and-coverage",
 | 
				
			||||||
		"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
 | 
							"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
 | 
				
			||||||
		"generate-api-json": "pnpm build && node ./generate_api_json.js"
 | 
							"generate-api-json": "pnpm build && node ./scripts/generate_api_json.js"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"optionalDependencies": {
 | 
						"optionalDependencies": {
 | 
				
			||||||
		"@swc/core-android-arm64": "1.3.11",
 | 
							"@swc/core-android-arm64": "1.3.11",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Redis from 'ioredis';
 | 
					import Redis from 'ioredis';
 | 
				
			||||||
import { loadConfig } from './built/config.js';
 | 
					import { loadConfig } from '../built/config.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const config = loadConfig();
 | 
					const config = loadConfig();
 | 
				
			||||||
const redis = new Redis(config.redis);
 | 
					const redis = new Redis(config.redis);
 | 
				
			||||||
							
								
								
									
										61
									
								
								packages/backend/scripts/dev.mjs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								packages/backend/scripts/dev.mjs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-FileCopyrightText: syuilo and misskey-project
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { execa, execaNode } from 'execa';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** @type {import('execa').ExecaChildProcess | undefined} */
 | 
				
			||||||
 | 
					let backendProcess;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function execBuildAssets() {
 | 
				
			||||||
 | 
						await execa('pnpm', ['run', 'build-assets'], {
 | 
				
			||||||
 | 
							cwd: '../../',
 | 
				
			||||||
 | 
							stdout: process.stdout,
 | 
				
			||||||
 | 
							stderr: process.stderr,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function execStart() {
 | 
				
			||||||
 | 
						// pnpm run start を呼び出したいが、windowsだとプロセスグループ単位でのkillが出来ずゾンビプロセス化するので
 | 
				
			||||||
 | 
						// 上記と同等の動きをするコマンドで子・孫プロセスを作らないようにしたい
 | 
				
			||||||
 | 
						backendProcess = execaNode('./built/boot/entry.js', [], {
 | 
				
			||||||
 | 
							stdout: process.stdout,
 | 
				
			||||||
 | 
							stderr: process.stderr,
 | 
				
			||||||
 | 
							env: {
 | 
				
			||||||
 | 
								'NODE_ENV': 'development',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function killProc() {
 | 
				
			||||||
 | 
						if (backendProcess) {
 | 
				
			||||||
 | 
							backendProcess.kill();
 | 
				
			||||||
 | 
							await new Promise(resolve => backendProcess.on('exit', resolve));
 | 
				
			||||||
 | 
							backendProcess = undefined;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(async () => {
 | 
				
			||||||
 | 
						execaNode(
 | 
				
			||||||
 | 
							'./node_modules/nodemon/bin/nodemon.js',
 | 
				
			||||||
 | 
							[
 | 
				
			||||||
 | 
								'-w', 'src',
 | 
				
			||||||
 | 
								'-e', 'ts,js,mjs,cjs,json',
 | 
				
			||||||
 | 
								'--exec', 'pnpm', 'run', 'build',
 | 
				
			||||||
 | 
							],
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								stdio: [process.stdin, process.stdout, process.stderr, 'ipc'],
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.on('message', async (message) => {
 | 
				
			||||||
 | 
								if (message.type === 'exit') {
 | 
				
			||||||
 | 
									// かならずbuild->build-assetsの順番で呼び出したいので、
 | 
				
			||||||
 | 
									// 少々トリッキーだがnodemonからのexitイベントを利用してbuild-assets->startを行う。
 | 
				
			||||||
 | 
									// pnpm restartをbuildが終わる前にbuild-assetsが動いてしまうので、バラバラに呼び出す必要がある
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									await killProc();
 | 
				
			||||||
 | 
									await execBuildAssets();
 | 
				
			||||||
 | 
									execStart();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					})();
 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,8 @@
 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 * SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { loadConfig } from './built/config.js'
 | 
					import { loadConfig } from '../built/config.js'
 | 
				
			||||||
import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js'
 | 
					import { genOpenapiSpec } from '../built/server/api/openapi/gen-spec.js'
 | 
				
			||||||
import { writeFileSync } from "node:fs";
 | 
					import { writeFileSync } from "node:fs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const config = loadConfig();
 | 
					const config = loadConfig();
 | 
				
			||||||
| 
						 | 
					@ -305,7 +305,7 @@ export class AccountMoveService {
 | 
				
			||||||
		let resultUser: MiLocalUser | MiRemoteUser | null = null;
 | 
							let resultUser: MiLocalUser | MiRemoteUser | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (this.userEntityService.isRemoteUser(dst)) {
 | 
							if (this.userEntityService.isRemoteUser(dst)) {
 | 
				
			||||||
			if ((new Date()).getTime() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
 | 
								if (Date.now() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
 | 
				
			||||||
				await this.apPersonService.updatePerson(dst.uri);
 | 
									await this.apPersonService.updatePerson(dst.uri);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			dst = await this.apPersonService.fetchPerson(dst.uri) ?? dst;
 | 
								dst = await this.apPersonService.fetchPerson(dst.uri) ?? dst;
 | 
				
			||||||
| 
						 | 
					@ -321,7 +321,7 @@ export class AccountMoveService {
 | 
				
			||||||
				if (!src) continue; // oldAccountを探してもこのサーバーに存在しない場合はフォロー関係もないということなのでスルー
 | 
									if (!src) continue; // oldAccountを探してもこのサーバーに存在しない場合はフォロー関係もないということなのでスルー
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if (this.userEntityService.isRemoteUser(dst)) {
 | 
									if (this.userEntityService.isRemoteUser(dst)) {
 | 
				
			||||||
					if ((new Date()).getTime() - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
 | 
										if (Date.now() - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
 | 
				
			||||||
						await this.apPersonService.updatePerson(srcUri);
 | 
											await this.apPersonService.updatePerson(srcUri);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,11 +8,13 @@ import * as crypto from 'node:crypto';
 | 
				
			||||||
import * as stream from 'node:stream/promises';
 | 
					import * as stream from 'node:stream/promises';
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import * as fileType from 'file-type';
 | 
					import * as fileType from 'file-type';
 | 
				
			||||||
 | 
					import FFmpeg from 'fluent-ffmpeg';
 | 
				
			||||||
import isSvg from 'is-svg';
 | 
					import isSvg from 'is-svg';
 | 
				
			||||||
import probeImageSize from 'probe-image-size';
 | 
					import probeImageSize from 'probe-image-size';
 | 
				
			||||||
import sharp from 'sharp';
 | 
					 | 
				
			||||||
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
 | 
					import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
 | 
				
			||||||
import { encode } from 'blurhash';
 | 
					import { encode } from 'blurhash';
 | 
				
			||||||
 | 
					import { LoggerService } from '@/core/LoggerService.js';
 | 
				
			||||||
 | 
					import type Logger from '@/logger.js';
 | 
				
			||||||
import { bindThis } from '@/decorators.js';
 | 
					import { bindThis } from '@/decorators.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type FileInfo = {
 | 
					export type FileInfo = {
 | 
				
			||||||
| 
						 | 
					@ -43,8 +45,12 @@ const TYPE_SVG = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class FileInfoService {
 | 
					export class FileInfoService {
 | 
				
			||||||
 | 
						private logger: Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
 | 
							private loggerService: LoggerService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
 | 
							this.logger = this.loggerService.getLogger('file-info');
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
| 
						 | 
					@ -147,6 +153,34 @@ export class FileInfoService {
 | 
				
			||||||
		return mime;
 | 
							return mime;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * ビデオファイルにビデオトラックがあるかどうかチェック
 | 
				
			||||||
 | 
						 * (ない場合:m4a, webmなど)
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param path ファイルパス
 | 
				
			||||||
 | 
						 * @returns ビデオトラックがあるかどうか(エラー発生時は常に`true`を返す)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@bindThis
 | 
				
			||||||
 | 
						private hasVideoTrackOnVideoFile(path: string): Promise<boolean> {
 | 
				
			||||||
 | 
							const sublogger = this.logger.createSubLogger('ffprobe');
 | 
				
			||||||
 | 
							sublogger.info(`Checking the video file. File path: ${path}`);
 | 
				
			||||||
 | 
							return new Promise((resolve) => {
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									FFmpeg.ffprobe(path, (err, metadata) => {
 | 
				
			||||||
 | 
										if (err) {
 | 
				
			||||||
 | 
											sublogger.warn(`Could not check the video file. Returns true. File path: ${path}`, err);
 | 
				
			||||||
 | 
											resolve(true);
 | 
				
			||||||
 | 
											return;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										resolve(metadata.streams.some((stream) => stream.codec_type === 'video'));
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								} catch (err) {
 | 
				
			||||||
 | 
									sublogger.warn(`Could not check the video file. Returns true. File path: ${path}`, err as Error);
 | 
				
			||||||
 | 
									resolve(true);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Detect MIME Type and extension
 | 
						 * Detect MIME Type and extension
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
| 
						 | 
					@ -169,6 +203,20 @@ export class FileInfoService {
 | 
				
			||||||
				return TYPE_SVG;
 | 
									return TYPE_SVG;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if ((type.mime.startsWith('video') || type.mime === 'application/ogg') && !(await this.hasVideoTrackOnVideoFile(path))) {
 | 
				
			||||||
 | 
									const newMime = `audio/${type.mime.split('/')[1]}`;
 | 
				
			||||||
 | 
									if (newMime === 'audio/mp4') {
 | 
				
			||||||
 | 
										return {
 | 
				
			||||||
 | 
											mime: 'audio/mp4',
 | 
				
			||||||
 | 
											ext: 'm4a',
 | 
				
			||||||
 | 
										};
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return {
 | 
				
			||||||
 | 
										mime: newMime,
 | 
				
			||||||
 | 
										ext: type.ext,
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				mime: this.fixMime(type.mime),
 | 
									mime: this.fixMime(type.mime),
 | 
				
			||||||
				ext: type.ext,
 | 
									ext: type.ext,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,7 +101,7 @@ export class PushNotificationService implements OnApplicationShutdown {
 | 
				
			||||||
				type,
 | 
									type,
 | 
				
			||||||
				body: (type === 'notification' || type === 'unreadAntennaNote') ? truncateBody(type, body) : body,
 | 
									body: (type === 'notification' || type === 'unreadAntennaNote') ? truncateBody(type, body) : body,
 | 
				
			||||||
				userId,
 | 
									userId,
 | 
				
			||||||
				dateTime: (new Date()).getTime(),
 | 
									dateTime: Date.now(),
 | 
				
			||||||
			}), {
 | 
								}), {
 | 
				
			||||||
				proxy: this.config.proxy,
 | 
									proxy: this.config.proxy,
 | 
				
			||||||
			}).catch((err: any) => {
 | 
								}).catch((err: any) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,7 +63,7 @@ export class CleanRemoteFilesProcessorService {
 | 
				
			||||||
				isLink: false,
 | 
									isLink: false,
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			job.updateProgress(deletedCount / total);
 | 
								job.updateProgress(100 / total * deletedCount);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.logger.succ('All cached remote files has been deleted.');
 | 
							this.logger.succ('All cached remote files has been deleted.');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
 | 
								const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
 | 
				
			||||||
				me,
 | 
									me,
 | 
				
			||||||
				(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
 | 
									(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(),
 | 
				
			||||||
				true,
 | 
									true,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
 | 
								if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
 | 
								const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
 | 
				
			||||||
				me,
 | 
									me,
 | 
				
			||||||
				(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
 | 
									(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(),
 | 
				
			||||||
				true,
 | 
									true,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
 | 
								if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
 | 
								const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
 | 
				
			||||||
				me,
 | 
									me,
 | 
				
			||||||
				(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
 | 
									(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(),
 | 
				
			||||||
				true,
 | 
									true,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
 | 
								if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
 | 
								const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
 | 
				
			||||||
				me,
 | 
									me,
 | 
				
			||||||
				(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
 | 
									(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(),
 | 
				
			||||||
				true,
 | 
									true,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
 | 
								if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,7 @@ export const meta = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: {
 | 
						res: {
 | 
				
			||||||
		type: 'object',
 | 
							type: 'object',
 | 
				
			||||||
		optional: false, nullable: false,
 | 
							optional: true, nullable: false,
 | 
				
			||||||
		properties: {
 | 
							properties: {
 | 
				
			||||||
			sourceLang: { type: 'string' },
 | 
								sourceLang: { type: 'string' },
 | 
				
			||||||
			text: { type: 'string' },
 | 
								text: { type: 'string' },
 | 
				
			||||||
| 
						 | 
					@ -39,6 +39,11 @@ export const meta = {
 | 
				
			||||||
			code: 'NO_SUCH_NOTE',
 | 
								code: 'NO_SUCH_NOTE',
 | 
				
			||||||
			id: 'bea9b03f-36e0-49c5-a4db-627a029f8971',
 | 
								id: 'bea9b03f-36e0-49c5-a4db-627a029f8971',
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							cannotTranslateInvisibleNote: {
 | 
				
			||||||
 | 
								message: 'Cannot translate invisible note.',
 | 
				
			||||||
 | 
								code: 'CANNOT_TRANSLATE_INVISIBLE_NOTE',
 | 
				
			||||||
 | 
								id: 'ea29f2ca-c368-43b3-aaf1-5ac3e74bbe5d',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,21 +77,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (!(await this.noteEntityService.isVisibleForMe(note, me.id))) {
 | 
								if (!(await this.noteEntityService.isVisibleForMe(note, me.id))) {
 | 
				
			||||||
				return 204; // TODO: 良い感じのエラー返す
 | 
									throw new ApiError(meta.errors.cannotTranslateInvisibleNote);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (note.text == null) {
 | 
								if (note.text == null) {
 | 
				
			||||||
				return 204;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const instance = await this.metaService.fetch();
 | 
								const instance = await this.metaService.fetch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (instance.deeplAuthKey == null && !instance.deeplFreeMode) {
 | 
								if (instance.deeplAuthKey == null && !instance.deeplFree				throw new ApiError(meta.errors.unavailable);
 | 
				
			||||||
				return 204; // TODO: 良い感じのエラー返す
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (instance.deeplFreeMode && !instance.deeplFreeInstance) {
 | 
								if (instance.deeplFreeMode && !instance.deeplFreeInstance) {
 | 
				
			||||||
				return 204;
 | 
									throw new ApiError(meta.errors.unavailable);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			let targetLang = ps.targetLang;
 | 
								let targetLang = ps.targetLang;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@
 | 
				
			||||||
import { IsNull } from 'typeorm';
 | 
					import { IsNull } from 'typeorm';
 | 
				
			||||||
import { Inject, Injectable } from '@nestjs/common';
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/_.js';
 | 
					import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/_.js';
 | 
				
			||||||
 | 
					import { birthdaySchema } from '@/models/User.js';
 | 
				
			||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
 | 
					import { Endpoint } from '@/server/api/endpoint-base.js';
 | 
				
			||||||
import { QueryService } from '@/core/QueryService.js';
 | 
					import { QueryService } from '@/core/QueryService.js';
 | 
				
			||||||
import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
 | 
					import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
 | 
				
			||||||
| 
						 | 
					@ -66,7 +67,7 @@ export const paramDef = {
 | 
				
			||||||
			description: 'The local host is represented with `null`.',
 | 
								description: 'The local host is represented with `null`.',
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		birthday: { type: 'string', nullable: true },
 | 
							birthday: { ...birthdaySchema, nullable: true },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	anyOf: [
 | 
						anyOf: [
 | 
				
			||||||
		{ required: ['userId'] },
 | 
							{ required: ['userId'] },
 | 
				
			||||||
| 
						 | 
					@ -127,9 +128,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (ps.birthday) {
 | 
								if (ps.birthday) {
 | 
				
			||||||
				try {
 | 
									try {
 | 
				
			||||||
					const d = new Date(ps.birthday);
 | 
										const birthday = ps.birthday.substring(5, 10);
 | 
				
			||||||
					d.setHours(0, 0, 0, 0);
 | 
					 | 
				
			||||||
					const birthday = `${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
 | 
					 | 
				
			||||||
					const birthdayUserQuery = this.userProfilesRepository.createQueryBuilder('user_profile');
 | 
										const birthdayUserQuery = this.userProfilesRepository.createQueryBuilder('user_profile');
 | 
				
			||||||
					birthdayUserQuery.select('user_profile.userId')
 | 
										birthdayUserQuery.select('user_profile.userId')
 | 
				
			||||||
						.where(`SUBSTR(user_profile.birthday, 6, 5) = '${birthday}'`);
 | 
											.where(`SUBSTR(user_profile.birthday, 6, 5) = '${birthday}'`);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,12 +8,13 @@ process.env.NODE_ENV = 'test';
 | 
				
			||||||
import * as assert from 'assert';
 | 
					import * as assert from 'assert';
 | 
				
			||||||
import { MiNote } from '@/models/Note.js';
 | 
					import { MiNote } from '@/models/Note.js';
 | 
				
			||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 | 
					import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 | 
				
			||||||
import { api, initTestDb, post, signup, uploadFile, uploadUrl } from '../utils.js';
 | 
					import { api, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js';
 | 
				
			||||||
import type * as misskey from 'misskey-js';
 | 
					import type * as misskey from 'misskey-js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Note', () => {
 | 
					describe('Note', () => {
 | 
				
			||||||
	let Notes: any;
 | 
						let Notes: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let root: misskey.entities.SignupResponse;
 | 
				
			||||||
	let alice: misskey.entities.SignupResponse;
 | 
						let alice: misskey.entities.SignupResponse;
 | 
				
			||||||
	let bob: misskey.entities.SignupResponse;
 | 
						let bob: misskey.entities.SignupResponse;
 | 
				
			||||||
	let tom: misskey.entities.SignupResponse;
 | 
						let tom: misskey.entities.SignupResponse;
 | 
				
			||||||
| 
						 | 
					@ -21,6 +22,7 @@ describe('Note', () => {
 | 
				
			||||||
	beforeAll(async () => {
 | 
						beforeAll(async () => {
 | 
				
			||||||
		const connection = await initTestDb(true);
 | 
							const connection = await initTestDb(true);
 | 
				
			||||||
		Notes = connection.getRepository(MiNote);
 | 
							Notes = connection.getRepository(MiNote);
 | 
				
			||||||
 | 
							root = await signup({ username: 'root' });
 | 
				
			||||||
		alice = await signup({ username: 'alice' });
 | 
							alice = await signup({ username: 'alice' });
 | 
				
			||||||
		bob = await signup({ username: 'bob' });
 | 
							bob = await signup({ username: 'bob' });
 | 
				
			||||||
		tom = await signup({ username: 'tom', host: 'example.com' });
 | 
							tom = await signup({ username: 'tom', host: 'example.com' });
 | 
				
			||||||
| 
						 | 
					@ -473,14 +475,14 @@ describe('Note', () => {
 | 
				
			||||||
						value: true,
 | 
											value: true,
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
				} as any,
 | 
									} as any,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(res.status, 200);
 | 
								assert.strictEqual(res.status, 200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const assign = await api('admin/roles/assign', {
 | 
								const assign = await api('admin/roles/assign', {
 | 
				
			||||||
				userId: alice.id,
 | 
									userId: alice.id,
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(assign.status, 204);
 | 
								assert.strictEqual(assign.status, 204);
 | 
				
			||||||
			assert.strictEqual(file.body!.isSensitive, false);
 | 
								assert.strictEqual(file.body!.isSensitive, false);
 | 
				
			||||||
| 
						 | 
					@ -508,11 +510,11 @@ describe('Note', () => {
 | 
				
			||||||
			await api('admin/roles/unassign', {
 | 
								await api('admin/roles/unassign', {
 | 
				
			||||||
				userId: alice.id,
 | 
									userId: alice.id,
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			});
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			await api('admin/roles/delete', {
 | 
								await api('admin/roles/delete', {
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -644,7 +646,7 @@ describe('Note', () => {
 | 
				
			||||||
				sensitiveWords: [
 | 
									sensitiveWords: [
 | 
				
			||||||
					'test',
 | 
										'test',
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(sensitive.status, 204);
 | 
								assert.strictEqual(sensitive.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -663,7 +665,7 @@ describe('Note', () => {
 | 
				
			||||||
				sensitiveWords: [
 | 
									sensitiveWords: [
 | 
				
			||||||
					'/Test/i',
 | 
										'/Test/i',
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(sensitive.status, 204);
 | 
								assert.strictEqual(sensitive.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -680,7 +682,7 @@ describe('Note', () => {
 | 
				
			||||||
				sensitiveWords: [
 | 
									sensitiveWords: [
 | 
				
			||||||
					'Test hoge',
 | 
										'Test hoge',
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(sensitive.status, 204);
 | 
								assert.strictEqual(sensitive.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -697,7 +699,7 @@ describe('Note', () => {
 | 
				
			||||||
				prohibitedWords: [
 | 
									prohibitedWords: [
 | 
				
			||||||
					'test',
 | 
										'test',
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(prohibited.status, 204);
 | 
								assert.strictEqual(prohibited.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -716,7 +718,7 @@ describe('Note', () => {
 | 
				
			||||||
				prohibitedWords: [
 | 
									prohibitedWords: [
 | 
				
			||||||
					'/Test/i',
 | 
										'/Test/i',
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(prohibited.status, 204);
 | 
								assert.strictEqual(prohibited.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -733,7 +735,7 @@ describe('Note', () => {
 | 
				
			||||||
				prohibitedWords: [
 | 
									prohibitedWords: [
 | 
				
			||||||
					'Test hoge',
 | 
										'Test hoge',
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(prohibited.status, 204);
 | 
								assert.strictEqual(prohibited.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -750,7 +752,7 @@ describe('Note', () => {
 | 
				
			||||||
				prohibitedWords: [
 | 
									prohibitedWords: [
 | 
				
			||||||
					'test',
 | 
										'test',
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(prohibited.status, 204);
 | 
								assert.strictEqual(prohibited.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -785,7 +787,7 @@ describe('Note', () => {
 | 
				
			||||||
						value: 0,
 | 
											value: 0,
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
				} as any,
 | 
									} as any,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(res.status, 200);
 | 
								assert.strictEqual(res.status, 200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -794,7 +796,7 @@ describe('Note', () => {
 | 
				
			||||||
			const assign = await api('admin/roles/assign', {
 | 
								const assign = await api('admin/roles/assign', {
 | 
				
			||||||
				userId: alice.id,
 | 
									userId: alice.id,
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(assign.status, 204);
 | 
								assert.strictEqual(assign.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -810,11 +812,11 @@ describe('Note', () => {
 | 
				
			||||||
			await api('admin/roles/unassign', {
 | 
								await api('admin/roles/unassign', {
 | 
				
			||||||
				userId: alice.id,
 | 
									userId: alice.id,
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			});
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			await api('admin/roles/delete', {
 | 
								await api('admin/roles/delete', {
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		test('ダイレクト投稿もエラーになる', async () => {
 | 
							test('ダイレクト投稿もエラーになる', async () => {
 | 
				
			||||||
| 
						 | 
					@ -839,7 +841,7 @@ describe('Note', () => {
 | 
				
			||||||
						value: 0,
 | 
											value: 0,
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
				} as any,
 | 
									} as any,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(res.status, 200);
 | 
								assert.strictEqual(res.status, 200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -848,7 +850,7 @@ describe('Note', () => {
 | 
				
			||||||
			const assign = await api('admin/roles/assign', {
 | 
								const assign = await api('admin/roles/assign', {
 | 
				
			||||||
				userId: alice.id,
 | 
									userId: alice.id,
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(assign.status, 204);
 | 
								assert.strictEqual(assign.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -866,11 +868,11 @@ describe('Note', () => {
 | 
				
			||||||
			await api('admin/roles/unassign', {
 | 
								await api('admin/roles/unassign', {
 | 
				
			||||||
				userId: alice.id,
 | 
									userId: alice.id,
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			});
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			await api('admin/roles/delete', {
 | 
								await api('admin/roles/delete', {
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		test('ダイレクトの宛先とメンションが同じ場合は重複してカウントしない', async () => {
 | 
							test('ダイレクトの宛先とメンションが同じ場合は重複してカウントしない', async () => {
 | 
				
			||||||
| 
						 | 
					@ -895,7 +897,7 @@ describe('Note', () => {
 | 
				
			||||||
						value: 1,
 | 
											value: 1,
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
				} as any,
 | 
									} as any,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(res.status, 200);
 | 
								assert.strictEqual(res.status, 200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -904,7 +906,7 @@ describe('Note', () => {
 | 
				
			||||||
			const assign = await api('admin/roles/assign', {
 | 
								const assign = await api('admin/roles/assign', {
 | 
				
			||||||
				userId: alice.id,
 | 
									userId: alice.id,
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert.strictEqual(assign.status, 204);
 | 
								assert.strictEqual(assign.status, 204);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -921,11 +923,11 @@ describe('Note', () => {
 | 
				
			||||||
			await api('admin/roles/unassign', {
 | 
								await api('admin/roles/unassign', {
 | 
				
			||||||
				userId: alice.id,
 | 
									userId: alice.id,
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			});
 | 
								}, root);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			await api('admin/roles/delete', {
 | 
								await api('admin/roles/delete', {
 | 
				
			||||||
				roleId: res.body.id,
 | 
									roleId: res.body.id,
 | 
				
			||||||
			}, alice);
 | 
								}, root);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -960,4 +962,61 @@ describe('Note', () => {
 | 
				
			||||||
			assert.strictEqual(mainNote.repliesCount, 0);
 | 
								assert.strictEqual(mainNote.repliesCount, 0);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						describe('notes/translate', () => {
 | 
				
			||||||
 | 
							describe('翻訳機能の利用が許可されていない場合', () => {
 | 
				
			||||||
 | 
								let cannotTranslateRole: misskey.entities.Role;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								beforeAll(async () => {
 | 
				
			||||||
 | 
									cannotTranslateRole = await role(root, {}, { canUseTranslator: false });
 | 
				
			||||||
 | 
									await api('admin/roles/assign', { roleId: cannotTranslateRole.id, userId: alice.id }, root);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								test('翻訳機能の利用が許可されていない場合翻訳できない', async () => {
 | 
				
			||||||
 | 
									const aliceNote = await post(alice, { text: 'Hello' });
 | 
				
			||||||
 | 
									const res = await api('notes/translate', {
 | 
				
			||||||
 | 
										noteId: aliceNote.id,
 | 
				
			||||||
 | 
										targetLang: 'ja',
 | 
				
			||||||
 | 
									}, alice);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									assert.strictEqual(res.status, 400);
 | 
				
			||||||
 | 
									assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								afterAll(async () => {
 | 
				
			||||||
 | 
									await api('admin/roles/unassign', { roleId: cannotTranslateRole.id, userId: alice.id }, root);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('存在しないノートは翻訳できない', async () => {
 | 
				
			||||||
 | 
								const res = await api('notes/translate', { noteId: 'foo', targetLang: 'ja' }, alice);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								assert.strictEqual(res.status, 400);
 | 
				
			||||||
 | 
								assert.strictEqual(res.body.error.code, 'NO_SUCH_NOTE');
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('不可視なノートは翻訳できない', async () => {
 | 
				
			||||||
 | 
								const aliceNote = await post(alice, { visibility: 'followers', text: 'Hello' });
 | 
				
			||||||
 | 
								const bobTranslateAttempt = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, bob);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								assert.strictEqual(bobTranslateAttempt.status, 400);
 | 
				
			||||||
 | 
								assert.strictEqual(bobTranslateAttempt.body.error.code, 'CANNOT_TRANSLATE_INVISIBLE_NOTE');
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('text: null なノートを翻訳すると空のレスポンスが返ってくる', async () => {
 | 
				
			||||||
 | 
								const aliceNote = await post(alice, { text: null, poll: { choices: ['kinoko', 'takenoko'] } });
 | 
				
			||||||
 | 
								const res = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, alice);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								assert.strictEqual(res.status, 204);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							test('サーバーに DeepL 認証キーが登録されていない場合翻訳できない', async () => {
 | 
				
			||||||
 | 
								const aliceNote = await post(alice, { text: 'Hello' });
 | 
				
			||||||
 | 
								const res = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, alice);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// NOTE: デフォルトでは登録されていないので落ちる
 | 
				
			||||||
 | 
								assert.strictEqual(res.status, 400);
 | 
				
			||||||
 | 
								assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.m4a
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/backend/test/resources/kick_gaba7.m4a
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
					@ -14,6 +14,7 @@ import { afterAll, beforeAll, describe, test } from '@jest/globals';
 | 
				
			||||||
import { GlobalModule } from '@/GlobalModule.js';
 | 
					import { GlobalModule } from '@/GlobalModule.js';
 | 
				
			||||||
import { FileInfoService } from '@/core/FileInfoService.js';
 | 
					import { FileInfoService } from '@/core/FileInfoService.js';
 | 
				
			||||||
//import { DI } from '@/di-symbols.js';
 | 
					//import { DI } from '@/di-symbols.js';
 | 
				
			||||||
 | 
					import { LoggerService } from '@/core/LoggerService.js';
 | 
				
			||||||
import type { TestingModule } from '@nestjs/testing';
 | 
					import type { TestingModule } from '@nestjs/testing';
 | 
				
			||||||
import type { MockFunctionMetadata } from 'jest-mock';
 | 
					import type { MockFunctionMetadata } from 'jest-mock';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,6 +34,7 @@ describe('FileInfoService', () => {
 | 
				
			||||||
				GlobalModule,
 | 
									GlobalModule,
 | 
				
			||||||
			],
 | 
								],
 | 
				
			||||||
			providers: [
 | 
								providers: [
 | 
				
			||||||
 | 
									LoggerService,
 | 
				
			||||||
				FileInfoService,
 | 
									FileInfoService,
 | 
				
			||||||
			],
 | 
								],
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
| 
						 | 
					@ -318,8 +320,26 @@ describe('FileInfoService', () => {
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*
 | 
							test('MPEG-4 AUDIO (M4A)', async () => {
 | 
				
			||||||
		 * video/webmとして検出されてしまう
 | 
								const path = `${resources}/kick_gaba7.m4a`;
 | 
				
			||||||
 | 
								const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
 | 
				
			||||||
 | 
								delete info.warnings;
 | 
				
			||||||
 | 
								delete info.blurhash;
 | 
				
			||||||
 | 
								delete info.sensitive;
 | 
				
			||||||
 | 
								delete info.porn;
 | 
				
			||||||
 | 
								delete info.width;
 | 
				
			||||||
 | 
								delete info.height;
 | 
				
			||||||
 | 
								delete info.orientation;
 | 
				
			||||||
 | 
								assert.deepStrictEqual(info, {
 | 
				
			||||||
 | 
									size: 9817,
 | 
				
			||||||
 | 
									md5: '74c9279a4abe98789565f1dc1a541a42',
 | 
				
			||||||
 | 
									type: {
 | 
				
			||||||
 | 
										mime: 'audio/mp4',
 | 
				
			||||||
 | 
										ext: 'm4a',
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		test('WEBM AUDIO', async () => {
 | 
							test('WEBM AUDIO', async () => {
 | 
				
			||||||
			const path = `${resources}/kick_gaba7.webm`;
 | 
								const path = `${resources}/kick_gaba7.webm`;
 | 
				
			||||||
			const info = await fileInfoService.getFileInfo(path) as any;
 | 
								const info = await fileInfoService.getFileInfo(path) as any;
 | 
				
			||||||
| 
						 | 
					@ -332,13 +352,12 @@ describe('FileInfoService', () => {
 | 
				
			||||||
			delete info.orientation;
 | 
								delete info.orientation;
 | 
				
			||||||
			assert.deepStrictEqual(info, {
 | 
								assert.deepStrictEqual(info, {
 | 
				
			||||||
				size: 8879,
 | 
									size: 8879,
 | 
				
			||||||
				md5: '3350083dec312419cfdc06c16413aca7',
 | 
									md5: '53bc1adcb6acbbda67ff9bd484896438',
 | 
				
			||||||
				type: {
 | 
									type: {
 | 
				
			||||||
					mime: 'audio/webm',
 | 
										mime: 'audio/webm',
 | 
				
			||||||
					ext: 'webm',
 | 
										ext: 'webm',
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
		 */
 | 
					 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@
 | 
				
			||||||
		"@twemoji/parser": "15.0.0",
 | 
							"@twemoji/parser": "15.0.0",
 | 
				
			||||||
		"@vitejs/plugin-vue": "5.0.4",
 | 
							"@vitejs/plugin-vue": "5.0.4",
 | 
				
			||||||
		"@vue/compiler-sfc": "3.4.21",
 | 
							"@vue/compiler-sfc": "3.4.21",
 | 
				
			||||||
		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2",
 | 
							"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.4",
 | 
				
			||||||
		"astring": "1.8.6",
 | 
							"astring": "1.8.6",
 | 
				
			||||||
		"broadcast-channel": "7.0.0",
 | 
							"broadcast-channel": "7.0.0",
 | 
				
			||||||
		"buraha": "0.0.1",
 | 
							"buraha": "0.0.1",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
	v-else class="_button"
 | 
						v-else class="_button"
 | 
				
			||||||
	:class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]"
 | 
						:class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]"
 | 
				
			||||||
	:to="to ?? '#'"
 | 
						:to="to ?? '#'"
 | 
				
			||||||
 | 
						:behavior="linkBehavior"
 | 
				
			||||||
	@mousedown="onMousedown"
 | 
						@mousedown="onMousedown"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
	<div ref="ripples" :class="$style.ripples" :data-children-class="$style.ripple"></div>
 | 
						<div ref="ripples" :class="$style.ripples" :data-children-class="$style.ripple"></div>
 | 
				
			||||||
| 
						 | 
					@ -43,6 +44,7 @@ const props = defineProps<{
 | 
				
			||||||
	inline?: boolean;
 | 
						inline?: boolean;
 | 
				
			||||||
	link?: boolean;
 | 
						link?: boolean;
 | 
				
			||||||
	to?: string;
 | 
						to?: string;
 | 
				
			||||||
 | 
						linkBehavior?: null | 'window' | 'browser';
 | 
				
			||||||
	autofocus?: boolean;
 | 
						autofocus?: boolean;
 | 
				
			||||||
	wait?: boolean;
 | 
						wait?: boolean;
 | 
				
			||||||
	danger?: boolean;
 | 
						danger?: boolean;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,11 +80,9 @@ function copy() {
 | 
				
			||||||
.codePlaceholderRoot {
 | 
					.codePlaceholderRoot {
 | 
				
			||||||
	display: block;
 | 
						display: block;
 | 
				
			||||||
	width: 100%;
 | 
						width: 100%;
 | 
				
			||||||
	background: none;
 | 
					 | 
				
			||||||
	border: none;
 | 
						border: none;
 | 
				
			||||||
	outline: none;
 | 
						outline: none;
 | 
				
			||||||
  font: inherit;
 | 
					  font: inherit;
 | 
				
			||||||
  color: inherit;
 | 
					 | 
				
			||||||
	cursor: pointer;
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	box-sizing: border-box;
 | 
						box-sizing: border-box;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,12 +47,12 @@ onMounted(() => {
 | 
				
			||||||
	const width = rootEl.value!.offsetWidth;
 | 
						const width = rootEl.value!.offsetWidth;
 | 
				
			||||||
	const height = rootEl.value!.offsetHeight;
 | 
						const height = rootEl.value!.offsetHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (left + width - window.pageXOffset >= (window.innerWidth - SCROLLBAR_THICKNESS)) {
 | 
						if (left + width - window.scrollX >= (window.innerWidth - SCROLLBAR_THICKNESS)) {
 | 
				
			||||||
		left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.pageXOffset;
 | 
							left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.scrollX;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (top + height - window.pageYOffset >= (window.innerHeight - SCROLLBAR_THICKNESS)) {
 | 
						if (top + height - window.scrollY >= (window.innerHeight - SCROLLBAR_THICKNESS)) {
 | 
				
			||||||
		top = (window.innerHeight - SCROLLBAR_THICKNESS) - height + window.pageYOffset;
 | 
							top = (window.innerHeight - SCROLLBAR_THICKNESS) - height + window.scrollY;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (top < 0) {
 | 
						if (top < 0) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -161,7 +161,7 @@ function onKeydown(evt: KeyboardEvent) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onInputKeydown(evt: KeyboardEvent) {
 | 
					function onInputKeydown(evt: KeyboardEvent) {
 | 
				
			||||||
	if (evt.key === 'Enter') {
 | 
						if (evt.key === 'Enter' && okButtonDisabledReason.value === null) {
 | 
				
			||||||
		evt.preventDefault();
 | 
							evt.preventDefault();
 | 
				
			||||||
		evt.stopPropagation();
 | 
							evt.stopPropagation();
 | 
				
			||||||
		ok();
 | 
							ok();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,11 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div
 | 
					<div
 | 
				
			||||||
 | 
						ref="playerEl"
 | 
				
			||||||
 | 
						v-hotkey="keymap"
 | 
				
			||||||
 | 
						tabindex="0"
 | 
				
			||||||
	:class="[
 | 
						:class="[
 | 
				
			||||||
		$style.audioContainer,
 | 
							$style.audioContainer,
 | 
				
			||||||
		(audio.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive,
 | 
							(audio.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive,
 | 
				
			||||||
	]"
 | 
						]"
 | 
				
			||||||
	@contextmenu.stop
 | 
						@contextmenu.stop
 | 
				
			||||||
 | 
						@keydown.stop
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
	<button v-if="hide" :class="$style.hidden" @click="hide = false">
 | 
						<button v-if="hide" :class="$style.hidden" @click="hide = false">
 | 
				
			||||||
		<div :class="$style.hiddenTextWrapper">
 | 
							<div :class="$style.hiddenTextWrapper">
 | 
				
			||||||
| 
						 | 
					@ -18,6 +22,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
			<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
 | 
								<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</button>
 | 
						</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<div v-else-if="defaultStore.reactiveState.useNativeUIForVideoAudioPlayer.value" :class="$style.nativeAudioContainer">
 | 
				
			||||||
 | 
							<audio
 | 
				
			||||||
 | 
								ref="audioEl"
 | 
				
			||||||
 | 
								preload="metadata"
 | 
				
			||||||
 | 
								controls
 | 
				
			||||||
 | 
								:class="$style.nativeAudio"
 | 
				
			||||||
 | 
								@keydown.prevent
 | 
				
			||||||
 | 
							>
 | 
				
			||||||
 | 
								<source :src="audio.url">
 | 
				
			||||||
 | 
							</audio>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div v-else :class="$style.audioControls">
 | 
						<div v-else :class="$style.audioControls">
 | 
				
			||||||
		<audio
 | 
							<audio
 | 
				
			||||||
			ref="audioEl"
 | 
								ref="audioEl"
 | 
				
			||||||
| 
						 | 
					@ -75,6 +92,41 @@ const props = defineProps<{
 | 
				
			||||||
	audio: Misskey.entities.DriveFile;
 | 
						audio: Misskey.entities.DriveFile;
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const keymap = {
 | 
				
			||||||
 | 
						'up': () => {
 | 
				
			||||||
 | 
							if (hasFocus() && audioEl.value) {
 | 
				
			||||||
 | 
								volume.value = Math.min(volume.value + 0.1, 1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						'down': () => {
 | 
				
			||||||
 | 
							if (hasFocus() && audioEl.value) {
 | 
				
			||||||
 | 
								volume.value = Math.max(volume.value - 0.1, 0);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						'left': () => {
 | 
				
			||||||
 | 
							if (hasFocus() && audioEl.value) {
 | 
				
			||||||
 | 
								audioEl.value.currentTime = Math.max(audioEl.value.currentTime - 5, 0);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						'right': () => {
 | 
				
			||||||
 | 
							if (hasFocus() && audioEl.value) {
 | 
				
			||||||
 | 
								audioEl.value.currentTime = Math.min(audioEl.value.currentTime + 5, audioEl.value.duration);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						'space': () => {
 | 
				
			||||||
 | 
							if (hasFocus()) {
 | 
				
			||||||
 | 
								togglePlayPause();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PlayerElもしくはその子要素にフォーカスがあるかどうか
 | 
				
			||||||
 | 
					function hasFocus() {
 | 
				
			||||||
 | 
						if (!playerEl.value) return false;
 | 
				
			||||||
 | 
						return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const playerEl = shallowRef<HTMLDivElement>();
 | 
				
			||||||
const audioEl = shallowRef<HTMLAudioElement>();
 | 
					const audioEl = shallowRef<HTMLAudioElement>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line vue/no-setup-props-destructure
 | 
					// eslint-disable-next-line vue/no-setup-props-destructure
 | 
				
			||||||
| 
						 | 
					@ -88,6 +140,30 @@ function showMenu(ev: MouseEvent) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	menu = [
 | 
						menu = [
 | 
				
			||||||
		// TODO: 再生キューに追加
 | 
							// TODO: 再生キューに追加
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								type: 'switch',
 | 
				
			||||||
 | 
								text: i18n.ts._mediaControls.loop,
 | 
				
			||||||
 | 
								icon: 'ti ti-repeat',
 | 
				
			||||||
 | 
								ref: loop,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								type: 'radio',
 | 
				
			||||||
 | 
								text: i18n.ts._mediaControls.playbackRate,
 | 
				
			||||||
 | 
								icon: 'ti ti-clock-play',
 | 
				
			||||||
 | 
								ref: speed,
 | 
				
			||||||
 | 
								options: {
 | 
				
			||||||
 | 
									'0.25x': 0.25,
 | 
				
			||||||
 | 
									'0.5x': 0.5,
 | 
				
			||||||
 | 
									'0.75x': 0.75,
 | 
				
			||||||
 | 
									'1.0x': 1,
 | 
				
			||||||
 | 
									'1.25x': 1.25,
 | 
				
			||||||
 | 
									'1.5x': 1.5,
 | 
				
			||||||
 | 
									'2.0x': 2,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								type: 'divider',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			text: i18n.ts.hide,
 | 
								text: i18n.ts.hide,
 | 
				
			||||||
			icon: 'ph-eye-closed ph-bold ph-lg',
 | 
								icon: 'ph-eye-closed ph-bold ph-lg',
 | 
				
			||||||
| 
						 | 
					@ -150,6 +226,8 @@ const rangePercent = computed({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
const volume = ref(.25);
 | 
					const volume = ref(.25);
 | 
				
			||||||
 | 
					const speed = ref(1);
 | 
				
			||||||
 | 
					const loop = ref(false); // TODO: ドライブファイルのフラグに置き換える
 | 
				
			||||||
const bufferedEnd = ref(0);
 | 
					const bufferedEnd = ref(0);
 | 
				
			||||||
const bufferedDataRatio = computed(() => {
 | 
					const bufferedDataRatio = computed(() => {
 | 
				
			||||||
	if (!audioEl.value) return 0;
 | 
						if (!audioEl.value) return 0;
 | 
				
			||||||
| 
						 | 
					@ -179,6 +257,7 @@ function toggleMute() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let onceInit = false;
 | 
					let onceInit = false;
 | 
				
			||||||
 | 
					let mediaTickFrameId: number | null = null;
 | 
				
			||||||
let stopAudioElWatch: () => void;
 | 
					let stopAudioElWatch: () => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function init() {
 | 
					function init() {
 | 
				
			||||||
| 
						 | 
					@ -198,8 +277,12 @@ function init() {
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					elapsedTimeMs.value = audioEl.value.currentTime * 1000;
 | 
										elapsedTimeMs.value = audioEl.value.currentTime * 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (audioEl.value.loop !== loop.value) {
 | 
				
			||||||
 | 
											loop.value = audioEl.value.loop;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				window.requestAnimationFrame(updateMediaTick);
 | 
									mediaTickFrameId = window.requestAnimationFrame(updateMediaTick);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			updateMediaTick();
 | 
								updateMediaTick();
 | 
				
			||||||
| 
						 | 
					@ -237,6 +320,14 @@ watch(volume, (to) => {
 | 
				
			||||||
	if (audioEl.value) audioEl.value.volume = to;
 | 
						if (audioEl.value) audioEl.value.volume = to;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(speed, (to) => {
 | 
				
			||||||
 | 
						if (audioEl.value) audioEl.value.playbackRate = to;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(loop, (to) => {
 | 
				
			||||||
 | 
						if (audioEl.value) audioEl.value.loop = to;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
	init();
 | 
						init();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -255,6 +346,10 @@ onDeactivated(() => {
 | 
				
			||||||
	hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore');
 | 
						hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore');
 | 
				
			||||||
	stopAudioElWatch();
 | 
						stopAudioElWatch();
 | 
				
			||||||
	onceInit = false;
 | 
						onceInit = false;
 | 
				
			||||||
 | 
						if (mediaTickFrameId) {
 | 
				
			||||||
 | 
							window.cancelAnimationFrame(mediaTickFrameId);
 | 
				
			||||||
 | 
							mediaTickFrameId = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -265,6 +360,10 @@ onDeactivated(() => {
 | 
				
			||||||
	border: .5px solid var(--divider);
 | 
						border: .5px solid var(--divider);
 | 
				
			||||||
	border-radius: var(--radius);
 | 
						border-radius: var(--radius);
 | 
				
			||||||
	overflow: clip;
 | 
						overflow: clip;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						&:focus {
 | 
				
			||||||
 | 
							outline: none;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.sensitive {
 | 
					.sensitive {
 | 
				
			||||||
| 
						 | 
					@ -370,4 +469,15 @@ onDeactivated(() => {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.nativeAudioContainer {
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						align-items: center;
 | 
				
			||||||
 | 
						padding: 6px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.nativeAudio {
 | 
				
			||||||
 | 
						display: block;
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div
 | 
					<div
 | 
				
			||||||
	ref="playerEl"
 | 
						ref="playerEl"
 | 
				
			||||||
 | 
						v-hotkey="keymap"
 | 
				
			||||||
 | 
						tabindex="0"
 | 
				
			||||||
	:class="[
 | 
						:class="[
 | 
				
			||||||
		$style.videoContainer,
 | 
							$style.videoContainer,
 | 
				
			||||||
		controlsShowing && $style.active,
 | 
							controlsShowing && $style.active,
 | 
				
			||||||
| 
						 | 
					@ -14,6 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
	@mouseover="onMouseOver"
 | 
						@mouseover="onMouseOver"
 | 
				
			||||||
	@mouseleave="onMouseLeave"
 | 
						@mouseleave="onMouseLeave"
 | 
				
			||||||
	@contextmenu.stop
 | 
						@contextmenu.stop
 | 
				
			||||||
 | 
						@keydown.stop
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
	<button v-if="hide" :class="$style.hidden" @click="hide = false">
 | 
						<button v-if="hide" :class="$style.hidden" @click="hide = false">
 | 
				
			||||||
		<div :class="$style.hiddenTextWrapper">
 | 
							<div :class="$style.hiddenTextWrapper">
 | 
				
			||||||
| 
						 | 
					@ -22,7 +25,28 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
			<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
 | 
								<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</button>
 | 
						</button>
 | 
				
			||||||
	<div v-else :class="$style.videoRoot" @click.self="togglePlayPause">
 | 
					
 | 
				
			||||||
 | 
						<div v-else-if="defaultStore.reactiveState.useNativeUIForVideoAudioPlayer.value" :class="$style.videoRoot">
 | 
				
			||||||
 | 
							<video
 | 
				
			||||||
 | 
								ref="videoEl"
 | 
				
			||||||
 | 
								:class="$style.video"
 | 
				
			||||||
 | 
								:poster="video.thumbnailUrl ?? undefined"
 | 
				
			||||||
 | 
								:title="video.comment ?? undefined"
 | 
				
			||||||
 | 
								:alt="video.comment"
 | 
				
			||||||
 | 
								preload="metadata"
 | 
				
			||||||
 | 
								controls
 | 
				
			||||||
 | 
								@keydown.prevent
 | 
				
			||||||
 | 
							>
 | 
				
			||||||
 | 
								<source :src="video.url">
 | 
				
			||||||
 | 
							</video>
 | 
				
			||||||
 | 
							<i class="ti ti-eye-off" :class="$style.hide" @click="hide = true"></i>
 | 
				
			||||||
 | 
							<div :class="$style.indicators">
 | 
				
			||||||
 | 
								<div v-if="video.comment" :class="$style.indicator">ALT</div>
 | 
				
			||||||
 | 
								<div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<div v-else :class="$style.videoRoot">
 | 
				
			||||||
		<video
 | 
							<video
 | 
				
			||||||
			ref="videoEl"
 | 
								ref="videoEl"
 | 
				
			||||||
			:class="$style.video"
 | 
								:class="$style.video"
 | 
				
			||||||
| 
						 | 
					@ -31,6 +55,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
			:alt="video.comment"
 | 
								:alt="video.comment"
 | 
				
			||||||
			preload="metadata"
 | 
								preload="metadata"
 | 
				
			||||||
			playsinline
 | 
								playsinline
 | 
				
			||||||
 | 
								@keydown.prevent
 | 
				
			||||||
 | 
								@click.self="togglePlayPause"
 | 
				
			||||||
		>
 | 
							>
 | 
				
			||||||
			<source :src="video.url">
 | 
								<source :src="video.url">
 | 
				
			||||||
		</video>
 | 
							</video>
 | 
				
			||||||
| 
						 | 
					@ -103,6 +129,40 @@ const props = defineProps<{
 | 
				
			||||||
	video: Misskey.entities.DriveFile;
 | 
						video: Misskey.entities.DriveFile;
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const keymap = {
 | 
				
			||||||
 | 
						'up': () => {
 | 
				
			||||||
 | 
							if (hasFocus() && videoEl.value) {
 | 
				
			||||||
 | 
								volume.value = Math.min(volume.value + 0.1, 1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						'down': () => {
 | 
				
			||||||
 | 
							if (hasFocus() && videoEl.value) {
 | 
				
			||||||
 | 
								volume.value = Math.max(volume.value - 0.1, 0);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						'left': () => {
 | 
				
			||||||
 | 
							if (hasFocus() && videoEl.value) {
 | 
				
			||||||
 | 
								videoEl.value.currentTime = Math.max(videoEl.value.currentTime - 5, 0);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						'right': () => {
 | 
				
			||||||
 | 
							if (hasFocus() && videoEl.value) {
 | 
				
			||||||
 | 
								videoEl.value.currentTime = Math.min(videoEl.value.currentTime + 5, videoEl.value.duration);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						'space': () => {
 | 
				
			||||||
 | 
							if (hasFocus()) {
 | 
				
			||||||
 | 
								togglePlayPause();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PlayerElもしくはその子要素にフォーカスがあるかどうか
 | 
				
			||||||
 | 
					function hasFocus() {
 | 
				
			||||||
 | 
						if (!playerEl.value) return false;
 | 
				
			||||||
 | 
						return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line vue/no-setup-props-destructure
 | 
					// eslint-disable-next-line vue/no-setup-props-destructure
 | 
				
			||||||
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
 | 
					const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -114,6 +174,35 @@ function showMenu(ev: MouseEvent) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	menu = [
 | 
						menu = [
 | 
				
			||||||
		// TODO: 再生キューに追加
 | 
							// TODO: 再生キューに追加
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								type: 'switch',
 | 
				
			||||||
 | 
								text: i18n.ts._mediaControls.loop,
 | 
				
			||||||
 | 
								icon: 'ti ti-repeat',
 | 
				
			||||||
 | 
								ref: loop,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								type: 'radio',
 | 
				
			||||||
 | 
								text: i18n.ts._mediaControls.playbackRate,
 | 
				
			||||||
 | 
								icon: 'ti ti-clock-play',
 | 
				
			||||||
 | 
								ref: speed,
 | 
				
			||||||
 | 
								options: {
 | 
				
			||||||
 | 
									'0.25x': 0.25,
 | 
				
			||||||
 | 
									'0.5x': 0.5,
 | 
				
			||||||
 | 
									'0.75x': 0.75,
 | 
				
			||||||
 | 
									'1.0x': 1,
 | 
				
			||||||
 | 
									'1.25x': 1.25,
 | 
				
			||||||
 | 
									'1.5x': 1.5,
 | 
				
			||||||
 | 
									'2.0x': 2,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							...(document.pictureInPictureEnabled ? [{
 | 
				
			||||||
 | 
								text: i18n.ts._mediaControls.pip,
 | 
				
			||||||
 | 
								icon: 'ti ti-picture-in-picture',
 | 
				
			||||||
 | 
								action: togglePictureInPicture,
 | 
				
			||||||
 | 
							}] : []),
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								type: 'divider',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			text: i18n.ts.hide,
 | 
								text: i18n.ts.hide,
 | 
				
			||||||
			icon: 'ph-eye-closed ph-bold ph-lg',
 | 
								icon: 'ph-eye-closed ph-bold ph-lg',
 | 
				
			||||||
| 
						 | 
					@ -189,6 +278,8 @@ const rangePercent = computed({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
const volume = ref(.25);
 | 
					const volume = ref(.25);
 | 
				
			||||||
 | 
					const speed = ref(1);
 | 
				
			||||||
 | 
					const loop = ref(false); // TODO: ドライブファイルのフラグに置き換える
 | 
				
			||||||
const bufferedEnd = ref(0);
 | 
					const bufferedEnd = ref(0);
 | 
				
			||||||
const bufferedDataRatio = computed(() => {
 | 
					const bufferedDataRatio = computed(() => {
 | 
				
			||||||
	if (!videoEl.value) return 0;
 | 
						if (!videoEl.value) return 0;
 | 
				
			||||||
| 
						 | 
					@ -246,6 +337,16 @@ function toggleFullscreen() {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function togglePictureInPicture() {
 | 
				
			||||||
 | 
						if (videoEl.value) {
 | 
				
			||||||
 | 
							if (document.pictureInPictureElement) {
 | 
				
			||||||
 | 
								document.exitPictureInPicture();
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								videoEl.value.requestPictureInPicture();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function toggleMute() {
 | 
					function toggleMute() {
 | 
				
			||||||
	if (volume.value === 0) {
 | 
						if (volume.value === 0) {
 | 
				
			||||||
		volume.value = .25;
 | 
							volume.value = .25;
 | 
				
			||||||
| 
						 | 
					@ -255,6 +356,7 @@ function toggleMute() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let onceInit = false;
 | 
					let onceInit = false;
 | 
				
			||||||
 | 
					let mediaTickFrameId: number | null = null;
 | 
				
			||||||
let stopVideoElWatch: () => void;
 | 
					let stopVideoElWatch: () => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function init() {
 | 
					function init() {
 | 
				
			||||||
| 
						 | 
					@ -274,8 +376,12 @@ function init() {
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					elapsedTimeMs.value = videoEl.value.currentTime * 1000;
 | 
										elapsedTimeMs.value = videoEl.value.currentTime * 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (videoEl.value.loop !== loop.value) {
 | 
				
			||||||
 | 
											loop.value = videoEl.value.loop;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				window.requestAnimationFrame(updateMediaTick);
 | 
									mediaTickFrameId = window.requestAnimationFrame(updateMediaTick);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			updateMediaTick();
 | 
								updateMediaTick();
 | 
				
			||||||
| 
						 | 
					@ -319,6 +425,14 @@ watch(volume, (to) => {
 | 
				
			||||||
	if (videoEl.value) videoEl.value.volume = to;
 | 
						if (videoEl.value) videoEl.value.volume = to;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(speed, (to) => {
 | 
				
			||||||
 | 
						if (videoEl.value) videoEl.value.playbackRate = to;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(loop, (to) => {
 | 
				
			||||||
 | 
						if (videoEl.value) videoEl.value.loop = to;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(hide, (to) => {
 | 
					watch(hide, (to) => {
 | 
				
			||||||
	if (to && isFullscreen.value) {
 | 
						if (to && isFullscreen.value) {
 | 
				
			||||||
		document.exitFullscreen();
 | 
							document.exitFullscreen();
 | 
				
			||||||
| 
						 | 
					@ -344,6 +458,10 @@ onDeactivated(() => {
 | 
				
			||||||
	hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore');
 | 
						hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore');
 | 
				
			||||||
	stopVideoElWatch();
 | 
						stopVideoElWatch();
 | 
				
			||||||
	onceInit = false;
 | 
						onceInit = false;
 | 
				
			||||||
 | 
						if (mediaTickFrameId) {
 | 
				
			||||||
 | 
							window.cancelAnimationFrame(mediaTickFrameId);
 | 
				
			||||||
 | 
							mediaTickFrameId = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -352,6 +470,10 @@ onDeactivated(() => {
 | 
				
			||||||
	container-type: inline-size;
 | 
						container-type: inline-size;
 | 
				
			||||||
	position: relative;
 | 
						position: relative;
 | 
				
			||||||
	overflow: clip;
 | 
						overflow: clip;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						&:focus {
 | 
				
			||||||
 | 
							outline: none;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.sensitive {
 | 
					.sensitive {
 | 
				
			||||||
| 
						 | 
					@ -415,7 +537,7 @@ onDeactivated(() => {
 | 
				
			||||||
	font: inherit;
 | 
						font: inherit;
 | 
				
			||||||
	color: inherit;
 | 
						color: inherit;
 | 
				
			||||||
	cursor: pointer;
 | 
						cursor: pointer;
 | 
				
			||||||
	padding: 120px 0;
 | 
						padding: 60px 0;
 | 
				
			||||||
	display: flex;
 | 
						display: flex;
 | 
				
			||||||
	align-items: center;
 | 
						align-items: center;
 | 
				
			||||||
	justify-content: center;
 | 
						justify-content: center;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,9 +42,26 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</button>
 | 
								</button>
 | 
				
			||||||
			<button v-else-if="item.type === 'switch'" role="menuitemcheckbox" :tabindex="i" class="_button" :class="[$style.item, $style.switch, { [$style.switchDisabled]: item.disabled } ]" @click="switchItem(item)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
 | 
								<button v-else-if="item.type === 'switch'" role="menuitemcheckbox" :tabindex="i" class="_button" :class="[$style.item, $style.switch, { [$style.switchDisabled]: item.disabled } ]" @click="switchItem(item)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
 | 
				
			||||||
				<MkSwitchButton :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
 | 
									<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
 | 
				
			||||||
 | 
									<MkSwitchButton v-else :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
 | 
				
			||||||
				<div :class="$style.item_content">
 | 
									<div :class="$style.item_content">
 | 
				
			||||||
					<span :class="[$style.item_content_text, $style.switchText]">{{ item.text }}</span>
 | 
										<span :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">{{ item.text }}</span>
 | 
				
			||||||
 | 
										<MkSwitchButton v-if="item.icon" :class="[$style.switchButton, $style.caret]" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</button>
 | 
				
			||||||
 | 
								<button v-else-if="item.type === 'radio'" class="_button" role="menuitem" :tabindex="i" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="preferClick ? null : showRadioOptions(item, $event)" @click="!preferClick ? null : showRadioOptions(item, $event)">
 | 
				
			||||||
 | 
									<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
 | 
				
			||||||
 | 
									<div :class="$style.item_content">
 | 
				
			||||||
 | 
										<span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
 | 
				
			||||||
 | 
										<span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</button>
 | 
				
			||||||
 | 
								<button v-else-if="item.type === 'radioOption'" :tabindex="i" class="_button" role="menuitem" :class="[$style.item, { [$style.radioActive]: item.active }]" @click="clicked(item.action, $event, false)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
 | 
				
			||||||
 | 
									<div :class="$style.icon">
 | 
				
			||||||
 | 
										<span :class="[$style.radio, { [$style.radioChecked]: item.active }]"></span>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
									<div :class="$style.item_content">
 | 
				
			||||||
 | 
										<span :class="$style.item_content_text">{{ item.text }}</span>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</button>
 | 
								</button>
 | 
				
			||||||
			<button v-else-if="item.type === 'parent'" class="_button" role="menuitem" :tabindex="i" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="preferClick ? null : showChildren(item, $event)" @click="!preferClick ? null : showChildren(item, $event)">
 | 
								<button v-else-if="item.type === 'parent'" class="_button" role="menuitem" :tabindex="i" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="preferClick ? null : showChildren(item, $event)" @click="!preferClick ? null : showChildren(item, $event)">
 | 
				
			||||||
| 
						 | 
					@ -77,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
import { ComputedRef, computed, defineAsyncComponent, isRef, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
 | 
					import { ComputedRef, computed, defineAsyncComponent, isRef, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
 | 
				
			||||||
import { focusPrev, focusNext } from '@/scripts/focus.js';
 | 
					import { focusPrev, focusNext } from '@/scripts/focus.js';
 | 
				
			||||||
import MkSwitchButton from '@/components/MkSwitch.button.vue';
 | 
					import MkSwitchButton from '@/components/MkSwitch.button.vue';
 | 
				
			||||||
import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu.js';
 | 
					import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
 | 
				
			||||||
import * as os from '@/os.js';
 | 
					import * as os from '@/os.js';
 | 
				
			||||||
import { i18n } from '@/i18n.js';
 | 
					import { i18n } from '@/i18n.js';
 | 
				
			||||||
import { isTouchUsing } from '@/scripts/touch.js';
 | 
					import { isTouchUsing } from '@/scripts/touch.js';
 | 
				
			||||||
| 
						 | 
					@ -168,6 +185,31 @@ function onItemMouseLeave(item) {
 | 
				
			||||||
	if (childCloseTimer) window.clearTimeout(childCloseTimer);
 | 
						if (childCloseTimer) window.clearTimeout(childCloseTimer);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function showRadioOptions(item: MenuRadio, ev: MouseEvent) {
 | 
				
			||||||
 | 
						const children: MenuItem[] = Object.keys(item.options).map<MenuRadioOption>(key => {
 | 
				
			||||||
 | 
							const value = item.options[key];
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								type: 'radioOption',
 | 
				
			||||||
 | 
								text: key,
 | 
				
			||||||
 | 
								action: () => {
 | 
				
			||||||
 | 
									item.ref = value;
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								active: computed(() => item.ref === value),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (props.asDrawer) {
 | 
				
			||||||
 | 
							os.popupMenu(children, ev.currentTarget ?? ev.target).finally(() => {
 | 
				
			||||||
 | 
								emit('close');
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							emit('hide');
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							childTarget.value = (ev.currentTarget ?? ev.target) as HTMLElement;
 | 
				
			||||||
 | 
							childMenu.value = children;
 | 
				
			||||||
 | 
							childShowingItem.value = item;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function showChildren(item: MenuParent, ev: MouseEvent) {
 | 
					async function showChildren(item: MenuParent, ev: MouseEvent) {
 | 
				
			||||||
	const children: MenuItem[] = await (async () => {
 | 
						const children: MenuItem[] = await (async () => {
 | 
				
			||||||
		if (childrenCache.has(item)) {
 | 
							if (childrenCache.has(item)) {
 | 
				
			||||||
| 
						 | 
					@ -196,8 +238,10 @@ async function showChildren(item: MenuParent, ev: MouseEvent) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function clicked(fn: MenuAction, ev: MouseEvent) {
 | 
					function clicked(fn: MenuAction, ev: MouseEvent, doClose = true) {
 | 
				
			||||||
	fn(ev);
 | 
						fn(ev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!doClose) return;
 | 
				
			||||||
	close(true);
 | 
						close(true);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -350,6 +394,15 @@ onBeforeUnmount(() => {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						&.radioActive {
 | 
				
			||||||
 | 
							color: var(--accent) !important;
 | 
				
			||||||
 | 
							opacity: 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&:before {
 | 
				
			||||||
 | 
								background-color: var(--accentedBg) !important;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&:not(:active):focus-visible {
 | 
						&:not(:active):focus-visible {
 | 
				
			||||||
		box-shadow: 0 0 0 2px var(--focus) inset;
 | 
							box-shadow: 0 0 0 2px var(--focus) inset;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -417,11 +470,11 @@ onBeforeUnmount(() => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.switchButton {
 | 
					.switchButton {
 | 
				
			||||||
	margin-left: -2px;
 | 
						margin-left: -2px;
 | 
				
			||||||
 | 
						--height: 1.35em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.switchText {
 | 
					.switchText {
 | 
				
			||||||
	margin-left: 8px;
 | 
						margin-left: 8px;
 | 
				
			||||||
	margin-top: 2px;
 | 
					 | 
				
			||||||
	overflow: hidden;
 | 
						overflow: hidden;
 | 
				
			||||||
	text-overflow: ellipsis;
 | 
						text-overflow: ellipsis;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -461,4 +514,32 @@ onBeforeUnmount(() => {
 | 
				
			||||||
	margin: 8px 0;
 | 
						margin: 8px 0;
 | 
				
			||||||
	border-top: solid 0.5px var(--divider);
 | 
						border-top: solid 0.5px var(--divider);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.radio {
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
 | 
						width: 1em;
 | 
				
			||||||
 | 
						height: 1em;
 | 
				
			||||||
 | 
						vertical-align: -.125em;
 | 
				
			||||||
 | 
						border-radius: 50%;
 | 
				
			||||||
 | 
						border: solid 2px var(--divider);
 | 
				
			||||||
 | 
						background-color: var(--panel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						&.radioChecked {
 | 
				
			||||||
 | 
							border-color: var(--accent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&::after {
 | 
				
			||||||
 | 
								content: "";
 | 
				
			||||||
 | 
								display: block;
 | 
				
			||||||
 | 
								position: absolute;
 | 
				
			||||||
 | 
								top: 50%;
 | 
				
			||||||
 | 
								left: 50%;
 | 
				
			||||||
 | 
								transform: translate(-50%, -50%);
 | 
				
			||||||
 | 
								width: 50%;
 | 
				
			||||||
 | 
								height: 50%;
 | 
				
			||||||
 | 
								border-radius: 50%;
 | 
				
			||||||
 | 
								background-color: var(--accent);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -175,8 +175,8 @@ const align = () => {
 | 
				
			||||||
	let left;
 | 
						let left;
 | 
				
			||||||
	let top;
 | 
						let top;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
 | 
						const x = srcRect.left + (fixed.value ? 0 : window.scrollX);
 | 
				
			||||||
	const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
 | 
						const y = srcRect.top + (fixed.value ? 0 : window.scrollY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (props.anchor.x === 'center') {
 | 
						if (props.anchor.x === 'center') {
 | 
				
			||||||
		left = x + (props.src.offsetWidth / 2) - (width / 2);
 | 
							left = x + (props.src.offsetWidth / 2) - (width / 2);
 | 
				
			||||||
| 
						 | 
					@ -220,24 +220,24 @@ const align = () => {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		// 画面から横にはみ出る場合
 | 
							// 画面から横にはみ出る場合
 | 
				
			||||||
		if (left + width - window.pageXOffset > (window.innerWidth - SCROLLBAR_THICKNESS)) {
 | 
							if (left + width - window.scrollX > (window.innerWidth - SCROLLBAR_THICKNESS)) {
 | 
				
			||||||
			left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.pageXOffset - 1;
 | 
								left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.scrollX - 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const underSpace = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - (top - window.pageYOffset);
 | 
							const underSpace = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - (top - window.scrollY);
 | 
				
			||||||
		const upperSpace = (srcRect.top - MARGIN);
 | 
							const upperSpace = (srcRect.top - MARGIN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 画面から縦にはみ出る場合
 | 
							// 画面から縦にはみ出る場合
 | 
				
			||||||
		if (top + height - window.pageYOffset > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
 | 
							if (top + height - window.scrollY > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
 | 
				
			||||||
			if (props.noOverlap && props.anchor.x === 'center') {
 | 
								if (props.noOverlap && props.anchor.x === 'center') {
 | 
				
			||||||
				if (underSpace >= (upperSpace / 3)) {
 | 
									if (underSpace >= (upperSpace / 3)) {
 | 
				
			||||||
					maxHeight.value = underSpace;
 | 
										maxHeight.value = underSpace;
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					maxHeight.value = upperSpace;
 | 
										maxHeight.value = upperSpace;
 | 
				
			||||||
					top = window.pageYOffset + ((upperSpace + MARGIN) - height);
 | 
										top = window.scrollY + ((upperSpace + MARGIN) - height);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height + window.pageYOffset - 1;
 | 
									top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height + window.scrollY - 1;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			maxHeight.value = underSpace;
 | 
								maxHeight.value = underSpace;
 | 
				
			||||||
| 
						 | 
					@ -255,15 +255,15 @@ const align = () => {
 | 
				
			||||||
	let transformOriginX = 'center';
 | 
						let transformOriginX = 'center';
 | 
				
			||||||
	let transformOriginY = 'center';
 | 
						let transformOriginY = 'center';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (top >= srcRect.top + props.src.offsetHeight + (fixed.value ? 0 : window.pageYOffset)) {
 | 
						if (top >= srcRect.top + props.src.offsetHeight + (fixed.value ? 0 : window.scrollY)) {
 | 
				
			||||||
		transformOriginY = 'top';
 | 
							transformOriginY = 'top';
 | 
				
			||||||
	} else if ((top + height) <= srcRect.top + (fixed.value ? 0 : window.pageYOffset)) {
 | 
						} else if ((top + height) <= srcRect.top + (fixed.value ? 0 : window.scrollY)) {
 | 
				
			||||||
		transformOriginY = 'bottom';
 | 
							transformOriginY = 'bottom';
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (left >= srcRect.left + props.src.offsetWidth + (fixed.value ? 0 : window.pageXOffset)) {
 | 
						if (left >= srcRect.left + props.src.offsetWidth + (fixed.value ? 0 : window.scrollX)) {
 | 
				
			||||||
		transformOriginX = 'left';
 | 
							transformOriginX = 'left';
 | 
				
			||||||
	} else if ((left + width) <= srcRect.left + (fixed.value ? 0 : window.pageXOffset)) {
 | 
						} else if ((left + width) <= srcRect.left + (fixed.value ? 0 : window.scrollX)) {
 | 
				
			||||||
		transformOriginX = 'right';
 | 
							transformOriginX = 'right';
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,7 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16" @click.stop @mockUpdateMyReaction="emitUpdReaction">
 | 
								<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16" @click.stop @mockUpdateMyReaction="emitUpdReaction">
 | 
				
			||||||
				<template #more>
 | 
									<template #more>
 | 
				
			||||||
					<div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div>
 | 
										<MkA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</MkA>
 | 
				
			||||||
				</template>
 | 
									</template>
 | 
				
			||||||
			</MkReactionsViewer>
 | 
								</MkReactionsViewer>
 | 
				
			||||||
			<footer :class="$style.footer">
 | 
								<footer :class="$style.footer">
 | 
				
			||||||
| 
						 | 
					@ -275,6 +275,7 @@ if (noteViewInterruptors.length > 0) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isRenote = (
 | 
					const isRenote = (
 | 
				
			||||||
	note.value.renote != null &&
 | 
						note.value.renote != null &&
 | 
				
			||||||
 | 
						note.value.reply == null &&
 | 
				
			||||||
	note.value.text == null &&
 | 
						note.value.text == null &&
 | 
				
			||||||
	note.value.cw == null &&
 | 
						note.value.cw == null &&
 | 
				
			||||||
	note.value.fileIds && note.value.fileIds.length === 0 &&
 | 
						note.value.fileIds && note.value.fileIds.length === 0 &&
 | 
				
			||||||
| 
						 | 
					@ -1254,10 +1255,9 @@ function emitUpdReaction(emoji: string, delta: number) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.reactionOmitted {
 | 
					.reactionOmitted {
 | 
				
			||||||
	display: inline-block;
 | 
						display: inline-block;
 | 
				
			||||||
	height: 32px;
 | 
						margin-left: 8px;
 | 
				
			||||||
	margin: 2px;
 | 
					 | 
				
			||||||
	padding: 0 6px;
 | 
					 | 
				
			||||||
	opacity: .8;
 | 
						opacity: .8;
 | 
				
			||||||
 | 
						font-size: 95%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.clickToOpen {
 | 
					.clickToOpen {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,7 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
		<div :class="$style.noteContent">
 | 
							<div :class="$style.noteContent">
 | 
				
			||||||
			<p v-if="appearNote.cw != null" :class="$style.cw">
 | 
								<p v-if="appearNote.cw != null" :class="$style.cw">
 | 
				
			||||||
				<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
 | 
									<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
 | 
				
			||||||
				<MkCwButton v-model="showContent" :text="appearNote.text" :files="appearNote.files" :poll="appearNote.poll"/>
 | 
									<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll"/>
 | 
				
			||||||
			</p>
 | 
								</p>
 | 
				
			||||||
			<div v-show="appearNote.cw == null || showContent">
 | 
								<div v-show="appearNote.cw == null || showContent">
 | 
				
			||||||
				<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
 | 
									<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
 | 
				
			||||||
| 
						 | 
					@ -264,10 +264,13 @@ import MkButton from '@/components/MkButton.vue';
 | 
				
			||||||
import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
 | 
					import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
 | 
				
			||||||
import { isEnabledUrlPreview } from '@/instance.js';
 | 
					import { isEnabledUrlPreview } from '@/instance.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = withDefaults(defineProps<{
 | 
				
			||||||
	note: Misskey.entities.Note;
 | 
						note: Misskey.entities.Note;
 | 
				
			||||||
	expandAllCws?: boolean;
 | 
						expandAllCws?: boolean;
 | 
				
			||||||
}>();
 | 
						initialTab: string;
 | 
				
			||||||
 | 
					}>(), {
 | 
				
			||||||
 | 
						initialTab: 'replies',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const inChannel = inject('inChannel', null);
 | 
					const inChannel = inject('inChannel', null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -294,7 +297,9 @@ if (noteViewInterruptors.length > 0) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isRenote = (
 | 
					const isRenote = (
 | 
				
			||||||
	note.value.renote != null &&
 | 
						note.value.renote != null &&
 | 
				
			||||||
 | 
						note.value.reply == null &&
 | 
				
			||||||
	note.value.text == null &&
 | 
						note.value.text == null &&
 | 
				
			||||||
 | 
						note.value.cw == null &&
 | 
				
			||||||
	note.value.fileIds && note.value.fileIds.length === 0 &&
 | 
						note.value.fileIds && note.value.fileIds.length === 0 &&
 | 
				
			||||||
	note.value.poll == null
 | 
						note.value.poll == null
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
| 
						 | 
					@ -357,7 +362,7 @@ provide('react', (reaction: string) => {
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tab = ref('replies');
 | 
					const tab = ref(props.initialTab);
 | 
				
			||||||
const reactionTabType = ref<string | null>(null);
 | 
					const reactionTabType = ref<string | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const renotesPagination = computed<Paging>(() => ({
 | 
					const renotesPagination = computed<Paging>(() => ({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -255,7 +255,13 @@ const maxTextLength = computed((): number => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const canPost = computed((): boolean => {
 | 
					const canPost = computed((): boolean => {
 | 
				
			||||||
	return !props.mock && !posting.value && !posted.value &&
 | 
						return !props.mock && !posting.value && !posted.value &&
 | 
				
			||||||
		(1 <= textLength.value || 1 <= files.value.length || !!poll.value || !!props.renote) &&
 | 
							(
 | 
				
			||||||
 | 
								1 <= textLength.value ||
 | 
				
			||||||
 | 
								1 <= files.value.length ||
 | 
				
			||||||
 | 
								poll.value != null ||
 | 
				
			||||||
 | 
								props.renote != null ||
 | 
				
			||||||
 | 
								(props.reply != null && quoteId.value != null)
 | 
				
			||||||
 | 
							) &&
 | 
				
			||||||
		(textLength.value <= maxTextLength.value) &&
 | 
							(textLength.value <= maxTextLength.value) &&
 | 
				
			||||||
		(!poll.value || poll.value.choices.length >= 2);
 | 
							(!poll.value || poll.value.choices.length >= 2);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -100,6 +100,9 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.root {
 | 
					.root {
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						flex-wrap: wrap;
 | 
				
			||||||
 | 
						align-items: center;
 | 
				
			||||||
	margin: 4px -2px 0 -2px;
 | 
						margin: 4px -2px 0 -2px;
 | 
				
			||||||
	cursor: auto; /* not clickToOpen-able */
 | 
						cursor: auto; /* not clickToOpen-able */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,13 +41,15 @@ const toggle = () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" module>
 | 
					<style lang="scss" module>
 | 
				
			||||||
.button {
 | 
					.button {
 | 
				
			||||||
 | 
						--height: 21px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	position: relative;
 | 
						position: relative;
 | 
				
			||||||
	display: inline-flex;
 | 
						display: inline-flex;
 | 
				
			||||||
	flex-shrink: 0;
 | 
						flex-shrink: 0;
 | 
				
			||||||
	margin: 0;
 | 
						margin: 0;
 | 
				
			||||||
	box-sizing: border-box;
 | 
						box-sizing: border-box;
 | 
				
			||||||
	width: 32px;
 | 
						width: calc(var(--height) * 1.6);
 | 
				
			||||||
	height: 23px;
 | 
						height: calc(var(--height) + 2px); // 枠線
 | 
				
			||||||
	outline: none;
 | 
						outline: none;
 | 
				
			||||||
	background: var(--switchOffBg);
 | 
						background: var(--switchOffBg);
 | 
				
			||||||
	background-clip: content-box;
 | 
						background-clip: content-box;
 | 
				
			||||||
| 
						 | 
					@ -69,9 +71,10 @@ const toggle = () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.knob {
 | 
					.knob {
 | 
				
			||||||
	position: absolute;
 | 
						position: absolute;
 | 
				
			||||||
 | 
						box-sizing: border-box;
 | 
				
			||||||
	top: 3px;
 | 
						top: 3px;
 | 
				
			||||||
	width: 15px;
 | 
						width: calc(var(--height) - 6px);
 | 
				
			||||||
	height: 15px;
 | 
						height: calc(var(--height) - 6px);
 | 
				
			||||||
	border-radius: var(--radius-ellipse);
 | 
						border-radius: var(--radius-ellipse);
 | 
				
			||||||
	transition: all 0.2s ease;
 | 
						transition: all 0.2s ease;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,7 +85,7 @@ const toggle = () => {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.knobChecked {
 | 
					.knobChecked {
 | 
				
			||||||
	left: 12px;
 | 
						left: calc(calc(100% - var(--height)) + 3px);
 | 
				
			||||||
	background: var(--switchOnFg);
 | 
						background: var(--switchOnFg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,8 +33,8 @@ const left = ref(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
	const rect = props.source.getBoundingClientRect();
 | 
						const rect = props.source.getBoundingClientRect();
 | 
				
			||||||
	const x = Math.max((rect.left + (props.source.offsetWidth / 2)) - (300 / 2), 6) + window.pageXOffset;
 | 
						const x = Math.max((rect.left + (props.source.offsetWidth / 2)) - (300 / 2), 6) + window.scrollX;
 | 
				
			||||||
	const y = rect.top + props.source.offsetHeight + window.pageYOffset;
 | 
						const y = rect.top + props.source.offsetHeight + window.scrollY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	top.value = y;
 | 
						top.value = y;
 | 
				
			||||||
	left.value = x;
 | 
						left.value = x;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -118,8 +118,8 @@ onMounted(() => {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const rect = props.source.getBoundingClientRect();
 | 
						const rect = props.source.getBoundingClientRect();
 | 
				
			||||||
	const x = ((rect.left + (props.source.offsetWidth / 2)) - (300 / 2)) + window.pageXOffset;
 | 
						const x = ((rect.left + (props.source.offsetWidth / 2)) - (300 / 2)) + window.scrollX;
 | 
				
			||||||
	const y = rect.top + props.source.offsetHeight + window.pageYOffset;
 | 
						const y = rect.top + props.source.offsetHeight + window.scrollY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	top.value = y;
 | 
						top.value = y;
 | 
				
			||||||
	left.value = x;
 | 
						left.value = x;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,7 +47,7 @@ const invalid = Number.isNaN(_time);
 | 
				
			||||||
const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
 | 
					const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line vue/no-setup-props-destructure
 | 
					// eslint-disable-next-line vue/no-setup-props-destructure
 | 
				
			||||||
const now = ref((props.origin ?? new Date()).getTime());
 | 
					const now = ref(props.origin?.getTime() ?? Date.now());
 | 
				
			||||||
const ago = computed(() => (now.value - _time) / 1000/*ms*/);
 | 
					const ago = computed(() => (now.value - _time) / 1000/*ms*/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const relative = computed<string>(() => {
 | 
					const relative = computed<string>(() => {
 | 
				
			||||||
| 
						 | 
					@ -77,7 +77,7 @@ let tickId: number;
 | 
				
			||||||
let currentInterval: number;
 | 
					let currentInterval: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function tick() {
 | 
					function tick() {
 | 
				
			||||||
	now.value = (new Date()).getTime();
 | 
						now.value = Date.now();
 | 
				
			||||||
	const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000;
 | 
						const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (currentInterval !== nextInterval) {
 | 
						if (currentInterval !== nextInterval) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ import XText from './page.text.vue';
 | 
				
			||||||
import XSection from './page.section.vue';
 | 
					import XSection from './page.section.vue';
 | 
				
			||||||
import XImage from './page.image.vue';
 | 
					import XImage from './page.image.vue';
 | 
				
			||||||
import XNote from './page.note.vue';
 | 
					import XNote from './page.note.vue';
 | 
				
			||||||
 | 
					import XDynamic from './page.dynamic.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getComponent(type: string) {
 | 
					function getComponent(type: string) {
 | 
				
			||||||
	switch (type) {
 | 
						switch (type) {
 | 
				
			||||||
| 
						 | 
					@ -21,6 +22,20 @@ function getComponent(type: string) {
 | 
				
			||||||
		case 'section': return XSection;
 | 
							case 'section': return XSection;
 | 
				
			||||||
		case 'image': return XImage;
 | 
							case 'image': return XImage;
 | 
				
			||||||
		case 'note': return XNote;
 | 
							case 'note': return XNote;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 動的ページの代替用ブロック
 | 
				
			||||||
 | 
							case 'button':
 | 
				
			||||||
 | 
							case 'if':
 | 
				
			||||||
 | 
							case 'textarea':
 | 
				
			||||||
 | 
							case 'post':
 | 
				
			||||||
 | 
							case 'canvas':
 | 
				
			||||||
 | 
							case 'numberInput':
 | 
				
			||||||
 | 
							case 'textInput':
 | 
				
			||||||
 | 
							case 'switch':
 | 
				
			||||||
 | 
							case 'radioButton':
 | 
				
			||||||
 | 
							case 'counter':
 | 
				
			||||||
 | 
								return XDynamic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		default: return null;
 | 
							default: return null;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										43
									
								
								packages/frontend/src/components/page/page.dynamic.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								packages/frontend/src/components/page/page.dynamic.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					<!--
 | 
				
			||||||
 | 
					SPDX-FileCopyrightText: syuilo and misskey-project
 | 
				
			||||||
 | 
					SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					-->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- 動的ページのブロックの代替。利用できないということを表示する -->
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div :class="$style.root">
 | 
				
			||||||
 | 
						<div :class="$style.heading"><i class="ti ti-dice-5"></i> {{ i18n.ts._pages.blocks.dynamic }}</div>
 | 
				
			||||||
 | 
						<I18n :src="i18n.ts._pages.blocks.dynamicDescription" tag="div" :class="$style.text">
 | 
				
			||||||
 | 
							<template #play>
 | 
				
			||||||
 | 
								<MkA to="/play" class="_link">Play</MkA>
 | 
				
			||||||
 | 
							</template>
 | 
				
			||||||
 | 
						</I18n>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import * as Misskey from 'misskey-js';
 | 
				
			||||||
 | 
					import { i18n } from '@/i18n.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps<{
 | 
				
			||||||
 | 
						block: Misskey.entities.PageBlock,
 | 
				
			||||||
 | 
						page: Misskey.entities.Page,
 | 
				
			||||||
 | 
					}>();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" module>
 | 
				
			||||||
 | 
					.root {
 | 
				
			||||||
 | 
						border: 1px solid var(--divider);
 | 
				
			||||||
 | 
						border-radius: var(--radius);
 | 
				
			||||||
 | 
						padding: var(--margin);
 | 
				
			||||||
 | 
						text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.heading {
 | 
				
			||||||
 | 
						font-weight: 700;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.text {
 | 
				
			||||||
 | 
						font-size: 90%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="_gaps" :class="$style.textRoot">
 | 
					<div class="_gaps" :class="$style.textRoot">
 | 
				
			||||||
	<Mfm :text="block.text ?? ''" :isNote="false"/>
 | 
						<Mfm :text="block.text ?? ''" :isNote="false"/>
 | 
				
			||||||
	<div v-if="isEnabledUrlPreview">
 | 
						<div v-if="isEnabledUrlPreview" class="_gaps_s">
 | 
				
			||||||
		<MkUrlPreview v-for="url in urls" :key="url" :url="url"/>
 | 
							<MkUrlPreview v-for="url in urls" :key="url" :url="url"/>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,11 +21,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
						<div v-if="defaultStore.state.noteDesign === 'misskey'" class="_margin _gaps_s">
 | 
											<div v-if="defaultStore.state.noteDesign === 'misskey'" class="_margin _gaps_s">
 | 
				
			||||||
							<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
 | 
												<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
 | 
				
			||||||
							<MkNoteDetailed :key="note.id" v-model:note="note" :class="$style.note" :expandAllCws="expandAllCws"/>
 | 
												<MkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note" :expandAllCws="expandAllCws"/>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
						<div v-else-if="defaultStore.state.noteDesign === 'sharkey'" class="_margin _gaps_s">
 | 
											<div v-else-if="defaultStore.state.noteDesign === 'sharkey'" class="_margin _gaps_s">
 | 
				
			||||||
							<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
 | 
												<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
 | 
				
			||||||
							<SkNoteDetailed :key="note.id" v-model:note="note" :class="$style.note" :expandAllCws="expandAllCws"/>
 | 
												<SkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note" :expandAllCws="expandAllCws"/>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
						<div v-if="clips && clips.length > 0" class="_margin">
 | 
											<div v-if="clips && clips.length > 0" class="_margin">
 | 
				
			||||||
							<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
 | 
												<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
 | 
				
			||||||
| 
						 | 
					@ -71,6 +71,7 @@ import { defaultStore } from '@/store.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = defineProps<{
 | 
				
			||||||
	noteId: string;
 | 
						noteId: string;
 | 
				
			||||||
 | 
						initialTab?: string;
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const note = ref<null | Misskey.entities.Note>();
 | 
					const note = ref<null | Misskey.entities.Note>();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				<div style="height: 100cqh; overflow: auto; text-align: center;">
 | 
									<div style="height: 100cqh; overflow: auto; text-align: center;">
 | 
				
			||||||
					<MkSpacer :marginMin="20" :marginMax="28">
 | 
										<MkSpacer :marginMin="20" :marginMax="28">
 | 
				
			||||||
						<div class="_gaps">
 | 
											<div class="_gaps">
 | 
				
			||||||
 | 
												<MkInfo><MkLink url="https://misskey-hub.net/docs/for-users/stepped-guides/how-to-enable-2fa/" target="_blank">{{ i18n.ts._2fa.moreDetailedGuideHere }}</MkLink></MkInfo>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							<I18n :src="i18n.ts._2fa.step1" tag="div">
 | 
												<I18n :src="i18n.ts._2fa.step1" tag="div">
 | 
				
			||||||
								<template #a>
 | 
													<template #a>
 | 
				
			||||||
									<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
 | 
														<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
 | 
				
			||||||
| 
						 | 
					@ -33,8 +35,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
									<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a>
 | 
														<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a>
 | 
				
			||||||
								</template>
 | 
													</template>
 | 
				
			||||||
							</I18n>
 | 
												</I18n>
 | 
				
			||||||
							<div>{{ i18n.ts._2fa.step2 }}<br>{{ i18n.ts._2fa.step2Click }}</div>
 | 
												<div>{{ i18n.ts._2fa.step2 }}</div>
 | 
				
			||||||
							<a :href="twoFactorData.url"><img :class="$style.qr" :src="twoFactorData.qr"></a>
 | 
												<div>
 | 
				
			||||||
 | 
													<a :class="$style.qrRoot" :href="twoFactorData.url"><img :class="$style.qr" :src="twoFactorData.qr"></a>
 | 
				
			||||||
 | 
													<!-- QRコード側にマージンが入っているので直下でOK -->
 | 
				
			||||||
 | 
													<div><MkButton inline rounded link :to="twoFactorData.url" :linkBehavior="'browser'">{{ i18n.ts.launchApp }}</MkButton></div>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
							<MkKeyValue :copy="twoFactorData.url">
 | 
												<MkKeyValue :copy="twoFactorData.url">
 | 
				
			||||||
								<template #key>{{ i18n.ts._2fa.step2Uri }}</template>
 | 
													<template #key>{{ i18n.ts._2fa.step2Uri }}</template>
 | 
				
			||||||
								<template #value>{{ twoFactorData.url }}</template>
 | 
													<template #value>{{ twoFactorData.url }}</template>
 | 
				
			||||||
| 
						 | 
					@ -109,6 +115,7 @@ import { i18n } from '@/i18n.js';
 | 
				
			||||||
import * as os from '@/os.js';
 | 
					import * as os from '@/os.js';
 | 
				
			||||||
import MkFolder from '@/components/MkFolder.vue';
 | 
					import MkFolder from '@/components/MkFolder.vue';
 | 
				
			||||||
import MkInfo from '@/components/MkInfo.vue';
 | 
					import MkInfo from '@/components/MkInfo.vue';
 | 
				
			||||||
 | 
					import MkLink from '@/components/MkLink.vue';
 | 
				
			||||||
import { confetti } from '@/scripts/confetti.js';
 | 
					import { confetti } from '@/scripts/confetti.js';
 | 
				
			||||||
import { signinRequired } from '@/account.js';
 | 
					import { signinRequired } from '@/account.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -177,8 +184,14 @@ function allDone() {
 | 
				
			||||||
	transform: translateX(-50px);
 | 
						transform: translateX(-50px);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.qr {
 | 
					.qrRoot {
 | 
				
			||||||
 | 
						display: block;
 | 
				
			||||||
 | 
						margin: 0 auto;
 | 
				
			||||||
	width: 200px;
 | 
						width: 200px;
 | 
				
			||||||
	max-width: 100%;
 | 
						max-width: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.qr {
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				<MkButton v-else danger @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
 | 
									<MkButton v-else danger @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<MkButton v-else-if="!$i.twoFactorEnabled" primary gradate @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
 | 
								<div v-else-if="!$i.twoFactorEnabled" class="_gaps_s">
 | 
				
			||||||
 | 
									<MkButton primary gradate @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
 | 
				
			||||||
 | 
									<MkLink url="https://misskey-hub.net/docs/for-users/stepped-guides/how-to-enable-2fa/" target="_blank"><i class="ti ti-help-circle"></i> {{ i18n.ts.learnMore }}</MkLink>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
		</MkFolder>
 | 
							</MkFolder>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<MkFolder>
 | 
							<MkFolder>
 | 
				
			||||||
| 
						 | 
					@ -79,6 +82,7 @@ import MkInfo from '@/components/MkInfo.vue';
 | 
				
			||||||
import MkSwitch from '@/components/MkSwitch.vue';
 | 
					import MkSwitch from '@/components/MkSwitch.vue';
 | 
				
			||||||
import FormSection from '@/components/form/section.vue';
 | 
					import FormSection from '@/components/form/section.vue';
 | 
				
			||||||
import MkFolder from '@/components/MkFolder.vue';
 | 
					import MkFolder from '@/components/MkFolder.vue';
 | 
				
			||||||
 | 
					import MkLink from '@/components/MkLink.vue';
 | 
				
			||||||
import * as os from '@/os.js';
 | 
					import * as os from '@/os.js';
 | 
				
			||||||
import { signinRequired, updateAccount } from '@/account.js';
 | 
					import { signinRequired, updateAccount } from '@/account.js';
 | 
				
			||||||
import { i18n } from '@/i18n.js';
 | 
					import { i18n } from '@/i18n.js';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -148,6 +148,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
 | 
									<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
 | 
				
			||||||
				<MkSwitch v-model="oneko">{{ i18n.ts.oneko }}</MkSwitch>
 | 
									<MkSwitch v-model="oneko">{{ i18n.ts.oneko }}</MkSwitch>
 | 
				
			||||||
				<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
 | 
									<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
 | 
				
			||||||
 | 
									<MkSwitch v-model="useNativeUIForVideoAudioPlayer">{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</MkSwitch>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
				<MkRadios v-model="emojiStyle">
 | 
									<MkRadios v-model="emojiStyle">
 | 
				
			||||||
| 
						 | 
					@ -364,6 +365,7 @@ const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enabl
 | 
				
			||||||
const showVisibilitySelectorOnBoost = computed(defaultStore.makeGetterSetter('showVisibilitySelectorOnBoost'));
 | 
					const showVisibilitySelectorOnBoost = computed(defaultStore.makeGetterSetter('showVisibilitySelectorOnBoost'));
 | 
				
			||||||
const visibilityOnBoost = computed(defaultStore.makeGetterSetter('visibilityOnBoost'));
 | 
					const visibilityOnBoost = computed(defaultStore.makeGetterSetter('visibilityOnBoost'));
 | 
				
			||||||
const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
 | 
					const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
 | 
				
			||||||
 | 
					const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(lang, () => {
 | 
					watch(lang, () => {
 | 
				
			||||||
	miLocalStorage.setItem('lang', lang.value as string);
 | 
						miLocalStorage.setItem('lang', lang.value as string);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,7 @@ const routes: RouteDef[] = [{
 | 
				
			||||||
	component: page(() => import('@/pages/user/index.vue')),
 | 
						component: page(() => import('@/pages/user/index.vue')),
 | 
				
			||||||
}, {
 | 
					}, {
 | 
				
			||||||
	name: 'note',
 | 
						name: 'note',
 | 
				
			||||||
	path: '/notes/:noteId',
 | 
						path: '/notes/:noteId/:initialTab?',
 | 
				
			||||||
	component: page(() => import('@/pages/note.vue')),
 | 
						component: page(() => import('@/pages/note.vue')),
 | 
				
			||||||
}, {
 | 
					}, {
 | 
				
			||||||
	name: 'list',
 | 
						name: 'list',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ export default (input: string): string[] => {
 | 
				
			||||||
export const aliases = {
 | 
					export const aliases = {
 | 
				
			||||||
	'esc': 'Escape',
 | 
						'esc': 'Escape',
 | 
				
			||||||
	'enter': ['Enter', 'NumpadEnter'],
 | 
						'enter': ['Enter', 'NumpadEnter'],
 | 
				
			||||||
 | 
						'space': [' ', 'Spacebar'],
 | 
				
			||||||
	'up': 'ArrowUp',
 | 
						'up': 'ArrowUp',
 | 
				
			||||||
	'down': 'ArrowDown',
 | 
						'down': 'ArrowDown',
 | 
				
			||||||
	'left': 'ArrowLeft',
 | 
						'left': 'ArrowLeft',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,8 +26,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
		let top: number;
 | 
							let top: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (props.anchorElement) {
 | 
							if (props.anchorElement) {
 | 
				
			||||||
			left = rect.left + window.pageXOffset + (props.anchorElement.offsetWidth / 2);
 | 
								left = rect.left + window.scrollX + (props.anchorElement.offsetWidth / 2);
 | 
				
			||||||
			top = (rect.top + window.pageYOffset - contentHeight) - props.innerMargin;
 | 
								top = (rect.top + window.scrollY - contentHeight) - props.innerMargin;
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			left = props.x;
 | 
								left = props.x;
 | 
				
			||||||
			top = (props.y - contentHeight) - props.innerMargin;
 | 
								top = (props.y - contentHeight) - props.innerMargin;
 | 
				
			||||||
| 
						 | 
					@ -35,8 +35,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		left -= (el.offsetWidth / 2);
 | 
							left -= (el.offsetWidth / 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (left + contentWidth - window.pageXOffset > window.innerWidth) {
 | 
							if (left + contentWidth - window.scrollX > window.innerWidth) {
 | 
				
			||||||
			left = window.innerWidth - contentWidth + window.pageXOffset - 1;
 | 
								left = window.innerWidth - contentWidth + window.scrollX - 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return [left, top];
 | 
							return [left, top];
 | 
				
			||||||
| 
						 | 
					@ -47,8 +47,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
		let top: number;
 | 
							let top: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (props.anchorElement) {
 | 
							if (props.anchorElement) {
 | 
				
			||||||
			left = rect.left + window.pageXOffset + (props.anchorElement.offsetWidth / 2);
 | 
								left = rect.left + window.scrollX + (props.anchorElement.offsetWidth / 2);
 | 
				
			||||||
			top = (rect.top + window.pageYOffset + props.anchorElement.offsetHeight) + props.innerMargin;
 | 
								top = (rect.top + window.scrollY + props.anchorElement.offsetHeight) + props.innerMargin;
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			left = props.x;
 | 
								left = props.x;
 | 
				
			||||||
			top = (props.y) + props.innerMargin;
 | 
								top = (props.y) + props.innerMargin;
 | 
				
			||||||
| 
						 | 
					@ -56,8 +56,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		left -= (el.offsetWidth / 2);
 | 
							left -= (el.offsetWidth / 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (left + contentWidth - window.pageXOffset > window.innerWidth) {
 | 
							if (left + contentWidth - window.scrollX > window.innerWidth) {
 | 
				
			||||||
			left = window.innerWidth - contentWidth + window.pageXOffset - 1;
 | 
								left = window.innerWidth - contentWidth + window.scrollX - 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return [left, top];
 | 
							return [left, top];
 | 
				
			||||||
| 
						 | 
					@ -68,8 +68,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
		let top: number;
 | 
							let top: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (props.anchorElement) {
 | 
							if (props.anchorElement) {
 | 
				
			||||||
			left = (rect.left + window.pageXOffset - contentWidth) - props.innerMargin;
 | 
								left = (rect.left + window.scrollX - contentWidth) - props.innerMargin;
 | 
				
			||||||
			top = rect.top + window.pageYOffset + (props.anchorElement.offsetHeight / 2);
 | 
								top = rect.top + window.scrollY + (props.anchorElement.offsetHeight / 2);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			left = (props.x - contentWidth) - props.innerMargin;
 | 
								left = (props.x - contentWidth) - props.innerMargin;
 | 
				
			||||||
			top = props.y;
 | 
								top = props.y;
 | 
				
			||||||
| 
						 | 
					@ -77,8 +77,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		top -= (el.offsetHeight / 2);
 | 
							top -= (el.offsetHeight / 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (top + contentHeight - window.pageYOffset > window.innerHeight) {
 | 
							if (top + contentHeight - window.scrollY > window.innerHeight) {
 | 
				
			||||||
			top = window.innerHeight - contentHeight + window.pageYOffset - 1;
 | 
								top = window.innerHeight - contentHeight + window.scrollY - 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return [left, top];
 | 
							return [left, top];
 | 
				
			||||||
| 
						 | 
					@ -89,15 +89,15 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
		let top: number;
 | 
							let top: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (props.anchorElement) {
 | 
							if (props.anchorElement) {
 | 
				
			||||||
			left = (rect.left + props.anchorElement.offsetWidth + window.pageXOffset) + props.innerMargin;
 | 
								left = (rect.left + props.anchorElement.offsetWidth + window.scrollX) + props.innerMargin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (props.align === 'top') {
 | 
								if (props.align === 'top') {
 | 
				
			||||||
				top = rect.top + window.pageYOffset;
 | 
									top = rect.top + window.scrollY;
 | 
				
			||||||
				if (props.alignOffset != null) top += props.alignOffset;
 | 
									if (props.alignOffset != null) top += props.alignOffset;
 | 
				
			||||||
			} else if (props.align === 'bottom') {
 | 
								} else if (props.align === 'bottom') {
 | 
				
			||||||
				// TODO
 | 
									// TODO
 | 
				
			||||||
			} else { // center
 | 
								} else { // center
 | 
				
			||||||
				top = rect.top + window.pageYOffset + (props.anchorElement.offsetHeight / 2);
 | 
									top = rect.top + window.scrollY + (props.anchorElement.offsetHeight / 2);
 | 
				
			||||||
				top -= (el.offsetHeight / 2);
 | 
									top -= (el.offsetHeight / 2);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
| 
						 | 
					@ -106,8 +106,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
			top -= (el.offsetHeight / 2);
 | 
								top -= (el.offsetHeight / 2);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (top + contentHeight - window.pageYOffset > window.innerHeight) {
 | 
							if (top + contentHeight - window.scrollY > window.innerHeight) {
 | 
				
			||||||
			top = window.innerHeight - contentHeight + window.pageYOffset - 1;
 | 
								top = window.innerHeight - contentHeight + window.scrollY - 1;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return [left, top];
 | 
							return [left, top];
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,7 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
				const [left, top] = calcPosWhenTop();
 | 
									const [left, top] = calcPosWhenTop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// ツールチップを上に向かって表示するスペースがなければ下に向かって出す
 | 
									// ツールチップを上に向かって表示するスペースがなければ下に向かって出す
 | 
				
			||||||
				if (top - window.pageYOffset < 0) {
 | 
									if (top - window.scrollY < 0) {
 | 
				
			||||||
					const [left, top] = calcPosWhenBottom();
 | 
										const [left, top] = calcPosWhenBottom();
 | 
				
			||||||
					return { left, top, transformOrigin: 'center top' };
 | 
										return { left, top, transformOrigin: 'center top' };
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
| 
						 | 
					@ -141,7 +141,7 @@ export function calcPopupPosition(el: HTMLElement, props: {
 | 
				
			||||||
				const [left, top] = calcPosWhenLeft();
 | 
									const [left, top] = calcPosWhenLeft();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// ツールチップを左に向かって表示するスペースがなければ右に向かって出す
 | 
									// ツールチップを左に向かって表示するスペースがなければ右に向かって出す
 | 
				
			||||||
				if (left - window.pageXOffset < 0) {
 | 
									if (left - window.scrollX < 0) {
 | 
				
			||||||
					const [left, top] = calcPosWhenRight();
 | 
										const [left, top] = calcPosWhenRight();
 | 
				
			||||||
					return { left, top, transformOrigin: 'left center' };
 | 
										return { left, top, transformOrigin: 'left center' };
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,11 +53,11 @@ export function useChartTooltip(opts: { position: 'top' | 'middle' } = { positio
 | 
				
			||||||
		const rect = context.chart.canvas.getBoundingClientRect();
 | 
							const rect = context.chart.canvas.getBoundingClientRect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		tooltipShowing.value = true;
 | 
							tooltipShowing.value = true;
 | 
				
			||||||
		tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX;
 | 
							tooltipX.value = rect.left + window.scrollX + context.tooltip.caretX;
 | 
				
			||||||
		if (opts.position === 'top') {
 | 
							if (opts.position === 'top') {
 | 
				
			||||||
			tooltipY.value = rect.top + window.pageYOffset;
 | 
								tooltipY.value = rect.top + window.scrollY;
 | 
				
			||||||
		} else if (opts.position === 'middle') {
 | 
							} else if (opts.position === 'middle') {
 | 
				
			||||||
			tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY;
 | 
								tooltipY.value = rect.top + window.scrollY + context.tooltip.caretY;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -495,6 +495,10 @@ export const defaultStore = markRaw(new Storage('base', {
 | 
				
			||||||
		where: 'device',
 | 
							where: 'device',
 | 
				
			||||||
		default: true,
 | 
							default: true,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						useNativeUIForVideoAudioPlayer: {
 | 
				
			||||||
 | 
							where: 'device',
 | 
				
			||||||
 | 
							default: false,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sound_masterVolume: {
 | 
						sound_masterVolume: {
 | 
				
			||||||
		where: 'device',
 | 
							where: 'device',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,8 @@
 | 
				
			||||||
import * as Misskey from 'misskey-js';
 | 
					import * as Misskey from 'misskey-js';
 | 
				
			||||||
import { ComputedRef, Ref } from 'vue';
 | 
					import { ComputedRef, Ref } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface MenuRadioOptionsDef extends Record<string, any> { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type MenuAction = (ev: MouseEvent) => void;
 | 
					export type MenuAction = (ev: MouseEvent) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type MenuDivider = { type: 'divider' };
 | 
					export type MenuDivider = { type: 'divider' };
 | 
				
			||||||
| 
						 | 
					@ -14,13 +16,15 @@ export type MenuLabel = { type: 'label', text: string };
 | 
				
			||||||
export type MenuLink = { type: 'link', to: string, text: string, icon?: string, indicate?: boolean, avatar?: Misskey.entities.User };
 | 
					export type MenuLink = { type: 'link', to: string, text: string, icon?: string, indicate?: boolean, avatar?: Misskey.entities.User };
 | 
				
			||||||
export type MenuA = { type: 'a', href: string, target?: string, download?: string, text: string, icon?: string, indicate?: boolean };
 | 
					export type MenuA = { type: 'a', href: string, target?: string, download?: string, text: string, icon?: string, indicate?: boolean };
 | 
				
			||||||
export type MenuUser = { type: 'user', user: Misskey.entities.User, active?: boolean, indicate?: boolean, action: MenuAction };
 | 
					export type MenuUser = { type: 'user', user: Misskey.entities.User, active?: boolean, indicate?: boolean, action: MenuAction };
 | 
				
			||||||
export type MenuSwitch = { type: 'switch', ref: Ref<boolean>, text: string, disabled?: boolean | Ref<boolean> };
 | 
					export type MenuSwitch = { type: 'switch', ref: Ref<boolean>, text: string, icon?: string, disabled?: boolean | Ref<boolean> };
 | 
				
			||||||
export type MenuButton = { type?: 'button', text: string, icon?: string, indicate?: boolean, danger?: boolean, active?: boolean | ComputedRef<boolean>, avatar?: Misskey.entities.User; action: MenuAction };
 | 
					export type MenuButton = { type?: 'button', text: string, icon?: string, indicate?: boolean, danger?: boolean, active?: boolean | ComputedRef<boolean>, avatar?: Misskey.entities.User; action: MenuAction };
 | 
				
			||||||
 | 
					export type MenuRadio = { type: 'radio', text: string, icon?: string, ref: Ref<MenuRadioOptionsDef[keyof MenuRadioOptionsDef]>, options: MenuRadioOptionsDef, disabled?: boolean | Ref<boolean> };
 | 
				
			||||||
 | 
					export type MenuRadioOption = { type: 'radioOption', text: string, action: MenuAction; active?: boolean | ComputedRef<boolean> };
 | 
				
			||||||
export type MenuParent = { type: 'parent', text: string, icon?: string, children: MenuItem[] | (() => Promise<MenuItem[]> | MenuItem[]) };
 | 
					export type MenuParent = { type: 'parent', text: string, icon?: string, children: MenuItem[] | (() => Promise<MenuItem[]> | MenuItem[]) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type MenuPending = { type: 'pending' };
 | 
					export type MenuPending = { type: 'pending' };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type OuterMenuItem = MenuDivider | MenuNull | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuParent;
 | 
					type OuterMenuItem = MenuDivider | MenuNull | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuParent;
 | 
				
			||||||
type OuterPromiseMenuItem = Promise<MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuParent>;
 | 
					type OuterPromiseMenuItem = Promise<MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuParent>;
 | 
				
			||||||
export type MenuItem = OuterMenuItem | OuterPromiseMenuItem;
 | 
					export type MenuItem = OuterMenuItem | OuterPromiseMenuItem;
 | 
				
			||||||
export type InnerMenuItem = MenuDivider | MenuPending | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuParent;
 | 
					export type InnerMenuItem = MenuDivider | MenuPending | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuParent;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
<MkContainer :showHeader="widgetProps.showHeader" class="mkw-bdayfollowings">
 | 
					<MkContainer :showHeader="widgetProps.showHeader" class="mkw-bdayfollowings">
 | 
				
			||||||
	<template #icon><i class="ph-cake ph-bold ph-lg"></i></template>
 | 
						<template #icon><i class="ph-cake ph-bold ph-lg"></i></template>
 | 
				
			||||||
	<template #header>{{ i18n.ts._widgets.birthdayFollowings }}</template>
 | 
						<template #header>{{ i18n.ts._widgets.birthdayFollowings }}</template>
 | 
				
			||||||
 | 
						<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="actualFetch()"><i class="ti ti-refresh"></i></button></template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div :class="$style.bdayFRoot">
 | 
						<div :class="$style.bdayFRoot">
 | 
				
			||||||
		<MkLoading v-if="fetching"/>
 | 
							<MkLoading v-if="fetching"/>
 | 
				
			||||||
| 
						 | 
					@ -53,7 +54,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 | 
				
			||||||
	emit,
 | 
						emit,
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const users = ref<Misskey.entities.FollowingFolloweePopulated[]>([]);
 | 
					const users = ref<Misskey.Endpoints['users/following']['res']>([]);
 | 
				
			||||||
const fetching = ref(true);
 | 
					const fetching = ref(true);
 | 
				
			||||||
let lastFetchedAt = '1970-01-01';
 | 
					let lastFetchedAt = '1970-01-01';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,19 +71,35 @@ const fetch = () => {
 | 
				
			||||||
	now.setHours(0, 0, 0, 0);
 | 
						now.setHours(0, 0, 0, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (now > lfAtD) {
 | 
						if (now > lfAtD) {
 | 
				
			||||||
		misskeyApi('users/following', {
 | 
							actualFetch();
 | 
				
			||||||
			limit: 18,
 | 
					 | 
				
			||||||
			birthday: now.toISOString(),
 | 
					 | 
				
			||||||
			userId: $i.id,
 | 
					 | 
				
			||||||
		}).then(res => {
 | 
					 | 
				
			||||||
			users.value = res;
 | 
					 | 
				
			||||||
			fetching.value = false;
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		lastFetchedAt = now.toISOString();
 | 
							lastFetchedAt = now.toISOString();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function actualFetch() {
 | 
				
			||||||
 | 
						if ($i == null) {
 | 
				
			||||||
 | 
							users.value = [];
 | 
				
			||||||
 | 
							fetching.value = false;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const now = new Date();
 | 
				
			||||||
 | 
						now.setHours(0, 0, 0, 0);
 | 
				
			||||||
 | 
						fetching.value = true;
 | 
				
			||||||
 | 
						misskeyApi('users/following', {
 | 
				
			||||||
 | 
							limit: 18,
 | 
				
			||||||
 | 
							birthday: `${now.getFullYear().toString().padStart(4, '0')}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')}`,
 | 
				
			||||||
 | 
							userId: $i.id,
 | 
				
			||||||
 | 
						}).then(res => {
 | 
				
			||||||
 | 
							users.value = res;
 | 
				
			||||||
 | 
							window.setTimeout(() => {
 | 
				
			||||||
 | 
								// 早すぎるとチカチカする
 | 
				
			||||||
 | 
								fetching.value = false;
 | 
				
			||||||
 | 
							}, 100);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
useInterval(fetch, 1000 * 60, {
 | 
					useInterval(fetch, 1000 * 60, {
 | 
				
			||||||
	immediate: true,
 | 
						immediate: true,
 | 
				
			||||||
	afterMounted: true,
 | 
						afterMounted: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,9 +68,9 @@ watch(showColon, (v) => {
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tick = () => {
 | 
					const tick = () => {
 | 
				
			||||||
	const now = new Date();
 | 
						const now = Date.now();
 | 
				
			||||||
	ss.value = Math.floor(now.getTime() / 1000).toString();
 | 
						ss.value = Math.floor(now / 1000).toString();
 | 
				
			||||||
	ms.value = Math.floor(now.getTime() % 1000 / 10).toString().padStart(2, '0');
 | 
						ms.value = Math.floor(now % 1000 / 10).toString().padStart(2, '0');
 | 
				
			||||||
	if (ss.value !== prevSec) showColon.value = true;
 | 
						if (ss.value !== prevSec) showColon.value = true;
 | 
				
			||||||
	prevSec = ss.value;
 | 
						prevSec = ss.value;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,3 +5,4 @@ node_modules
 | 
				
			||||||
/jest.config.ts
 | 
					/jest.config.ts
 | 
				
			||||||
/test
 | 
					/test
 | 
				
			||||||
/test-d
 | 
					/test-d
 | 
				
			||||||
 | 
					build.js
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,31 +1,105 @@
 | 
				
			||||||
 | 
					import * as esbuild from "esbuild";
 | 
				
			||||||
import { build } from "esbuild";
 | 
					import { build } from "esbuild";
 | 
				
			||||||
import { globSync } from "glob";
 | 
					import { globSync } from "glob";
 | 
				
			||||||
 | 
					import { execa } from "execa";
 | 
				
			||||||
 | 
					import fs from "node:fs";
 | 
				
			||||||
 | 
					import { fileURLToPath } from "node:url";
 | 
				
			||||||
 | 
					import { dirname } from "node:path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const _filename = fileURLToPath(import.meta.url);
 | 
				
			||||||
 | 
					const _dirname = dirname(_filename);
 | 
				
			||||||
 | 
					const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const entryPoints = globSync("./src/**/**.{ts,tsx}");
 | 
					const entryPoints = globSync("./src/**/**.{ts,tsx}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** @type {import('esbuild').BuildOptions} */
 | 
					/** @type {import('esbuild').BuildOptions} */
 | 
				
			||||||
const options = {
 | 
					const options = {
 | 
				
			||||||
  entryPoints,
 | 
						entryPoints,
 | 
				
			||||||
  minify: true,
 | 
						minify: process.env.NODE_ENV === 'production',
 | 
				
			||||||
  outdir: "./built/esm",
 | 
						outdir: "./built",
 | 
				
			||||||
  target: "es2022",
 | 
						target: "es2022",
 | 
				
			||||||
  platform: "browser",
 | 
						platform: "browser",
 | 
				
			||||||
  format: "esm",
 | 
						format: "esm",
 | 
				
			||||||
 | 
						sourcemap: 'linked',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (process.env.WATCH === "true") {
 | 
					// built配下をすべて削除する
 | 
				
			||||||
  options.watch = {
 | 
					fs.rmSync('./built', { recursive: true, force: true });
 | 
				
			||||||
    onRebuild(error, result) {
 | 
					
 | 
				
			||||||
      if (error) {
 | 
					if (process.argv.map(arg => arg.toLowerCase()).includes("--watch")) {
 | 
				
			||||||
        console.error("watch build failed:", error);
 | 
						await watchSrc();
 | 
				
			||||||
      } else {
 | 
					} else {
 | 
				
			||||||
        console.log("watch build succeeded:", result);
 | 
						await buildSrc();
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
build(options).catch((err) => {
 | 
					async function buildSrc() {
 | 
				
			||||||
  process.stderr.write(err.stderr);
 | 
						console.log(`[${_package.name}] start building...`);
 | 
				
			||||||
  process.exit(1);
 | 
					
 | 
				
			||||||
});
 | 
						await build(options)
 | 
				
			||||||
 | 
							.then(it => {
 | 
				
			||||||
 | 
								console.log(`[${_package.name}] build succeeded.`);
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.catch((err) => {
 | 
				
			||||||
 | 
								process.stderr.write(err.stderr);
 | 
				
			||||||
 | 
								process.exit(1);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (process.env.NODE_ENV === 'production') {
 | 
				
			||||||
 | 
							console.log(`[${_package.name}] skip building d.ts because NODE_ENV is production.`);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							await buildDts();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log(`[${_package.name}] finish building.`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function buildDts() {
 | 
				
			||||||
 | 
						return execa(
 | 
				
			||||||
 | 
							'tsc',
 | 
				
			||||||
 | 
							[
 | 
				
			||||||
 | 
								'--project', 'tsconfig.json',
 | 
				
			||||||
 | 
								'--outDir', 'built',
 | 
				
			||||||
 | 
								'--declaration', 'true',
 | 
				
			||||||
 | 
								'--emitDeclarationOnly', 'true',
 | 
				
			||||||
 | 
							],
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								stdout: process.stdout,
 | 
				
			||||||
 | 
								stderr: process.stderr,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function watchSrc() {
 | 
				
			||||||
 | 
						const plugins = [{
 | 
				
			||||||
 | 
							name: 'gen-dts',
 | 
				
			||||||
 | 
							setup(build) {
 | 
				
			||||||
 | 
								build.onStart(() => {
 | 
				
			||||||
 | 
									console.log(`[${_package.name}] detect changed...`);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								build.onEnd(async result => {
 | 
				
			||||||
 | 
									if (result.errors.length > 0) {
 | 
				
			||||||
 | 
										console.error(`[${_package.name}] watch build failed:`, result);
 | 
				
			||||||
 | 
										return;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									await buildDts();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log(`[${_package.name}] start watching...`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const context = await esbuild.context({ ...options, plugins });
 | 
				
			||||||
 | 
						await context.watch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						await new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
							process.on('SIGHUP', resolve);
 | 
				
			||||||
 | 
							process.on('SIGINT', resolve);
 | 
				
			||||||
 | 
							process.on('SIGTERM', resolve);
 | 
				
			||||||
 | 
							process.on('SIGKILL', resolve);
 | 
				
			||||||
 | 
							process.on('uncaughtException', reject);
 | 
				
			||||||
 | 
							process.on('exit', resolve);
 | 
				
			||||||
 | 
						}).finally(async () => {
 | 
				
			||||||
 | 
							await context.dispose();
 | 
				
			||||||
 | 
							console.log(`[${_package.name}] finish watching.`);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,24 +2,21 @@
 | 
				
			||||||
	"type": "module",
 | 
						"type": "module",
 | 
				
			||||||
	"name": "misskey-bubble-game",
 | 
						"name": "misskey-bubble-game",
 | 
				
			||||||
	"version": "0.0.1",
 | 
						"version": "0.0.1",
 | 
				
			||||||
	"types": "./built/dts/index.d.ts",
 | 
						"main": "./built/index.js",
 | 
				
			||||||
 | 
						"types": "./built/index.d.ts",
 | 
				
			||||||
	"exports": {
 | 
						"exports": {
 | 
				
			||||||
		".": {
 | 
							".": {
 | 
				
			||||||
			"import": "./built/esm/index.js",
 | 
								"import": "./built/index.js",
 | 
				
			||||||
			"types": "./built/dts/index.d.ts"
 | 
								"types": "./built/index.d.ts"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"./*": {
 | 
							"./*": {
 | 
				
			||||||
			"import": "./built/esm/*",
 | 
								"import": "./built/*",
 | 
				
			||||||
			"types": "./built/dts/*"
 | 
								"types": "./built/*"
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"scripts": {
 | 
						"scripts": {
 | 
				
			||||||
		"build": "node ./build.js",
 | 
							"build": "node ./build.js",
 | 
				
			||||||
		"build:tsc": "npm run tsc",
 | 
							"watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"",
 | 
				
			||||||
		"tsc": "npm run tsc-esm && npm run tsc-dts",
 | 
					 | 
				
			||||||
		"tsc-esm": "tsc --outDir built/esm",
 | 
					 | 
				
			||||||
		"tsc-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true",
 | 
					 | 
				
			||||||
		"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build:tsc\"",
 | 
					 | 
				
			||||||
		"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
 | 
							"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
 | 
				
			||||||
		"typecheck": "tsc --noEmit",
 | 
							"typecheck": "tsc --noEmit",
 | 
				
			||||||
		"lint": "pnpm typecheck && pnpm eslint"
 | 
							"lint": "pnpm typecheck && pnpm eslint"
 | 
				
			||||||
| 
						 | 
					@ -27,21 +24,22 @@
 | 
				
			||||||
	"devDependencies": {
 | 
						"devDependencies": {
 | 
				
			||||||
		"@misskey-dev/eslint-plugin": "1.0.0",
 | 
							"@misskey-dev/eslint-plugin": "1.0.0",
 | 
				
			||||||
		"@types/matter-js": "0.19.6",
 | 
							"@types/matter-js": "0.19.6",
 | 
				
			||||||
		"@types/node": "20.11.5",
 | 
					 | 
				
			||||||
		"@types/seedrandom": "3.0.8",
 | 
							"@types/seedrandom": "3.0.8",
 | 
				
			||||||
 | 
							"@types/node": "20.11.5",
 | 
				
			||||||
		"@typescript-eslint/eslint-plugin": "7.1.0",
 | 
							"@typescript-eslint/eslint-plugin": "7.1.0",
 | 
				
			||||||
		"@typescript-eslint/parser": "7.1.0",
 | 
							"@typescript-eslint/parser": "7.1.0",
 | 
				
			||||||
		"eslint": "8.57.0",
 | 
							"eslint": "8.57.0",
 | 
				
			||||||
		"nodemon": "3.0.2",
 | 
							"nodemon": "3.0.2",
 | 
				
			||||||
		"typescript": "5.3.3"
 | 
							"execa": "8.0.1",
 | 
				
			||||||
 | 
							"typescript": "5.3.3",
 | 
				
			||||||
 | 
							"esbuild": "0.19.11",
 | 
				
			||||||
 | 
							"glob": "10.3.10"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"files": [
 | 
						"files": [
 | 
				
			||||||
		"built"
 | 
							"built"
 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
	"dependencies": {
 | 
						"dependencies": {
 | 
				
			||||||
		"esbuild": "0.19.11",
 | 
					 | 
				
			||||||
		"eventemitter3": "5.0.1",
 | 
							"eventemitter3": "5.0.1",
 | 
				
			||||||
		"glob": "^10.3.10",
 | 
					 | 
				
			||||||
		"matter-js": "0.19.0",
 | 
							"matter-js": "0.19.0",
 | 
				
			||||||
		"seedrandom": "3.0.5"
 | 
							"seedrandom": "3.0.5"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,5 +6,9 @@
 | 
				
			||||||
import { DropAndFusionGame, Mono } from './game.js';
 | 
					import { DropAndFusionGame, Mono } from './game.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
	DropAndFusionGame, Mono,
 | 
						DropAndFusionGame,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type {
 | 
				
			||||||
 | 
						Mono,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@
 | 
				
			||||||
		"moduleResolution": "nodenext",
 | 
							"moduleResolution": "nodenext",
 | 
				
			||||||
		"declaration": true,
 | 
							"declaration": true,
 | 
				
			||||||
		"declarationMap": true,
 | 
							"declarationMap": true,
 | 
				
			||||||
		"sourceMap": true,
 | 
							"sourceMap": false,
 | 
				
			||||||
		"outDir": "./built/",
 | 
							"outDir": "./built/",
 | 
				
			||||||
		"removeComments": true,
 | 
							"removeComments": true,
 | 
				
			||||||
		"strict": true,
 | 
							"strict": true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,3 +5,4 @@ node_modules
 | 
				
			||||||
/jest.config.ts
 | 
					/jest.config.ts
 | 
				
			||||||
/test
 | 
					/test
 | 
				
			||||||
/test-d
 | 
					/test-d
 | 
				
			||||||
 | 
					build.js
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,7 @@
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
 | 
					   * SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  "mainEntryPointFilePath": "<projectFolder>/built/dts/index.d.ts",
 | 
					  "mainEntryPointFilePath": "<projectFolder>/built/index.d.ts",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * A list of NPM package names whose exports should be treated as part of this package.
 | 
					   * A list of NPM package names whose exports should be treated as part of this package.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										105
									
								
								packages/misskey-js/build.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								packages/misskey-js/build.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,105 @@
 | 
				
			||||||
 | 
					import * as esbuild from "esbuild";
 | 
				
			||||||
 | 
					import { build } from "esbuild";
 | 
				
			||||||
 | 
					import { globSync } from "glob";
 | 
				
			||||||
 | 
					import { execa } from "execa";
 | 
				
			||||||
 | 
					import fs from "node:fs";
 | 
				
			||||||
 | 
					import { fileURLToPath } from "node:url";
 | 
				
			||||||
 | 
					import { dirname } from "node:path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const _filename = fileURLToPath(import.meta.url);
 | 
				
			||||||
 | 
					const _dirname = dirname(_filename);
 | 
				
			||||||
 | 
					const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const entryPoints = globSync("./src/**/**.{ts,tsx}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** @type {import('esbuild').BuildOptions} */
 | 
				
			||||||
 | 
					const options = {
 | 
				
			||||||
 | 
						entryPoints,
 | 
				
			||||||
 | 
						minify: process.env.NODE_ENV === 'production',
 | 
				
			||||||
 | 
						outdir: "./built",
 | 
				
			||||||
 | 
						target: "es2022",
 | 
				
			||||||
 | 
						platform: "browser",
 | 
				
			||||||
 | 
						format: "esm",
 | 
				
			||||||
 | 
						sourcemap: 'linked',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// built配下をすべて削除する
 | 
				
			||||||
 | 
					fs.rmSync('./built', { recursive: true, force: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (process.argv.map(arg => arg.toLowerCase()).includes("--watch")) {
 | 
				
			||||||
 | 
						await watchSrc();
 | 
				
			||||||
 | 
					} else {
 | 
				
			||||||
 | 
						await buildSrc();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function buildSrc() {
 | 
				
			||||||
 | 
						console.log(`[${_package.name}] start building...`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						await build(options)
 | 
				
			||||||
 | 
							.then(it => {
 | 
				
			||||||
 | 
								console.log(`[${_package.name}] build succeeded.`);
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.catch((err) => {
 | 
				
			||||||
 | 
								process.stderr.write(err.stderr);
 | 
				
			||||||
 | 
								process.exit(1);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (process.env.NODE_ENV === 'production') {
 | 
				
			||||||
 | 
							console.log(`[${_package.name}] skip building d.ts because NODE_ENV is production.`);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							await buildDts();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log(`[${_package.name}] finish building.`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function buildDts() {
 | 
				
			||||||
 | 
						return execa(
 | 
				
			||||||
 | 
							'tsc',
 | 
				
			||||||
 | 
							[
 | 
				
			||||||
 | 
								'--project', 'tsconfig.json',
 | 
				
			||||||
 | 
								'--outDir', 'built',
 | 
				
			||||||
 | 
								'--declaration', 'true',
 | 
				
			||||||
 | 
								'--emitDeclarationOnly', 'true',
 | 
				
			||||||
 | 
							],
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								stdout: process.stdout,
 | 
				
			||||||
 | 
								stderr: process.stderr,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function watchSrc() {
 | 
				
			||||||
 | 
						const plugins = [{
 | 
				
			||||||
 | 
							name: 'gen-dts',
 | 
				
			||||||
 | 
							setup(build) {
 | 
				
			||||||
 | 
								build.onStart(() => {
 | 
				
			||||||
 | 
									console.log(`[${_package.name}] detect changed...`);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								build.onEnd(async result => {
 | 
				
			||||||
 | 
									if (result.errors.length > 0) {
 | 
				
			||||||
 | 
										console.error(`[${_package.name}] watch build failed:`, result);
 | 
				
			||||||
 | 
										return;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									await buildDts();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log(`[${_package.name}] start watching...`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const context = await esbuild.context({ ...options, plugins });
 | 
				
			||||||
 | 
						await context.watch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						await new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
							process.on('SIGHUP', resolve);
 | 
				
			||||||
 | 
							process.on('SIGINT', resolve);
 | 
				
			||||||
 | 
							process.on('SIGTERM', resolve);
 | 
				
			||||||
 | 
							process.on('SIGKILL', resolve);
 | 
				
			||||||
 | 
							process.on('uncaughtException', reject);
 | 
				
			||||||
 | 
							process.on('exit', resolve);
 | 
				
			||||||
 | 
						}).finally(async () => {
 | 
				
			||||||
 | 
							await context.dispose();
 | 
				
			||||||
 | 
							console.log(`[${_package.name}] finish watching.`);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
					@ -60,13 +60,17 @@ async function generateEndpoints(
 | 
				
			||||||
	// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
 | 
						// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
 | 
				
			||||||
	const paths = openApiDocs.paths ?? {};
 | 
						const paths = openApiDocs.paths ?? {};
 | 
				
			||||||
	const postPathItems = Object.keys(paths)
 | 
						const postPathItems = Object.keys(paths)
 | 
				
			||||||
		.map(it => paths[it]?.post)
 | 
							.map(it => ({
 | 
				
			||||||
 | 
								_path_: it.replace(/^\//, ''),
 | 
				
			||||||
 | 
								...paths[it]?.post,
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
		.filter(filterUndefined);
 | 
							.filter(filterUndefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (const operation of postPathItems) {
 | 
						for (const operation of postPathItems) {
 | 
				
			||||||
 | 
							const path = operation._path_;
 | 
				
			||||||
		// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | 
							// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | 
				
			||||||
		const operationId = operation.operationId!;
 | 
							const operationId = operation.operationId!;
 | 
				
			||||||
		const endpoint = new Endpoint(operationId);
 | 
							const endpoint = new Endpoint(path);
 | 
				
			||||||
		endpoints.push(endpoint);
 | 
							endpoints.push(endpoint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (isRequestBodyObject(operation.requestBody)) {
 | 
							if (isRequestBodyObject(operation.requestBody)) {
 | 
				
			||||||
| 
						 | 
					@ -76,19 +80,21 @@ async function generateEndpoints(
 | 
				
			||||||
				// いまのところ複数のメディアタイプをとるエンドポイントは無いので決め打ちする
 | 
									// いまのところ複数のメディアタイプをとるエンドポイントは無いので決め打ちする
 | 
				
			||||||
				endpoint.request = new OperationTypeAlias(
 | 
									endpoint.request = new OperationTypeAlias(
 | 
				
			||||||
					operationId,
 | 
										operationId,
 | 
				
			||||||
 | 
										path,
 | 
				
			||||||
					supportMediaTypes[0],
 | 
										supportMediaTypes[0],
 | 
				
			||||||
					OperationsAliasType.REQUEST,
 | 
										OperationsAliasType.REQUEST,
 | 
				
			||||||
				);
 | 
									);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (isResponseObject(operation.responses['200']) && operation.responses['200'].content) {
 | 
							if (operation.responses && isResponseObject(operation.responses['200']) && operation.responses['200'].content) {
 | 
				
			||||||
			const resContent = operation.responses['200'].content;
 | 
								const resContent = operation.responses['200'].content;
 | 
				
			||||||
			const supportMediaTypes = Object.keys(resContent);
 | 
								const supportMediaTypes = Object.keys(resContent);
 | 
				
			||||||
			if (supportMediaTypes.length > 0) {
 | 
								if (supportMediaTypes.length > 0) {
 | 
				
			||||||
				// いまのところ複数のメディアタイプを返すエンドポイントは無いので決め打ちする
 | 
									// いまのところ複数のメディアタイプを返すエンドポイントは無いので決め打ちする
 | 
				
			||||||
				endpoint.response = new OperationTypeAlias(
 | 
									endpoint.response = new OperationTypeAlias(
 | 
				
			||||||
					operationId,
 | 
										operationId,
 | 
				
			||||||
 | 
										path,
 | 
				
			||||||
					supportMediaTypes[0],
 | 
										supportMediaTypes[0],
 | 
				
			||||||
					OperationsAliasType.RESPONSE,
 | 
										OperationsAliasType.RESPONSE,
 | 
				
			||||||
				);
 | 
									);
 | 
				
			||||||
| 
						 | 
					@ -140,12 +146,19 @@ async function generateApiClientJSDoc(
 | 
				
			||||||
	endpointsFileName: string,
 | 
						endpointsFileName: string,
 | 
				
			||||||
	warningsOutputPath: string,
 | 
						warningsOutputPath: string,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	const endpoints: { operationId: string; description: string; }[] = [];
 | 
						const endpoints: {
 | 
				
			||||||
 | 
							operationId: string;
 | 
				
			||||||
 | 
							path: string;
 | 
				
			||||||
 | 
							description: string;
 | 
				
			||||||
 | 
						}[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
 | 
						// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
 | 
				
			||||||
	const paths = openApiDocs.paths ?? {};
 | 
						const paths = openApiDocs.paths ?? {};
 | 
				
			||||||
	const postPathItems = Object.keys(paths)
 | 
						const postPathItems = Object.keys(paths)
 | 
				
			||||||
		.map(it => paths[it]?.post)
 | 
							.map(it => ({
 | 
				
			||||||
 | 
								_path_: it.replace(/^\//, ''),
 | 
				
			||||||
 | 
								...paths[it]?.post,
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
		.filter(filterUndefined);
 | 
							.filter(filterUndefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (const operation of postPathItems) {
 | 
						for (const operation of postPathItems) {
 | 
				
			||||||
| 
						 | 
					@ -155,6 +168,7 @@ async function generateApiClientJSDoc(
 | 
				
			||||||
		if (operation.description) {
 | 
							if (operation.description) {
 | 
				
			||||||
			endpoints.push({
 | 
								endpoints.push({
 | 
				
			||||||
				operationId: operationId,
 | 
									operationId: operationId,
 | 
				
			||||||
 | 
									path: operation._path_,
 | 
				
			||||||
				description: operation.description,
 | 
									description: operation.description,
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -175,7 +189,7 @@ async function generateApiClientJSDoc(
 | 
				
			||||||
			'    /**',
 | 
								'    /**',
 | 
				
			||||||
			`     * ${endpoint.description.split('\n').join('\n     * ')}`,
 | 
								`     * ${endpoint.description.split('\n').join('\n     * ')}`,
 | 
				
			||||||
			'     */',
 | 
								'     */',
 | 
				
			||||||
			`    request<E extends '${endpoint.operationId}', P extends Endpoints[E][\'req\']>(`,
 | 
								`    request<E extends '${endpoint.path}', P extends Endpoints[E][\'req\']>(`,
 | 
				
			||||||
			'      endpoint: E,',
 | 
								'      endpoint: E,',
 | 
				
			||||||
			'      params: P,',
 | 
								'      params: P,',
 | 
				
			||||||
			'      credential?: string | null,',
 | 
								'      credential?: string | null,',
 | 
				
			||||||
| 
						 | 
					@ -234,21 +248,24 @@ interface IOperationTypeAlias {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OperationTypeAlias implements IOperationTypeAlias {
 | 
					class OperationTypeAlias implements IOperationTypeAlias {
 | 
				
			||||||
	public readonly operationId: string;
 | 
						public readonly operationId: string;
 | 
				
			||||||
 | 
						public readonly path: string;
 | 
				
			||||||
	public readonly mediaType: string;
 | 
						public readonly mediaType: string;
 | 
				
			||||||
	public readonly type: OperationsAliasType;
 | 
						public readonly type: OperationsAliasType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
		operationId: string,
 | 
							operationId: string,
 | 
				
			||||||
 | 
							path: string,
 | 
				
			||||||
		mediaType: string,
 | 
							mediaType: string,
 | 
				
			||||||
		type: OperationsAliasType,
 | 
							type: OperationsAliasType,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		this.operationId = operationId;
 | 
							this.operationId = operationId;
 | 
				
			||||||
 | 
							this.path = path;
 | 
				
			||||||
		this.mediaType = mediaType;
 | 
							this.mediaType = mediaType;
 | 
				
			||||||
		this.type = type;
 | 
							this.type = type;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	generateName(): string {
 | 
						generateName(): string {
 | 
				
			||||||
		const nameBase = this.operationId.replace(/\//g, '-');
 | 
							const nameBase = this.path.replace(/\//g, '-');
 | 
				
			||||||
		return toPascal(nameBase + this.type);
 | 
							return toPascal(nameBase + this.type);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -281,19 +298,19 @@ const emptyRequest = new EmptyTypeAlias(OperationsAliasType.REQUEST);
 | 
				
			||||||
const emptyResponse = new EmptyTypeAlias(OperationsAliasType.RESPONSE);
 | 
					const emptyResponse = new EmptyTypeAlias(OperationsAliasType.RESPONSE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Endpoint {
 | 
					class Endpoint {
 | 
				
			||||||
	public readonly operationId: string;
 | 
						public readonly path: string;
 | 
				
			||||||
	public request?: IOperationTypeAlias;
 | 
						public request?: IOperationTypeAlias;
 | 
				
			||||||
	public response?: IOperationTypeAlias;
 | 
						public response?: IOperationTypeAlias;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(operationId: string) {
 | 
						constructor(path: string) {
 | 
				
			||||||
		this.operationId = operationId;
 | 
							this.path = path;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	toLine(): string {
 | 
						toLine(): string {
 | 
				
			||||||
		const reqName = this.request?.generateName() ?? emptyRequest.generateName();
 | 
							const reqName = this.request?.generateName() ?? emptyRequest.generateName();
 | 
				
			||||||
		const resName = this.response?.generateName() ?? emptyResponse.generateName();
 | 
							const resName = this.response?.generateName() ?? emptyResponse.generateName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return `'${this.operationId}': { req: ${reqName}; res: ${resName} };`;
 | 
							return `'${this.path}': { req: ${reqName}; res: ${resName} };`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,23 +3,21 @@
 | 
				
			||||||
	"name": "misskey-js",
 | 
						"name": "misskey-js",
 | 
				
			||||||
	"version": "2024.3.1",
 | 
						"version": "2024.3.1",
 | 
				
			||||||
	"description": "Misskey SDK for JavaScript",
 | 
						"description": "Misskey SDK for JavaScript",
 | 
				
			||||||
	"types": "./built/dts/index.d.ts",
 | 
						"main": "./built/index.js",
 | 
				
			||||||
 | 
						"types": "./built/index.d.ts",
 | 
				
			||||||
	"exports": {
 | 
						"exports": {
 | 
				
			||||||
		".": {
 | 
							".": {
 | 
				
			||||||
			"import": "./built/esm/index.js",
 | 
								"import": "./built/index.js",
 | 
				
			||||||
			"types": "./built/dts/index.d.ts"
 | 
								"types": "./built/index.d.ts"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"./*": {
 | 
							"./*": {
 | 
				
			||||||
			"import": "./built/esm/*",
 | 
								"import": "./built/*",
 | 
				
			||||||
			"types": "./built/dts/*"
 | 
								"types": "./built/*"
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"scripts": {
 | 
						"scripts": {
 | 
				
			||||||
		"build": "npm run ts",
 | 
							"build": "node ./build.js",
 | 
				
			||||||
		"ts": "npm run ts-esm && npm run ts-dts",
 | 
							"watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"",
 | 
				
			||||||
		"ts-esm": "tsc --outDir built/esm",
 | 
					 | 
				
			||||||
		"ts-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true",
 | 
					 | 
				
			||||||
		"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run ts\"",
 | 
					 | 
				
			||||||
		"tsd": "tsd",
 | 
							"tsd": "tsd",
 | 
				
			||||||
		"api": "pnpm api-extractor run --local --verbose",
 | 
							"api": "pnpm api-extractor run --local --verbose",
 | 
				
			||||||
		"api-prod": "pnpm api-extractor run --verbose",
 | 
							"api-prod": "pnpm api-extractor run --verbose",
 | 
				
			||||||
| 
						 | 
					@ -49,17 +47,16 @@
 | 
				
			||||||
		"mock-socket": "9.3.1",
 | 
							"mock-socket": "9.3.1",
 | 
				
			||||||
		"ncp": "2.0.0",
 | 
							"ncp": "2.0.0",
 | 
				
			||||||
		"nodemon": "3.1.0",
 | 
							"nodemon": "3.1.0",
 | 
				
			||||||
 | 
							"execa": "8.0.1",
 | 
				
			||||||
		"tsd": "0.30.7",
 | 
							"tsd": "0.30.7",
 | 
				
			||||||
		"typescript": "5.3.3"
 | 
							"typescript": "5.3.3",
 | 
				
			||||||
 | 
							"esbuild": "0.19.11",
 | 
				
			||||||
 | 
							"glob": "10.3.10"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"files": [
 | 
						"files": [
 | 
				
			||||||
		"built",
 | 
							"built"
 | 
				
			||||||
		"built/esm",
 | 
					 | 
				
			||||||
		"built/dts"
 | 
					 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
	"dependencies": {
 | 
						"dependencies": {
 | 
				
			||||||
		"@swc/cli": "0.1.63",
 | 
					 | 
				
			||||||
		"@swc/core": "1.3.105",
 | 
					 | 
				
			||||||
		"eventemitter3": "5.0.1",
 | 
							"eventemitter3": "5.0.1",
 | 
				
			||||||
		"reconnecting-websocket": "4.4.0"
 | 
							"reconnecting-websocket": "4.4.0"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ import './autogen/apiClientJSDoc.js';
 | 
				
			||||||
import { SwitchCaseResponseType } from './api.types.js';
 | 
					import { SwitchCaseResponseType } from './api.types.js';
 | 
				
			||||||
import type { Endpoints } from './api.types.js';
 | 
					import type { Endpoints } from './api.types.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export type {
 | 
				
			||||||
	SwitchCaseResponseType,
 | 
						SwitchCaseResponseType,
 | 
				
			||||||
} from './api.types.js';
 | 
					} from './api.types.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
					@ -1,17 +1,20 @@
 | 
				
			||||||
import { Endpoints } from './api.types.js';
 | 
					import { type Endpoints } from './api.types.js';
 | 
				
			||||||
import Stream, { Connection } from './streaming.js';
 | 
					import Stream, { Connection } from './streaming.js';
 | 
				
			||||||
import { Channels } from './streaming.types.js';
 | 
					import { type Channels } from './streaming.types.js';
 | 
				
			||||||
import { Acct } from './acct.js';
 | 
					import { type Acct } from './acct.js';
 | 
				
			||||||
import * as consts from './consts.js';
 | 
					import * as consts from './consts.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export type {
 | 
				
			||||||
	Endpoints,
 | 
						Endpoints,
 | 
				
			||||||
	Stream,
 | 
					 | 
				
			||||||
	Connection as ChannelConnection,
 | 
					 | 
				
			||||||
	Channels,
 | 
						Channels,
 | 
				
			||||||
	Acct,
 | 
						Acct,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
						Stream,
 | 
				
			||||||
 | 
						Connection as ChannelConnection,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const permissions = consts.permissions;
 | 
					export const permissions = consts.permissions;
 | 
				
			||||||
export const notificationTypes = consts.notificationTypes;
 | 
					export const notificationTypes = consts.notificationTypes;
 | 
				
			||||||
export const noteVisibilities = consts.noteVisibilities;
 | 
					export const noteVisibilities = consts.noteVisibilities;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@
 | 
				
			||||||
		"moduleResolution": "nodenext",
 | 
							"moduleResolution": "nodenext",
 | 
				
			||||||
		"declaration": true,
 | 
							"declaration": true,
 | 
				
			||||||
		"declarationMap": true,
 | 
							"declarationMap": true,
 | 
				
			||||||
		"sourceMap": true,
 | 
							"sourceMap": false,
 | 
				
			||||||
		"outDir": "./built/",
 | 
							"outDir": "./built/",
 | 
				
			||||||
		"removeComments": true,
 | 
							"removeComments": true,
 | 
				
			||||||
		"strict": true,
 | 
							"strict": true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,3 +5,4 @@ node_modules
 | 
				
			||||||
/jest.config.ts
 | 
					/jest.config.ts
 | 
				
			||||||
/test
 | 
					/test
 | 
				
			||||||
/test-d
 | 
					/test-d
 | 
				
			||||||
 | 
					build.js
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,31 +1,105 @@
 | 
				
			||||||
 | 
					import * as esbuild from "esbuild";
 | 
				
			||||||
import { build } from "esbuild";
 | 
					import { build } from "esbuild";
 | 
				
			||||||
import { globSync } from "glob";
 | 
					import { globSync } from "glob";
 | 
				
			||||||
 | 
					import { execa } from "execa";
 | 
				
			||||||
 | 
					import fs from "node:fs";
 | 
				
			||||||
 | 
					import { fileURLToPath } from "node:url";
 | 
				
			||||||
 | 
					import { dirname } from "node:path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const _filename = fileURLToPath(import.meta.url);
 | 
				
			||||||
 | 
					const _dirname = dirname(_filename);
 | 
				
			||||||
 | 
					const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const entryPoints = globSync("./src/**/**.{ts,tsx}");
 | 
					const entryPoints = globSync("./src/**/**.{ts,tsx}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** @type {import('esbuild').BuildOptions} */
 | 
					/** @type {import('esbuild').BuildOptions} */
 | 
				
			||||||
const options = {
 | 
					const options = {
 | 
				
			||||||
  entryPoints,
 | 
						entryPoints,
 | 
				
			||||||
  minify: true,
 | 
						minify: process.env.NODE_ENV === 'production',
 | 
				
			||||||
  outdir: "./built/esm",
 | 
						outdir: "./built",
 | 
				
			||||||
  target: "es2022",
 | 
						target: "es2022",
 | 
				
			||||||
  platform: "browser",
 | 
						platform: "browser",
 | 
				
			||||||
  format: "esm",
 | 
						format: "esm",
 | 
				
			||||||
 | 
						sourcemap: 'linked',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (process.env.WATCH === "true") {
 | 
					// built配下をすべて削除する
 | 
				
			||||||
  options.watch = {
 | 
					fs.rmSync('./built', { recursive: true, force: true });
 | 
				
			||||||
    onRebuild(error, result) {
 | 
					
 | 
				
			||||||
      if (error) {
 | 
					if (process.argv.map(arg => arg.toLowerCase()).includes("--watch")) {
 | 
				
			||||||
        console.error("watch build failed:", error);
 | 
						await watchSrc();
 | 
				
			||||||
      } else {
 | 
					} else {
 | 
				
			||||||
        console.log("watch build succeeded:", result);
 | 
						await buildSrc();
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
build(options).catch((err) => {
 | 
					async function buildSrc() {
 | 
				
			||||||
  process.stderr.write(err.stderr);
 | 
						console.log(`[${_package.name}] start building...`);
 | 
				
			||||||
  process.exit(1);
 | 
					
 | 
				
			||||||
});
 | 
						await build(options)
 | 
				
			||||||
 | 
							.then(it => {
 | 
				
			||||||
 | 
								console.log(`[${_package.name}] build succeeded.`);
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.catch((err) => {
 | 
				
			||||||
 | 
								process.stderr.write(err.stderr);
 | 
				
			||||||
 | 
								process.exit(1);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (process.env.NODE_ENV === 'production') {
 | 
				
			||||||
 | 
							console.log(`[${_package.name}] skip building d.ts because NODE_ENV is production.`);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							await buildDts();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log(`[${_package.name}] finish building.`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function buildDts() {
 | 
				
			||||||
 | 
						return execa(
 | 
				
			||||||
 | 
							'tsc',
 | 
				
			||||||
 | 
							[
 | 
				
			||||||
 | 
								'--project', 'tsconfig.json',
 | 
				
			||||||
 | 
								'--outDir', 'built',
 | 
				
			||||||
 | 
								'--declaration', 'true',
 | 
				
			||||||
 | 
								'--emitDeclarationOnly', 'true',
 | 
				
			||||||
 | 
							],
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								stdout: process.stdout,
 | 
				
			||||||
 | 
								stderr: process.stderr,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function watchSrc() {
 | 
				
			||||||
 | 
						const plugins = [{
 | 
				
			||||||
 | 
							name: 'gen-dts',
 | 
				
			||||||
 | 
							setup(build) {
 | 
				
			||||||
 | 
								build.onStart(() => {
 | 
				
			||||||
 | 
									console.log(`[${_package.name}] detect changed...`);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								build.onEnd(async result => {
 | 
				
			||||||
 | 
									if (result.errors.length > 0) {
 | 
				
			||||||
 | 
										console.error(`[${_package.name}] watch build failed:`, result);
 | 
				
			||||||
 | 
										return;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									await buildDts();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log(`[${_package.name}] start watching...`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const context = await esbuild.context({ ...options, plugins });
 | 
				
			||||||
 | 
						await context.watch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						await new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
							process.on('SIGHUP', resolve);
 | 
				
			||||||
 | 
							process.on('SIGINT', resolve);
 | 
				
			||||||
 | 
							process.on('SIGTERM', resolve);
 | 
				
			||||||
 | 
							process.on('SIGKILL', resolve);
 | 
				
			||||||
 | 
							process.on('uncaughtException', reject);
 | 
				
			||||||
 | 
							process.on('exit', resolve);
 | 
				
			||||||
 | 
						}).finally(async () => {
 | 
				
			||||||
 | 
							await context.dispose();
 | 
				
			||||||
 | 
							console.log(`[${_package.name}] finish watching.`);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,24 +2,21 @@
 | 
				
			||||||
	"type": "module",
 | 
						"type": "module",
 | 
				
			||||||
	"name": "misskey-reversi",
 | 
						"name": "misskey-reversi",
 | 
				
			||||||
	"version": "0.0.1",
 | 
						"version": "0.0.1",
 | 
				
			||||||
	"types": "./built/dts/index.d.ts",
 | 
						"main": "./built/index.js",
 | 
				
			||||||
 | 
						"types": "./built/index.d.ts",
 | 
				
			||||||
	"exports": {
 | 
						"exports": {
 | 
				
			||||||
		".": {
 | 
							".": {
 | 
				
			||||||
			"import": "./built/esm/index.js",
 | 
								"import": "./built/index.js",
 | 
				
			||||||
			"types": "./built/dts/index.d.ts"
 | 
								"types": "./built/index.d.ts"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"./*": {
 | 
							"./*": {
 | 
				
			||||||
			"import": "./built/esm/*",
 | 
								"import": "./built/*",
 | 
				
			||||||
			"types": "./built/dts/*"
 | 
								"types": "./built/*"
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"scripts": {
 | 
						"scripts": {
 | 
				
			||||||
		"build": "node ./build.js",
 | 
							"build": "node ./build.js",
 | 
				
			||||||
		"build:tsc": "npm run tsc",
 | 
							"watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"",
 | 
				
			||||||
		"tsc": "npm run tsc-esm && npm run tsc-dts",
 | 
					 | 
				
			||||||
		"tsc-esm": "tsc --outDir built/esm",
 | 
					 | 
				
			||||||
		"tsc-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true",
 | 
					 | 
				
			||||||
		"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build:tsc\"",
 | 
					 | 
				
			||||||
		"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
 | 
							"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
 | 
				
			||||||
		"typecheck": "tsc --noEmit",
 | 
							"typecheck": "tsc --noEmit",
 | 
				
			||||||
		"lint": "pnpm typecheck && pnpm eslint"
 | 
							"lint": "pnpm typecheck && pnpm eslint"
 | 
				
			||||||
| 
						 | 
					@ -30,15 +27,16 @@
 | 
				
			||||||
		"@typescript-eslint/eslint-plugin": "7.1.0",
 | 
							"@typescript-eslint/eslint-plugin": "7.1.0",
 | 
				
			||||||
		"@typescript-eslint/parser": "7.1.0",
 | 
							"@typescript-eslint/parser": "7.1.0",
 | 
				
			||||||
		"eslint": "8.57.0",
 | 
							"eslint": "8.57.0",
 | 
				
			||||||
 | 
							"execa": "8.0.1",
 | 
				
			||||||
		"nodemon": "3.0.2",
 | 
							"nodemon": "3.0.2",
 | 
				
			||||||
		"typescript": "5.3.3"
 | 
							"typescript": "5.3.3",
 | 
				
			||||||
 | 
							"esbuild": "0.19.11",
 | 
				
			||||||
 | 
							"glob": "10.3.10"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"files": [
 | 
						"files": [
 | 
				
			||||||
		"built"
 | 
							"built"
 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
	"dependencies": {
 | 
						"dependencies": {
 | 
				
			||||||
		"crc-32": "1.2.2",
 | 
							"crc-32": "1.2.2"
 | 
				
			||||||
		"esbuild": "0.19.11",
 | 
					 | 
				
			||||||
		"glob": "10.3.10"
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@
 | 
				
			||||||
		"moduleResolution": "nodenext",
 | 
							"moduleResolution": "nodenext",
 | 
				
			||||||
		"declaration": true,
 | 
							"declaration": true,
 | 
				
			||||||
		"declarationMap": true,
 | 
							"declarationMap": true,
 | 
				
			||||||
		"sourceMap": true,
 | 
							"sourceMap": false,
 | 
				
			||||||
		"outDir": "./built/",
 | 
							"outDir": "./built/",
 | 
				
			||||||
		"removeComments": true,
 | 
							"removeComments": true,
 | 
				
			||||||
		"strict": true,
 | 
							"strict": true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -76,7 +76,7 @@ globalThis.addEventListener('push', ev => {
 | 
				
			||||||
			case 'notification':
 | 
								case 'notification':
 | 
				
			||||||
			case 'unreadAntennaNote':
 | 
								case 'unreadAntennaNote':
 | 
				
			||||||
				// 1日以上経過している場合は無視
 | 
									// 1日以上経過している場合は無視
 | 
				
			||||||
				if ((new Date()).getTime() - data.dateTime > 1000 * 60 * 60 * 24) break;
 | 
									if (Date.now() - data.dateTime > 1000 * 60 * 60 * 24) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return createNotification(data);
 | 
									return createNotification(data);
 | 
				
			||||||
			case 'readAllNotifications':
 | 
								case 'readAllNotifications':
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										232
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										232
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -15,12 +15,18 @@ importers:
 | 
				
			||||||
      cssnano:
 | 
					      cssnano:
 | 
				
			||||||
        specifier: 6.0.5
 | 
					        specifier: 6.0.5
 | 
				
			||||||
        version: 6.0.5(postcss@8.4.35)
 | 
					        version: 6.0.5(postcss@8.4.35)
 | 
				
			||||||
 | 
					      esbuild:
 | 
				
			||||||
 | 
					        specifier: 0.19.11
 | 
				
			||||||
 | 
					        version: 0.19.11
 | 
				
			||||||
      execa:
 | 
					      execa:
 | 
				
			||||||
        specifier: 8.0.1
 | 
					        specifier: 8.0.1
 | 
				
			||||||
        version: 8.0.1
 | 
					        version: 8.0.1
 | 
				
			||||||
      fast-glob:
 | 
					      fast-glob:
 | 
				
			||||||
        specifier: 3.3.2
 | 
					        specifier: 3.3.2
 | 
				
			||||||
        version: 3.3.2
 | 
					        version: 3.3.2
 | 
				
			||||||
 | 
					      glob:
 | 
				
			||||||
 | 
					        specifier: 10.3.10
 | 
				
			||||||
 | 
					        version: 10.3.10
 | 
				
			||||||
      ignore-walk:
 | 
					      ignore-walk:
 | 
				
			||||||
        specifier: 6.0.4
 | 
					        specifier: 6.0.4
 | 
				
			||||||
        version: 6.0.4
 | 
					        version: 6.0.4
 | 
				
			||||||
| 
						 | 
					@ -724,8 +730,8 @@ importers:
 | 
				
			||||||
        specifier: 3.4.21
 | 
					        specifier: 3.4.21
 | 
				
			||||||
        version: 3.4.21
 | 
					        version: 3.4.21
 | 
				
			||||||
      aiscript-vscode:
 | 
					      aiscript-vscode:
 | 
				
			||||||
        specifier: github:aiscript-dev/aiscript-vscode#v0.1.2
 | 
					        specifier: github:aiscript-dev/aiscript-vscode#v0.1.4
 | 
				
			||||||
        version: github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07
 | 
					        version: github.com/aiscript-dev/aiscript-vscode/3f79d6f0550369267220aa67702287948d885424
 | 
				
			||||||
      astring:
 | 
					      astring:
 | 
				
			||||||
        specifier: 1.8.6
 | 
					        specifier: 1.8.6
 | 
				
			||||||
        version: 1.8.6
 | 
					        version: 1.8.6
 | 
				
			||||||
| 
						 | 
					@ -1129,22 +1135,16 @@ importers:
 | 
				
			||||||
        version: 3.1.0
 | 
					        version: 3.1.0
 | 
				
			||||||
      ts-jest:
 | 
					      ts-jest:
 | 
				
			||||||
        specifier: ^29.1.1
 | 
					        specifier: ^29.1.1
 | 
				
			||||||
        version: 29.1.1(@babel/core@7.24.0)(jest@29.7.0)(typescript@5.1.6)
 | 
					        version: 29.1.1(@babel/core@7.24.0)(esbuild@0.19.11)(jest@29.7.0)(typescript@5.1.6)
 | 
				
			||||||
      typedoc:
 | 
					      typedoc:
 | 
				
			||||||
        specifier: ^0.25.3
 | 
					        specifier: ^0.25.3
 | 
				
			||||||
        version: 0.25.3(typescript@5.1.6)
 | 
					        version: 0.25.3(typescript@5.1.6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  packages/misskey-bubble-game:
 | 
					  packages/misskey-bubble-game:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      esbuild:
 | 
					 | 
				
			||||||
        specifier: 0.19.11
 | 
					 | 
				
			||||||
        version: 0.19.11
 | 
					 | 
				
			||||||
      eventemitter3:
 | 
					      eventemitter3:
 | 
				
			||||||
        specifier: 5.0.1
 | 
					        specifier: 5.0.1
 | 
				
			||||||
        version: 5.0.1
 | 
					        version: 5.0.1
 | 
				
			||||||
      glob:
 | 
					 | 
				
			||||||
        specifier: ^10.3.10
 | 
					 | 
				
			||||||
        version: 10.3.10
 | 
					 | 
				
			||||||
      matter-js:
 | 
					      matter-js:
 | 
				
			||||||
        specifier: 0.19.0
 | 
					        specifier: 0.19.0
 | 
				
			||||||
        version: 0.19.0
 | 
					        version: 0.19.0
 | 
				
			||||||
| 
						 | 
					@ -1170,9 +1170,18 @@ importers:
 | 
				
			||||||
      '@typescript-eslint/parser':
 | 
					      '@typescript-eslint/parser':
 | 
				
			||||||
        specifier: 7.1.0
 | 
					        specifier: 7.1.0
 | 
				
			||||||
        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
 | 
					        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
 | 
				
			||||||
 | 
					      esbuild:
 | 
				
			||||||
 | 
					        specifier: 0.19.11
 | 
				
			||||||
 | 
					        version: 0.19.11
 | 
				
			||||||
      eslint:
 | 
					      eslint:
 | 
				
			||||||
        specifier: 8.57.0
 | 
					        specifier: 8.57.0
 | 
				
			||||||
        version: 8.57.0
 | 
					        version: 8.57.0
 | 
				
			||||||
 | 
					      execa:
 | 
				
			||||||
 | 
					        specifier: 8.0.1
 | 
				
			||||||
 | 
					        version: 8.0.1
 | 
				
			||||||
 | 
					      glob:
 | 
				
			||||||
 | 
					        specifier: 10.3.10
 | 
				
			||||||
 | 
					        version: 10.3.10
 | 
				
			||||||
      nodemon:
 | 
					      nodemon:
 | 
				
			||||||
        specifier: 3.0.2
 | 
					        specifier: 3.0.2
 | 
				
			||||||
        version: 3.0.2
 | 
					        version: 3.0.2
 | 
				
			||||||
| 
						 | 
					@ -1182,12 +1191,6 @@ importers:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  packages/misskey-js:
 | 
					  packages/misskey-js:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      '@swc/cli':
 | 
					 | 
				
			||||||
        specifier: 0.1.63
 | 
					 | 
				
			||||||
        version: 0.1.63(@swc/core@1.3.105)
 | 
					 | 
				
			||||||
      '@swc/core':
 | 
					 | 
				
			||||||
        specifier: 1.3.105
 | 
					 | 
				
			||||||
        version: 1.3.105
 | 
					 | 
				
			||||||
      eventemitter3:
 | 
					      eventemitter3:
 | 
				
			||||||
        specifier: 5.0.1
 | 
					        specifier: 5.0.1
 | 
				
			||||||
        version: 5.0.1
 | 
					        version: 5.0.1
 | 
				
			||||||
| 
						 | 
					@ -1203,7 +1206,7 @@ importers:
 | 
				
			||||||
        version: 1.0.0(@typescript-eslint/eslint-plugin@7.1.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
 | 
					        version: 1.0.0(@typescript-eslint/eslint-plugin@7.1.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
 | 
				
			||||||
      '@swc/jest':
 | 
					      '@swc/jest':
 | 
				
			||||||
        specifier: 0.2.31
 | 
					        specifier: 0.2.31
 | 
				
			||||||
        version: 0.2.31(@swc/core@1.3.105)
 | 
					        version: 0.2.31(@swc/core@1.3.107)
 | 
				
			||||||
      '@types/jest':
 | 
					      '@types/jest':
 | 
				
			||||||
        specifier: 29.5.12
 | 
					        specifier: 29.5.12
 | 
				
			||||||
        version: 29.5.12
 | 
					        version: 29.5.12
 | 
				
			||||||
| 
						 | 
					@ -1216,9 +1219,18 @@ importers:
 | 
				
			||||||
      '@typescript-eslint/parser':
 | 
					      '@typescript-eslint/parser':
 | 
				
			||||||
        specifier: 7.1.0
 | 
					        specifier: 7.1.0
 | 
				
			||||||
        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
 | 
					        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
 | 
				
			||||||
 | 
					      esbuild:
 | 
				
			||||||
 | 
					        specifier: 0.19.11
 | 
				
			||||||
 | 
					        version: 0.19.11
 | 
				
			||||||
      eslint:
 | 
					      eslint:
 | 
				
			||||||
        specifier: 8.57.0
 | 
					        specifier: 8.57.0
 | 
				
			||||||
        version: 8.57.0
 | 
					        version: 8.57.0
 | 
				
			||||||
 | 
					      execa:
 | 
				
			||||||
 | 
					        specifier: 8.0.1
 | 
				
			||||||
 | 
					        version: 8.0.1
 | 
				
			||||||
 | 
					      glob:
 | 
				
			||||||
 | 
					        specifier: 10.3.10
 | 
				
			||||||
 | 
					        version: 10.3.10
 | 
				
			||||||
      jest:
 | 
					      jest:
 | 
				
			||||||
        specifier: 29.7.0
 | 
					        specifier: 29.7.0
 | 
				
			||||||
        version: 29.7.0(@types/node@20.11.22)
 | 
					        version: 29.7.0(@types/node@20.11.22)
 | 
				
			||||||
| 
						 | 
					@ -1285,12 +1297,6 @@ importers:
 | 
				
			||||||
      crc-32:
 | 
					      crc-32:
 | 
				
			||||||
        specifier: 1.2.2
 | 
					        specifier: 1.2.2
 | 
				
			||||||
        version: 1.2.2
 | 
					        version: 1.2.2
 | 
				
			||||||
      esbuild:
 | 
					 | 
				
			||||||
        specifier: 0.19.11
 | 
					 | 
				
			||||||
        version: 0.19.11
 | 
					 | 
				
			||||||
      glob:
 | 
					 | 
				
			||||||
        specifier: 10.3.10
 | 
					 | 
				
			||||||
        version: 10.3.10
 | 
					 | 
				
			||||||
    devDependencies:
 | 
					    devDependencies:
 | 
				
			||||||
      '@misskey-dev/eslint-plugin':
 | 
					      '@misskey-dev/eslint-plugin':
 | 
				
			||||||
        specifier: 1.0.0
 | 
					        specifier: 1.0.0
 | 
				
			||||||
| 
						 | 
					@ -1304,9 +1310,18 @@ importers:
 | 
				
			||||||
      '@typescript-eslint/parser':
 | 
					      '@typescript-eslint/parser':
 | 
				
			||||||
        specifier: 7.1.0
 | 
					        specifier: 7.1.0
 | 
				
			||||||
        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
 | 
					        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
 | 
				
			||||||
 | 
					      esbuild:
 | 
				
			||||||
 | 
					        specifier: 0.19.11
 | 
				
			||||||
 | 
					        version: 0.19.11
 | 
				
			||||||
      eslint:
 | 
					      eslint:
 | 
				
			||||||
        specifier: 8.57.0
 | 
					        specifier: 8.57.0
 | 
				
			||||||
        version: 8.57.0
 | 
					        version: 8.57.0
 | 
				
			||||||
 | 
					      execa:
 | 
				
			||||||
 | 
					        specifier: 8.0.1
 | 
				
			||||||
 | 
					        version: 8.0.1
 | 
				
			||||||
 | 
					      glob:
 | 
				
			||||||
 | 
					        specifier: 10.3.10
 | 
				
			||||||
 | 
					        version: 10.3.10
 | 
				
			||||||
      nodemon:
 | 
					      nodemon:
 | 
				
			||||||
        specifier: 3.0.2
 | 
					        specifier: 3.0.2
 | 
				
			||||||
        version: 3.0.2
 | 
					        version: 3.0.2
 | 
				
			||||||
| 
						 | 
					@ -5262,7 +5277,7 @@ packages:
 | 
				
			||||||
    resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==}
 | 
					    resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==}
 | 
				
			||||||
    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
 | 
					    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      semver: 7.5.4
 | 
					      semver: 7.6.0
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@nuxtjs/opencollective@0.3.2:
 | 
					  /@nuxtjs/opencollective@0.3.2:
 | 
				
			||||||
| 
						 | 
					@ -6953,32 +6968,12 @@ packages:
 | 
				
			||||||
      ts-dedent: 2.2.0
 | 
					      ts-dedent: 2.2.0
 | 
				
			||||||
      type-fest: 2.19.0
 | 
					      type-fest: 2.19.0
 | 
				
			||||||
      vue: 3.4.21(typescript@5.3.3)
 | 
					      vue: 3.4.21(typescript@5.3.3)
 | 
				
			||||||
      vue-component-type-helpers: 2.0.7
 | 
					      vue-component-type-helpers: 2.0.12
 | 
				
			||||||
    transitivePeerDependencies:
 | 
					    transitivePeerDependencies:
 | 
				
			||||||
      - encoding
 | 
					      - encoding
 | 
				
			||||||
      - supports-color
 | 
					      - supports-color
 | 
				
			||||||
    dev: true
 | 
					    dev: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/cli@0.1.63(@swc/core@1.3.105):
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-EM9oxxHzmmsprYRbGqsS2M4M/Gr5Gkcl0ROYYIdlUyTkhOiX822EQiRCpPCwdutdnzH2GyaTN7wc6i0Y+CKd3A==}
 | 
					 | 
				
			||||||
    engines: {node: '>= 12.13'}
 | 
					 | 
				
			||||||
    hasBin: true
 | 
					 | 
				
			||||||
    peerDependencies:
 | 
					 | 
				
			||||||
      '@swc/core': ^1.2.66
 | 
					 | 
				
			||||||
      chokidar: 3.5.3
 | 
					 | 
				
			||||||
    peerDependenciesMeta:
 | 
					 | 
				
			||||||
      chokidar:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      '@mole-inc/bin-wrapper': 8.0.1
 | 
					 | 
				
			||||||
      '@swc/core': 1.3.105
 | 
					 | 
				
			||||||
      commander: 7.2.0
 | 
					 | 
				
			||||||
      fast-glob: 3.3.2
 | 
					 | 
				
			||||||
      semver: 7.5.4
 | 
					 | 
				
			||||||
      slash: 3.0.0
 | 
					 | 
				
			||||||
      source-map: 0.7.4
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/cli@0.1.63(@swc/core@1.3.107)(chokidar@3.5.3):
 | 
					  /@swc/cli@0.1.63(@swc/core@1.3.107)(chokidar@3.5.3):
 | 
				
			||||||
    resolution: {integrity: sha512-EM9oxxHzmmsprYRbGqsS2M4M/Gr5Gkcl0ROYYIdlUyTkhOiX822EQiRCpPCwdutdnzH2GyaTN7wc6i0Y+CKd3A==}
 | 
					    resolution: {integrity: sha512-EM9oxxHzmmsprYRbGqsS2M4M/Gr5Gkcl0ROYYIdlUyTkhOiX822EQiRCpPCwdutdnzH2GyaTN7wc6i0Y+CKd3A==}
 | 
				
			||||||
    engines: {node: '>= 12.13'}
 | 
					    engines: {node: '>= 12.13'}
 | 
				
			||||||
| 
						 | 
					@ -7011,14 +7006,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-darwin-arm64@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-buWeweLVDXXmcnfIemH4PGnpjwsDTUGitnPchdftb0u1FU8zSSP/lw/pUCBDG/XvWAp7c/aFxgN4CyG0j7eayA==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [arm64]
 | 
					 | 
				
			||||||
    os: [darwin]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-darwin-arm64@1.3.107:
 | 
					  /@swc/core-darwin-arm64@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-47tD/5vSXWxPd0j/ZllyQUg4bqalbQTsmqSw0J4dDdS82MWqCAwUErUrAZPRjBkjNQ6Kmrf5rpCWaGTtPw+ngw==}
 | 
					    resolution: {integrity: sha512-47tD/5vSXWxPd0j/ZllyQUg4bqalbQTsmqSw0J4dDdS82MWqCAwUErUrAZPRjBkjNQ6Kmrf5rpCWaGTtPw+ngw==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7036,14 +7023,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-darwin-x64@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-hFmXPApqjA/8sy/9NpljHVaKi1OvL9QkJ2MbbTCCbJERuHMpMUeMBUWipHRfepGHFhU+9B9zkEup/qJaJR4XIg==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [x64]
 | 
					 | 
				
			||||||
    os: [darwin]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-darwin-x64@1.3.107:
 | 
					  /@swc/core-darwin-x64@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-hwiLJ2ulNkBGAh1m1eTfeY1417OAYbRGcb/iGsJ+LuVLvKAhU/itzsl535CvcwAlt2LayeCFfcI8gdeOLeZa9A==}
 | 
					    resolution: {integrity: sha512-hwiLJ2ulNkBGAh1m1eTfeY1417OAYbRGcb/iGsJ+LuVLvKAhU/itzsl535CvcwAlt2LayeCFfcI8gdeOLeZa9A==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7072,14 +7051,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-linux-arm-gnueabihf@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-mwXyMC41oMKkKrPpL8uJpOxw7fyfQoVtIw3Y5p0Blabk+espNYqix0E8VymHdRKuLmM//z5wVmMsuHdGBHvZeg==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [arm]
 | 
					 | 
				
			||||||
    os: [linux]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-linux-arm-gnueabihf@1.3.107:
 | 
					  /@swc/core-linux-arm-gnueabihf@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-I2wzcC0KXqh0OwymCmYwNRgZ9nxX7DWnOOStJXV3pS0uB83TXAkmqd7wvMBuIl9qu4Hfomi9aDM7IlEEn9tumQ==}
 | 
					    resolution: {integrity: sha512-I2wzcC0KXqh0OwymCmYwNRgZ9nxX7DWnOOStJXV3pS0uB83TXAkmqd7wvMBuIl9qu4Hfomi9aDM7IlEEn9tumQ==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7097,14 +7068,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-linux-arm64-gnu@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-H7yEIVydnUtqBSUxwmO6vpIQn7j+Rr0DF6ZOORPyd/SFzQJK9cJRtmJQ3ZMzlJ1Bb+1gr3MvjgLEnmyCYEm2Hg==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [arm64]
 | 
					 | 
				
			||||||
    os: [linux]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-linux-arm64-gnu@1.3.107:
 | 
					  /@swc/core-linux-arm64-gnu@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-HWgnn7JORYlOYnGsdunpSF8A+BCZKPLzLtEUA27/M/ZuANcMZabKL9Zurt7XQXq888uJFAt98Gy+59PU90aHKg==}
 | 
					    resolution: {integrity: sha512-HWgnn7JORYlOYnGsdunpSF8A+BCZKPLzLtEUA27/M/ZuANcMZabKL9Zurt7XQXq888uJFAt98Gy+59PU90aHKg==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7122,14 +7085,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-linux-arm64-musl@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-Jg7RTFT3pGFdGt5elPV6oDkinRy7q9cXpenjXnJnM2uvx3jOwnsAhexPyCDHom8SHL0j+9kaLLC66T3Gz1E4UA==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [arm64]
 | 
					 | 
				
			||||||
    os: [linux]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-linux-arm64-musl@1.3.107:
 | 
					  /@swc/core-linux-arm64-musl@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-vfPF74cWfAm8hyhS8yvYI94ucMHIo8xIYU+oFOW9uvDlGQRgnUf/6DEVbLyt/3yfX5723Ln57U8uiMALbX5Pyw==}
 | 
					    resolution: {integrity: sha512-vfPF74cWfAm8hyhS8yvYI94ucMHIo8xIYU+oFOW9uvDlGQRgnUf/6DEVbLyt/3yfX5723Ln57U8uiMALbX5Pyw==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7147,14 +7102,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-linux-x64-gnu@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-DJghplpyusAmp1X5pW/y93MmS/u83Sx5GrpJxI6KLPa82+NItTgMcl8KBQmW5GYAJpVKZyaIvBanS5TdR8aN2w==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [x64]
 | 
					 | 
				
			||||||
    os: [linux]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-linux-x64-gnu@1.3.107:
 | 
					  /@swc/core-linux-x64-gnu@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-uBVNhIg0ip8rH9OnOsCARUFZ3Mq3tbPHxtmWk9uAa5u8jQwGWeBx5+nTHpDOVd3YxKb6+5xDEI/edeeLpha/9g==}
 | 
					    resolution: {integrity: sha512-uBVNhIg0ip8rH9OnOsCARUFZ3Mq3tbPHxtmWk9uAa5u8jQwGWeBx5+nTHpDOVd3YxKb6+5xDEI/edeeLpha/9g==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7172,14 +7119,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-linux-x64-musl@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-wD5jL2dZH/5nPNssBo6jhOvkI0lmWnVR4vnOXWjuXgjq1S0AJpO5jdre/6pYLmf26hft3M42bteDnjR4AAZ38w==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [x64]
 | 
					 | 
				
			||||||
    os: [linux]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-linux-x64-musl@1.3.107:
 | 
					  /@swc/core-linux-x64-musl@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-mvACkUvzSIB12q1H5JtabWATbk3AG+pQgXEN95AmEX2ZA5gbP9+B+mijsg7Sd/3tboHr7ZHLz/q3SHTvdFJrEw==}
 | 
					    resolution: {integrity: sha512-mvACkUvzSIB12q1H5JtabWATbk3AG+pQgXEN95AmEX2ZA5gbP9+B+mijsg7Sd/3tboHr7ZHLz/q3SHTvdFJrEw==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7197,14 +7136,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-win32-arm64-msvc@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-UqJtwILUHRw2+3UTPnRkZrzM/bGdQtbR4UFdp79mZQYfryeOUVNg7aJj/bWUTkKtLiZ3o+FBNrM/x2X1mJX5bA==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [arm64]
 | 
					 | 
				
			||||||
    os: [win32]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-win32-arm64-msvc@1.3.107:
 | 
					  /@swc/core-win32-arm64-msvc@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-J3P14Ngy/1qtapzbguEH41kY109t6DFxfbK4Ntz9dOWNuVY3o9/RTB841ctnJk0ZHEG+BjfCJjsD2n8H5HcaOA==}
 | 
					    resolution: {integrity: sha512-J3P14Ngy/1qtapzbguEH41kY109t6DFxfbK4Ntz9dOWNuVY3o9/RTB841ctnJk0ZHEG+BjfCJjsD2n8H5HcaOA==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7222,14 +7153,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-win32-ia32-msvc@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-Z95C6vZgBEJ1snidYyjVKnVWiy/ZpPiIFIXGWkDr4ZyBgL3eZX12M6LzZ+NApHKffrbO4enbFyFomueBQgS2oA==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [ia32]
 | 
					 | 
				
			||||||
    os: [win32]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-win32-ia32-msvc@1.3.107:
 | 
					  /@swc/core-win32-ia32-msvc@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-ZBUtgyjTHlz8TPJh7kfwwwFma+ktr6OccB1oXC8fMSopD0AxVnQasgun3l3099wIsAB9eEsJDQ/3lDkOLs1gBA==}
 | 
					    resolution: {integrity: sha512-ZBUtgyjTHlz8TPJh7kfwwwFma+ktr6OccB1oXC8fMSopD0AxVnQasgun3l3099wIsAB9eEsJDQ/3lDkOLs1gBA==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7247,14 +7170,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core-win32-x64-msvc@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-3J8fkyDPFsS3mszuYUY4Wfk7/B2oio9qXUwF3DzOs2MK+XgdyMLIptIxL7gdfitXJBH8k39uVjrIw1JGJDjyFA==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    cpu: [x64]
 | 
					 | 
				
			||||||
    os: [win32]
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core-win32-x64-msvc@1.3.107:
 | 
					  /@swc/core-win32-x64-msvc@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-Eyzo2XRqWOxqhE1gk9h7LWmUf4Bp4Xn2Ttb0ayAXFp6YSTxQIThXcT9kipXZqcpxcmDwoq8iWbbf2P8XL743EA==}
 | 
					    resolution: {integrity: sha512-Eyzo2XRqWOxqhE1gk9h7LWmUf4Bp4Xn2Ttb0ayAXFp6YSTxQIThXcT9kipXZqcpxcmDwoq8iWbbf2P8XL743EA==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7272,30 +7187,6 @@ packages:
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/core@1.3.105:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-me2VZyr3OjqRpFrYQJJYy7x/zbFSl9nt+MAGnIcBtjDsN00iTVqEaKxBjPBFQV9BDAgPz2SRWes/DhhVm5SmMw==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    peerDependencies:
 | 
					 | 
				
			||||||
      '@swc/helpers': ^0.5.0
 | 
					 | 
				
			||||||
    peerDependenciesMeta:
 | 
					 | 
				
			||||||
      '@swc/helpers':
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      '@swc/counter': 0.1.2
 | 
					 | 
				
			||||||
      '@swc/types': 0.1.5
 | 
					 | 
				
			||||||
    optionalDependencies:
 | 
					 | 
				
			||||||
      '@swc/core-darwin-arm64': 1.3.105
 | 
					 | 
				
			||||||
      '@swc/core-darwin-x64': 1.3.105
 | 
					 | 
				
			||||||
      '@swc/core-linux-arm-gnueabihf': 1.3.105
 | 
					 | 
				
			||||||
      '@swc/core-linux-arm64-gnu': 1.3.105
 | 
					 | 
				
			||||||
      '@swc/core-linux-arm64-musl': 1.3.105
 | 
					 | 
				
			||||||
      '@swc/core-linux-x64-gnu': 1.3.105
 | 
					 | 
				
			||||||
      '@swc/core-linux-x64-musl': 1.3.105
 | 
					 | 
				
			||||||
      '@swc/core-win32-arm64-msvc': 1.3.105
 | 
					 | 
				
			||||||
      '@swc/core-win32-ia32-msvc': 1.3.105
 | 
					 | 
				
			||||||
      '@swc/core-win32-x64-msvc': 1.3.105
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/core@1.3.107:
 | 
					  /@swc/core@1.3.107:
 | 
				
			||||||
    resolution: {integrity: sha512-zKhqDyFcTsyLIYK1iEmavljZnf4CCor5pF52UzLAz4B6Nu/4GLU+2LQVAf+oRHjusG39PTPjd2AlRT3f3QWfsQ==}
 | 
					    resolution: {integrity: sha512-zKhqDyFcTsyLIYK1iEmavljZnf4CCor5pF52UzLAz4B6Nu/4GLU+2LQVAf+oRHjusG39PTPjd2AlRT3f3QWfsQ==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
| 
						 | 
					@ -7323,17 +7214,6 @@ packages:
 | 
				
			||||||
  /@swc/counter@0.1.2:
 | 
					  /@swc/counter@0.1.2:
 | 
				
			||||||
    resolution: {integrity: sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==}
 | 
					    resolution: {integrity: sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /@swc/jest@0.2.31(@swc/core@1.3.105):
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-Gh0Ste380O8KUY1IqsKr+aOvqqs2Loa+WcWWVNwl+lhXqOWK1iTFAP1K0IDfLqAuFP68+D/PxcpBJn21e6Quvw==}
 | 
					 | 
				
			||||||
    engines: {npm: '>= 7.0.0'}
 | 
					 | 
				
			||||||
    peerDependencies:
 | 
					 | 
				
			||||||
      '@swc/core': '*'
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      '@jest/create-cache-key-function': 29.7.0
 | 
					 | 
				
			||||||
      '@swc/core': 1.3.105
 | 
					 | 
				
			||||||
      jsonc-parser: 3.2.0
 | 
					 | 
				
			||||||
    dev: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@swc/jest@0.2.31(@swc/core@1.3.107):
 | 
					  /@swc/jest@0.2.31(@swc/core@1.3.107):
 | 
				
			||||||
    resolution: {integrity: sha512-Gh0Ste380O8KUY1IqsKr+aOvqqs2Loa+WcWWVNwl+lhXqOWK1iTFAP1K0IDfLqAuFP68+D/PxcpBJn21e6Quvw==}
 | 
					    resolution: {integrity: sha512-Gh0Ste380O8KUY1IqsKr+aOvqqs2Loa+WcWWVNwl+lhXqOWK1iTFAP1K0IDfLqAuFP68+D/PxcpBJn21e6Quvw==}
 | 
				
			||||||
    engines: {npm: '>= 7.0.0'}
 | 
					    engines: {npm: '>= 7.0.0'}
 | 
				
			||||||
| 
						 | 
					@ -11123,7 +11003,7 @@ packages:
 | 
				
			||||||
      '@one-ini/wasm': 0.1.1
 | 
					      '@one-ini/wasm': 0.1.1
 | 
				
			||||||
      commander: 10.0.1
 | 
					      commander: 10.0.1
 | 
				
			||||||
      minimatch: 9.0.1
 | 
					      minimatch: 9.0.1
 | 
				
			||||||
      semver: 7.5.4
 | 
					      semver: 7.6.0
 | 
				
			||||||
    dev: true
 | 
					    dev: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /ee-first@1.1.1:
 | 
					  /ee-first@1.1.1:
 | 
				
			||||||
| 
						 | 
					@ -13614,7 +13494,7 @@ packages:
 | 
				
			||||||
      '@babel/parser': 7.23.6
 | 
					      '@babel/parser': 7.23.6
 | 
				
			||||||
      '@istanbuljs/schema': 0.1.3
 | 
					      '@istanbuljs/schema': 0.1.3
 | 
				
			||||||
      istanbul-lib-coverage: 3.2.2
 | 
					      istanbul-lib-coverage: 3.2.2
 | 
				
			||||||
      semver: 7.5.4
 | 
					      semver: 7.6.0
 | 
				
			||||||
    transitivePeerDependencies:
 | 
					    transitivePeerDependencies:
 | 
				
			||||||
      - supports-color
 | 
					      - supports-color
 | 
				
			||||||
    dev: true
 | 
					    dev: true
 | 
				
			||||||
| 
						 | 
					@ -14708,6 +14588,8 @@ packages:
 | 
				
			||||||
  /lru-cache@10.1.0:
 | 
					  /lru-cache@10.1.0:
 | 
				
			||||||
    resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==}
 | 
					    resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==}
 | 
				
			||||||
    engines: {node: 14 || >=16.14}
 | 
					    engines: {node: 14 || >=16.14}
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      semver: 7.6.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /lru-cache@4.1.5:
 | 
					  /lru-cache@4.1.5:
 | 
				
			||||||
    resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
 | 
					    resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
 | 
				
			||||||
| 
						 | 
					@ -14790,7 +14672,7 @@ packages:
 | 
				
			||||||
    resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
 | 
					    resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      semver: 7.5.4
 | 
					      semver: 7.6.0
 | 
				
			||||||
    dev: true
 | 
					    dev: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /make-error@1.3.6:
 | 
					  /make-error@1.3.6:
 | 
				
			||||||
| 
						 | 
					@ -15858,7 +15740,7 @@ packages:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      hosted-git-info: 4.1.0
 | 
					      hosted-git-info: 4.1.0
 | 
				
			||||||
      is-core-module: 2.13.1
 | 
					      is-core-module: 2.13.1
 | 
				
			||||||
      semver: 7.5.4
 | 
					      semver: 7.6.0
 | 
				
			||||||
      validate-npm-package-license: 3.0.4
 | 
					      validate-npm-package-license: 3.0.4
 | 
				
			||||||
    dev: true
 | 
					    dev: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17939,7 +17821,6 @@ packages:
 | 
				
			||||||
    hasBin: true
 | 
					    hasBin: true
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      lru-cache: 6.0.0
 | 
					      lru-cache: 6.0.0
 | 
				
			||||||
    dev: true
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /send@0.18.0:
 | 
					  /send@0.18.0:
 | 
				
			||||||
    resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
 | 
					    resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
 | 
				
			||||||
| 
						 | 
					@ -19060,7 +18941,7 @@ packages:
 | 
				
			||||||
    engines: {node: '>=6.10'}
 | 
					    engines: {node: '>=6.10'}
 | 
				
			||||||
    dev: true
 | 
					    dev: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /ts-jest@29.1.1(@babel/core@7.24.0)(jest@29.7.0)(typescript@5.1.6):
 | 
					  /ts-jest@29.1.1(@babel/core@7.24.0)(esbuild@0.19.11)(jest@29.7.0)(typescript@5.1.6):
 | 
				
			||||||
    resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==}
 | 
					    resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==}
 | 
				
			||||||
    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
 | 
					    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
 | 
				
			||||||
    hasBin: true
 | 
					    hasBin: true
 | 
				
			||||||
| 
						 | 
					@ -19083,6 +18964,7 @@ packages:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      '@babel/core': 7.24.0
 | 
					      '@babel/core': 7.24.0
 | 
				
			||||||
      bs-logger: 0.2.6
 | 
					      bs-logger: 0.2.6
 | 
				
			||||||
 | 
					      esbuild: 0.19.11
 | 
				
			||||||
      fast-json-stable-stringify: 2.1.0
 | 
					      fast-json-stable-stringify: 2.1.0
 | 
				
			||||||
      jest: 29.7.0(@types/node@20.11.30)
 | 
					      jest: 29.7.0(@types/node@20.11.30)
 | 
				
			||||||
      jest-util: 29.7.0
 | 
					      jest-util: 29.7.0
 | 
				
			||||||
| 
						 | 
					@ -19843,7 +19725,7 @@ packages:
 | 
				
			||||||
    engines: {vscode: ^1.82.0}
 | 
					    engines: {vscode: ^1.82.0}
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      minimatch: 5.1.6
 | 
					      minimatch: 5.1.6
 | 
				
			||||||
      semver: 7.5.4
 | 
					      semver: 7.6.0
 | 
				
			||||||
      vscode-languageserver-protocol: 3.17.5
 | 
					      vscode-languageserver-protocol: 3.17.5
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19900,8 +19782,8 @@ packages:
 | 
				
			||||||
    resolution: {integrity: sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==}
 | 
					    resolution: {integrity: sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==}
 | 
				
			||||||
    dev: true
 | 
					    dev: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /vue-component-type-helpers@2.0.7:
 | 
					  /vue-component-type-helpers@2.0.12:
 | 
				
			||||||
    resolution: {integrity: sha512-7e12Evdll7JcTIocojgnCgwocX4WzIYStGClBQ+QuWPinZo/vQolv2EMq4a3lg16TKfwWafLimG77bxb56UauA==}
 | 
					    resolution: {integrity: sha512-iVJugClQdu3ZyF0N4CF3Egi+gWYfnxlIPPGtFXZG29rF3kQIuziP+k7rVGCCHiibIOQ1SlspKjrh+LRYzMpwTA==}
 | 
				
			||||||
    dev: true
 | 
					    dev: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /vue-demi@0.14.7(vue@3.4.21):
 | 
					  /vue-demi@0.14.7(vue@3.4.21):
 | 
				
			||||||
| 
						 | 
					@ -20441,10 +20323,10 @@ packages:
 | 
				
			||||||
    resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
 | 
					    resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
 | 
				
			||||||
    dev: true
 | 
					    dev: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz':
 | 
					  '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz':
 | 
				
			||||||
    resolution: {tarball: https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz}
 | 
					    resolution: {tarball: https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz}
 | 
				
			||||||
    name: '@aiscript-dev/aiscript-languageserver'
 | 
					    name: '@aiscript-dev/aiscript-languageserver'
 | 
				
			||||||
    version: 0.1.5
 | 
					    version: 0.1.6
 | 
				
			||||||
    hasBin: true
 | 
					    hasBin: true
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      seedrandom: 3.0.5
 | 
					      seedrandom: 3.0.5
 | 
				
			||||||
| 
						 | 
					@ -20454,13 +20336,13 @@ packages:
 | 
				
			||||||
      vscode-languageserver-textdocument: 1.0.11
 | 
					      vscode-languageserver-textdocument: 1.0.11
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07:
 | 
					  github.com/aiscript-dev/aiscript-vscode/3f79d6f0550369267220aa67702287948d885424:
 | 
				
			||||||
    resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/793211d40243c8775f6b85f015c221c82cbffb07}
 | 
					    resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/3f79d6f0550369267220aa67702287948d885424}
 | 
				
			||||||
    name: aiscript-vscode
 | 
					    name: aiscript-vscode
 | 
				
			||||||
    version: 0.1.2
 | 
					    version: 0.1.4
 | 
				
			||||||
    engines: {vscode: ^1.83.0}
 | 
					    engines: {vscode: ^1.83.0}
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      '@aiscript-dev/aiscript-languageserver': '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz'
 | 
					      '@aiscript-dev/aiscript-languageserver': '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz'
 | 
				
			||||||
      vscode-languageclient: 9.0.1
 | 
					      vscode-languageclient: 9.0.1
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,41 +16,41 @@ await execa('pnpm', ['clean'], {
 | 
				
			||||||
	stderr: process.stderr,
 | 
						stderr: process.stderr,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
await execa('pnpm', ['build-pre'], {
 | 
					await Promise.all([
 | 
				
			||||||
	cwd: _dirname + '/../',
 | 
						execa('pnpm', ['build-pre'], {
 | 
				
			||||||
	stdout: process.stdout,
 | 
							cwd: _dirname + '/../',
 | 
				
			||||||
	stderr: process.stderr,
 | 
							stdout: process.stdout,
 | 
				
			||||||
});
 | 
							stderr: process.stderr,
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
						execa('pnpm', ['build-assets'], {
 | 
				
			||||||
 | 
							cwd: _dirname + '/../',
 | 
				
			||||||
 | 
							stdout: process.stdout,
 | 
				
			||||||
 | 
							stderr: process.stderr,
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
						execa('pnpm', ['--filter', 'misskey-js', 'build'], {
 | 
				
			||||||
 | 
							cwd: _dirname + '/../',
 | 
				
			||||||
 | 
							stdout: process.stdout,
 | 
				
			||||||
 | 
							stderr: process.stderr,
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
						execa('pnpm', ['--filter', 'megalodon', 'build'], {
 | 
				
			||||||
 | 
							cwd: _dirname + '/../',
 | 
				
			||||||
 | 
							stdout: process.stdout,
 | 
				
			||||||
 | 
							stderr: process.stderr,
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
await execa('pnpm', ['build-assets'], {
 | 
					await Promise.all([
 | 
				
			||||||
	cwd: _dirname + '/../',
 | 
						execa('pnpm', ['--filter', 'misskey-reversi', 'build'], {
 | 
				
			||||||
	stdout: process.stdout,
 | 
							cwd: _dirname + '/../',
 | 
				
			||||||
	stderr: process.stderr,
 | 
							stdout: process.stdout,
 | 
				
			||||||
});
 | 
							stderr: process.stderr,
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
await execa('pnpm', ['--filter', 'misskey-js', 'ts'], {
 | 
						execa('pnpm', ['--filter', 'misskey-bubble-game', 'build'], {
 | 
				
			||||||
	cwd: _dirname + '/../',
 | 
							cwd: _dirname + '/../',
 | 
				
			||||||
	stdout: process.stdout,
 | 
							stdout: process.stdout,
 | 
				
			||||||
	stderr: process.stderr,
 | 
							stderr: process.stderr,
 | 
				
			||||||
});
 | 
						}),
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
await execa("pnpm", ['--filter', 'megalodon', 'build'], {
 | 
					 | 
				
			||||||
	cwd: _dirname + '/../',
 | 
					 | 
				
			||||||
	stdout: process.stdout,
 | 
					 | 
				
			||||||
	stderr: process.stderr,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
await execa('pnpm', ['--filter', 'misskey-reversi', 'build:tsc'], {
 | 
					 | 
				
			||||||
	cwd: _dirname + '/../',
 | 
					 | 
				
			||||||
	stdout: process.stdout,
 | 
					 | 
				
			||||||
	stderr: process.stderr,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
await execa('pnpm', ['--filter', 'misskey-bubble-game', 'build:tsc'], {
 | 
					 | 
				
			||||||
	cwd: _dirname + '/../',
 | 
					 | 
				
			||||||
	stdout: process.stdout,
 | 
					 | 
				
			||||||
	stderr: process.stderr,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
execa('pnpm', ['build-pre', '--watch'], {
 | 
					execa('pnpm', ['build-pre', '--watch'], {
 | 
				
			||||||
	cwd: _dirname + '/../',
 | 
						cwd: _dirname + '/../',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue