refactor: post-form-attaches to composition api #57

Closed
norm wants to merge 18 commits from (deleted):refactor/post-form-attaches into main
282 changed files with 2291 additions and 1949 deletions

677
CHANGELOG-OLD.md Normal file
View file

@ -0,0 +1,677 @@
<!--
## 12.x.x (unreleased)
### Improvements
### Bugfixes
-
You should also include the user name that made the change.
-->
## 12.x.x (unreleased)
### Changes
- ハイライトがみつけるに統合されました
- カスタム絵文字ページはインスタンス情報ページに統合されました
- 連合ページはインスタンス情報ページに統合されました
### Improvements
- Client: Fix URL-encoded routing
- Server: Allow GET method for some endpoints @syuilo
- Server: Add rate limit to i/notifications @tamaina
- Client: Improve control panel @syuilo
- Client: Show warning in control panel when there is an unresolved abuse report @syuilo
- Client: For notes with specified visibility, show recipients when hovering over visibility symbol. @Johann150
- Client: Add rss-ticker widget @syuilo
- Client: Removing entries from a clip @futchitwo
- Client: Poll highlights in explore page @syuilo
- Make possible to delete an account by admin @syuilo
- Improve player detection in URL preview @mei23
- Add Badge Image to Push Notification #8012 @tamaina
- Server: Improve performance
- Server: Supports IPv6 on Redis transport. @mei23
IPv4/IPv6 is used by default. You can tune this behavior via `redis.family`.
### Bugfixes
- Server: Fix GenerateVideoThumbnail failed @mei23
- Server: Ensure temp directory cleanup @Johann150
- favicons of federated instances not showing @syuilo
- Client: fix switch to receive email notifications @syuilo
- Client: Page freezes when trying to open configuration page of existing webhooks @syuilo
## 12.111.1 (2022/06/13)
### Bugfixes
- some fixes of multiple notification read @tamaina
- some GenerateVideoThumbnail failed @Johann150
- Client: デッキでウィジェットの情報が保存されない問題を修正 @syuilo
- Client: ギャラリーの投稿を開こうとすると編集画面が表示される @futchitwo
## 12.111.0 (2022/06/11)
### Note
- Node.js 16.15.0 or later is required
### Improvements
- Supports Unicode Emoji 14.0 @mei23
- プッシュ通知を複数アカウント対応に #7667 @tamaina
- プッシュ通知にクリックやactionを設定 #7667 @tamaina
- ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
- Server: always remove completed tasks of job queue @Johann150
- Client: アバターの設定で画像をクロップできるように @syuilo
- Client: make emoji stand out more on reaction button @Johann150
- Client: display URL of QR code for TOTP registration @tamaina
- Client: render quote renote CWs as MFM @pixeldesu
- API: notifications/readは配列でも受け付けるように #7667 @tamaina
- API: ユーザー検索で、クエリがusernameの条件を満たす場合はusernameもLIKE検索するように @tamaina
- MFM: Allow speed changes in all animated MFMs @Johann150
- The theme color is now better validated. @Johann150
Your own theme color may be unset if it was in an invalid format.
Admins should check their instance settings if in doubt.
- Perform port diagnosis at startup only when Listen fails @mei23
- Rate limiting is now also usable for non-authenticated users. @Johann150 @mei23
Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
### Bugfixes
- Server: keep file order of note attachement @Johann150
- Server: fix missing foreign key for reports leading to reports page being unusable @Johann150
- Server: fix internal in-memory caching @Johann150
- Server: prevent crash when processing certain PNGs @syuilo
- Server: Fix unable to generate video thumbnails @mei23
- Server: Fix `Cannot find module` issue @mei23
- Federation: Add rel attribute to host-meta @mei23
- Federation: add id for activitypub follows @Johann150
- Federation: use `source` instead of `_misskey_content` @Johann150
- Federation: ensure resolver does not fetch local resources via HTTP(S) @Johann150
- Federation: correctly render empty note text @Johann150
- Federation: Fix quote renotes containing no text being federated correctly @Johann150
- Federation: remove duplicate br tag/newline @Johann150
- Federation: add missing authorization checks @Johann150
- Client: fix profile picture height in mentions @tamaina
- Client: fix abuse reports page to be able to show all reports @Johann150
- Client: fix settings page @tamaina
- Client: fix profile tabs @futchitwo
- Client: fix popout URL @futchitwo
- Client: correctly handle MiAuth URLs with query string @sn0w
- Client: ノート詳細ページの新しいノートを表示する機能の動作が正しくなるように修正する @xianonn
- MFM: more animated functions support `speed` parameter @futchitwo
- MFM: limit large MFM @Johann150
## 12.110.1 (2022/04/23)
### Bugfixes
- Fix GOP rendering @syuilo
- Improve performance of antenna, clip, and list @xianonn
## 12.110.0 (2022/04/11)
### Improvements
- Improve webhook @syuilo
- Client: Show loading icon on splash screen @syuilo
### Bugfixes
- API: parameter validation of users/show was wrong
- Federation: リモートインスタンスへのダイレクト投稿が届かない問題を修正 @syuilo
## 12.109.2 (2022/04/03)
### Bugfixes
- API: admin/update-meta was not working @syuilo
- Client: テーマを切り替えたり読み込んだりするとmeta[name="theme-color"]のcontentがundefinedになる問題を修正 @tamaina
## 12.109.1 (2022/04/02)
### Bugfixes
- API: Renoteが行えない問題を修正
## 12.109.0 (2022/04/02)
### Improvements
- Webhooks @syuilo
- Bull Dashboardを組み込み、ジョブキューの確認や操作を行えるように @syuilo
- Bull Dashboardを開くには、最初だけ一旦ログアウトしてから再度管理者権限を持つアカウントでログインする必要があります
- Check that installed Node.js version fulfills version requirement @ThatOneCalculator
- Server: overall performance improvements @syuilo
- Federation: avoid duplicate activity delivery @Johann150
- Federation: limit federation of reactions on direct notes @Johann150
- Client: タッチパッド・タッチスクリーンでのデッキの操作性を向上 @tamaina
### Bugfixes
- email address validation was not working @ybw2016v
- API: fix endpoint endpoint @Johann150
- API: fix admin/meta endpoint @syuilo
- API: improved validation and documentation for endpoints that accept different variants of input @Johann150
- API: `notes/create`: The `mediaIds` property is now deprecated. @Johann150
- Use `fileIds` instead, it has the same behaviour.
- Client: URIエンコーディングが異常でdecodeURIComponentが失敗するとURLが表示できなくなる問題を修正 @tamaina
## 12.108.1 (2022/03/12)
### Bugfixes
- リレーが動作しない問題を修正 @xianonn
- ulidを使用していると動作しない問題を修正 @syuilo
- 外部からOGPが正しく取得できない問題を修正 @syuilo
- instance can not get the files from other instance when there are items in allowedPrivateNetworks in .config/default.yml @ybw2016v
## 12.108.0 (2022/03/09)
### NOTE
このバージョンからNode v16.14.0以降が必要です
### Changes
- ートの最大文字数を設定できる機能が廃止され、デフォルトで一律3000文字になりました @syuilo
- Misskey can no longer terminate HTTPS connections. @Johann150
- If you did not use a reverse proxy (e.g. nginx) before, you will probably need to adjust
your configuration file and set up a reverse proxy. The `https` configuration key is no
longer recognized!
### Improvements
- インスタンスデフォルトテーマを設定できるように @syuilo
- ミュートに期限を設定できるように @syuilo
- アンケートが終了したときに通知が作成されるように @syuilo
- プロフィールの追加情報を最大16まで保存できるように @syuilo
- 連合チャートにPub&Subを追加 @syuilo
- 連合チャートにActiveを追加 @syuilo
- デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo
- 設定ファイルの`db.extra`に`statement_timeout`を設定することでタイムアウト時間を変更できます
- Client: スプラッシュスクリーンにインスタンスのアイコンを表示するように @syuilo
### Bugfixes
- Client: リアクションピッカーの高さが低くなったまま戻らないことがあるのを修正 @syuilo
- Client: ユーザー名オートコンプリートが正しく動作しない問題を修正 @syuilo
- Client: タッチ操作だとウィジェットの編集がしにくいのを修正 @xianonn
- Client: register_note_view_interruptor()が動かないのを修正 @syuilo
- Client: iPhone X以降(?)でページの内容が全て表示しきれないのを修正 @tamaina
- Client: fix image caption on mobile @nullobsi
## 12.107.0 (2022/02/12)
### Improvements
- クライアント: テーマを追加 @syuilo
### Bugfixes
- API: stats APIで内部エラーが発生する問題を修正 @syuilo
- クライアント: ソフトミュートですべてがマッチしてしまう場合があるのを修正 @tamaina
- クライアント: デバイスのスクリーンのセーフエリアを考慮するように @syuilo
- クライアント: 一部環境でサイドバーの投稿ボタンが表示されない問題を修正 @syuilo
## 12.106.3 (2022/02/11)
### Improvements
- クライアント: スマートフォンでの余白を調整 @syuilo
### Bugfixes
- クライアント: ノートの詳細が表示されない問題を修正 @syuilo
## 12.106.2 (2022/02/11)
### Bugfixes
- クライアント: 削除したノートがタイムラインから自動で消えない問題を修正 @syuilo
- クライアント: リアクション数が正しくないことがある問題を修正 @syuilo
- 一部環境でマイグレーションが動作しない問題を修正 @syuilo
## 12.106.1 (2022/02/11)
### Bugfixes
- クライアント: ワードミュートが保存できない問題を修正 @syuilo
## 12.106.0 (2022/02/11)
### Improvements
- Improve federation chart @syuilo
- クライアント: リアクションピッカーのサイズを設定できるように @syuilo
- クライアント: リアクションピッカーの幅、高さ制限を緩和 @syuilo
- Docker: Update to Node v16.13.2 @mei23
- Update dependencies
### Bugfixes
- validate regular expressions in word mutes @Johann150
## 12.105.0 (2022/02/09)
### Improvements
- インスタンスのテーマカラーを設定できるように @syuilo
### Bugfixes
- 一部環境でマイグレーションが失敗する問題を修正 @syuilo
## 12.104.0 (2022/02/09)
### Note
ビルドする前に`npm run clean`を実行してください。
このリリースはマイグレーションの規模が大きいため、インスタンスによってはマイグレーションに時間がかかる可能性があります。
マイグレーションが終わらない場合は、チャートの情報はリセットされてしまいますが`__chart__`で始まるテーブルの**レコード**を全て削除(テーブル自体は消さないでください)してから再度試す方法もあります。
### Improvements
- チャートエンジンの強化 @syuilo
- テーブルサイズの削減
- notes/instance/perUserNotesチャートに添付ファイル付きートの数を追加
- activeUsersチャートに新しい項目を追加
- federationチャートに新しい項目を追加
- apRequestチャートを追加
- networkチャート廃止
- クライアント: 自インスタンス情報ページでチャートを見れるように @syuilo
- クライアント: デバイスの種類を手動指定できるように @syuilo
- クライアント: UIのアイコンを更新 @syuilo
- クライアント: UIのアイコンをセルフホスティングするように @syuilo
- NodeInfo のユーザー数と投稿数の内容を見直す @xianonn
### Bugfixes
- Client: タイムライン種別を切り替えると「新しいノートがあります」の表示が残留してしまうのを修正 @tamaina
- Client: UIのサイズがおかしくなる問題の修正 @tamaina
- Client: Setting instance information of notes to always show breaks the timeline @Johann150
- Client: 環境に依っては返信する際のカーソル位置が正しくない問題を修正 @syuilo
- Client: コントロールパネルのユーザー、ファイルにて、インスタンスの表示範囲切り替えが機能しない問題を修正 @syuilo
- Client: アップデートお知らせダイアログが出ないのを修正 @syuilo
- Client: Follows/Followers Visibility changes won't be saved unless clicking on an other checkbox @Johann150
- API: Fix API cast @mei23
- add instance favicon where it's missing @solfisher
- チャートの定期resyncが動作していない問題を修正 @syuilo
## 12.103.1 (2022/02/02)
### Bugfixes
- クライアント: ツールチップの表示位置が正しくない問題を修正
## 12.103.0 (2022/02/02)
### Improvements
- クライアント: 連合インスタンスページからインスタンス情報再取得を行えるように
### Bugfixes
- クライアント: 投稿のNSFW画像を表示したあとにリアクションが更新されると画像が非表示になる問題を修正
- クライアント: 「クリップ」ページが開かない問題を修正
- クライアント: トレンドウィジェットが動作しないのを修正
- クライアント: フェデレーションウィジェットが動作しないのを修正
- クライアント: リアクション設定で絵文字ピッカーが開かないのを修正
- クライアント: DMページでメンションが含まれる問題を修正
- クライアント: 投稿フォームのハッシュタグ保持フィールドが動作しない問題を修正
- クライアント: サイドビューが動かないのを修正
- クライアント: ensure that specified users does not get duplicates
- Add `img-src` and `media-src` directives to `Content-Security-Policy` for
files and media proxy
## 12.102.1 (2022/01/27)
### Bugfixes
- チャットが表示できない問題を修正
## 12.102.0 (2022/01/27)
### NOTE
アップデート後、一部カスタム絵文字が表示できなくなる場合があります。その場合、一旦絵文字管理ページから絵文字を一括エクスポートし、再度コントロールパネルから一括インポートすると直ります。
⚠ 12.102.0以前にエクスポートされたzipとは互換性がありません。アップデートしてからエクスポートを行なってください。
### Changes
- Room機能が削除されました
- 後日別リポジトリとして復活予定です
- リバーシ機能が削除されました
- 後日別リポジトリとして復活予定です
- Chat UIが削除されました
- ートに添付できるファイルの数が16に増えました
- カスタム絵文字にSVGを指定した場合、PNGに変換されて表示されるようになりました
### Improvements
- カスタム絵文字一括編集機能
- カスタム絵文字一括インポート
- 投稿フォームで一時的に投稿するアカウントを切り替えられるように
- Unifying Misskey-specific IRIs in JSON-LD `@context`
- クライアントのパフォーマンス向上
- セキュリティの向上
### Bugfixes
- アップロードエラー時の処理を修正
## 12.101.1 (2021/12/29)
### Bugfixes
- SVG絵文字が表示できないのを修正
- エクスポートした絵文字の拡張子がfalseになることがあるのを修正
## 12.101.0 (2021/12/29)
### Improvements
- クライアント: ノートプレビューの精度を改善
- クライアント: MFM sparkleエフェクトの改善
- クライアント: デザインの調整
- セキュリティの向上
### Bugfixes
- クライアント: 一部のコンポーネントが裏に隠れるのを修正
- fix html blockquote conversion
## 12.100.2 (2021/12/18)
### Bugfixes
- クライアント: Deckカラムの増減がページをリロードするまで正しく反映されない問題を修正
- クライアント: 一部のコンポーネントが裏に隠れるのを修正
- クライアント: カスタム絵文字一覧ページの負荷が高いのを修正
## 12.100.1 (2021/12/17)
### Bugfixes
- クライアント: デザインの調整
## 12.100.0 (2021/12/17)
### Improvements
- クライアント: モバイルでの各種メニュー、リアクションピッカーの表示を改善
### Bugfixes
- クライアント: 一部のコンポーネントが裏に隠れるのを修正
## 12.99.3 (2021/12/14)
### Bugfixes
- クライアント: オートコンプリートがダイアログの裏に隠れる問題を修正
## 12.99.2 (2021/12/14)
## 12.99.1 (2021/12/14)
## 12.99.0 (2021/12/14)
### Improvements
- Added a user-level instance mute in user settings
- フォローエクスポートでミュートしているユーザーを含めないオプションを追加
- フォローエクスポートで使われていないアカウントを含めないオプションを追加
- カスタム絵文字エクスポート機能
- チャートのパフォーマンスの改善
- グループから抜けられるように
### Bugfixes
- クライアント: タッチ機能付きディスプレイを使っていてマウス操作をしている場合に一部機能が動作しない問題を修正
- クライアント: クリップの設定を編集できない問題を修正
- クライアント: メニューなどがウィンドウの裏に隠れる問題を修正
## 12.98.0 (2021/12/03)
### Improvements
- API: /antennas/notes API で日付による絞り込みができるように
- クライアント: アンケートに投票する際に確認ダイアログを出すように
- クライアント: Renoteなート詳細ページから元のートページに遷移できるように
- クライアント: 画像ポップアップでクリックで閉じられるように
- クライアント: デザインの調整
- フォロワーを解除できる機能
### Bugfixes
- クライアント: LTLやGTLが無効になっている場合でもUI上にタブが表示される問題を修正
- クライアント: ログインにおいてパスワードが誤っている際のエラーメッセージが正しく表示されない問題を修正
- クライアント: リアクションツールチップ、Renoteツールチップのユーザーの並び順を修正
- クライアント: サウンドのマスターボリュームが正しく保存されない問題を修正
- クライアント: 一部環境において通知が表示されると操作不能になる問題を修正
- クライアント: モバイルでタップしたときにツールチップが表示される問題を修正
- クライアント: リモートインスタンスのノートに返信するとき、対象のノートにそのリモートインスタンス内のユーザーへのメンションが含まれていると、返信テキスト内にローカルユーザーへのメンションとして引き継がれてしまう場合がある問題を修正
- クライアント: 画像ビューワーで全体表示した時に上側の一部しか表示されない画像がある問題を修正
- API: ユーザーを取得時に条件によっては内部エラーになる問題を修正
### Changes
- クライアント: ノートにモデレーターバッジを表示するのを廃止
## 12.97.0 (2021/11/19)
### Improvements
- クライアント: 返信先やRenoteに対しても自動折りたたみされるように
- クライアント: 長いスレッドの表示を改善
- クライアント: 翻訳にもMFMを適用し、元の文章の改行などを保持するように
- クライアント: アカウント削除に確認ダイアログを出すように
### Bugfixes
- クライアント: ユーザー検索の「全て」が動作しない問題を修正
- クライアント: リアクション一覧、Renote一覧ツールチップのスタイルを修正
## 12.96.1 (2021/11/13)
### Improvements
- npm scriptの互換性を向上
## 12.96.0 (2021/11/13)
### Improvements
- フォロー/フォロワーを非公開にできるように
- インスタンスプロフィールレンダリング ready
- 通知のリアクションアイコンをホバーで拡大できるように
- RenoteボタンをホバーでRenoteしたユーザー一覧を表示するように
- 返信の際にメンションを含めるように
- 通報があったときに管理者へEメールで通知されるように
- メールアドレスのバリデーションを強化
### Bugfixes
- アカウント削除処理があると高負荷になる問題を修正
- クライアント: 長いメニューが画面からはみ出す問題を修正
- クライアント: コントロールパネルのジョブキューに個々のジョブが表示されないのを修正
- クライアント: fix missing i18n string
- fix html conversion issue with code blocks
### Changes
- ノートにモバイルからの投稿か否かの情報を含めないように
## 12.95.0 (2021/10/31)
### Improvements
- スレッドミュート機能
### Bugfixes
- リレー向けのActivityが一部実装で除外されてしまうことがあるのを修正
- 削除したノートやユーザーがリモートから参照されると復活することがあるのを修正
- クライアント: ページ編集時のドロップダウンメニューなどが動作しない問題を修正
- クライアント: コントロールパネルのカスタム絵文字タブが切り替わらないように見える問題を修正
- API: ユーザー情報の hasUnreadChannel が常に false になっている問題を修正
## 12.94.1 (2021/10/25)
### Improvements
### Bugfixes
- クライアント: ユーザーページのナビゲーションが失敗する問題を修正
## 12.94.0 (2021/10/25)
### Improvements
- クライアント: 画像ビューアを強化
- クライアント: メンションにユーザーのアバターを表示するように
- クライアント: デザインの調整
- クライアント: twemojiをセルフホスティングするように
### Bugfixes
- クライアント: CWで画像が隠されたとき、画像の高さがおかしいことになる問題を修正
### NOTE
- このバージョンから、iOS 15未満のサポートがされなくなります。対象のバージョンをお使いの方は、iOSのバージョンアップを行ってください。
## 12.93.2 (2021/10/23)
### Bugfixes
- クライアント: ウィジェットを追加できない問題を修正
## 12.93.1 (2021/10/23)
### Bugfixes
- クライアント: 通知上でローカルのリアクションが表示されないのを修正
## 12.93.0 (2021/10/23)
### Improvements
- クライアント: コントロールパネルのパフォーマンスを改善
- クライアント: 自分のリアクション一覧を見れるように
- 設定により、リアクション一覧を全員に公開することも可能
- クライアント: ユーザー検索の精度を強化
- クライアント: 新しいライトテーマを追加
- クライアント: 新しいダークテーマを追加
- API: ユーザーのリアクション一覧を取得する users/reactions を追加
- API: users/search および users/search-by-username-and-host を強化
- ミュート及びブロックのインポートを行えるように
- クライアント: /share のクエリでリプライやファイル等の情報を渡せるように
- チャートのsyncを毎日0時に自動で行うように
### Bugfixes
- クライアント: テーマの管理が行えない問題を修正
- API: アプリケーション通知が取得できない問題を修正
- クライアント: リモートノートで意図せずローカルカスタム絵文字が使われてしまうことがあるのを修正
- ActivityPub: not reacted な Undo.Like がinboxに滞留するのを修正
### Changes
- 連合の考慮に問題があることなどが分かったため、モデレーターをブロックできない仕様を廃止しました
- データベースにログを保存しないようになりました
- ログを永続化したい場合はsyslogを利用してください
## 12.92.0 (2021/10/16)
### Improvements
- アカウント登録にメールアドレスの設定を必須にするオプション
- クライアント: 全体的なUIのブラッシュアップ
- クライアント: MFM関数構文のサジェストを実装
- クライアント: ノート本文を投稿フォーム内でプレビューできるように
- クライアント: 未読の通知のみ表示する機能
- クライアント: 通知ページで通知の種類によるフィルタ
- クライアント: アニメーションを減らす設定の適用範囲を拡充
- クライアント: 新しいダークテーマを追加
- クライアント: テーマコンパイラに hue と saturate 関数を追加
- ActivityPub: HTML -> MFMの変換を強化
- API: グループから抜ける users/groups/leave エンドポイントを実装
- API: i/notifications に unreadOnly オプションを追加
- API: ap系のエンドポイントをログイン必須化+レートリミット追加
- MFM: Add tag syntaxes of bold <b></b> and strikethrough <s></s>
### Bugfixes
- Fix createDeleteAccountJob
- admin inbox queue does not show individual jobs
- クライアント: ヘッダーのタブが折り返される問題を修正
- クライアント: ヘッダーにタブが表示されている状態でタイトルをクリックしたときにタブ選択が表示されるのを修正
- クライアント: ユーザーページのタブが機能していない問題を修正
- クライアント: ピン留めユーザーの設定項目がない問題を修正
- クライアント: Deck UIにおいて、重ねたカラムの片方を畳んだ状態で右に出すと表示が壊れる問題を修正
- API: 管理者およびモデレーターをブロックできてしまう問題を修正
- MFM: Mentions in the link label are parsed as text
- MFM: Add a property to the URL node indicating whether it was enclosed in <>
- MFM: Disallows < and > in hashtags
### Changes
- 保守性やユーザビリティの観点から、Misskeyのコマンドラインオプションが削除されました。
- 必要であれば、代わりに環境変数で設定することができます
- MFM: パフォーマンス、保守性、構文誤認識抑制の観点から、旧関数構文のサポートが削除されました。
- 旧構文(`[foo bar]`)を使用せず、現行の構文(`$[foo bar]`)を使用してください。
## 12.91.0 (2021/09/22)
### Improvements
- ActivityPub: リモートユーザーのDeleteアクティビティに対応
- ActivityPub: add resolver check for blocked instance
- ActivityPub: deliverキューのメモリ使用量を削減
- API: 管理者用アカウント削除APIを実装(/admin/accounts/delete)
- リモートユーザーの削除も可能に
- アカウントが凍結された場合に、凍結された旨を表示してからログアウトするように
- 凍結されたアカウントにログインしようとしたときに、凍結されている旨を表示するように
- リスト、アンテナタイムラインを個別ページとして分割
- UIの改善
- MFMにsparklesエフェクトを追加
- 非ログイン自は更新ダイアログを出さないように
- クライアント起動時、アップデートが利用可能な場合エラー表示およびダイアログ表示しないように
### Bugfixes
- アカウントデータのエクスポート/インポート処理ができない問題を修正
- アンテナの既読が付かない問題を修正
- popupで設定ページを表示すると、アカウントの削除ページにアクセスすることができない問題を修正
- "問題が発生しました"ウィンドウを開くと☓ボタンがなくて閉じれない問題を修正
## 12.90.1 (2021/09/05)
### Bugfixes
- Dockerfileを修正
- ノート翻訳時に公開範囲が考慮されていない問題を修正
## 12.90.0 (2021/09/04)
### Improvements
- 藍モード、および藍ウィジェット
- クライアントに藍ちゃんを召喚することができるようになりました。
- URLからのアップロード, APの添付ファイル, 外部ファイルのプロキシ等では、Privateアドレス等へのリクエストは拒否されるようになりました。
- developmentで動作している場合は、この制限は適用されません。
- Proxy使用時には、この制限は適用されません。
Proxy使用時に同等の制限を行いたい場合は、Proxy側で設定を行う必要があります。
- `default.yml`にて`allowedPrivateNetworks`にCIDRを追加することにより、宛先ネットワークを指定してこの制限から除外することが出来ます。
- アップロード, ダウンロード出来るファイルサイズにハードリミットが適用されるようになりました。(約250MB)
- `default.yml`にて`maxFileSize`を変更することにより、制限値を変更することが出来ます。
### Bugfixes
- 管理者が最初にサインアップするページでログインされないのを修正
- CWを維持する設定を復活
- クライアントの表示を修正
## 12.89.2 (2021/08/24)
### Bugfixes
- カスタムCSSを有効にしているとエラーになる問題を修正
## 12.89.1 (2021/08/24)
### Improvements
- クライアントのデザインの調整
### Bugfixes
- 翻訳でDeepLのProアカウントに対応していない問題を修正
- インスタンス設定でDeepLのAuth Keyが空で表示される問題を修正
- セキュリティの向上
## 12.89.0 (2021/08/21)
### Improvements
- アカウント削除の安定性を向上
- 絵文字オートコンプリートの挙動を改修
- localStorageのaccountsはindexedDBで保持するように
- ActivityPub: ジョブキューの試行タイミングを調整 (#7635)
- API: sw/unregisterを追加
- ワードミュートのドキュメントを追加
- クライアントのデザインの調整
- 依存関係の更新
### Bugfixes
- チャンネルを作成しているとアカウントを削除できないのを修正
- ノートの「削除して編集」をするとアンケートの選択肢が[object Object]になる問題を修正
## 12.88.0 (2021/08/17)
### Features
- ノートの翻訳機能を追加
- 有効にするには、サーバー管理者がDeepLの無料アカウントを登録し、取得した認証キーを「インスタンス設定 > その他 > DeepL Auth Key」に設定する必要があります。
- Misskey更新時にダイアログを表示するように
- ジョブキューウィジェットに警報音を鳴らす設定を追加
### Improvements
- ブロックの挙動を改修
- ブロックされたユーザーがブロックしたユーザーに対してアクション出来ないようになりました。詳細はドキュメントをご確認ください。
- UIデザインの調整
- データベースのインデックスを最適化
- Proxy使用時にKeep-Aliveをサポート
- DNSキャッシュでネガティブキャッシュをサポート
- 依存関係の更新
### Bugfixes
- タッチ操作でウィンドウを閉じることができない問題を修正
- Renoteされた時刻が投稿された時刻のように表示される問題を修正
- コントロールパネルでファイルを削除した際の表示を修正
- ActivityPub: 長いユーザーの名前や自己紹介の対応
## 12.87.0 (2021/08/12)
### Improvements
- 絵文字オートコンプリートで一文字目は最近使った絵文字をサジェストするように
- 絵文字オートコンプリートのパフォーマンスを改善
- about-misskeyページにドキュメントへのリンクを追加
- Docker: Node.jsを16.6.2に
- 依存関係の更新
- 翻訳の更新
### Bugfixes
- Misskey更新時、テーマキャッシュの影響でスタイルがおかしくなる問題を修正
## 12.86.0 (2021/08/11)
### Improvements
- ドキュメントの更新
- ドキュメントにchangelogを追加
- ぼかし効果のオプションを追加
- Vueを3.2.1に更新
- UIの調整
### Bugfixes
- ハッシュタグ入力が空のときに#が付くのを修正
- フォローリクエストのEメール通知を修正

View file

@ -1,677 +1,51 @@
<!-- # Changelog
## 12.x.x (unreleased) All notable changes to this project will be documented in this file.
### Improvements The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Bugfixes This changelog covers changes since Misskey v12.111.1, the version prior to the FoundKey fork.
- For older Misskey versions, see [CHANGELOG-OLD.md](./CHANGELOG-OLD.md).
You should also include the user name that made the change. ## [Unreleased]
--> ### Added
- Server: Replies can now be fetched recursively.
## 12.x.x (unreleased) ### Changed
- Server: Replies/quotes cannot have a more open visibility than the parent post
### Changes - Client: Searching in the emoji picker is now case insensitive
- ハイライトがみつけるに統合されました - Client: MFM search button changed to a no-op
- カスタム絵文字ページはインスタンス情報ページに統合されました
- 連合ページはインスタンス情報ページに統合されました
### Improvements
- Client: Fix URL-encoded routing - Client: Fix URL-encoded routing
- Server: Allow GET method for some endpoints @syuilo - Server: Allow GET method for some endpoints
- Server: Add rate limit to i/notifications @tamaina - Server: Add rate limit to i/notifications
- Client: Improve control panel @syuilo - Client: Improve control panel
- Client: Show warning in control panel when there is an unresolved abuse report @syuilo - Client: Show warning in control panel when there is an unresolved abuse report
- Client: For notes with specified visibility, show recipients when hovering over visibility symbol. @Johann150 - Client: For notes with specified visibility, show recipients when hovering over visibility symbol.
- Client: Add rss-ticker widget @syuilo - Client: Add rss-ticker widget
- Client: Removing entries from a clip @futchitwo - Client: Removing entries from a clip
- Client: Poll highlights in explore page @syuilo - Client: Poll highlights in explore page
- Make possible to delete an account by admin @syuilo - Make possible to delete an account by admin
- Improve player detection in URL preview @mei23 - Improve player detection in URL preview
- Add Badge Image to Push Notification #8012 @tamaina - Add Badge Image to Push Notification
- Server: Improve performance - Server: Improve performance
- Server: Supports IPv6 on Redis transport. @mei23 - Server: Supports IPv6 on Redis transport.
IPv4/IPv6 is used by default. You can tune this behavior via `redis.family`. IPv4/IPv6 is used by default. You can tune this behavior via `redis.family`.
- Custom Emoji pages have been merged into the Instance Info page
### Bugfixes - Mutes and blocks now apply recursively to replies and renotes.
- Server: Fix GenerateVideoThumbnail failed @mei23
- Server: Ensure temp directory cleanup @Johann150 ### Removed
- favicons of federated instances not showing @syuilo - Ability to show advertisements
- Client: fix switch to receive email notifications @syuilo - Server: ID generation methods other than `aid`
- Client: Page freezes when trying to open configuration page of existing webhooks @syuilo
### Fixed
## 12.111.1 (2022/06/13) - Server: Video thumbnails are now generated properly
- Server: Ensure temp directory cleanup
### Bugfixes - Favicons of remote instances now show up
- some fixes of multiple notification read @tamaina - Client: Fix switch to receive email notifications
- some GenerateVideoThumbnail failed @Johann150 - Client: Page freezes when trying to open configuration page of existing webhooks
- Client: デッキでウィジェットの情報が保存されない問題を修正 @syuilo - Client: Fix a bug where new chat messages don't show up
- Client: ギャラリーの投稿を開こうとすると編集画面が表示される @futchitwo - Client: Fix collapsing long notes
- Client: Add padding to pages
## 12.111.0 (2022/06/11)
### Note ### Security
- Node.js 16.15.0 or later is required - Hide metadata of private notes
### Improvements
- Supports Unicode Emoji 14.0 @mei23
- プッシュ通知を複数アカウント対応に #7667 @tamaina
- プッシュ通知にクリックやactionを設定 #7667 @tamaina
- ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
- Server: always remove completed tasks of job queue @Johann150
- Client: アバターの設定で画像をクロップできるように @syuilo
- Client: make emoji stand out more on reaction button @Johann150
- Client: display URL of QR code for TOTP registration @tamaina
- Client: render quote renote CWs as MFM @pixeldesu
- API: notifications/readは配列でも受け付けるように #7667 @tamaina
- API: ユーザー検索で、クエリがusernameの条件を満たす場合はusernameもLIKE検索するように @tamaina
- MFM: Allow speed changes in all animated MFMs @Johann150
- The theme color is now better validated. @Johann150
Your own theme color may be unset if it was in an invalid format.
Admins should check their instance settings if in doubt.
- Perform port diagnosis at startup only when Listen fails @mei23
- Rate limiting is now also usable for non-authenticated users. @Johann150 @mei23
Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
### Bugfixes
- Server: keep file order of note attachement @Johann150
- Server: fix missing foreign key for reports leading to reports page being unusable @Johann150
- Server: fix internal in-memory caching @Johann150
- Server: prevent crash when processing certain PNGs @syuilo
- Server: Fix unable to generate video thumbnails @mei23
- Server: Fix `Cannot find module` issue @mei23
- Federation: Add rel attribute to host-meta @mei23
- Federation: add id for activitypub follows @Johann150
- Federation: use `source` instead of `_misskey_content` @Johann150
- Federation: ensure resolver does not fetch local resources via HTTP(S) @Johann150
- Federation: correctly render empty note text @Johann150
- Federation: Fix quote renotes containing no text being federated correctly @Johann150
- Federation: remove duplicate br tag/newline @Johann150
- Federation: add missing authorization checks @Johann150
- Client: fix profile picture height in mentions @tamaina
- Client: fix abuse reports page to be able to show all reports @Johann150
- Client: fix settings page @tamaina
- Client: fix profile tabs @futchitwo
- Client: fix popout URL @futchitwo
- Client: correctly handle MiAuth URLs with query string @sn0w
- Client: ノート詳細ページの新しいノートを表示する機能の動作が正しくなるように修正する @xianonn
- MFM: more animated functions support `speed` parameter @futchitwo
- MFM: limit large MFM @Johann150
## 12.110.1 (2022/04/23)
### Bugfixes
- Fix GOP rendering @syuilo
- Improve performance of antenna, clip, and list @xianonn
## 12.110.0 (2022/04/11)
### Improvements
- Improve webhook @syuilo
- Client: Show loading icon on splash screen @syuilo
### Bugfixes
- API: parameter validation of users/show was wrong
- Federation: リモートインスタンスへのダイレクト投稿が届かない問題を修正 @syuilo
## 12.109.2 (2022/04/03)
### Bugfixes
- API: admin/update-meta was not working @syuilo
- Client: テーマを切り替えたり読み込んだりするとmeta[name="theme-color"]のcontentがundefinedになる問題を修正 @tamaina
## 12.109.1 (2022/04/02)
### Bugfixes
- API: Renoteが行えない問題を修正
## 12.109.0 (2022/04/02)
### Improvements
- Webhooks @syuilo
- Bull Dashboardを組み込み、ジョブキューの確認や操作を行えるように @syuilo
- Bull Dashboardを開くには、最初だけ一旦ログアウトしてから再度管理者権限を持つアカウントでログインする必要があります
- Check that installed Node.js version fulfills version requirement @ThatOneCalculator
- Server: overall performance improvements @syuilo
- Federation: avoid duplicate activity delivery @Johann150
- Federation: limit federation of reactions on direct notes @Johann150
- Client: タッチパッド・タッチスクリーンでのデッキの操作性を向上 @tamaina
### Bugfixes
- email address validation was not working @ybw2016v
- API: fix endpoint endpoint @Johann150
- API: fix admin/meta endpoint @syuilo
- API: improved validation and documentation for endpoints that accept different variants of input @Johann150
- API: `notes/create`: The `mediaIds` property is now deprecated. @Johann150
- Use `fileIds` instead, it has the same behaviour.
- Client: URIエンコーディングが異常でdecodeURIComponentが失敗するとURLが表示できなくなる問題を修正 @tamaina
## 12.108.1 (2022/03/12)
### Bugfixes
- リレーが動作しない問題を修正 @xianonn
- ulidを使用していると動作しない問題を修正 @syuilo
- 外部からOGPが正しく取得できない問題を修正 @syuilo
- instance can not get the files from other instance when there are items in allowedPrivateNetworks in .config/default.yml @ybw2016v
## 12.108.0 (2022/03/09)
### NOTE
このバージョンからNode v16.14.0以降が必要です
### Changes
- ートの最大文字数を設定できる機能が廃止され、デフォルトで一律3000文字になりました @syuilo
- Misskey can no longer terminate HTTPS connections. @Johann150
- If you did not use a reverse proxy (e.g. nginx) before, you will probably need to adjust
your configuration file and set up a reverse proxy. The `https` configuration key is no
longer recognized!
### Improvements
- インスタンスデフォルトテーマを設定できるように @syuilo
- ミュートに期限を設定できるように @syuilo
- アンケートが終了したときに通知が作成されるように @syuilo
- プロフィールの追加情報を最大16まで保存できるように @syuilo
- 連合チャートにPub&Subを追加 @syuilo
- 連合チャートにActiveを追加 @syuilo
- デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo
- 設定ファイルの`db.extra`に`statement_timeout`を設定することでタイムアウト時間を変更できます
- Client: スプラッシュスクリーンにインスタンスのアイコンを表示するように @syuilo
### Bugfixes
- Client: リアクションピッカーの高さが低くなったまま戻らないことがあるのを修正 @syuilo
- Client: ユーザー名オートコンプリートが正しく動作しない問題を修正 @syuilo
- Client: タッチ操作だとウィジェットの編集がしにくいのを修正 @xianonn
- Client: register_note_view_interruptor()が動かないのを修正 @syuilo
- Client: iPhone X以降(?)でページの内容が全て表示しきれないのを修正 @tamaina
- Client: fix image caption on mobile @nullobsi
## 12.107.0 (2022/02/12)
### Improvements
- クライアント: テーマを追加 @syuilo
### Bugfixes
- API: stats APIで内部エラーが発生する問題を修正 @syuilo
- クライアント: ソフトミュートですべてがマッチしてしまう場合があるのを修正 @tamaina
- クライアント: デバイスのスクリーンのセーフエリアを考慮するように @syuilo
- クライアント: 一部環境でサイドバーの投稿ボタンが表示されない問題を修正 @syuilo
## 12.106.3 (2022/02/11)
### Improvements
- クライアント: スマートフォンでの余白を調整 @syuilo
### Bugfixes
- クライアント: ノートの詳細が表示されない問題を修正 @syuilo
## 12.106.2 (2022/02/11)
### Bugfixes
- クライアント: 削除したノートがタイムラインから自動で消えない問題を修正 @syuilo
- クライアント: リアクション数が正しくないことがある問題を修正 @syuilo
- 一部環境でマイグレーションが動作しない問題を修正 @syuilo
## 12.106.1 (2022/02/11)
### Bugfixes
- クライアント: ワードミュートが保存できない問題を修正 @syuilo
## 12.106.0 (2022/02/11)
### Improvements
- Improve federation chart @syuilo
- クライアント: リアクションピッカーのサイズを設定できるように @syuilo
- クライアント: リアクションピッカーの幅、高さ制限を緩和 @syuilo
- Docker: Update to Node v16.13.2 @mei23
- Update dependencies
### Bugfixes
- validate regular expressions in word mutes @Johann150
## 12.105.0 (2022/02/09)
### Improvements
- インスタンスのテーマカラーを設定できるように @syuilo
### Bugfixes
- 一部環境でマイグレーションが失敗する問題を修正 @syuilo
## 12.104.0 (2022/02/09)
### Note
ビルドする前に`npm run clean`を実行してください。
このリリースはマイグレーションの規模が大きいため、インスタンスによってはマイグレーションに時間がかかる可能性があります。
マイグレーションが終わらない場合は、チャートの情報はリセットされてしまいますが`__chart__`で始まるテーブルの**レコード**を全て削除(テーブル自体は消さないでください)してから再度試す方法もあります。
### Improvements
- チャートエンジンの強化 @syuilo
- テーブルサイズの削減
- notes/instance/perUserNotesチャートに添付ファイル付きートの数を追加
- activeUsersチャートに新しい項目を追加
- federationチャートに新しい項目を追加
- apRequestチャートを追加
- networkチャート廃止
- クライアント: 自インスタンス情報ページでチャートを見れるように @syuilo
- クライアント: デバイスの種類を手動指定できるように @syuilo
- クライアント: UIのアイコンを更新 @syuilo
- クライアント: UIのアイコンをセルフホスティングするように @syuilo
- NodeInfo のユーザー数と投稿数の内容を見直す @xianonn
### Bugfixes
- Client: タイムライン種別を切り替えると「新しいノートがあります」の表示が残留してしまうのを修正 @tamaina
- Client: UIのサイズがおかしくなる問題の修正 @tamaina
- Client: Setting instance information of notes to always show breaks the timeline @Johann150
- Client: 環境に依っては返信する際のカーソル位置が正しくない問題を修正 @syuilo
- Client: コントロールパネルのユーザー、ファイルにて、インスタンスの表示範囲切り替えが機能しない問題を修正 @syuilo
- Client: アップデートお知らせダイアログが出ないのを修正 @syuilo
- Client: Follows/Followers Visibility changes won't be saved unless clicking on an other checkbox @Johann150
- API: Fix API cast @mei23
- add instance favicon where it's missing @solfisher
- チャートの定期resyncが動作していない問題を修正 @syuilo
## 12.103.1 (2022/02/02)
### Bugfixes
- クライアント: ツールチップの表示位置が正しくない問題を修正
## 12.103.0 (2022/02/02)
### Improvements
- クライアント: 連合インスタンスページからインスタンス情報再取得を行えるように
### Bugfixes
- クライアント: 投稿のNSFW画像を表示したあとにリアクションが更新されると画像が非表示になる問題を修正
- クライアント: 「クリップ」ページが開かない問題を修正
- クライアント: トレンドウィジェットが動作しないのを修正
- クライアント: フェデレーションウィジェットが動作しないのを修正
- クライアント: リアクション設定で絵文字ピッカーが開かないのを修正
- クライアント: DMページでメンションが含まれる問題を修正
- クライアント: 投稿フォームのハッシュタグ保持フィールドが動作しない問題を修正
- クライアント: サイドビューが動かないのを修正
- クライアント: ensure that specified users does not get duplicates
- Add `img-src` and `media-src` directives to `Content-Security-Policy` for
files and media proxy
## 12.102.1 (2022/01/27)
### Bugfixes
- チャットが表示できない問題を修正
## 12.102.0 (2022/01/27)
### NOTE
アップデート後、一部カスタム絵文字が表示できなくなる場合があります。その場合、一旦絵文字管理ページから絵文字を一括エクスポートし、再度コントロールパネルから一括インポートすると直ります。
⚠ 12.102.0以前にエクスポートされたzipとは互換性がありません。アップデートしてからエクスポートを行なってください。
### Changes
- Room機能が削除されました
- 後日別リポジトリとして復活予定です
- リバーシ機能が削除されました
- 後日別リポジトリとして復活予定です
- Chat UIが削除されました
- ートに添付できるファイルの数が16に増えました
- カスタム絵文字にSVGを指定した場合、PNGに変換されて表示されるようになりました
### Improvements
- カスタム絵文字一括編集機能
- カスタム絵文字一括インポート
- 投稿フォームで一時的に投稿するアカウントを切り替えられるように
- Unifying Misskey-specific IRIs in JSON-LD `@context`
- クライアントのパフォーマンス向上
- セキュリティの向上
### Bugfixes
- アップロードエラー時の処理を修正
## 12.101.1 (2021/12/29)
### Bugfixes
- SVG絵文字が表示できないのを修正
- エクスポートした絵文字の拡張子がfalseになることがあるのを修正
## 12.101.0 (2021/12/29)
### Improvements
- クライアント: ノートプレビューの精度を改善
- クライアント: MFM sparkleエフェクトの改善
- クライアント: デザインの調整
- セキュリティの向上
### Bugfixes
- クライアント: 一部のコンポーネントが裏に隠れるのを修正
- fix html blockquote conversion
## 12.100.2 (2021/12/18)
### Bugfixes
- クライアント: Deckカラムの増減がページをリロードするまで正しく反映されない問題を修正
- クライアント: 一部のコンポーネントが裏に隠れるのを修正
- クライアント: カスタム絵文字一覧ページの負荷が高いのを修正
## 12.100.1 (2021/12/17)
### Bugfixes
- クライアント: デザインの調整
## 12.100.0 (2021/12/17)
### Improvements
- クライアント: モバイルでの各種メニュー、リアクションピッカーの表示を改善
### Bugfixes
- クライアント: 一部のコンポーネントが裏に隠れるのを修正
## 12.99.3 (2021/12/14)
### Bugfixes
- クライアント: オートコンプリートがダイアログの裏に隠れる問題を修正
## 12.99.2 (2021/12/14)
## 12.99.1 (2021/12/14)
## 12.99.0 (2021/12/14)
### Improvements
- Added a user-level instance mute in user settings
- フォローエクスポートでミュートしているユーザーを含めないオプションを追加
- フォローエクスポートで使われていないアカウントを含めないオプションを追加
- カスタム絵文字エクスポート機能
- チャートのパフォーマンスの改善
- グループから抜けられるように
### Bugfixes
- クライアント: タッチ機能付きディスプレイを使っていてマウス操作をしている場合に一部機能が動作しない問題を修正
- クライアント: クリップの設定を編集できない問題を修正
- クライアント: メニューなどがウィンドウの裏に隠れる問題を修正
## 12.98.0 (2021/12/03)
### Improvements
- API: /antennas/notes API で日付による絞り込みができるように
- クライアント: アンケートに投票する際に確認ダイアログを出すように
- クライアント: Renoteなート詳細ページから元のートページに遷移できるように
- クライアント: 画像ポップアップでクリックで閉じられるように
- クライアント: デザインの調整
- フォロワーを解除できる機能
### Bugfixes
- クライアント: LTLやGTLが無効になっている場合でもUI上にタブが表示される問題を修正
- クライアント: ログインにおいてパスワードが誤っている際のエラーメッセージが正しく表示されない問題を修正
- クライアント: リアクションツールチップ、Renoteツールチップのユーザーの並び順を修正
- クライアント: サウンドのマスターボリュームが正しく保存されない問題を修正
- クライアント: 一部環境において通知が表示されると操作不能になる問題を修正
- クライアント: モバイルでタップしたときにツールチップが表示される問題を修正
- クライアント: リモートインスタンスのノートに返信するとき、対象のノートにそのリモートインスタンス内のユーザーへのメンションが含まれていると、返信テキスト内にローカルユーザーへのメンションとして引き継がれてしまう場合がある問題を修正
- クライアント: 画像ビューワーで全体表示した時に上側の一部しか表示されない画像がある問題を修正
- API: ユーザーを取得時に条件によっては内部エラーになる問題を修正
### Changes
- クライアント: ノートにモデレーターバッジを表示するのを廃止
## 12.97.0 (2021/11/19)
### Improvements
- クライアント: 返信先やRenoteに対しても自動折りたたみされるように
- クライアント: 長いスレッドの表示を改善
- クライアント: 翻訳にもMFMを適用し、元の文章の改行などを保持するように
- クライアント: アカウント削除に確認ダイアログを出すように
### Bugfixes
- クライアント: ユーザー検索の「全て」が動作しない問題を修正
- クライアント: リアクション一覧、Renote一覧ツールチップのスタイルを修正
## 12.96.1 (2021/11/13)
### Improvements
- npm scriptの互換性を向上
## 12.96.0 (2021/11/13)
### Improvements
- フォロー/フォロワーを非公開にできるように
- インスタンスプロフィールレンダリング ready
- 通知のリアクションアイコンをホバーで拡大できるように
- RenoteボタンをホバーでRenoteしたユーザー一覧を表示するように
- 返信の際にメンションを含めるように
- 通報があったときに管理者へEメールで通知されるように
- メールアドレスのバリデーションを強化
### Bugfixes
- アカウント削除処理があると高負荷になる問題を修正
- クライアント: 長いメニューが画面からはみ出す問題を修正
- クライアント: コントロールパネルのジョブキューに個々のジョブが表示されないのを修正
- クライアント: fix missing i18n string
- fix html conversion issue with code blocks
### Changes
- ノートにモバイルからの投稿か否かの情報を含めないように
## 12.95.0 (2021/10/31)
### Improvements
- スレッドミュート機能
### Bugfixes
- リレー向けのActivityが一部実装で除外されてしまうことがあるのを修正
- 削除したノートやユーザーがリモートから参照されると復活することがあるのを修正
- クライアント: ページ編集時のドロップダウンメニューなどが動作しない問題を修正
- クライアント: コントロールパネルのカスタム絵文字タブが切り替わらないように見える問題を修正
- API: ユーザー情報の hasUnreadChannel が常に false になっている問題を修正
## 12.94.1 (2021/10/25)
### Improvements
### Bugfixes
- クライアント: ユーザーページのナビゲーションが失敗する問題を修正
## 12.94.0 (2021/10/25)
### Improvements
- クライアント: 画像ビューアを強化
- クライアント: メンションにユーザーのアバターを表示するように
- クライアント: デザインの調整
- クライアント: twemojiをセルフホスティングするように
### Bugfixes
- クライアント: CWで画像が隠されたとき、画像の高さがおかしいことになる問題を修正
### NOTE
- このバージョンから、iOS 15未満のサポートがされなくなります。対象のバージョンをお使いの方は、iOSのバージョンアップを行ってください。
## 12.93.2 (2021/10/23)
### Bugfixes
- クライアント: ウィジェットを追加できない問題を修正
## 12.93.1 (2021/10/23)
### Bugfixes
- クライアント: 通知上でローカルのリアクションが表示されないのを修正
## 12.93.0 (2021/10/23)
### Improvements
- クライアント: コントロールパネルのパフォーマンスを改善
- クライアント: 自分のリアクション一覧を見れるように
- 設定により、リアクション一覧を全員に公開することも可能
- クライアント: ユーザー検索の精度を強化
- クライアント: 新しいライトテーマを追加
- クライアント: 新しいダークテーマを追加
- API: ユーザーのリアクション一覧を取得する users/reactions を追加
- API: users/search および users/search-by-username-and-host を強化
- ミュート及びブロックのインポートを行えるように
- クライアント: /share のクエリでリプライやファイル等の情報を渡せるように
- チャートのsyncを毎日0時に自動で行うように
### Bugfixes
- クライアント: テーマの管理が行えない問題を修正
- API: アプリケーション通知が取得できない問題を修正
- クライアント: リモートノートで意図せずローカルカスタム絵文字が使われてしまうことがあるのを修正
- ActivityPub: not reacted な Undo.Like がinboxに滞留するのを修正
### Changes
- 連合の考慮に問題があることなどが分かったため、モデレーターをブロックできない仕様を廃止しました
- データベースにログを保存しないようになりました
- ログを永続化したい場合はsyslogを利用してください
## 12.92.0 (2021/10/16)
### Improvements
- アカウント登録にメールアドレスの設定を必須にするオプション
- クライアント: 全体的なUIのブラッシュアップ
- クライアント: MFM関数構文のサジェストを実装
- クライアント: ノート本文を投稿フォーム内でプレビューできるように
- クライアント: 未読の通知のみ表示する機能
- クライアント: 通知ページで通知の種類によるフィルタ
- クライアント: アニメーションを減らす設定の適用範囲を拡充
- クライアント: 新しいダークテーマを追加
- クライアント: テーマコンパイラに hue と saturate 関数を追加
- ActivityPub: HTML -> MFMの変換を強化
- API: グループから抜ける users/groups/leave エンドポイントを実装
- API: i/notifications に unreadOnly オプションを追加
- API: ap系のエンドポイントをログイン必須化+レートリミット追加
- MFM: Add tag syntaxes of bold <b></b> and strikethrough <s></s>
### Bugfixes
- Fix createDeleteAccountJob
- admin inbox queue does not show individual jobs
- クライアント: ヘッダーのタブが折り返される問題を修正
- クライアント: ヘッダーにタブが表示されている状態でタイトルをクリックしたときにタブ選択が表示されるのを修正
- クライアント: ユーザーページのタブが機能していない問題を修正
- クライアント: ピン留めユーザーの設定項目がない問題を修正
- クライアント: Deck UIにおいて、重ねたカラムの片方を畳んだ状態で右に出すと表示が壊れる問題を修正
- API: 管理者およびモデレーターをブロックできてしまう問題を修正
- MFM: Mentions in the link label are parsed as text
- MFM: Add a property to the URL node indicating whether it was enclosed in <>
- MFM: Disallows < and > in hashtags
### Changes
- 保守性やユーザビリティの観点から、Misskeyのコマンドラインオプションが削除されました。
- 必要であれば、代わりに環境変数で設定することができます
- MFM: パフォーマンス、保守性、構文誤認識抑制の観点から、旧関数構文のサポートが削除されました。
- 旧構文(`[foo bar]`)を使用せず、現行の構文(`$[foo bar]`)を使用してください。
## 12.91.0 (2021/09/22)
### Improvements
- ActivityPub: リモートユーザーのDeleteアクティビティに対応
- ActivityPub: add resolver check for blocked instance
- ActivityPub: deliverキューのメモリ使用量を削減
- API: 管理者用アカウント削除APIを実装(/admin/accounts/delete)
- リモートユーザーの削除も可能に
- アカウントが凍結された場合に、凍結された旨を表示してからログアウトするように
- 凍結されたアカウントにログインしようとしたときに、凍結されている旨を表示するように
- リスト、アンテナタイムラインを個別ページとして分割
- UIの改善
- MFMにsparklesエフェクトを追加
- 非ログイン自は更新ダイアログを出さないように
- クライアント起動時、アップデートが利用可能な場合エラー表示およびダイアログ表示しないように
### Bugfixes
- アカウントデータのエクスポート/インポート処理ができない問題を修正
- アンテナの既読が付かない問題を修正
- popupで設定ページを表示すると、アカウントの削除ページにアクセスすることができない問題を修正
- "問題が発生しました"ウィンドウを開くと☓ボタンがなくて閉じれない問題を修正
## 12.90.1 (2021/09/05)
### Bugfixes
- Dockerfileを修正
- ノート翻訳時に公開範囲が考慮されていない問題を修正
## 12.90.0 (2021/09/04)
### Improvements
- 藍モード、および藍ウィジェット
- クライアントに藍ちゃんを召喚することができるようになりました。
- URLからのアップロード, APの添付ファイル, 外部ファイルのプロキシ等では、Privateアドレス等へのリクエストは拒否されるようになりました。
- developmentで動作している場合は、この制限は適用されません。
- Proxy使用時には、この制限は適用されません。
Proxy使用時に同等の制限を行いたい場合は、Proxy側で設定を行う必要があります。
- `default.yml`にて`allowedPrivateNetworks`にCIDRを追加することにより、宛先ネットワークを指定してこの制限から除外することが出来ます。
- アップロード, ダウンロード出来るファイルサイズにハードリミットが適用されるようになりました。(約250MB)
- `default.yml`にて`maxFileSize`を変更することにより、制限値を変更することが出来ます。
### Bugfixes
- 管理者が最初にサインアップするページでログインされないのを修正
- CWを維持する設定を復活
- クライアントの表示を修正
## 12.89.2 (2021/08/24)
### Bugfixes
- カスタムCSSを有効にしているとエラーになる問題を修正
## 12.89.1 (2021/08/24)
### Improvements
- クライアントのデザインの調整
### Bugfixes
- 翻訳でDeepLのProアカウントに対応していない問題を修正
- インスタンス設定でDeepLのAuth Keyが空で表示される問題を修正
- セキュリティの向上
## 12.89.0 (2021/08/21)
### Improvements
- アカウント削除の安定性を向上
- 絵文字オートコンプリートの挙動を改修
- localStorageのaccountsはindexedDBで保持するように
- ActivityPub: ジョブキューの試行タイミングを調整 (#7635)
- API: sw/unregisterを追加
- ワードミュートのドキュメントを追加
- クライアントのデザインの調整
- 依存関係の更新
### Bugfixes
- チャンネルを作成しているとアカウントを削除できないのを修正
- ノートの「削除して編集」をするとアンケートの選択肢が[object Object]になる問題を修正
## 12.88.0 (2021/08/17)
### Features
- ノートの翻訳機能を追加
- 有効にするには、サーバー管理者がDeepLの無料アカウントを登録し、取得した認証キーを「インスタンス設定 > その他 > DeepL Auth Key」に設定する必要があります。
- Misskey更新時にダイアログを表示するように
- ジョブキューウィジェットに警報音を鳴らす設定を追加
### Improvements
- ブロックの挙動を改修
- ブロックされたユーザーがブロックしたユーザーに対してアクション出来ないようになりました。詳細はドキュメントをご確認ください。
- UIデザインの調整
- データベースのインデックスを最適化
- Proxy使用時にKeep-Aliveをサポート
- DNSキャッシュでネガティブキャッシュをサポート
- 依存関係の更新
### Bugfixes
- タッチ操作でウィンドウを閉じることができない問題を修正
- Renoteされた時刻が投稿された時刻のように表示される問題を修正
- コントロールパネルでファイルを削除した際の表示を修正
- ActivityPub: 長いユーザーの名前や自己紹介の対応
## 12.87.0 (2021/08/12)
### Improvements
- 絵文字オートコンプリートで一文字目は最近使った絵文字をサジェストするように
- 絵文字オートコンプリートのパフォーマンスを改善
- about-misskeyページにドキュメントへのリンクを追加
- Docker: Node.jsを16.6.2に
- 依存関係の更新
- 翻訳の更新
### Bugfixes
- Misskey更新時、テーマキャッシュの影響でスタイルがおかしくなる問題を修正
## 12.86.0 (2021/08/11)
### Improvements
- ドキュメントの更新
- ドキュメントにchangelogを追加
- ぼかし効果のオプションを追加
- Vueを3.2.1に更新
- UIの調整
### Bugfixes
- ハッシュタグ入力が空のときに#が付くのを修正
- フォローリクエストのEメール通知を修正

View file

@ -1,6 +1,6 @@
{ {
"name": "foundkey", "name": "foundkey",
"version": "12.111.1-test.1", "version": "13.0.0-preview.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://akkoma.dev/FoundKeyGang/FoundKey.git" "url": "https://akkoma.dev/FoundKeyGang/FoundKey.git"

View file

@ -8,10 +8,10 @@ const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
export function fromHtml(html: string, hashtagNames?: string[]): string { export function fromHtml(html: string, hashtagNames?: string[]): string {
const dom = parse5.parseFragment(
// some AP servers like Pixelfed use br tags as well as newlines // some AP servers like Pixelfed use br tags as well as newlines
html = html.replace(/<br\s?\/?>\r?\n/gi, '\n'); html.replace(/<br\s?\/?>\r?\n/gi, '\n'),
);
const dom = parse5.parseFragment(html);
let text = ''; let text = '';

View file

@ -4,8 +4,11 @@ export type Acct = {
}; };
export function parse(acct: string): Acct { export function parse(acct: string): Acct {
if (acct.startsWith('@')) acct = acct.substr(1); const split = acct.split('@');
const split = acct.split('@', 2); if (split[0].length === 0) {
// there was an initial at
split.shift();
}
return { username: split[0], host: split[1] || null }; return { username: split[0], host: split[1] || null };
} }

View file

@ -9,7 +9,7 @@ export async function verifyRecaptcha(secret: string, response: string) {
}); });
if (result.success !== true) { if (result.success !== true) {
const errorCodes = result['error-codes'] ? result['error-codes']?.join(', ') : ''; const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
throw new Error(`recaptcha-failed: ${errorCodes}`); throw new Error(`recaptcha-failed: ${errorCodes}`);
} }
} }
@ -20,7 +20,7 @@ export async function verifyHcaptcha(secret: string, response: string) {
}); });
if (result.success !== true) { if (result.success !== true) {
const errorCodes = result['error-codes'] ? result['error-codes']?.join(', ') : ''; const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
throw new Error(`hcaptcha-failed: ${errorCodes}`); throw new Error(`hcaptcha-failed: ${errorCodes}`);
} }
} }

View file

@ -65,7 +65,7 @@ export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'No
and.every(keyword => and.every(keyword =>
antenna.caseSensitive antenna.caseSensitive
? note.text!.includes(keyword) ? note.text!.includes(keyword)
: note.text!.toLowerCase().includes(keyword.toLowerCase()) : note.text!.toLowerCase().includes(keyword.toLowerCase()),
)); ));
if (!matched) return false; if (!matched) return false;
@ -83,7 +83,7 @@ export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'No
and.every(keyword => and.every(keyword =>
antenna.caseSensitive antenna.caseSensitive
? note.text!.includes(keyword) ? note.text!.includes(keyword)
: note.text!.toLowerCase().includes(keyword.toLowerCase()) : note.text!.toLowerCase().includes(keyword.toLowerCase()),
)); ));
if (matched) return false; if (matched) return false;

View file

@ -1,12 +1,12 @@
import { URL } from 'node:url';
import { toASCII } from 'punycode'; import { toASCII } from 'punycode';
import { URL } from 'node:url';
import config from '@/config/index.js'; import config from '@/config/index.js';
export function getFullApAccount(username: string, host: string | null) { export function getFullApAccount(username: string, host: string | null): string {
return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`; return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`;
} }
export function isSelfHost(host: string) { export function isSelfHost(host: string | null): boolean {
if (host == null) return true; if (host == null) return true;
return toPuny(config.host) === toPuny(host); return toPuny(config.host) === toPuny(host);
} }

View file

@ -18,7 +18,7 @@ export function createTempDir(): Promise<[string, () => void]> {
(e, path, cleanup) => { (e, path, cleanup) => {
if (e) return rej(e); if (e) return rej(e);
res([path, cleanup]); res([path, cleanup]);
} },
); );
}); });
} }

View file

@ -7,10 +7,8 @@ import * as crypto from 'node:crypto';
const TIME2000 = 946684800000; const TIME2000 = 946684800000;
let counter = crypto.randomBytes(2).readUInt16LE(0); let counter = crypto.randomBytes(2).readUInt16LE(0);
export function genId(date?: Date): string { export function genId(date?: Date = new Date()): string {
if (!date || (date > new Date())) date = new Date(); let t = Math.min(date, new Date());
let t = date.getTime();
t -= TIME2000; t -= TIME2000;
if (t < 0) t = 0; if (t < 0) t = 0;
if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date'); if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date');

View file

@ -1,15 +1,15 @@
import { Packed } from './schema.js'; import { Packed } from './schema.js';
export function isInstanceMuted(note: Packed<'Note'>, mutedInstances: Set<string>): boolean { export function isInstanceMuted(note: Packed<'Note'>, mutedInstances: Set<string>): boolean {
if (mutedInstances.has(note?.user?.host ?? '')) return true; if (mutedInstances.has(note.user.host ?? '')) return true;
if (mutedInstances.has(note?.reply?.user?.host ?? '')) return true; if (mutedInstances.has(note.reply?.user.host ?? '')) return true;
if (mutedInstances.has(note?.renote?.user?.host ?? '')) return true; if (mutedInstances.has(note.renote?.user.host ?? '')) return true;
return false; return false;
} }
export function isUserFromMutedInstance(notif: Packed<'Notification'>, mutedInstances: Set<string>): boolean { export function isUserFromMutedInstance(notif: Packed<'Notification'>, mutedInstances: Set<string>): boolean {
if (mutedInstances.has(notif?.user?.host ?? '')) return true; if (mutedInstances.has(notif.user?.host ?? '')) return true;
return false; return false;
} }

View file

@ -8,7 +8,7 @@ export function nyaize(text: string): string {
.replace(/(?<=every)one/gi, x => x === 'ONE' ? 'NYAN' : 'nyan') .replace(/(?<=every)one/gi, x => x === 'ONE' ? 'NYAN' : 'nyan')
// ko-KR // ko-KR
.replace(/[나-낳]/g, match => String.fromCharCode( .replace(/[나-낳]/g, match => String.fromCharCode(
match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0) match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0),
)) ))
.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, '다냥') .replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, '다냥')
.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, '냥'); .replace(/(야(?=\?))|(야$)|(야(?= ))/gm, '냥');

View file

@ -54,10 +54,10 @@ export function convertLegacyReactions(reactions: Record<string, number>) {
return _reactions2; return _reactions2;
} }
export async function toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise<string> { export async function toDbReaction(reaction?: string | null, idnReacterHost?: string | null): Promise<string> {
if (reaction == null) return await getFallbackReaction(); if (reaction == null) return await getFallbackReaction();
reacterHost = toPunyNullable(reacterHost); const reacterHost = toPunyNullable(idnReacterHost);
// 文字列タイプのリアクションを絵文字に変換 // 文字列タイプのリアクションを絵文字に変換
if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
@ -124,8 +124,8 @@ export function decodeReaction(str: string): DecodedReaction {
}; };
} }
export function convertLegacyReaction(reaction: string): string { export function convertLegacyReaction(_reaction: string): string {
reaction = decodeReaction(reaction).reaction; const reaction = decodeReaction(_reaction).reaction;
if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
return reaction; return reaction;
} }

View file

@ -52,7 +52,7 @@ export class AbuseUserReport {
public resolved: boolean; public resolved: boolean;
@Column('boolean', { @Column('boolean', {
default: false default: false,
}) })
public forwarded: boolean; public forwarded: boolean;

View file

@ -12,7 +12,7 @@ export const AppRepository = db.getRepository(App).extend({
detail?: boolean, detail?: boolean,
includeSecret?: boolean, includeSecret?: boolean,
includeProfileImageIds?: boolean includeProfileImageIds?: boolean
} },
): Promise<Packed<'App'>> { ): Promise<Packed<'App'>> {
const opts = Object.assign({ const opts = Object.assign({
detail: false, detail: false,

View file

@ -7,7 +7,7 @@ import { Apps } from '../index.js';
export const AuthSessionRepository = db.getRepository(AuthSession).extend({ export const AuthSessionRepository = db.getRepository(AuthSession).extend({
async pack( async pack(
src: AuthSession['id'] | AuthSession, src: AuthSession['id'] | AuthSession,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined,
) { ) {
const session = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const session = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });

View file

@ -8,7 +8,7 @@ import { Users } from '../index.js';
export const BlockingRepository = db.getRepository(Blocking).extend({ export const BlockingRepository = db.getRepository(Blocking).extend({
async pack( async pack(
src: Blocking['id'] | Blocking, src: Blocking['id'] | Blocking,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'Blocking'>> { ): Promise<Packed<'Blocking'>> {
const blocking = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const blocking = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
@ -24,7 +24,7 @@ export const BlockingRepository = db.getRepository(Blocking).extend({
packMany( packMany(
blockings: any[], blockings: any[],
me: { id: User['id'] } me: { id: User['id'] },
) { ) {
return Promise.all(blockings.map(x => this.pack(x, me))); return Promise.all(blockings.map(x => this.pack(x, me)));
}, },

View file

@ -9,7 +9,7 @@ export const DriveFolderRepository = db.getRepository(DriveFolder).extend({
src: DriveFolder['id'] | DriveFolder, src: DriveFolder['id'] | DriveFolder,
options?: { options?: {
detail: boolean detail: boolean
} },
): Promise<Packed<'DriveFolder'>> { ): Promise<Packed<'DriveFolder'>> {
const opts = Object.assign({ const opts = Object.assign({
detail: false, detail: false,

View file

@ -6,7 +6,7 @@ import { Users } from '../index.js';
export const FollowRequestRepository = db.getRepository(FollowRequest).extend({ export const FollowRequestRepository = db.getRepository(FollowRequest).extend({
async pack( async pack(
src: FollowRequest['id'] | FollowRequest, src: FollowRequest['id'] | FollowRequest,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined,
) { ) {
const request = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const request = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });

View file

@ -52,12 +52,10 @@ export const FollowingRepository = db.getRepository(Following).extend({
opts?: { opts?: {
populateFollowee?: boolean; populateFollowee?: boolean;
populateFollower?: boolean; populateFollower?: boolean;
} } = {},
): Promise<Packed<'Following'>> { ): Promise<Packed<'Following'>> {
const following = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const following = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
if (opts == null) opts = {};
return await awaitAll({ return await awaitAll({
id: following.id, id: following.id,
createdAt: following.createdAt.toISOString(), createdAt: following.createdAt.toISOString(),
@ -78,7 +76,7 @@ export const FollowingRepository = db.getRepository(Following).extend({
opts?: { opts?: {
populateFollowee?: boolean; populateFollowee?: boolean;
populateFollower?: boolean; populateFollower?: boolean;
} },
) { ) {
return Promise.all(followings.map(x => this.pack(x, me, opts))); return Promise.all(followings.map(x => this.pack(x, me, opts)));
}, },

View file

@ -5,7 +5,7 @@ import { GalleryPosts } from '../index.js';
export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({ export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({
async pack( async pack(
src: GalleryLike['id'] | GalleryLike, src: GalleryLike['id'] | GalleryLike,
me?: any me?: any,
) { ) {
const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
@ -17,7 +17,7 @@ export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({
packMany( packMany(
likes: any[], likes: any[],
me: any me: any,
) { ) {
return Promise.all(likes.map(x => this.pack(x, me))); return Promise.all(likes.map(x => this.pack(x, me)));
}, },

View file

@ -11,7 +11,7 @@ export const MessagingMessageRepository = db.getRepository(MessagingMessage).ext
options?: { options?: {
populateRecipient?: boolean, populateRecipient?: boolean,
populateGroup?: boolean, populateGroup?: boolean,
} },
): Promise<Packed<'MessagingMessage'>> { ): Promise<Packed<'MessagingMessage'>> {
const opts = options || { const opts = options || {
populateRecipient: true, populateRecipient: true,

View file

@ -8,7 +8,7 @@ import { Users } from '../index.js';
export const MutingRepository = db.getRepository(Muting).extend({ export const MutingRepository = db.getRepository(Muting).extend({
async pack( async pack(
src: Muting['id'] | Muting, src: Muting['id'] | Muting,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'Muting'>> { ): Promise<Packed<'Muting'>> {
const muting = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const muting = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
@ -25,7 +25,7 @@ export const MutingRepository = db.getRepository(Muting).extend({
packMany( packMany(
mutings: any[], mutings: any[],
me: { id: User['id'] } me: { id: User['id'] },
) { ) {
return Promise.all(mutings.map(x => this.pack(x, me))); return Promise.all(mutings.map(x => this.pack(x, me)));
}, },

View file

@ -6,7 +6,7 @@ import { Notes } from '../index.js';
export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({ export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
async pack( async pack(
src: NoteFavorite['id'] | NoteFavorite, src: NoteFavorite['id'] | NoteFavorite,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined,
) { ) {
const favorite = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const favorite = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
@ -21,7 +21,7 @@ export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
packMany( packMany(
favorites: any[], favorites: any[],
me: { id: User['id'] } me: { id: User['id'] },
) { ) {
return Promise.allSettled(favorites.map(x => this.pack(x, me))) return Promise.allSettled(favorites.map(x => this.pack(x, me)))
.then(promises => promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : [])); .then(promises => promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : []));

View file

@ -42,5 +42,5 @@ export const NoteReactionRepository = db.getRepository(NoteReaction).extend({
// filter out rejected promises, only keep fulfilled values // filter out rejected promises, only keep fulfilled values
return reactions.flatMap(result => result.status === 'fulfilled' ? [result.value] : []); return reactions.flatMap(result => result.status === 'fulfilled' ? [result.value] : []);
} },
}); });

View file

@ -137,7 +137,7 @@ export const NoteRepository = db.getRepository(Note).extend({
_hint_?: { _hint_?: {
myReactions: Map<Note['id'], NoteReaction | null>; myReactions: Map<Note['id'], NoteReaction | null>;
}; };
} },
): Promise<Packed<'Note'>> { ): Promise<Packed<'Note'>> {
const opts = Object.assign({ const opts = Object.assign({
detail: true, detail: true,
@ -163,7 +163,7 @@ export const NoteRepository = db.getRepository(Note).extend({
: await Channels.findOneBy({ id: note.channelId }) : await Channels.findOneBy({ id: note.channelId })
: null; : null;
const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, '')); const reactionEmojiNames = Object.keys(note.reactions).filter(x => x.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, ''));
const packed: Packed<'Note'> = await awaitAll({ const packed: Packed<'Note'> = await awaitAll({
id: note.id, id: note.id,
@ -233,7 +233,7 @@ export const NoteRepository = db.getRepository(Note).extend({
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
detail?: boolean; detail?: boolean;
} },
) { ) {
if (notes.length === 0) return []; if (notes.length === 0) return [];

View file

@ -16,7 +16,7 @@ export const NotificationRepository = db.getRepository(Notification).extend({
_hintForEachNotes_?: { _hintForEachNotes_?: {
myReactions: Map<Note['id'], NoteReaction | null>; myReactions: Map<Note['id'], NoteReaction | null>;
}; };
} },
): Promise<Packed<'Notification'>> { ): Promise<Packed<'Notification'>> {
const notification = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const notification = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const token = notification.appAccessTokenId ? await AccessTokens.findOneByOrFail({ id: notification.appAccessTokenId }) : null; const token = notification.appAccessTokenId ? await AccessTokens.findOneByOrFail({ id: notification.appAccessTokenId }) : null;
@ -85,7 +85,7 @@ export const NotificationRepository = db.getRepository(Notification).extend({
async packMany( async packMany(
notifications: Notification[], notifications: Notification[],
meId: User['id'] meId: User['id'],
) { ) {
if (notifications.length === 0) return []; if (notifications.length === 0) return [];

View file

@ -6,7 +6,7 @@ import { Pages } from '../index.js';
export const PageLikeRepository = db.getRepository(PageLike).extend({ export const PageLikeRepository = db.getRepository(PageLike).extend({
async pack( async pack(
src: PageLike['id'] | PageLike, src: PageLike['id'] | PageLike,
me?: { id: User['id'] } | null | undefined me?: { id: User['id'] } | null | undefined,
) { ) {
const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
@ -18,7 +18,7 @@ export const PageLikeRepository = db.getRepository(PageLike).extend({
packMany( packMany(
likes: any[], likes: any[],
me: { id: User['id'] } me: { id: User['id'] },
) { ) {
return Promise.all(likes.map(x => this.pack(x, me))); return Promise.all(likes.map(x => this.pack(x, me)));
}, },

View file

@ -10,7 +10,7 @@ export async function awaitAll<T>(obj: Promiseable<T>): Promise<T> {
const resolvedValues = await Promise.all(values.map(value => const resolvedValues = await Promise.all(values.map(value =>
(!value || !value.constructor || value.constructor.name !== 'Object') (!value || !value.constructor || value.constructor.name !== 'Object')
? value ? value
: awaitAll(value) : awaitAll(value),
)); ));
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {

View file

@ -21,9 +21,9 @@ import { ThinUser } from './types.js';
function renderError(e: Error): any { function renderError(e: Error): any {
return { return {
stack: e?.stack, stack: e.stack,
message: e?.message, message: e.message,
name: e?.name, name: e.name,
}; };
} }

View file

@ -21,7 +21,7 @@ export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, c
const limit = promiseLimit<CacheableUser | null>(2); const limit = promiseLimit<CacheableUser | null>(2);
const mentionedUsers = (await Promise.all( const mentionedUsers = (await Promise.all(
others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null))) others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null))),
)).filter((x): x is CacheableUser => x != null); )).filter((x): x is CacheableUser => x != null);
if (toGroups.public.length > 0) { if (toGroups.public.length > 0) {

View file

@ -35,7 +35,7 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi
uri: image.url, uri: image.url,
sensitive: image.sensitive, sensitive: image.sensitive,
isLink: !instance.cacheRemoteFiles, isLink: !instance.cacheRemoteFiles,
comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH) comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH),
}); });
if (file.isLink) { if (file.isLink) {

View file

@ -65,9 +65,7 @@ export async function fetchNote(object: string | IObject): Promise<Note | null>
/** /**
* Noteを作成します * Noteを作成します
*/ */
export async function createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> { export async function createNote(value: string | IObject, resolver?: Resolver = new Resolver(), silent = false): Promise<Note | null> {
if (resolver == null) resolver = new Resolver();
const object: any = await resolver.resolve(value); const object: any = await resolver.resolve(value);
const entryUri = getApId(value); const entryUri = getApId(value);
@ -198,7 +196,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
// テキストのパース // テキストのパース
let text: string | null = null; let text: string | null = null;
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source?.content === 'string') { if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
text = note.source.content; text = note.source.content;
} else if (typeof note._misskey_content !== 'undefined') { } else if (typeof note._misskey_content !== 'undefined') {
text = note._misskey_content; text = note._misskey_content;
@ -302,8 +300,8 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
} }
} }
export async function extractEmojis(tags: IObject | IObject[], host: string): Promise<Emoji[]> { export async function extractEmojis(tags: IObject | IObject[], idnHost: string): Promise<Emoji[]> {
host = toPuny(host); const host = toPuny(idnHost);
if (!tags) return []; if (!tags) return [];

View file

@ -134,15 +134,13 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise<Cac
/** /**
* Personを作成します * Personを作成します
*/ */
export async function createPerson(uri: string, resolver?: Resolver): Promise<User> { export async function createPerson(uri: string, resolver?: Resolver = new Resolver()): Promise<User> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
if (uri.startsWith(config.url)) { if (uri.startsWith(config.url)) {
throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user');
} }
if (resolver == null) resolver = new Resolver();
const object = await resolver.resolve(uri) as any; const object = await resolver.resolve(uri) as any;
const person = validateActor(object, uri); const person = validateActor(object, uri);
@ -283,7 +281,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
* @param resolver Resolver * @param resolver Resolver
* @param hint Hint of Person object (Personの場合Remote resolveをせずに更新に利用します) * @param hint Hint of Person object (Personの場合Remote resolveをせずに更新に利用します)
*/ */
export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise<void> { export async function updatePerson(uri: string, resolver?: Resolver = new Resolver(), hint?: IObject): Promise<void> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
// URIがこのサーバーを指しているならスキップ // URIがこのサーバーを指しているならスキップ
@ -299,8 +297,6 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
} }
//#endregion //#endregion
if (resolver == null) resolver = new Resolver();
const object = hint || await resolver.resolve(uri); const object = hint || await resolver.resolve(uri);
const person = validateActor(object, uri); const person = validateActor(object, uri);
@ -405,8 +401,7 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<C
//#endregion //#endregion
// リモートサーバーからフェッチしてきて登録 // リモートサーバーからフェッチしてきて登録
if (resolver == null) resolver = new Resolver(); return await createPerson(uri, resolver ?? new Resolver());
return await createPerson(uri, resolver);
} }
const services: { const services: {
@ -419,10 +414,11 @@ const services: {
const $discord = (id: string, name: string) => { const $discord = (id: string, name: string) => {
if (typeof name !== 'string') { if (typeof name !== 'string') {
name = 'unknown#0000'; return { id, username: 'unknown', discriminator: '0000' };
} } else {
const [username, discriminator] = name.split('#'); const [username, discriminator] = name.split('#');
return { id, username, discriminator }; return { id, username, discriminator };
}
}; };
function addService(target: { [x: string]: any }, source: IApPropertyValue) { function addService(target: { [x: string]: any }, source: IApPropertyValue) {

View file

@ -5,9 +5,7 @@ import Resolver from '../resolver.js';
import { IObject, IQuestion, isQuestion } from '../type.js'; import { IObject, IQuestion, isQuestion } from '../type.js';
import { apLogger } from '../logger.js'; import { apLogger } from '../logger.js';
export async function extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise<IPoll> { export async function extractPollFromQuestion(source: string | IObject, resolver?: Resolver = new Resolver()): Promise<IPoll> {
if (resolver == null) resolver = new Resolver();
const question = await resolver.resolve(source); const question = await resolver.resolve(source);
if (!isQuestion(question)) { if (!isQuestion(question)) {

View file

@ -53,7 +53,5 @@ export const attachLdSignature = async (activity: any, user: { id: User['id']; h
const ldSignature = new LdSignature(); const ldSignature = new LdSignature();
ldSignature.debug = false; ldSignature.debug = false;
activity = await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${config.url}/users/${user.id}#main-key`); return await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${config.url}/users/${user.id}#main-key`);
return activity;
}; };

View file

@ -77,11 +77,11 @@ export async function renderPerson(user: ILocalUser) {
attachment: attachment.length ? attachment : undefined, attachment: attachment.length ? attachment : undefined,
} as any; } as any;
if (profile?.birthday) { if (profile.birthday) {
person['vcard:bday'] = profile.birthday; person['vcard:bday'] = profile.birthday;
} }
if (profile?.location) { if (profile.location) {
person['vcard:Address'] = profile.location; person['vcard:Address'] = profile.location;
} }

View file

@ -123,7 +123,7 @@ export default class Resolver {
if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI');
return Promise.all( return Promise.all(
[parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id })) [parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id })),
) )
.then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url))); .then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url)));
default: default:

View file

@ -2,7 +2,7 @@ import { URL } from 'node:url';
import chalk from 'chalk'; import chalk from 'chalk';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import config from '@/config/index.js'; import config from '@/config/index.js';
import { toPuny } from '@/misc/convert-host.js'; import { isSelfHost, toPuny } from '@/misc/convert-host.js';
import { User, IRemoteUser } from '@/models/entities/user.js'; import { User, IRemoteUser } from '@/models/entities/user.js';
import { Users } from '@/models/index.js'; import { Users } from '@/models/index.js';
import webFinger from './webfinger.js'; import webFinger from './webfinger.js';
@ -11,10 +11,10 @@ import { remoteLogger } from './logger.js';
const logger = remoteLogger.createSubLogger('resolve-user'); const logger = remoteLogger.createSubLogger('resolve-user');
export async function resolveUser(username: string, host: string | null): Promise<User> { export async function resolveUser(username: string, idnHost: string | null): Promise<User> {
const usernameLower = username.toLowerCase(); const usernameLower = username.toLowerCase();
if (host == null) { if (idnHost == null) {
logger.info(`return local user: ${usernameLower}`); logger.info(`return local user: ${usernameLower}`);
return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => { return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => {
if (u == null) { if (u == null) {
@ -25,9 +25,7 @@ export async function resolveUser(username: string, host: string | null): Promis
}); });
} }
host = toPuny(host); if (isSelfHost(idnHost)) {
if (config.host === host) {
logger.info(`return local user: ${usernameLower}`); logger.info(`return local user: ${usernameLower}`);
return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => { return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => {
if (u == null) { if (u == null) {
@ -38,6 +36,8 @@ export async function resolveUser(username: string, host: string | null): Promis
}); });
} }
// `idnHost` can not be null here because that would have branched off with `isSelfHost`.
const host = toPuny(idnHost!);
const user = await Users.findOneBy({ usernameLower, host }) as IRemoteUser | null; const user = await Users.findOneBy({ usernameLower, host }) as IRemoteUser | null;
const acctLower = `${usernameLower}@${host}`; const acctLower = `${usernameLower}@${host}`;

View file

@ -18,7 +18,7 @@ import orderedCollection from '@/remote/activitypub/renderer/ordered-collection.
export async function readUserMessagingMessage( export async function readUserMessagingMessage(
userId: User['id'], userId: User['id'],
otherpartyId: User['id'], otherpartyId: User['id'],
messageIds: MessagingMessage['id'][] messageIds: MessagingMessage['id'][],
) { ) {
if (messageIds.length === 0) return; if (messageIds.length === 0) return;
@ -58,7 +58,7 @@ export async function readUserMessagingMessage(
recipientId: userId, recipientId: userId,
isRead: false, isRead: false,
}, },
take: 1 take: 1,
}); });
if (!count) { if (!count) {
@ -73,7 +73,7 @@ export async function readUserMessagingMessage(
export async function readGroupMessagingMessage( export async function readGroupMessagingMessage(
userId: User['id'], userId: User['id'],
groupId: UserGroup['id'], groupId: UserGroup['id'],
messageIds: MessagingMessage['id'][] messageIds: MessagingMessage['id'][],
) { ) {
if (messageIds.length === 0) return; if (messageIds.length === 0) return;
@ -135,8 +135,9 @@ export async function readGroupMessagingMessage(
} }
export async function deliverReadActivity(user: { id: User['id']; host: null; }, recipient: IRemoteUser, messages: MessagingMessage | MessagingMessage[]) { export async function deliverReadActivity(user: { id: User['id']; host: null; }, recipient: IRemoteUser, messages: MessagingMessage | MessagingMessage[]) {
messages = toArray(messages).filter(x => x.uri); const contents = toArray(messages)
const contents = messages.map(x => renderReadActivity(user, x)); .filter(x => x.uri)
.map(x => renderReadActivity(user, x));
if (contents.length > 1) { if (contents.length > 1) {
const collection = orderedCollection(null, contents.length, undefined, undefined, contents); const collection = orderedCollection(null, contents.length, undefined, undefined, contents);

View file

@ -7,7 +7,7 @@ import { Notifications, Users } from '@/models/index.js';
export async function readNotification( export async function readNotification(
userId: User['id'], userId: User['id'],
notificationIds: Notification['id'][] notificationIds: Notification['id'][],
) { ) {
if (notificationIds.length === 0) return; if (notificationIds.length === 0) return;
@ -27,7 +27,7 @@ export async function readNotification(
export async function readNotificationByQuery( export async function readNotificationByQuery(
userId: User['id'], userId: User['id'],
query: Record<string, any> query: Record<string, any>,
) { ) {
const notificationIds = await Notifications.findBy({ const notificationIds = await Notifications.findBy({
...query, ...query,

View file

@ -64,7 +64,7 @@ export async function signup(opts: {
passphrase: undefined, passphrase: undefined,
}, },
} as any, (err, publicKey, privateKey) => } as any, (err, publicKey, privateKey) =>
err ? rej(err) : res([publicKey, privateKey]) err ? rej(err) : res([publicKey, privateKey]),
)); ));
let account!: User; let account!: User;

View file

@ -25,7 +25,7 @@ export const paramDef = {
export default define(meta, paramDef, async (ps, me) => { export default define(meta, paramDef, async (ps, me) => {
const [user, profile] = await Promise.all([ const [user, profile] = await Promise.all([
Users.findOneBy({ id: ps.userId }), Users.findOneBy({ id: ps.userId }),
UserProfiles.findOneBy({ userId: ps.userId }) UserProfiles.findOneBy({ userId: ps.userId }),
]); ]);
if (user == null || profile == null) { if (user == null || profile == null) {

View file

@ -47,8 +47,8 @@ export const meta = {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'UserDetailedNotMe', ref: 'UserDetailedNotMe',
} },
} },
}, },
{ {
type: 'object', type: 'object',
@ -62,9 +62,9 @@ export const meta = {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
ref: 'Note', ref: 'Note',
} },
} },
} },
], ],
}, },
} as const; } as const;

View file

@ -47,7 +47,7 @@ export default define(meta, paramDef, async (ps, user) => {
DriveFiles.findOneBy({ DriveFiles.findOneBy({
id: fileId, id: fileId,
userId: user.id, userId: user.id,
}) }),
))).filter((file): file is DriveFile => file != null); ))).filter((file): file is DriveFile => file != null);
if (files.length === 0) { if (files.length === 0) {

View file

@ -46,7 +46,7 @@ export default define(meta, paramDef, async (ps, user) => {
DriveFiles.findOneBy({ DriveFiles.findOneBy({
id: fileId, id: fileId,
userId: user.id, userId: user.id,
}) }),
))).filter((file): file is DriveFile => file != null); ))).filter((file): file is DriveFile => file != null);
if (files.length === 0) { if (files.length === 0) {

View file

@ -127,7 +127,7 @@ export default define(meta, paramDef, async () => {
.andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) }) .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) })
.cache(60000) // 1 min .cache(60000) // 1 min
.getRawOne() .getRawOne()
.then(x => parseInt(x.count, 10)) .then(x => parseInt(x.count, 10)),
))); )));
} }
@ -140,7 +140,7 @@ export default define(meta, paramDef, async () => {
.andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) }) .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) })
.cache(60000 * 60) // 60 min .cache(60000 * 60) // 60 min
.getRawOne() .getRawOne()
.then(x => parseInt(x.count, 10)) .then(x => parseInt(x.count, 10)),
)); ));
const stats = hots.map((tag, i) => ({ const stats = hots.map((tag, i) => ({

View file

@ -27,7 +27,7 @@ export const meta = {
ref: 'GalleryPost', ref: 'GalleryPost',
}, },
}, },
} },
}, },
} as const; } as const;

View file

@ -26,7 +26,7 @@ export const meta = {
ref: 'Page', ref: 'Page',
}, },
}, },
} },
}, },
} as const; } as const;

View file

@ -57,7 +57,7 @@ export const meta = {
message: 'Invalid Regular Expression.', message: 'Invalid Regular Expression.',
code: 'INVALID_REGEXP', code: 'INVALID_REGEXP',
id: '0d786918-10df-41cd-8f33-8dec7d9a89a5', id: '0d786918-10df-41cd-8f33-8dec7d9a89a5',
} },
}, },
res: { res: {
@ -77,7 +77,8 @@ export const paramDef = {
lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true }, lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true },
avatarId: { type: 'string', format: 'misskey:id', nullable: true }, avatarId: { type: 'string', format: 'misskey:id', nullable: true },
bannerId: { type: 'string', format: 'misskey:id', nullable: true }, bannerId: { type: 'string', format: 'misskey:id', nullable: true },
fields: { type: 'array', fields: {
type: 'array',
minItems: 0, minItems: 0,
maxItems: 16, maxItems: 16,
items: { items: {

View file

@ -1,4 +1,4 @@
import { IsNull, MoreThan } from 'typeorm'; import { IsNull } from 'typeorm';
import config from '@/config/index.js'; import config from '@/config/index.js';
import { fetchMeta } from '@/misc/fetch-meta.js'; import { fetchMeta } from '@/misc/fetch-meta.js';
import { Emojis, Users } from '@/models/index.js'; import { Emojis, Users } from '@/models/index.js';

View file

@ -1,4 +1,3 @@
import { Brackets } from 'typeorm';
import { Notes } from '@/models/index.js'; import { Notes } from '@/models/index.js';
import define from '../../define.js'; import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js';

View file

@ -8,21 +8,22 @@ export class ApiError extends Error {
public httpStatusCode?: number; public httpStatusCode?: number;
public info?: any; public info?: any;
constructor(e?: E | null | undefined, info?: any | null | undefined) { constructor(
if (e == null) e = { e?: E | null | undefined = {
message: 'Internal error occurred. Please contact us if the error persists.', message: 'Internal error occurred. Please contact us if the error persists.',
code: 'INTERNAL_ERROR', code: 'INTERNAL_ERROR',
id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac', id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
kind: 'server', kind: 'server',
httpStatusCode: 500, httpStatusCode: 500,
}; },
info?: any | null | undefined,
) {
super(e.message); super(e.message);
this.message = e.message; this.message = e.message;
this.code = e.code; this.code = e.code;
this.id = e.id; this.id = e.id;
this.kind = e.kind || 'client'; this.kind = e.kind ?? 'client';
this.httpStatusCode = e.httpStatusCode; this.httpStatusCode = e.httpStatusCode ?? 500;
this.info = info; this.info = info;
} }
} }

View file

@ -37,7 +37,7 @@ export function genOpenapiSpec() {
Bearer: { Bearer: {
type: 'http', type: 'http',
scheme: 'bearer', scheme: 'bearer',
} },
}, },
}, },
}; };

View file

@ -55,6 +55,6 @@ export const schemas = {
}, },
...Object.fromEntries( ...Object.fromEntries(
Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]) Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]),
), ),
}; };

View file

@ -172,7 +172,7 @@ export default async (ctx: Koa.Context) => {
body.credentialId body.credentialId
.replace(/-/g, '+') .replace(/-/g, '+')
.replace(/_/g, '/'), .replace(/_/g, '/'),
'base64' 'base64',
).toString('hex'), ).toString('hex'),
}); });

View file

@ -1,6 +1,6 @@
import * as http from 'node:http';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { ParsedUrlQuery } from 'querystring'; import { ParsedUrlQuery } from 'querystring';
import * as http from 'node:http';
import * as websocket from 'websocket'; import * as websocket from 'websocket';
import { subscriber as redisClient } from '@/db/redis.js'; import { subscriber as redisClient } from '@/db/redis.js';
@ -8,13 +8,13 @@ import { Users } from '@/models/index.js';
import MainStreamConnection from './stream/index.js'; import MainStreamConnection from './stream/index.js';
import authenticate from './authenticate.js'; import authenticate from './authenticate.js';
export const initializeStreamingServer = (server: http.Server) => { export const initializeStreamingServer = (server: http.Server): void => {
// Init websocket server // Init websocket server
const ws = new websocket.server({ const ws = new websocket.server({
httpServer: server, httpServer: server,
}); });
ws.on('request', async (request) => { ws.on('request', async (request): Promise<void> => {
const q = request.resourceURL.query as ParsedUrlQuery; const q = request.resourceURL.query as ParsedUrlQuery;
const [user, app] = await authenticate(request.httpRequest.headers.authorization, q.i) const [user, app] = await authenticate(request.httpRequest.headers.authorization, q.i)

View file

@ -13,7 +13,7 @@ const nodeinfo2_0path = '/nodeinfo/2.0';
export const links = [{ export const links = [{
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
href: config.url + nodeinfo2_1path href: config.url + nodeinfo2_1path,
}, { }, {
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
href: config.url + nodeinfo2_0path, href: config.url + nodeinfo2_0path,

View file

@ -2,7 +2,7 @@ import Koa from 'koa';
import { fetchMeta } from '@/misc/fetch-meta.js'; import { fetchMeta } from '@/misc/fetch-meta.js';
import manifest from './manifest.json' assert { type: 'json' }; import manifest from './manifest.json' assert { type: 'json' };
export const manifestHandler = async (ctx: Koa.Context) => { export const manifestHandler = async (ctx: Koa.Context): Promise<void> => {
// TODO // TODO
//const res = structuredClone(manifest); //const res = structuredClone(manifest);
const res = JSON.parse(JSON.stringify(manifest)); const res = JSON.parse(JSON.stringify(manifest));

View file

@ -8,7 +8,7 @@ import { getJson } from '@/misc/fetch.js';
const logger = new Logger('url-preview'); const logger = new Logger('url-preview');
export const urlPreviewHandler = async (ctx: Koa.Context) => { export const urlPreviewHandler = async (ctx: Koa.Context): Promise<void> => {
const url = ctx.query.url; const url = ctx.query.url;
if (typeof url !== 'string') { if (typeof url !== 'string') {
ctx.status = 400; ctx.status = 400;

View file

@ -9,7 +9,7 @@ import { sendEmailNotification } from './send-email-notification.js';
export async function createNotification( export async function createNotification(
notifieeId: User['id'], notifieeId: User['id'],
type: Notification['type'], type: Notification['type'],
data: Partial<Notification> data: Partial<Notification>,
) { ) {
if (data.notifierId && (notifieeId === data.notifierId)) { if (data.notifierId && (notifieeId === data.notifierId)) {
return null; return null;

View file

@ -10,7 +10,7 @@ import { UsedUsername } from '@/models/entities/used-username.js';
import { db } from '@/db/postgre.js'; import { db } from '@/db/postgre.js';
import generateNativeUserToken from '../server/api/common/generate-native-user-token.js'; import generateNativeUserToken from '../server/api/common/generate-native-user-token.js';
export async function createSystemUser(username: string) { export async function createSystemUser(username: string): Promise<User> {
const password = uuid(); const password = uuid();
// Generate hash of password // Generate hash of password

View file

@ -8,7 +8,7 @@ export async function deleteAccount(user: {
host: string | null; host: string | null;
}): Promise<void> { }): Promise<void> {
// 物理削除する前にDelete activityを送信する // 物理削除する前にDelete activityを送信する
await doPostSuspend(user).catch(e => {}); await doPostSuspend(user).catch(() => {});
createDeleteAccountJob(user, { createDeleteAccountJob(user, {
soft: false, soft: false,

View file

@ -256,9 +256,12 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
/** /**
* Upload to ObjectStorage * Upload to ObjectStorage
*/ */
async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { async function upload(key: string, stream: fs.ReadStream | Buffer, _type: string, filename?: string) {
if (type === 'image/apng') type = 'image/png'; const type = (_type === 'image/apng')
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; ? 'image/png'
: (FILE_TYPE_BROWSERSAFE.includes(_type))
? _type
: 'application/octet-stream';
const meta = await fetchMeta(); const meta = await fetchMeta();
@ -276,7 +279,7 @@ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string,
const s3 = getS3(meta); const s3 = getS3(meta);
const upload = s3.upload(params, { const upload = s3.upload(params, {
partSize: s3.endpoint?.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024, partSize: s3.endpoint.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024,
}); });
const result = await upload.promise(); const result = await upload.promise();
@ -342,7 +345,7 @@ export async function addFile({
isLink = false, isLink = false,
url = null, url = null,
uri = null, uri = null,
sensitive = null sensitive = null,
}: AddFileArgs): Promise<DriveFile> { }: AddFileArgs): Promise<DriveFile> {
const info = await getFileInfo(path); const info = await getFileInfo(path);
logger.info(`${JSON.stringify(info)}`); logger.info(`${JSON.stringify(info)}`);
@ -428,10 +431,9 @@ export async function addFile({
file.blurhash = info.blurhash || null; file.blurhash = info.blurhash || null;
file.isLink = isLink; file.isLink = isLink;
file.isSensitive = user file.isSensitive = user
? Users.isLocalUser(user) && profile!.alwaysMarkNsfw ? true : ? Users.isLocalUser(user) && profile!.alwaysMarkNsfw
(sensitive !== null && sensitive !== undefined) ? true
? sensitive : sensitive ?? false
: false
: false; : false;
if (url !== null) { if (url !== null) {

View file

@ -38,11 +38,11 @@ export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, heig
* Convert to WebP * Convert to WebP
* with resize, remove metadata, resolve orientation, stop animation * with resize, remove metadata, resolve orientation, stop animation
*/ */
export async function convertToWebp(path: string, width: number, height: number, quality: number = 85): Promise<IImage> { export async function convertToWebp(path: string, width: number, height: number, quality = 85): Promise<IImage> {
return convertSharpToWebp(await sharp(path), width, height, quality); return convertSharpToWebp(await sharp(path), width, height, quality);
} }
export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality: number = 85): Promise<IImage> { export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality = 85): Promise<IImage> {
const data = await sharp const data = await sharp
.resize(width, height, { .resize(width, height, {
fit: 'inside', fit: 'inside',

View file

@ -36,12 +36,6 @@ export async function uploadFromUrl({
name = null; name = null;
} }
// If the comment is same as the name, skip comment
// (image.name is passed in when receiving attachment)
if (comment !== null && name === comment) {
comment = null;
}
// Create temp file // Create temp file
const [path, cleanup] = await createTemp(); const [path, cleanup] = await createTemp();
@ -49,7 +43,20 @@ export async function uploadFromUrl({
// write content at URL to temp file // write content at URL to temp file
await downloadUrl(url, path); await downloadUrl(url, path);
const driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive }); const driveFile = await addFile({
user,
path,
name,
// If the comment is same as the name, skip comment
// (image.name is passed in when receiving attachment)
comment: name === comment ? null : comment,
folderId,
force,
isLink,
url,
uri,
sensitive,
});
logger.succ(`Got: ${driveFile.id}`); logger.succ(`Got: ${driveFile.id}`);
return driveFile!; return driveFile!;
} catch (e) { } catch (e) {

View file

@ -235,7 +235,7 @@ async function getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | n
} }
if (manifest) { if (manifest) {
return manifest?.name || manifest?.short_name; return manifest.name || manifest.short_name;
} }
return null; return null;
@ -261,7 +261,7 @@ async function getDescription(info: NodeInfo | null, doc: DOMWindow['document']
} }
if (manifest) { if (manifest) {
return manifest?.name || manifest?.short_name; return manifest.name || manifest.short_name;
} }
return null; return null;

View file

@ -47,10 +47,9 @@ export default class Logger {
return logger; return logger;
} }
private log(level: Level, message: string, data?: Record<string, any> | null, important = false, subDomains: Domain[] = [], store = true): void { private log(level: Level, message: string, data?: Record<string, any> | null, important = false, subDomains: Domain[] = [], _store = true): void {
if (envOption.quiet) return; if (envOption.quiet) return;
if (!this.store) store = false; const store = _store && this.store && (level !== 'debug');
if (level === 'debug') store = false;
if (this.parentLogger) { if (this.parentLogger) {
this.parentLogger.log(level, message, data, important, [this.domain].concat(subDomains), store); this.parentLogger.log(level, message, data, important, [this.domain].concat(subDomains), store);
@ -95,9 +94,8 @@ export default class Logger {
} }
} }
public error(x: string | Error, data?: Record<string, any> | null, important = false): void { // 実行を継続できない状況で使う public error(x: string | Error, data?: Record<string, any> = {}, important = false): void { // 実行を継続できない状況で使う
if (x instanceof Error) { if (x instanceof Error) {
data = data || {};
data.e = x; data.e = x;
this.log('error', x.toString(), data, important); this.log('error', x.toString(), data, important);
} else if (typeof x === 'object') { } else if (typeof x === 'object') {

View file

@ -7,12 +7,12 @@ import renderDelete from '@/remote/activitypub/renderer/delete.js';
import renderTombstone from '@/remote/activitypub/renderer/tombstone.js'; import renderTombstone from '@/remote/activitypub/renderer/tombstone.js';
import { deliver } from '@/queue/index.js'; import { deliver } from '@/queue/index.js';
export async function deleteMessage(message: MessagingMessage) { export async function deleteMessage(message: MessagingMessage): Promise<void> {
await MessagingMessages.delete(message.id); await MessagingMessages.delete(message.id);
postDeleteMessage(message); await postDeleteMessage(message);
} }
async function postDeleteMessage(message: MessagingMessage) { async function postDeleteMessage(message: MessagingMessage): Promise<void> {
if (message.recipientId) { if (message.recipientId) {
const user = await Users.findOneByOrFail({ id: message.userId }); const user = await Users.findOneByOrFail({ id: message.userId });
const recipient = await Users.findOneByOrFail({ id: message.recipientId }); const recipient = await Users.findOneByOrFail({ id: message.recipientId });

View file

@ -679,12 +679,12 @@ async function extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.
const mentions = extractMentions(tokens); const mentions = extractMentions(tokens);
let mentionedUsers = (await Promise.all(mentions.map(m => let mentionedUsers = (await Promise.all(mentions.map(m =>
resolveUser(m.username, m.host || user.host).catch(() => null) resolveUser(m.username, m.host || user.host).catch(() => null),
))).filter(x => x != null) as User[]; ))).filter(x => x != null) as User[];
// Drop duplicate users // Drop duplicate users
mentionedUsers = mentionedUsers.filter((u, i, self) => mentionedUsers = mentionedUsers.filter((u, i, self) =>
i === self.findIndex(u2 => u.id === u2.id) i === self.findIndex(u2 => u.id === u2.id),
); );
return mentionedUsers; return mentionedUsers;

View file

@ -21,7 +21,7 @@ import { deliverToRelays } from '../relay.js';
* @param user 稿 * @param user 稿
* @param note 稿 * @param note 稿
*/ */
export default async function(user: { id: User['id']; uri: User['uri']; host: User['host']; }, note: Note, quiet = false) { export default async function(user: { id: User['id']; uri: User['uri']; host: User['host']; }, note: Note, quiet = false): Promise<void> {
const deletedAt = new Date(); const deletedAt = new Date();
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
@ -83,7 +83,7 @@ export default async function(user: { id: User['id']; uri: User['uri']; host: Us
}); });
} }
async function findCascadingNotes(note: Note) { async function findCascadingNotes(note: Note): Promise<Note[]> {
const cascadingNotes: Note[] = []; const cascadingNotes: Note[] = [];
const recursive = async (noteId: string) => { const recursive = async (noteId: string) => {
@ -105,7 +105,7 @@ async function findCascadingNotes(note: Note) {
return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users
} }
async function getMentionedRemoteUsers(note: Note) { async function getMentionedRemoteUsers(note: Note): Promise<IRemoteUser[]> {
const where = [] as any[]; const where = [] as any[];
// mention / reply / dm // mention / reply / dm

View file

@ -6,7 +6,7 @@ import { Note } from '@/models/entities/note.js';
import { deliverToFollowers } from '@/remote/activitypub/deliver-manager.js'; import { deliverToFollowers } from '@/remote/activitypub/deliver-manager.js';
import { deliverToRelays } from '../../relay.js'; import { deliverToRelays } from '../../relay.js';
export async function deliverQuestionUpdate(noteId: Note['id']) { export async function deliverQuestionUpdate(noteId: Note['id']): Promise<void> {
const note = await Notes.findOneBy({ id: noteId }); const note = await Notes.findOneBy({ id: noteId });
if (note == null) throw new Error('note not found'); if (note == null) throw new Error('note not found');

View file

@ -33,14 +33,14 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
} }
// TODO: cache // TODO: cache
reaction = await toDbReaction(reaction, user.host); const dbReaction = await toDbReaction(reaction, user.host);
const record: NoteReaction = { const record: NoteReaction = {
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
noteId: note.id, noteId: note.id,
userId: user.id, userId: user.id,
reaction, reaction: dbReaction,
}; };
// Create reaction // Create reaction
@ -53,7 +53,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
userId: user.id, userId: user.id,
}); });
if (exists.reaction !== reaction) { if (exists.reaction !== dbReaction) {
// 別のリアクションがすでにされていたら置き換える // 別のリアクションがすでにされていたら置き換える
await deleteReaction(user, note); await deleteReaction(user, note);
await NoteReactions.insert(record); await NoteReactions.insert(record);
@ -67,7 +67,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
} }
// Increment reactions count // Increment reactions count
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`; const sql = `jsonb_set("reactions", '{${dbReaction}}', (COALESCE("reactions"->>'${dbReaction}', '0')::int + 1)::text::jsonb)`;
await Notes.createQueryBuilder().update() await Notes.createQueryBuilder().update()
.set({ .set({
reactions: () => sql, reactions: () => sql,
@ -79,7 +79,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
perUserReactionsChart.update(user, note); perUserReactionsChart.update(user, note);
// カスタム絵文字リアクションだったら絵文字情報も送る // カスタム絵文字リアクションだったら絵文字情報も送る
const decodedReaction = decodeReaction(reaction); const decodedReaction = decodeReaction(dbReaction);
const emoji = await Emojis.findOne({ const emoji = await Emojis.findOne({
where: { where: {
@ -103,7 +103,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
createNotification(note.userId, 'reaction', { createNotification(note.userId, 'reaction', {
notifierId: user.id, notifierId: user.id,
noteId: note.id, noteId: note.id,
reaction, reaction: dbReaction,
}); });
} }
@ -116,7 +116,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
createNotification(watcher.userId, 'reaction', { createNotification(watcher.userId, 'reaction', {
notifierId: user.id, notifierId: user.id,
noteId: note.id, noteId: note.id,
reaction, reaction: dbReaction,
}); });
} }
}); });

View file

@ -18,7 +18,7 @@ export default async function(
info?: { info?: {
following: Set<User['id']>; following: Set<User['id']>;
followingChannels: Set<Channel['id']>; followingChannels: Set<Channel['id']>;
} },
) { ) {
const following = info?.following ? info.following : new Set<string>((await Followings.find({ const following = info?.following ? info.following : new Set<string>((await Followings.find({
where: { where: {

View file

@ -8,7 +8,7 @@ export async function insertNoteUnread(userId: User['id'], note: Note, params: {
// NOTE: isSpecifiedがtrueならisMentionedは必ずfalse // NOTE: isSpecifiedがtrueならisMentionedは必ずfalse
isSpecified: boolean; isSpecified: boolean;
isMentioned: boolean; isMentioned: boolean;
}) { }): Promise<void> {
//#region ミュートしているなら無視 //#region ミュートしているなら無視
// TODO: 現在の仕様ではChannelにミュートは適用されないのでよしなにケアする // TODO: 現在の仕様ではChannelにミュートは適用されないのでよしなにケアする
const mute = await Mutings.findBy({ const mute = await Mutings.findBy({

View file

@ -29,7 +29,7 @@ function truncateNotification(notification: Packed<'Notification'>): any {
reply: undefined, reply: undefined,
renote: undefined, renote: undefined,
user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる
} },
}; };
} }

View file

@ -6,8 +6,8 @@ import { Cache } from '@/misc/cache.js';
const cache = new Cache<Instance>(1000 * 60 * 60); const cache = new Cache<Instance>(1000 * 60 * 60);
export async function registerOrFetchInstanceDoc(host: string): Promise<Instance> { export async function registerOrFetchInstanceDoc(idnHost: string): Promise<Instance> {
host = toPuny(host); const host = toPuny(idnHost);
const cached = cache.get(host); const cached = cache.get(host);
if (cached) return cached; if (cached) return cached;

View file

@ -5,7 +5,7 @@ import Logger from './logger.js';
export const logger = new Logger('email'); export const logger = new Logger('email');
export async function sendEmail(to: string, subject: string, html: string, text: string) { export async function sendEmail(to: string, subject: string, html: string, text: string): Promise<void> {
const meta = await fetchMeta(true); const meta = await fetchMeta(true);
const iconUrl = `${config.url}/static-assets/mi-white.png`; const iconUrl = `${config.url}/static-assets/mi-white.png`;

View file

@ -7,7 +7,7 @@ import { User } from '@/models/entities/user.js';
import { Users, Followings } from '@/models/index.js'; import { Users, Followings } from '@/models/index.js';
import { publishInternalEvent } from '@/services/stream.js'; import { publishInternalEvent } from '@/services/stream.js';
export async function doPostSuspend(user: { id: User['id']; host: User['host'] }) { export async function doPostSuspend(user: { id: User['id']; host: User['host'] }): Promise<void> {
publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
if (Users.isLocalUser(user)) { if (Users.isLocalUser(user)) {

View file

@ -5,13 +5,13 @@ import { genId } from '@/misc/gen-id.js';
import { Hashtag } from '@/models/entities/hashtag.js'; import { Hashtag } from '@/models/entities/hashtag.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js';
export async function updateHashtags(user: { id: User['id']; host: User['host']; }, tags: string[]) { export async function updateHashtags(user: { id: User['id']; host: User['host']; }, tags: string[]): Promise<void> {
for (const tag of tags) { for (const tag of tags) {
await updateHashtag(user, tag); await updateHashtag(user, tag);
} }
} }
export async function updateUsertags(user: User, tags: string[]) { export async function updateUsertags(user: User, tags: string[]): Promise<void> {
for (const tag of tags) { for (const tag of tags) {
await updateHashtag(user, tag, true, true); await updateHashtag(user, tag, true, true);
} }
@ -21,8 +21,8 @@ export async function updateUsertags(user: User, tags: string[]) {
} }
} }
export async function updateHashtag(user: { id: User['id']; host: User['host']; }, tag: string, isUserAttached = false, inc = true) { export async function updateHashtag(user: { id: User['id']; host: User['host']; }, _tag: string, isUserAttached = false, inc = true): Promise<void> {
tag = normalizeForSearch(tag); const tag = normalizeForSearch(_tag);
const index = await Hashtags.findOneBy({ name: tag }); const index = await Hashtags.findOneBy({ name: tag });

View file

@ -7,7 +7,7 @@ import { genId } from '@/misc/gen-id.js';
import { fetchProxyAccount } from '@/misc/fetch-proxy-account.js'; import { fetchProxyAccount } from '@/misc/fetch-proxy-account.js';
import createFollowing from '../following/create.js'; import createFollowing from '../following/create.js';
export async function pushUserToUserList(target: User, list: UserList) { export async function pushUserToUserList(target: User, list: UserList): Promise<void> {
await UserListJoinings.insert({ await UserListJoinings.insert({
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),

View file

@ -51,7 +51,7 @@ function send() {
}).then(res => { }).then(res => {
os.alert({ os.alert({
type: 'success', type: 'success',
text: i18n.ts.abuseReported text: i18n.ts.abuseReported,
}); });
uiWindow.value?.close(); uiWindow.value?.close();
emit('closed'); emit('closed');

View file

@ -63,7 +63,7 @@ let forward = $ref(props.report.forwarded);
function resolve() { function resolve() {
os.apiWithDialog('admin/resolve-abuse-user-report', { os.apiWithDialog('admin/resolve-abuse-user-report', {
forward: forward, forward,
reportId: props.report.id, reportId: props.report.id,
}).then(() => { }).then(() => {
emit('resolved', props.report.id); emit('resolved', props.report.id);

View file

@ -1,6 +1,7 @@
<template> <template>
<svg class="mbcofsoe" viewBox="0 0 10 10" preserveAspectRatio="none"> <svg class="mbcofsoe" viewBox="0 0 10 10" preserveAspectRatio="none">
<circle v-for="(angle, i) in graduations" <circle
v-for="(angle, i) in graduations"
:key="i" :key="i"
:cx="5 + (Math.sin(angle) * (5 - graduationsPadding))" :cx="5 + (Math.sin(angle) * (5 - graduationsPadding))"
:cy="5 - (Math.cos(angle) * (5 - graduationsPadding))" :cy="5 - (Math.cos(angle) * (5 - graduationsPadding))"

View file

@ -86,7 +86,7 @@ for (const x of customEmojis) {
name: x.name, name: x.name,
emoji: `:${x.name}:`, emoji: `:${x.name}:`,
url: x.url, url: x.url,
isCustomEmoji: true isCustomEmoji: true,
}); });
if (x.aliases) { if (x.aliases) {
@ -96,7 +96,7 @@ for (const x of customEmojis) {
aliasOf: x.name, aliasOf: x.name,
emoji: `:${x.name}:`, emoji: `:${x.name}:`,
url: x.url, url: x.url,
isCustomEmoji: true isCustomEmoji: true,
}); });
} }
} }
@ -193,7 +193,7 @@ function exec() {
os.api('users/search-by-username-and-host', { os.api('users/search-by-username-and-host', {
username: props.q, username: props.q,
limit: 10, limit: 10,
detail: false detail: false,
}).then(searchedUsers => { }).then(searchedUsers => {
users.value = searchedUsers as any[]; users.value = searchedUsers as any[];
fetching.value = false; fetching.value = false;
@ -215,7 +215,7 @@ function exec() {
} else { } else {
os.api('hashtags/search', { os.api('hashtags/search', {
query: props.q, query: props.q,
limit: 30 limit: 30,
}).then(searchedHashtags => { }).then(searchedHashtags => {
hashtags.value = searchedHashtags as any[]; hashtags.value = searchedHashtags as any[];
fetching.value = false; fetching.value = false;

View file

@ -18,7 +18,7 @@ const users = ref([]);
onMounted(async () => { onMounted(async () => {
users.value = await os.api('users/show', { users.value = await os.api('users/show', {
userIds: props.userIds userIds: props.userIds,
}); });
}); });
</script> </script>

View file

@ -82,7 +82,7 @@ function requestRender() {
captcha.value.render(captchaEl.value, { captcha.value.render(captchaEl.value, {
sitekey: props.sitekey, sitekey: props.sitekey,
theme: defaultStore.state.darkMode ? 'dark' : 'light', theme: defaultStore.state.darkMode ? 'dark' : 'light',
callback: callback, callback,
'expired-callback': callback, 'expired-callback': callback,
'error-callback': callback, 'error-callback': callback,
}); });

View file

@ -1,5 +1,6 @@
<template> <template>
<button class="hdcaacmi _button" <button
class="hdcaacmi _button"
:class="{ wait, active: isFollowing, full }" :class="{ wait, active: isFollowing, full }"
:disabled="wait" :disabled="wait"
@click="onClick" @click="onClick"
@ -39,12 +40,12 @@ async function onClick() {
try { try {
if (isFollowing.value) { if (isFollowing.value) {
await os.api('channels/unfollow', { await os.api('channels/unfollow', {
channelId: props.channel.id channelId: props.channel.id,
}); });
isFollowing.value = false; isFollowing.value = false;
} else { } else {
await os.api('channels/follow', { await os.api('channels/follow', {
channelId: props.channel.id channelId: props.channel.id,
}); });
isFollowing.value = true; isFollowing.value = true;
} }

View file

@ -25,7 +25,7 @@ const label = computed(() => {
return concat([ return concat([
props.note.text ? [i18n.t('_cw.chars', { count: length(props.note.text) })] : [], props.note.text ? [i18n.t('_cw.chars', { count: length(props.note.text) })] : [],
props.note.files && props.note.files.length !== 0 ? [i18n.t('_cw.files', { count: props.note.files.length }) ] : [], props.note.files && props.note.files.length !== 0 ? [i18n.t('_cw.files', { count: props.note.files.length }) ] : [],
props.note.poll != null ? [i18n.ts.poll] : [] props.note.poll != null ? [i18n.ts.poll] : [],
] as string[][]).join(' / '); ] as string[][]).join(' / ');
}); });

View file

@ -12,17 +12,17 @@ export default defineComponent({
direction: { direction: {
type: String, type: String,
required: false, required: false,
default: 'down' default: 'down',
}, },
reversed: { reversed: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false default: false,
}, },
noGap: { noGap: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false default: false,
}, },
}, },
@ -32,7 +32,7 @@ export default defineComponent({
const month = new Date(time).getMonth() + 1; const month = new Date(time).getMonth() + 1;
return i18n.t('monthAndDay', { return i18n.t('monthAndDay', {
month: month.toString(), month: month.toString(),
day: date.toString() day: date.toString(),
}); });
} }
@ -42,7 +42,7 @@ export default defineComponent({
if (!slots || !slots.default) return; if (!slots || !slots.default) return;
const el = slots.default({ const el = slots.default({
item: item item,
})[0]; })[0];
if (el.key == null && item.id) el.key = item.id; if (el.key == null && item.id) el.key = item.id;
@ -54,20 +54,20 @@ export default defineComponent({
class: 'separator', class: 'separator',
key: item.id + ':separator', key: item.id + ':separator',
}, h('p', { }, h('p', {
class: 'date' class: 'date',
}, [ }, [
h('span', [ h('span', [
h('i', { h('i', {
class: 'fas fa-angle-up icon', class: 'fas fa-angle-up icon',
}), }),
getDateText(item.createdAt) getDateText(item.createdAt),
]), ]),
h('span', [ h('span', [
getDateText(props.items[i + 1].createdAt), getDateText(props.items[i + 1].createdAt),
h('i', { h('i', {
class: 'fas fa-angle-down icon', class: 'fas fa-angle-down icon',
}) }),
]) ]),
])); ]));
return [el, separator]; return [el, separator];
@ -88,7 +88,7 @@ export default defineComponent({
class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''), class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''),
}, },
{ default: renderChildren }); { default: renderChildren });
} },
}); });
</script> </script>

View file

@ -1,5 +1,6 @@
<template> <template>
<XModalWindow ref="dialog" <XModalWindow
ref="dialog"
:width="800" :width="800"
:height="500" :height="500"
:with-ok-button="true" :with-ok-button="true"

View file

@ -1,5 +1,6 @@
<template> <template>
<XWindow ref="window" <XWindow
ref="window"
:initial-width="800" :initial-width="800"
:initial-height="500" :initial-height="500"
:can-resize="true" :can-resize="true"

View file

@ -1,5 +1,6 @@
<template> <template>
<div class="ncvczrfv" <div
class="ncvczrfv"
:class="{ isSelected }" :class="{ isSelected }"
draggable="true" draggable="true"
:title="title" :title="title"
@ -33,8 +34,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, defineAsyncComponent, ref } from 'vue'; import { computed, defineAsyncComponent, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import MkDriveFileThumbnail from './drive-file-thumbnail.vue'; import MkDriveFileThumbnail from './drive-file-thumbnail.vue';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import bytes from '@/filters/bytes'; import bytes from '@/filters/bytes';
import * as os from '@/os'; import * as os from '@/os';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -63,31 +64,31 @@ function getMenu() {
return [{ return [{
text: i18n.ts.rename, text: i18n.ts.rename,
icon: 'fas fa-i-cursor', icon: 'fas fa-i-cursor',
action: rename action: rename,
}, { }, {
text: props.file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, text: props.file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
icon: props.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', icon: props.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash',
action: toggleSensitive action: toggleSensitive,
}, { }, {
text: i18n.ts.describeFile, text: i18n.ts.describeFile,
icon: 'fas fa-i-cursor', icon: 'fas fa-i-cursor',
action: describe action: describe,
}, null, { }, null, {
text: i18n.ts.copyUrl, text: i18n.ts.copyUrl,
icon: 'fas fa-link', icon: 'fas fa-link',
action: copyUrl action: copyUrl,
}, { }, {
type: 'a', type: 'a',
href: props.file.url, href: props.file.url,
target: '_blank', target: '_blank',
text: i18n.ts.download, text: i18n.ts.download,
icon: 'fas fa-download', icon: 'fas fa-download',
download: props.file.name download: props.file.name,
}, null, { }, null, {
text: i18n.ts.delete, text: i18n.ts.delete,
icon: 'fas fa-trash-alt', icon: 'fas fa-trash-alt',
danger: true, danger: true,
action: deleteFile action: deleteFile,
}]; }];
} }
@ -127,7 +128,7 @@ function rename() {
if (canceled) return; if (canceled) return;
os.api('drive/files/update', { os.api('drive/files/update', {
fileId: props.file.id, fileId: props.file.id,
name: name name,
}); });
}); });
} }
@ -139,23 +140,23 @@ function describe() {
placeholder: i18n.ts.inputNewDescription, placeholder: i18n.ts.inputNewDescription,
default: props.file.comment != null ? props.file.comment : '', default: props.file.comment != null ? props.file.comment : '',
}, },
image: props.file image: props.file,
}, { }, {
done: result => { done: result => {
if (!result || result.canceled) return; if (!result || result.canceled) return;
let comment = result.result; let comment = result.result;
os.api('drive/files/update', { os.api('drive/files/update', {
fileId: props.file.id, fileId: props.file.id,
comment: comment.length === 0 ? null : comment comment: comment.length === 0 ? null : comment,
}); });
} },
}, 'closed'); }, 'closed');
} }
function toggleSensitive() { function toggleSensitive() {
os.api('drive/files/update', { os.api('drive/files/update', {
fileId: props.file.id, fileId: props.file.id,
isSensitive: !props.file.isSensitive isSensitive: !props.file.isSensitive,
}); });
} }
@ -176,7 +177,7 @@ async function deleteFile() {
if (canceled) return; if (canceled) return;
os.api('drive/files/delete', { os.api('drive/files/delete', {
fileId: props.file.id fileId: props.file.id,
}); });
} }
</script> </script>

View file

@ -1,5 +1,6 @@
<template> <template>
<div class="rghtznwe" <div
class="rghtznwe"
:class="{ draghover }" :class="{ draghover }"
draggable="true" draggable="true"
:title="title" :title="title"
@ -123,7 +124,7 @@ function onDrop(ev: DragEvent) {
emit('removeFile', file.id); emit('removeFile', file.id);
os.api('drive/files/update', { os.api('drive/files/update', {
fileId: file.id, fileId: file.id,
folderId: props.folder.id folderId: props.folder.id,
}); });
} }
//#endregion //#endregion
@ -139,7 +140,7 @@ function onDrop(ev: DragEvent) {
emit('removeFolder', folder.id); emit('removeFolder', folder.id);
os.api('drive/folders/update', { os.api('drive/folders/update', {
folderId: folder.id, folderId: folder.id,
parentId: props.folder.id parentId: props.folder.id,
}).then(() => { }).then(() => {
// noop // noop
}).catch(err => { }).catch(err => {
@ -147,13 +148,13 @@ function onDrop(ev: DragEvent) {
case 'detected-circular-definition': case 'detected-circular-definition':
os.alert({ os.alert({
title: i18n.ts.unableToProcess, title: i18n.ts.unableToProcess,
text: i18n.ts.circularReferenceFolder text: i18n.ts.circularReferenceFolder,
}); });
break; break;
default: default:
os.alert({ os.alert({
type: 'error', type: 'error',
text: i18n.ts.somethingHappened text: i18n.ts.somethingHappened,
}); });
} }
}); });
@ -186,19 +187,19 @@ function rename() {
os.inputText({ os.inputText({
title: i18n.ts.renameFolder, title: i18n.ts.renameFolder,
placeholder: i18n.ts.inputNewFolderName, placeholder: i18n.ts.inputNewFolderName,
default: props.folder.name default: props.folder.name,
}).then(({ canceled, result: name }) => { }).then(({ canceled, result: name }) => {
if (canceled) return; if (canceled) return;
os.api('drive/folders/update', { os.api('drive/folders/update', {
folderId: props.folder.id, folderId: props.folder.id,
name: name name,
}); });
}); });
} }
function deleteFolder() { function deleteFolder() {
os.api('drive/folders/delete', { os.api('drive/folders/delete', {
folderId: props.folder.id folderId: props.folder.id,
}).then(() => { }).then(() => {
if (defaultStore.state.uploadFolder === props.folder.id) { if (defaultStore.state.uploadFolder === props.folder.id) {
defaultStore.set('uploadFolder', null); defaultStore.set('uploadFolder', null);
@ -209,13 +210,13 @@ function deleteFolder() {
os.alert({ os.alert({
type: 'error', type: 'error',
title: i18n.ts.unableToDelete, title: i18n.ts.unableToDelete,
text: i18n.ts.hasChildFilesOrFolders text: i18n.ts.hasChildFilesOrFolders,
}); });
break; break;
default: default:
os.alert({ os.alert({
type: 'error', type: 'error',
text: i18n.ts.unableToDelete text: i18n.ts.unableToDelete,
}); });
} }
}); });
@ -231,10 +232,10 @@ function onContextmenu(ev: MouseEvent) {
icon: 'fas fa-window-restore', icon: 'fas fa-window-restore',
action: () => { action: () => {
os.popup(defineAsyncComponent(() => import('./drive-window.vue')), { os.popup(defineAsyncComponent(() => import('./drive-window.vue')), {
initialFolder: props.folder initialFolder: props.folder,
}, { }, {
}, 'closed'); }, 'closed');
} },
}, null, { }, null, {
text: i18n.ts.rename, text: i18n.ts.rename,
icon: 'fas fa-i-cursor', icon: 'fas fa-i-cursor',

View file

@ -1,5 +1,6 @@
<template> <template>
<div class="drylbebk" <div
class="drylbebk"
:class="{ draghover }" :class="{ draghover }"
@click="onClick" @click="onClick"
@dragover.prevent.stop="onDragover" @dragover.prevent.stop="onDragover"
@ -94,7 +95,7 @@ function onDrop(ev: DragEvent) {
emit('removeFile', file.id); emit('removeFile', file.id);
os.api('drive/files/update', { os.api('drive/files/update', {
fileId: file.id, fileId: file.id,
folderId: props.folder ? props.folder.id : null folderId: props.folder ? props.folder.id : null,
}); });
} }
//#endregion //#endregion
@ -108,7 +109,7 @@ function onDrop(ev: DragEvent) {
emit('removeFolder', folder.id); emit('removeFolder', folder.id);
os.api('drive/folders/update', { os.api('drive/folders/update', {
folderId: folder.id, folderId: folder.id,
parentId: props.folder ? props.folder.id : null parentId: props.folder ? props.folder.id : null,
}); });
} }
//#endregion //#endregion

View file

@ -26,7 +26,8 @@
</div> </div>
<button class="menu _button" @click="showMenu"><i class="fas fa-ellipsis-h"></i></button> <button class="menu _button" @click="showMenu"><i class="fas fa-ellipsis-h"></i></button>
</nav> </nav>
<div ref="main" class="main" <div
ref="main" class="main"
:class="{ uploading: uploadings.length > 0, fetching }" :class="{ uploading: uploadings.length > 0, fetching }"
@dragover.prevent.stop="onDragover" @dragover.prevent.stop="onDragover"
@dragenter="onDragenter" @dragenter="onDragenter"
@ -142,7 +143,7 @@ const isDragSource = ref(false);
const fetching = ref(true); const fetching = ref(true);
const ilFilesObserver = new IntersectionObserver( const ilFilesObserver = new IntersectionObserver(
(entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles() (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles(),
); );
watch(folder, () => emit('cd', folder.value)); watch(folder, () => emit('cd', folder.value));
@ -232,7 +233,7 @@ function onDrop(ev: DragEvent): any {
removeFile(file.id); removeFile(file.id);
os.api('drive/files/update', { os.api('drive/files/update', {
fileId: file.id, fileId: file.id,
folderId: folder.value ? folder.value.id : null folderId: folder.value ? folder.value.id : null,
}); });
} }
//#endregion //#endregion
@ -248,7 +249,7 @@ function onDrop(ev: DragEvent): any {
removeFolder(droppedFolder.id); removeFolder(droppedFolder.id);
os.api('drive/folders/update', { os.api('drive/folders/update', {
folderId: droppedFolder.id, folderId: droppedFolder.id,
parentId: folder.value ? folder.value.id : null parentId: folder.value ? folder.value.id : null,
}).then(() => { }).then(() => {
// noop // noop
}).catch(err => { }).catch(err => {
@ -256,13 +257,13 @@ function onDrop(ev: DragEvent): any {
case 'detected-circular-definition': case 'detected-circular-definition':
os.alert({ os.alert({
title: i18n.ts.unableToProcess, title: i18n.ts.unableToProcess,
text: i18n.ts.circularReferenceFolder text: i18n.ts.circularReferenceFolder,
}); });
break; break;
default: default:
os.alert({ os.alert({
type: 'error', type: 'error',
text: i18n.ts.somethingHappened text: i18n.ts.somethingHappened,
}); });
} }
}); });
@ -278,17 +279,17 @@ function urlUpload() {
os.inputText({ os.inputText({
title: i18n.ts.uploadFromUrl, title: i18n.ts.uploadFromUrl,
type: 'url', type: 'url',
placeholder: i18n.ts.uploadFromUrlDescription placeholder: i18n.ts.uploadFromUrlDescription,
}).then(({ canceled, result: url }) => { }).then(({ canceled, result: url }) => {
if (canceled || !url) return; if (canceled || !url) return;
os.api('drive/files/upload-from-url', { os.api('drive/files/upload-from-url', {
url: url, url,
folderId: folder.value ? folder.value.id : undefined folderId: folder.value ? folder.value.id : undefined,
}); });
os.alert({ os.alert({
title: i18n.ts.uploadFromUrlRequested, title: i18n.ts.uploadFromUrlRequested,
text: i18n.ts.uploadFromUrlMayTakeTime text: i18n.ts.uploadFromUrlMayTakeTime,
}); });
}); });
} }
@ -296,12 +297,12 @@ function urlUpload() {
function createFolder() { function createFolder() {
os.inputText({ os.inputText({
title: i18n.ts.createFolder, title: i18n.ts.createFolder,
placeholder: i18n.ts.folderName placeholder: i18n.ts.folderName,
}).then(({ canceled, result: name }) => { }).then(({ canceled, result: name }) => {
if (canceled) return; if (canceled) return;
os.api('drive/folders/create', { os.api('drive/folders/create', {
name: name, name,
parentId: folder.value ? folder.value.id : undefined parentId: folder.value ? folder.value.id : undefined,
}).then(createdFolder => { }).then(createdFolder => {
addFolder(createdFolder, true); addFolder(createdFolder, true);
}); });
@ -312,12 +313,12 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
os.inputText({ os.inputText({
title: i18n.ts.renameFolder, title: i18n.ts.renameFolder,
placeholder: i18n.ts.inputNewFolderName, placeholder: i18n.ts.inputNewFolderName,
default: folderToRename.name default: folderToRename.name,
}).then(({ canceled, result: name }) => { }).then(({ canceled, result: name }) => {
if (canceled) return; if (canceled) return;
os.api('drive/folders/update', { os.api('drive/folders/update', {
folderId: folderToRename.id, folderId: folderToRename.id,
name: name name,
}).then(updatedFolder => { }).then(updatedFolder => {
// FIXME: // FIXME:
move(updatedFolder); move(updatedFolder);
@ -327,7 +328,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) {
os.api('drive/folders/delete', { os.api('drive/folders/delete', {
folderId: folderToDelete.id folderId: folderToDelete.id,
}).then(() => { }).then(() => {
// //
move(folderToDelete.parentId); move(folderToDelete.parentId);
@ -337,13 +338,13 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) {
os.alert({ os.alert({
type: 'error', type: 'error',
title: i18n.ts.unableToDelete, title: i18n.ts.unableToDelete,
text: i18n.ts.hasChildFilesOrFolders text: i18n.ts.hasChildFilesOrFolders,
}); });
break; break;
default: default:
os.alert({ os.alert({
type: 'error', type: 'error',
text: i18n.ts.unableToDelete text: i18n.ts.unableToDelete,
}); });
} }
}); });
@ -400,18 +401,18 @@ function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) {
} }
} }
function move(target?: Misskey.entities.DriveFolder) { function move(target?: string | Misskey.entities.DriveFolder) {
if (!target) { if (!target) {
goRoot(); goRoot();
return; return;
} else if (typeof target === 'object') {
target = target.id;
} }
const targetId = typeof target === 'string' ? target : target.id;
fetching.value = true; fetching.value = true;
os.api('drive/folders/show', { os.api('drive/folders/show', {
folderId: target folderId: targetId,
}).then(folderToMove => { }).then(folderToMove => {
folder.value = folderToMove; folder.value = folderToMove;
hierarchyFolders.value = []; hierarchyFolders.value = [];
@ -510,7 +511,7 @@ async function fetch() {
const foldersPromise = os.api('drive/folders', { const foldersPromise = os.api('drive/folders', {
folderId: folder.value ? folder.value.id : null, folderId: folder.value ? folder.value.id : null,
limit: foldersMax + 1 limit: foldersMax + 1,
}).then(fetchedFolders => { }).then(fetchedFolders => {
if (fetchedFolders.length === foldersMax + 1) { if (fetchedFolders.length === foldersMax + 1) {
moreFolders.value = true; moreFolders.value = true;
@ -522,7 +523,7 @@ async function fetch() {
const filesPromise = os.api('drive/files', { const filesPromise = os.api('drive/files', {
folderId: folder.value ? folder.value.id : null, folderId: folder.value ? folder.value.id : null,
type: props.type, type: props.type,
limit: filesMax + 1 limit: filesMax + 1,
}).then(fetchedFiles => { }).then(fetchedFiles => {
if (fetchedFiles.length === filesMax + 1) { if (fetchedFiles.length === filesMax + 1) {
moreFiles.value = true; moreFiles.value = true;
@ -549,7 +550,7 @@ function fetchMoreFiles() {
folderId: folder.value ? folder.value.id : null, folderId: folder.value ? folder.value.id : null,
type: props.type, type: props.type,
untilId: files.value[files.value.length - 1].id, untilId: files.value[files.value.length - 1].id,
limit: max + 1 limit: max + 1,
}).then(files => { }).then(files => {
if (files.length === max + 1) { if (files.length === max + 1) {
moreFiles.value = true; moreFiles.value = true;
@ -569,30 +570,30 @@ function getMenu() {
ref: keepOriginal, ref: keepOriginal,
}, null, { }, null, {
text: i18n.ts.addFile, text: i18n.ts.addFile,
type: 'label' type: 'label',
}, { }, {
text: i18n.ts.upload, text: i18n.ts.upload,
icon: 'fas fa-upload', icon: 'fas fa-upload',
action: () => { selectLocalFile(); } action: () => { selectLocalFile(); },
}, { }, {
text: i18n.ts.fromUrl, text: i18n.ts.fromUrl,
icon: 'fas fa-link', icon: 'fas fa-link',
action: () => { urlUpload(); } action: () => { urlUpload(); },
}, null, { }, null, {
text: folder.value ? folder.value.name : i18n.ts.drive, text: folder.value ? folder.value.name : i18n.ts.drive,
type: 'label' type: 'label',
}, folder.value ? { }, folder.value ? {
text: i18n.ts.renameFolder, text: i18n.ts.renameFolder,
icon: 'fas fa-i-cursor', icon: 'fas fa-i-cursor',
action: () => { renameFolder(folder.value); } action: () => { renameFolder(folder.value); },
} : undefined, folder.value ? { } : undefined, folder.value ? {
text: i18n.ts.deleteFolder, text: i18n.ts.deleteFolder,
icon: 'fas fa-trash-alt', icon: 'fas fa-trash-alt',
action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); } action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); },
} : undefined, { } : undefined, {
text: i18n.ts.createFolder, text: i18n.ts.createFolder,
icon: 'fas fa-folder-plus', icon: 'fas fa-folder-plus',
action: () => { createFolder(); } action: () => { createFolder(); },
}]; }];
} }

View file

@ -1,5 +1,6 @@
<template> <template>
<MkWindow ref="window" <MkWindow
ref="window"
:initial-width="null" :initial-width="null"
:initial-height="null" :initial-height="null"
:can-resize="false" :can-resize="false"

View file

@ -4,7 +4,8 @@
<i class="toggle fa-fw" :class="shown ? 'fas fa-chevron-down' : 'fas fa-chevron-up'"></i> <slot></slot> ({{ emojis.length }}) <i class="toggle fa-fw" :class="shown ? 'fas fa-chevron-down' : 'fas fa-chevron-up'"></i> <slot></slot> ({{ emojis.length }})
</header> </header>
<div v-if="shown"> <div v-if="shown">
<button v-for="emoji in emojis" <button
v-for="emoji in emojis"
:key="emoji" :key="emoji"
class="_button" class="_button"
@click="emit('chosen', emoji, $event)" @click="emit('chosen', emoji, $event)"

View file

@ -4,7 +4,8 @@
<div ref="emojis" class="emojis"> <div ref="emojis" class="emojis">
<section class="result"> <section class="result">
<div v-if="searchResultCustom.length > 0"> <div v-if="searchResultCustom.length > 0">
<button v-for="emoji in searchResultCustom" <button
v-for="emoji in searchResultCustom"
:key="emoji.id" :key="emoji.id"
class="_button" class="_button"
:title="emoji.name" :title="emoji.name"
@ -16,7 +17,8 @@
</button> </button>
</div> </div>
<div v-if="searchResultUnicode.length > 0"> <div v-if="searchResultUnicode.length > 0">
<button v-for="emoji in searchResultUnicode" <button
v-for="emoji in searchResultUnicode"
:key="emoji.name" :key="emoji.name"
class="_button" class="_button"
:title="emoji.name" :title="emoji.name"
@ -31,7 +33,8 @@
<div v-if="tab === 'index'" class="index"> <div v-if="tab === 'index'" class="index">
<section v-if="showPinned"> <section v-if="showPinned">
<div> <div>
<button v-for="emoji in pinned" <button
v-for="emoji in pinned"
:key="emoji" :key="emoji"
class="_button" class="_button"
tabindex="0" tabindex="0"
@ -45,7 +48,8 @@
<section> <section>
<header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.ts.recentUsed }}</header> <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.ts.recentUsed }}</header>
<div> <div>
<button v-for="emoji in recentlyUsedEmojis" <button
v-for="emoji in recentlyUsedEmojis"
:key="emoji" :key="emoji"
class="_button" class="_button"
@click="chosen(emoji, $event)" @click="chosen(emoji, $event)"
@ -76,6 +80,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'; import { ref, computed, watch, onMounted } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XSection from './emoji-picker.section.vue';
import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist'; import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist';
import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import Ripple from '@/components/ripple.vue'; import Ripple from '@/components/ripple.vue';
@ -83,7 +88,6 @@ import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch'; import { isTouchUsing } from '@/scripts/touch';
import { deviceKind } from '@/scripts/device-kind'; import { deviceKind } from '@/scripts/device-kind';
import { emojiCategories, instance } from '@/instance'; import { emojiCategories, instance } from '@/instance';
import XSection from './emoji-picker.section.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
@ -266,7 +270,7 @@ watch(q, () => {
function focus() { function focus() {
if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) { if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
search.value?.focus({ search.value?.focus({
preventScroll: true preventScroll: true,
}); });
} }
} }
@ -308,8 +312,7 @@ function paste(event: ClipboardEvent) {
} }
} }
function done(query?: any): boolean | void { function done(query?: any = q.value): boolean | void {
if (query == null) query = q.value;
if (query == null || typeof query !== 'string') return; if (query == null || typeof query !== 'string') return;
const q2 = query.replace(/:/g, ''); const q2 = query.replace(/:/g, '');

View file

@ -1,5 +1,6 @@
<template> <template>
<button class="kpoogebi _button" <button
class="kpoogebi _button"
:class="{ wait, active: isFollowing || hasPendingFollowRequestFromYou, full, large }" :class="{ wait, active: isFollowing || hasPendingFollowRequestFromYou, full, large }"
:disabled="wait" :disabled="wait"
@click="onClick" @click="onClick"
@ -8,7 +9,8 @@
<template v-if="hasPendingFollowRequestFromYou && user.isLocked"> <template v-if="hasPendingFollowRequestFromYou && user.isLocked">
<span v-if="full">{{ i18n.ts.followRequestPending }}</span><i class="fas fa-hourglass-half"></i> <span v-if="full">{{ i18n.ts.followRequestPending }}</span><i class="fas fa-hourglass-half"></i>
</template> </template>
<template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"> <!-- つまりリモートフォローの場合 --> <template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked">
<!-- つまりリモートフォローの場合 -->
<span v-if="full">{{ i18n.ts.processing }}</span><i class="fas fa-spinner fa-pulse"></i> <span v-if="full">{{ i18n.ts.processing }}</span><i class="fas fa-spinner fa-pulse"></i>
</template> </template>
<template v-else-if="isFollowing"> <template v-else-if="isFollowing">
@ -50,7 +52,7 @@ const connection = stream.useChannel('main');
if (props.user.isFollowing == null) { if (props.user.isFollowing == null) {
os.api('users/show', { os.api('users/show', {
userId: props.user.id userId: props.user.id,
}) })
.then(onFollowChange); .then(onFollowChange);
} }
@ -75,17 +77,17 @@ async function onClick() {
if (canceled) return; if (canceled) return;
await os.api('following/delete', { await os.api('following/delete', {
userId: props.user.id userId: props.user.id,
}); });
} else { } else {
if (hasPendingFollowRequestFromYou) { if (hasPendingFollowRequestFromYou) {
await os.api('following/requests/cancel', { await os.api('following/requests/cancel', {
userId: props.user.id userId: props.user.id,
}); });
hasPendingFollowRequestFromYou = false; hasPendingFollowRequestFromYou = false;
} else { } else {
await os.api('following/create', { await os.api('following/create', {
userId: props.user.id userId: props.user.id,
}); });
hasPendingFollowRequestFromYou = true; hasPendingFollowRequestFromYou = true;
} }

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