merge: 2024.8 (!610)

View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/610

Approved-by: Julia <julia@insertdomain.name>
This commit is contained in:
Julia 2024-09-13 15:57:16 +00:00
commit d4c5322f9c
87 changed files with 1501 additions and 883 deletions

2
.gitignore vendored
View File

@ -35,6 +35,8 @@ coverage
!/.config/example.yml !/.config/example.yml
!/.config/docker_example.yml !/.config/docker_example.yml
!/.config/docker_example.env !/.config/docker_example.env
docker-compose.yml
compose.yml
.devcontainer/compose.yml .devcontainer/compose.yml
!/.devcontainer/compose.yml !/.devcontainer/compose.yml

View File

@ -1,3 +1,41 @@
## 2024.8.0
### General
- Enhance: モデレーターはすべてのユーザーのフォロー・フォロワーの一覧を見られるように
- Enhance: アカウントの削除のモデレーションログを残すように
- Enhance: 不適切なページ、ギャラリー、Playを管理者権限で削除できるように
- Fix: リモートユーザのフォロー・フォロワーの一覧が非公開設定の場合も表示できてしまう問題を修正
### Client
- Enhance: 「自分のPlay」ページにおいてPlayが非公開かどうかが一目でわかるように
- Enhance: 不適切なページ、ギャラリー、Playを通報できるように
- Fix: Play編集時に公開範囲が「パブリック」にリセットされる問題を修正
- Fix: ページ遷移に失敗することがある問題を修正
- Fix: iOSでユーザー名などがリンクとして誤検知される現象を抑制
- Fix: mCaptchaを使用していてもbotプロテクションに関する警告が消えないのを修正
- Fix: ユーザーのモデレーションページにおいてユーザー名にドットが入っているとシステムアカウントとして表示されてしまう問題を修正
- Fix: 特定の条件下でノートの削除ボタンが出ないのを修正
### Server
- Enhance: 照会時にURLがhtmlかつheadタグ内に`rel="alternate"`, `type="application/activity+json"`の`link`タグがある場合に追ってリンク先を照会できるように
- Enhance: 凍結されたアカウントのフォローリクエストを表示しないように
- Fix: WSの`readAllNotifications` メッセージが `body` を持たない場合に動作しない問題 #14374
- 通知ページや通知カラム(デッキ)を開いている状態において、新たに発生した通知が既読されない問題が修正されます。
- これにより、プッシュ通知が有効な同条件下の環境において、プッシュ通知が常に発生してしまう問題も修正されます。
- Fix: Play各種エンドポイントの返り値に`visibility`が含まれていない問題を修正
- Fix: サーバー情報取得の際にモデレーター限定の情報が取得できないことがあるのを修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/582)
- Fix: 公開範囲がダイレクトのノートをユーザーアクティビティのチャート生成に使用しないように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/679)
- Fix: ActivityPubのエンティティタイプ判定で不明なタイプを受け取った場合でも処理を継続するように
- キュー処理のつまりが改善される可能性があります
- Fix: リバーシの対局設定の変更が反映されないのを修正
- Fix: 無制限にストリーミングのチャンネルに接続できる問題を修正
- Fix: ベースロールのポリシーを変更した際にモデログに記録されないのを修正
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/700)
- Fix: Prevent memory leak from memory caches (#14310)
- Fix: More reliable memory cache eviction (#14311)
## 2024.7.0 ## 2024.7.0
### Note ### Note

View File

@ -592,6 +592,9 @@ seems to do a decent job)
* copy all changes (commit after each step): * copy all changes (commit after each step):
* in `packages/backend/src/core/NoteCreateService.ts`, from `create` to * in `packages/backend/src/core/NoteCreateService.ts`, from `create` to
`import` (and vice versa if `git` got confused!) `import` (and vice versa if `git` got confused!)
* in
`packages/backend/src/core/activitypub/models/ApNoteService.ts`,
from `createNote` to `updateNote`
* from `packages/backend/src/core/NoteCreateService.ts` to * from `packages/backend/src/core/NoteCreateService.ts` to
`packages/backend/src/core/NoteEditService.vue` `packages/backend/src/core/NoteEditService.vue`
* in `packages/backend/src/core/activitypub/models/ApNoteService.ts`, * in `packages/backend/src/core/activitypub/models/ApNoteService.ts`,
@ -608,6 +611,8 @@ seems to do a decent job)
`packages/frontend/src/pages/timeline.vue`, `packages/frontend/src/pages/timeline.vue`,
`packages/frontend/src/ui/deck/tl-column.vue`, `packages/frontend/src/ui/deck/tl-column.vue`,
`packages/frontend/src/widgets/WidgetTimeline.vue`) `packages/frontend/src/widgets/WidgetTimeline.vue`)
* check the changes against our `develop` (`git diff develop`) and
against Misskey (`git diff misskey/develop`)
* re-generate `misskey-js` (`pnpm build-misskey-js-with-types`) and commit * re-generate `misskey-js` (`pnpm build-misskey-js-with-types`) and commit
* build the frontend: `rm -rf built/; NODE_ENV=development pnpm --filter=frontend * build the frontend: `rm -rf built/; NODE_ENV=development pnpm --filter=frontend
build` (the `development` tells it to keep some of the original build` (the `development` tells it to keep some of the original

View File

@ -1426,7 +1426,7 @@ _initialTutorial:
_exampleNote: _exampleNote:
cw: "This will surely make you hungry!" cw: "This will surely make you hungry!"
note: "Just had a chocolate-glazed donut 🍩😋" note: "Just had a chocolate-glazed donut 🍩😋"
useCases: "This is used when following the server guidelines for necessary notes or for self-restriction of spoiler or sensitive text." useCases: "This is used when following the server guidelines, for necessary notes, or for self-restriction of spoiler or sensitive text."
_howToMakeAttachmentsSensitive: _howToMakeAttachmentsSensitive:
title: "How to Mark Attachments as Sensitive?" title: "How to Mark Attachments as Sensitive?"
description: "For attachments that are required by server guidelines or that should not be left intact, add a \"sensitive\" flag." description: "For attachments that are required by server guidelines or that should not be left intact, add a \"sensitive\" flag."
@ -2393,6 +2393,7 @@ _pages:
eyeCatchingImageSet: "Set thumbnail" eyeCatchingImageSet: "Set thumbnail"
eyeCatchingImageRemove: "Delete thumbnail" eyeCatchingImageRemove: "Delete thumbnail"
chooseBlock: "Add a block" chooseBlock: "Add a block"
enterSectionTitle: "Enter a section title"
selectType: "Select a type" selectType: "Select a type"
contentBlocks: "Content" contentBlocks: "Content"
inputBlocks: "Input" inputBlocks: "Input"
@ -2574,11 +2575,16 @@ _moderationLogTypes:
unsetUserAvatar: "Unset this user's avatar" unsetUserAvatar: "Unset this user's avatar"
unsetUserBanner: "Unset this user's banner" unsetUserBanner: "Unset this user's banner"
createSystemWebhook: "Create SystemWebhook" createSystemWebhook: "Create SystemWebhook"
updateSystemWebhook: "Update SystemWebHook" updateSystemWebhook: "Update SystemWebhook"
deleteSystemWebhook: "Delete SystemWebhook" deleteSystemWebhook: "Delete SystemWebhook"
createAbuseReportNotificationRecipient: "Create a recipient for abuse reports" createAbuseReportNotificationRecipient: "Create a recipient for abuse reports"
updateAbuseReportNotificationRecipient: "Update recipients for abuse reports" updateAbuseReportNotificationRecipient: "Update recipients for abuse reports"
deleteAbuseReportNotificationRecipient: "Delete a recipient for abuse reports" deleteAbuseReportNotificationRecipient: "Delete a recipient for abuse reports"
deleteAccount: "Delete the account"
deletePage: "Delete the page"
deleteFlash: "Delete Play"
deleteGalleryPost: "Delete the gallery post"
_mfm: _mfm:
uncommonFeature: "This is not a widespread feature, it may not display properly on most other fedi software, including other Misskey forks" uncommonFeature: "This is not a widespread feature, it may not display properly on most other fedi software, including other Misskey forks"
intro: "MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax." intro: "MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax."

View File

@ -60,6 +60,7 @@ copyFileId: "Copiar ID del archivo"
copyFolderId: "Copiar ID de carpeta" copyFolderId: "Copiar ID de carpeta"
copyProfileUrl: "Copiar la URL del perfil" copyProfileUrl: "Copiar la URL del perfil"
searchUser: "Buscar un usuario" searchUser: "Buscar un usuario"
searchThisUsersNotes: ""
reply: "Responder" reply: "Responder"
loadMore: "Ver más" loadMore: "Ver más"
showMore: "Ver más" showMore: "Ver más"

View File

@ -1094,6 +1094,8 @@ preservedUsernames: "Noms d'utilisateur·rice réservés"
preservedUsernamesDescription: "Énumérez les noms d'utilisateur à réserver, séparés par des nouvelles lignes. Les noms d'utilisateur spécifiés ici ne seront plus utilisables lors de la création d'un compte, sauf la création manuelle par un administrateur. De plus, les comptes existants ne seront pas affectés." preservedUsernamesDescription: "Énumérez les noms d'utilisateur à réserver, séparés par des nouvelles lignes. Les noms d'utilisateur spécifiés ici ne seront plus utilisables lors de la création d'un compte, sauf la création manuelle par un administrateur. De plus, les comptes existants ne seront pas affectés."
createNoteFromTheFile: "Rédiger une note de ce fichier" createNoteFromTheFile: "Rédiger une note de ce fichier"
archive: "Archive" archive: "Archive"
archived: "Archivé"
unarchive: "Annuler l'archivage"
channelArchiveConfirmTitle: "Voulez-vous vraiment archiver {name} ?" channelArchiveConfirmTitle: "Voulez-vous vraiment archiver {name} ?"
channelArchiveConfirmDescription: "Une fois archivé, le canal n'apparaîtra plus dans la liste des canaux ni dans les résultats de recherche, et la publication des nouvelles notes sera impossible." channelArchiveConfirmDescription: "Une fois archivé, le canal n'apparaîtra plus dans la liste des canaux ni dans les résultats de recherche, et la publication des nouvelles notes sera impossible."
thisChannelArchived: "Ce canal a été archivé." thisChannelArchived: "Ce canal a été archivé."
@ -1224,7 +1226,10 @@ enableHorizontalSwipe: "Glisser pour changer d'onglet"
loading: "Chargement en cours" loading: "Chargement en cours"
surrender: "Annuler" surrender: "Annuler"
gameRetry: "Réessayer" gameRetry: "Réessayer"
launchApp: "Lancer l'app"
inquiry: "Contact"
_delivery: _delivery:
status: "Statut de la diffusion"
stop: "Suspendu·e" stop: "Suspendu·e"
_type: _type:
none: "Publié" none: "Publié"

22
locales/index.d.ts vendored
View File

@ -2921,7 +2921,7 @@ export interface Locale extends ILocale {
*/ */
"reportAbuseOf": ParameterizedString<"name">; "reportAbuseOf": ParameterizedString<"name">;
/** /**
* URLも記入してください * URLも記入してください
*/ */
"fillAbuseReportDescription": string; "fillAbuseReportDescription": string;
/** /**
@ -9294,6 +9294,10 @@ export interface Locale extends ILocale {
* *
*/ */
"chooseBlock": string; "chooseBlock": string;
/**
*
*/
"enterSectionTitle": string;
/** /**
* *
*/ */
@ -10000,6 +10004,22 @@ export interface Locale extends ILocale {
* *
*/ */
"deleteAbuseReportNotificationRecipient": string; "deleteAbuseReportNotificationRecipient": string;
/**
*
*/
"deleteAccount": string;
/**
*
*/
"deletePage": string;
/**
* Playを削除
*/
"deleteFlash": string;
/**
* 稿
*/
"deleteGalleryPost": string;
}; };
"_mfm": { "_mfm": {
/** /**

View File

@ -726,7 +726,7 @@ abuseReports: "通報"
reportAbuse: "通報" reportAbuse: "通報"
reportAbuseRenote: "ブーストを通報" reportAbuseRenote: "ブーストを通報"
reportAbuseOf: "{name}を通報する" reportAbuseOf: "{name}を通報する"
fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。" fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートやページなどがある場合はそのURLも記入してください。"
abuseReported: "内容が送信されました。ご報告ありがとうございました。" abuseReported: "内容が送信されました。ご報告ありがとうございました。"
reporter: "通報者" reporter: "通報者"
reporteeOrigin: "通報先" reporteeOrigin: "通報先"
@ -2448,6 +2448,7 @@ _pages:
eyeCatchingImageSet: "アイキャッチ画像を設定" eyeCatchingImageSet: "アイキャッチ画像を設定"
eyeCatchingImageRemove: "アイキャッチ画像を削除" eyeCatchingImageRemove: "アイキャッチ画像を削除"
chooseBlock: "ブロックを追加" chooseBlock: "ブロックを追加"
enterSectionTitle: "セクションタイトルを入力"
selectType: "種類を選択" selectType: "種類を選択"
contentBlocks: "コンテンツ" contentBlocks: "コンテンツ"
inputBlocks: "入力" inputBlocks: "入力"
@ -2647,6 +2648,10 @@ _moderationLogTypes:
createAbuseReportNotificationRecipient: "通報の通知先を作成" createAbuseReportNotificationRecipient: "通報の通知先を作成"
updateAbuseReportNotificationRecipient: "通報の通知先を更新" updateAbuseReportNotificationRecipient: "通報の通知先を更新"
deleteAbuseReportNotificationRecipient: "通報の通知先を削除" deleteAbuseReportNotificationRecipient: "通報の通知先を削除"
deleteAccount: "アカウントを削除"
deletePage: "ページを削除"
deleteFlash: "Playを削除"
deleteGalleryPost: "ギャラリーの投稿を削除"
_mfm: _mfm:
uncommonFeature: "この機能は一般的に普及していないため、他のMisskeyフォークを含めた多くのFediverseソフトウェアで表示できないことがあります。" uncommonFeature: "この機能は一般的に普及していないため、他のMisskeyフォークを含めた多くのFediverseソフトウェアで表示できないことがあります。"

View File

@ -60,6 +60,7 @@ copyFileId: "ファイルIDをコピー"
copyFolderId: "フォルダーIDをコピー" copyFolderId: "フォルダーIDをコピー"
copyProfileUrl: "プロフィールURLをコピー" copyProfileUrl: "プロフィールURLをコピー"
searchUser: "ユーザーを探す" searchUser: "ユーザーを探す"
searchThisUsersNotes: "ユーザーのノートを検索"
reply: "返事" reply: "返事"
loadMore: "まだまだあるで!" loadMore: "まだまだあるで!"
showMore: "まだまだあるで!" showMore: "まだまだあるで!"
@ -114,6 +115,8 @@ cantReRenote: "リノート自体はリノートできへんで。"
quote: "引用" quote: "引用"
inChannelRenote: "チャンネルの中でブースト" inChannelRenote: "チャンネルの中でブースト"
inChannelQuote: "チャンネル内引用" inChannelQuote: "チャンネル内引用"
renoteToChannel: "チャンネルにリノート"
renoteToOtherChannel: "他のチャンネルにリノート"
pinnedNote: "ピン留めされとるノート" pinnedNote: "ピン留めされとるノート"
pinned: "ピン留めしとく" pinned: "ピン留めしとく"
you: "あんた" you: "あんた"
@ -152,6 +155,7 @@ editList: "リストいじる"
selectChannel: "チャンネルを選ぶ" selectChannel: "チャンネルを選ぶ"
selectAntenna: "アンテナを選ぶ" selectAntenna: "アンテナを選ぶ"
editAntenna: "アンテナいじる" editAntenna: "アンテナいじる"
createAntenna: "アンテナを作成"
selectWidget: "ウィジェットを選ぶ" selectWidget: "ウィジェットを選ぶ"
editWidgets: "ウィジェットをいじる" editWidgets: "ウィジェットをいじる"
editWidgetsExit: "いじるのをやめる" editWidgetsExit: "いじるのをやめる"
@ -180,6 +184,10 @@ addAccount: "アカウントを追加"
reloadAccountsList: "アカウントリストの情報を更新" reloadAccountsList: "アカウントリストの情報を更新"
loginFailed: "ログインに失敗してもうた…" loginFailed: "ログインに失敗してもうた…"
showOnRemote: "リモートで見る" showOnRemote: "リモートで見る"
continueOnRemote: "リモートで続行"
chooseServerOnMisskeyHub: "Misskey Hubからサーバーを選択"
specifyServerHost: "サーバーのドメインを直接指定"
inputHostName: "ドメインを入力せえや"
general: "全般" general: "全般"
wallpaper: "壁紙" wallpaper: "壁紙"
setWallpaper: "壁紙を設定" setWallpaper: "壁紙を設定"
@ -190,6 +198,7 @@ followConfirm: "{name}をフォローしてええか?"
proxyAccount: "プロキシアカウント" proxyAccount: "プロキシアカウント"
proxyAccountDescription: "プロキシアカウントは、代わりにフォローしてくれるアカウントや。例えば、551に豚まんが無いときやったり、ユーザーがリモートユーザーをアカウントに入れたとき、リストに入れられたユーザーが誰からもフォローされてないと寂しいやん。寂しいし、アクティビティも配達されへんから、プロキシアカウントがフォローしてくれるで。ええやつやん…" proxyAccountDescription: "プロキシアカウントは、代わりにフォローしてくれるアカウントや。例えば、551に豚まんが無いときやったり、ユーザーがリモートユーザーをアカウントに入れたとき、リストに入れられたユーザーが誰からもフォローされてないと寂しいやん。寂しいし、アクティビティも配達されへんから、プロキシアカウントがフォローしてくれるで。ええやつやん…"
host: "ホスト" host: "ホスト"
selectSelf: "自分を選択"
selectUser: "ユーザーを選ぶ" selectUser: "ユーザーを選ぶ"
recipient: "宛先" recipient: "宛先"
annotation: "注釈" annotation: "注釈"
@ -205,6 +214,7 @@ perDay: "1日ごと"
stopActivityDelivery: "アクティビティの配送をやめる" stopActivityDelivery: "アクティビティの配送をやめる"
blockThisInstance: "このサーバーをブロックすんで" blockThisInstance: "このサーバーをブロックすんで"
silenceThisInstance: "サーバーサイレンスすんで?" silenceThisInstance: "サーバーサイレンスすんで?"
mediaSilenceThisInstance: "サーバーをメディアサイレンス"
operations: "操作" operations: "操作"
software: "ソフトウェア" software: "ソフトウェア"
version: "バージョン" version: "バージョン"
@ -226,6 +236,8 @@ blockedInstances: "ブロックしたサーバー"
blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定してな。ブロックされてもうたサーバーとはもう金輪際やり取りできひんくなるで。" blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定してな。ブロックされてもうたサーバーとはもう金輪際やり取りできひんくなるで。"
silencedInstances: "サーバーサイレンスされてんねん" silencedInstances: "サーバーサイレンスされてんねん"
silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定すんで。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなんねん。ブロックしたインスタンスには影響せーへんで。" silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定すんで。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなんねん。ブロックしたインスタンスには影響せーへんで。"
mediaSilencedInstances: "メディアサイレンスしたサーバー"
mediaSilencedInstancesDescription: "メディアサイレンスしたいサーバーのホストを改行で区切って設定するで。メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われてな、カスタム絵文字が使えへんようになるで。ブロックしたインスタンスには影響せえへんで。"
muteAndBlock: "ミュートとブロック" muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしとるユーザー" mutedUsers: "ミュートしとるユーザー"
blockedUsers: "ブロックしとるユーザー" blockedUsers: "ブロックしとるユーザー"
@ -477,6 +489,7 @@ noMessagesYet: "まだチャットはあらへんで"
newMessageExists: "新しいメッセージがきたで" newMessageExists: "新しいメッセージがきたで"
onlyOneFileCanBeAttached: "ごめんな、メッセージに添付できるファイルはひとつだけなんよ。" onlyOneFileCanBeAttached: "ごめんな、メッセージに添付できるファイルはひとつだけなんよ。"
signinRequired: "ログインしてくれへん?" signinRequired: "ログインしてくれへん?"
signinOrContinueOnRemote: "続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があるで"
invitations: "来てや" invitations: "来てや"
invitationCode: "招待コード" invitationCode: "招待コード"
checking: "確認しとるで" checking: "確認しとるで"
@ -1027,6 +1040,7 @@ thisPostMayBeAnnoyingHome: "ホームに投稿"
thisPostMayBeAnnoyingCancel: "やめとく" thisPostMayBeAnnoyingCancel: "やめとく"
thisPostMayBeAnnoyingIgnore: "このまま投稿" thisPostMayBeAnnoyingIgnore: "このまま投稿"
collapseRenotes: "見たことあるブーストは飛ばして表示するで" collapseRenotes: "見たことあるブーストは飛ばして表示するで"
collapseRenotesDescription: "リアクションやブーストをしたことがあるノートをたたんで表示するで。"
internalServerError: "サーバー内部エラー" internalServerError: "サーバー内部エラー"
internalServerErrorDescription: "サーバーでなんか変なこと起こっとるわ。" internalServerErrorDescription: "サーバーでなんか変なこと起こっとるわ。"
copyErrorInfo: "エラー情報をコピるで" copyErrorInfo: "エラー情報をコピるで"
@ -1100,6 +1114,8 @@ preservedUsernames: "予約ユーザー名"
preservedUsernamesDescription: "予約しとくユーザー名を行ごとに挙げるで。ここで指定されたユーザー名はアカウント作るときに使えへんくなるけど、管理者は例外や。あと、もうあるアカウントも例外やな。" preservedUsernamesDescription: "予約しとくユーザー名を行ごとに挙げるで。ここで指定されたユーザー名はアカウント作るときに使えへんくなるけど、管理者は例外や。あと、もうあるアカウントも例外やな。"
createNoteFromTheFile: "このファイル使うてノート作るで" createNoteFromTheFile: "このファイル使うてノート作るで"
archive: "アーカイブ" archive: "アーカイブ"
archived: "アーカイブ済み"
unarchive: "アーカイブ解除"
channelArchiveConfirmTitle: "{name}をアーカイブしてええか?" channelArchiveConfirmTitle: "{name}をアーカイブしてええか?"
channelArchiveConfirmDescription: "アーカイブしたら、チャンネル一覧とか検索結果からなくなるし、新しく書き込みもできへんなるで。" channelArchiveConfirmDescription: "アーカイブしたら、チャンネル一覧とか検索結果からなくなるし、新しく書き込みもできへんなるで。"
thisChannelArchived: "このチャンネル、アーカイブされとるで。" thisChannelArchived: "このチャンネル、アーカイブされとるで。"
@ -1110,6 +1126,9 @@ preventAiLearning: "生成AIの学習に使わんといて"
preventAiLearningDescription: "他の文章生成AIとか画像生成AIに、投稿したートとか画像なんかを勝手に使わんように頼むで。具体的にはnoaiフラグをHTMLレスポンスに含めるんやけど、これ聞いてくれるんはAIの気分次第やから、使われる可能性もちょっとはあるな。" preventAiLearningDescription: "他の文章生成AIとか画像生成AIに、投稿したートとか画像なんかを勝手に使わんように頼むで。具体的にはnoaiフラグをHTMLレスポンスに含めるんやけど、これ聞いてくれるんはAIの気分次第やから、使われる可能性もちょっとはあるな。"
options: "オプション" options: "オプション"
specifyUser: "ユーザー指定" specifyUser: "ユーザー指定"
lookupConfirm: "照会するけどええか?"
openTagPageConfirm: "ハッシュタグのページを開くんか?"
specifyHost: "ホスト指定"
failedToPreviewUrl: "プレビューできへん" failedToPreviewUrl: "プレビューできへん"
update: "更新" update: "更新"
rolesThatCanBeUsedThisEmojiAsReaction: "ツッコミとして使えるロール" rolesThatCanBeUsedThisEmojiAsReaction: "ツッコミとして使えるロール"
@ -1241,10 +1260,20 @@ keepOriginalFilenameDescription: "この設定をオフにすると、アップ
noDescription: "説明文はあらへんで" noDescription: "説明文はあらへんで"
alwaysConfirmFollow: "フォローの際常に確認する" alwaysConfirmFollow: "フォローの際常に確認する"
inquiry: "問い合わせ" inquiry: "問い合わせ"
tryAgain: "もう一度試しいや。"
confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示するとき確認する"
sensitiveMediaRevealConfirm: "センシティブなメディアやで。表示するんか?"
createdLists: "作成したリスト"
createdAntennas: "作成したアンテナ"
_delivery: _delivery:
status: "配信状態"
stop: "配信せぇへん" stop: "配信せぇへん"
resume: "配信再開"
_type: _type:
none: "配信しとる" none: "配信しとる"
manuallySuspended: "手動停止中"
goneSuspended: "サーバー削除のため停止中"
autoSuspendedForNotResponding: "サーバー応答せえへんから停止中"
_bubbleGame: _bubbleGame:
howToPlay: "遊び方" howToPlay: "遊び方"
hold: "ホールド" hold: "ホールド"
@ -1370,6 +1399,8 @@ _serverSettings:
fanoutTimelineDescription: "入れると、おのおのタイムラインを取得するときにめちゃめちゃ動きが良うなって、データベースが軽くなるわ。でも、Redisのメモリ使う量が増えるから注意な。サーバーのメモリが足りんときとか、動きが変なときは切れるで。" fanoutTimelineDescription: "入れると、おのおのタイムラインを取得するときにめちゃめちゃ動きが良うなって、データベースが軽くなるわ。でも、Redisのメモリ使う量が増えるから注意な。サーバーのメモリが足りんときとか、動きが変なときは切れるで。"
fanoutTimelineDbFallback: "データベースにフォールバックする" fanoutTimelineDbFallback: "データベースにフォールバックする"
fanoutTimelineDbFallbackDescription: "有効にしたら、タイムラインがキャッシュん中に入ってないときにDBにもっかい問い合わせるフォールバック処理ってのをやっとくで。切ったらフォールバック処理をやらんからサーバーはもっと軽くなんねんけど、タイムラインの取得範囲がちょっと減るで。" fanoutTimelineDbFallbackDescription: "有効にしたら、タイムラインがキャッシュん中に入ってないときにDBにもっかい問い合わせるフォールバック処理ってのをやっとくで。切ったらフォールバック処理をやらんからサーバーはもっと軽くなんねんけど、タイムラインの取得範囲がちょっと減るで。"
inquiryUrl: "問い合わせ先URL"
inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定するで。"
_accountMigration: _accountMigration:
moveFrom: "別のアカウントからこのアカウントに引っ越す" moveFrom: "別のアカウントからこのアカウントに引っ越す"
moveFromSub: "別のアカウントへエイリアスを作る" moveFromSub: "別のアカウントへエイリアスを作る"
@ -1686,6 +1717,7 @@ _role:
canManageAvatarDecorations: "アバターを飾るモンの管理" canManageAvatarDecorations: "アバターを飾るモンの管理"
driveCapacity: "ドライブ容量" driveCapacity: "ドライブ容量"
alwaysMarkNsfw: "勝手にファイルにNSFWをくっつける" alwaysMarkNsfw: "勝手にファイルにNSFWをくっつける"
canUpdateBioMedia: "アイコンとバナーの更新を許可"
pinMax: "ノートピン留めできる数" pinMax: "ノートピン留めできる数"
antennaMax: "アンテナ作れる数" antennaMax: "アンテナ作れる数"
wordMuteMax: "ワードミュートの最大文字数" wordMuteMax: "ワードミュートの最大文字数"
@ -1937,6 +1969,7 @@ _soundSettings:
driveFileTypeWarnDescription: "音声ファイルを選びや" driveFileTypeWarnDescription: "音声ファイルを選びや"
driveFileDurationWarn: "音が長すぎるわ" driveFileDurationWarn: "音が長すぎるわ"
driveFileDurationWarnDescription: "長い音使うたらSharkey使うのに良うないかもしれへんで。それでもええか" driveFileDurationWarnDescription: "長い音使うたらSharkey使うのに良うないかもしれへんで。それでもええか"
driveFileError: "音声が読み込めへんかったで。設定を変更せえや"
_ago: _ago:
future: "未来" future: "未来"
justNow: "ついさっき" justNow: "ついさっき"
@ -2353,6 +2386,7 @@ _deck:
alwaysShowMainColumn: "いつもメインカラムを表示" alwaysShowMainColumn: "いつもメインカラムを表示"
columnAlign: "カラムの寄せ" columnAlign: "カラムの寄せ"
addColumn: "カラムを追加" addColumn: "カラムを追加"
newNoteNotificationSettings: "新着ノート通知の設定"
configureColumn: "カラムの設定" configureColumn: "カラムの設定"
swapLeft: "左に移動" swapLeft: "左に移動"
swapRight: "右に移動" swapRight: "右に移動"
@ -2391,8 +2425,10 @@ _drivecleaner:
orderByCreatedAtAsc: "追加日の古い順" orderByCreatedAtAsc: "追加日の古い順"
_webhookSettings: _webhookSettings:
createWebhook: "Webhookをつくる" createWebhook: "Webhookをつくる"
modifyWebhook: "Webhookを編集"
name: "名前" name: "名前"
secret: "シークレット" secret: "シークレット"
trigger: "トリガー"
active: "有効" active: "有効"
_events: _events:
follow: "フォローしたとき~!" follow: "フォローしたとき~!"
@ -2402,11 +2438,25 @@ _webhookSettings:
renote: "ブーストされるとき~!" renote: "ブーストされるとき~!"
reaction: "ツッコまれたとき~!" reaction: "ツッコまれたとき~!"
mention: "メンションがあるとき~!" mention: "メンションがあるとき~!"
_systemEvents:
abuseReport: "ユーザーから通報があったとき"
abuseReportResolved: "ユーザーからの通報を処理したとき"
userCreated: "ユーザーが作成されたとき"
deleteConfirm: "ほんまにWebhookをほかしてもええんか" deleteConfirm: "ほんまにWebhookをほかしてもええんか"
_abuseReport: _abuseReport:
_notificationRecipient: _notificationRecipient:
createRecipient: "通報の通知先を追加"
modifyRecipient: "通報の通知先を編集"
recipientType: "通知先の種類"
_recipientType: _recipientType:
mail: "メール" mail: "メール"
webhook: "Webhook"
_captions:
mail: "モデレーター権限を持つユーザーのメアドに通知を送るで(通報を受けた時のみ)"
webhook: "指定したSystemWebhookに通知を送るで(通報を受けた時と通報を解決した時にそれぞれ発信)"
keywords: "キーワード"
notifiedUser: "通知先ユーザー"
notifiedWebhook: "使用するWebhook"
deleteConfirm: "通知先を削除してもええか?" deleteConfirm: "通知先を削除してもええか?"
_moderationLogTypes: _moderationLogTypes:
createRole: "ロールを追加すんで" createRole: "ロールを追加すんで"
@ -2445,6 +2495,8 @@ _moderationLogTypes:
deleteAvatarDecoration: "アイコンデコレーションを削除" deleteAvatarDecoration: "アイコンデコレーションを削除"
unsetUserAvatar: "この子のアイコン元に戻す" unsetUserAvatar: "この子のアイコン元に戻す"
unsetUserBanner: "この子のバナー元に戻す" unsetUserBanner: "この子のバナー元に戻す"
createSystemWebhook: "SystemWebhookを作成"
updateSystemWebhook: "SystemWebhookを更新"
_fileViewer: _fileViewer:
title: "ファイルの詳しい情報" title: "ファイルの詳しい情報"
type: "ファイルの種類" type: "ファイルの種類"

View File

@ -887,7 +887,7 @@ accountDeletionInProgress: "กำลังดำเนินการลบบ
usernameInfo: "ชื่อที่ระบุบัญชีของคุณจากผู้อื่นในเซิร์ฟเวอร์นี้ คุณสามารถใช้ตัวอักษร (a~z, A~Z), ตัวเลข (0~9) หรือขีดล่าง (_) ชื่อผู้ใช้ไม่สามารถเปลี่ยนแปลงได้ในภายหลัง" usernameInfo: "ชื่อที่ระบุบัญชีของคุณจากผู้อื่นในเซิร์ฟเวอร์นี้ คุณสามารถใช้ตัวอักษร (a~z, A~Z), ตัวเลข (0~9) หรือขีดล่าง (_) ชื่อผู้ใช้ไม่สามารถเปลี่ยนแปลงได้ในภายหลัง"
aiChanMode: "โหมด Ai " aiChanMode: "โหมด Ai "
devMode: "โหมดนักพัฒนา" devMode: "โหมดนักพัฒนา"
keepCw: "เก็บคำเตือนเนื้อหา" keepCw: "คงการเตือนเนื้อหาไว้"
pubSub: "บัญชี Pub/Sub" pubSub: "บัญชี Pub/Sub"
lastCommunication: "การสื่อสารครั้งสุดท้ายล่าสุด" lastCommunication: "การสื่อสารครั้งสุดท้ายล่าสุด"
resolved: "คลี่คลายแล้ว" resolved: "คลี่คลายแล้ว"
@ -1034,15 +1034,15 @@ achievements: "ความสำเร็จ"
gotInvalidResponseError: "การตอบสนองเซิร์ฟเวอร์ไม่ถูกต้อง" gotInvalidResponseError: "การตอบสนองเซิร์ฟเวอร์ไม่ถูกต้อง"
gotInvalidResponseErrorDescription: "เซิร์ฟเวอร์อาจไม่สามารถเข้าถึงได้หรืออาจจะกำลังอยู่ในระหว่างปรับปรุง กรุณาลองใหม่อีกครั้งในภายหลังนะคะ" gotInvalidResponseErrorDescription: "เซิร์ฟเวอร์อาจไม่สามารถเข้าถึงได้หรืออาจจะกำลังอยู่ในระหว่างปรับปรุง กรุณาลองใหม่อีกครั้งในภายหลังนะคะ"
thisPostMayBeAnnoying: "โน้ตนี้อาจจะเป็นการรบกวนผู้อื่นนะคะ" thisPostMayBeAnnoying: "โน้ตนี้อาจจะเป็นการรบกวนผู้อื่นนะคะ"
thisPostMayBeAnnoyingHome: "โพสต์ไปยังไทม์ไลน์หลัก" thisPostMayBeAnnoyingHome: "โพสต์ลงไทม์ไลน์หลักเท่านั้น"
thisPostMayBeAnnoyingCancel: "เลิก" thisPostMayBeAnnoyingCancel: "ยกเลิก"
thisPostMayBeAnnoyingIgnore: "โพสต์ยังไงก็แล้วแต่" thisPostMayBeAnnoyingIgnore: "โพสต์ไปเลย ไม่ต้องปรับการมองเห็น"
collapseRenotes: "ยุบรีโน้ตที่คุณเคยเห็นแล้ว" collapseRenotes: "ยุบรีโน้ตที่คุณเคยเห็นแล้ว"
collapseRenotesDescription: "พับย่อโน้ตที่เคยตอบสนองหรือรีโน้ตแล้ว" collapseRenotesDescription: "พับย่อโน้ตที่เคยตอบสนองหรือรีโน้ตแล้ว"
internalServerError: "เซิร์ฟเวอร์ภายในเกิดข้อผิดพลาด" internalServerError: "เซิร์ฟเวอร์ภายในเกิดข้อผิดพลาด"
internalServerErrorDescription: "เกิดข้อผิดพลาดที่ไม่คาดคิดภายในเซิร์ฟเวอร์" internalServerErrorDescription: "เกิดข้อผิดพลาดที่ไม่คาดคิดภายในเซิร์ฟเวอร์"
copyErrorInfo: "คัดลอกรายละเอียดข้อผิดพลาด" copyErrorInfo: "คัดลอกรายละเอียดข้อผิดพลาด"
joinThisServer: "ลงทะเบียนนเซิร์ฟเวอร์นี้" joinThisServer: "ลงทะเบียนนเซิร์ฟเวอร์นี้"
exploreOtherServers: "มองหาเซิร์ฟเวอร์อื่น" exploreOtherServers: "มองหาเซิร์ฟเวอร์อื่น"
letsLookAtTimeline: "มาดูไทม์ไลน์กัน" letsLookAtTimeline: "มาดูไทม์ไลน์กัน"
disableFederationConfirm: "ปิดใช้งานสหพันธ์เลยใช่ไหม?" disableFederationConfirm: "ปิดใช้งานสหพันธ์เลยใช่ไหม?"
@ -1105,7 +1105,7 @@ vertical: "แนวตั้ง"
horizontal: "แนวนอน" horizontal: "แนวนอน"
position: "ตำแหน่ง" position: "ตำแหน่ง"
serverRules: "กฎของเซิร์ฟเวอร์" serverRules: "กฎของเซิร์ฟเวอร์"
pleaseConfirmBelowBeforeSignup: "หากต้องการลงทะเบียนนเซิร์ฟเวอร์นี้ คุณต้องตรวจสอบและยอมรับสิ่งต่อไปนี้" pleaseConfirmBelowBeforeSignup: "หากต้องการลงทะเบียนนเซิร์ฟเวอร์นี้ คุณต้องตรวจสอบและยอมรับสิ่งต่อไปนี้"
pleaseAgreeAllToContinue: "คุณต้องยอมรับทุกช่องตรงด้านบนเพื่อดำเนินการต่อค่ะ" pleaseAgreeAllToContinue: "คุณต้องยอมรับทุกช่องตรงด้านบนเพื่อดำเนินการต่อค่ะ"
continue: "ดำเนินการต่อ" continue: "ดำเนินการต่อ"
preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว้" preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว้"
@ -1361,9 +1361,9 @@ _initialTutorial:
localOnly: "การโพสต์ด้วย flag นี้จะไม่รวมโน้ตไปยังเซิร์ฟเวอร์อื่น ผู้ใช้บนเซิร์ฟเวอร์อื่นจะไม่สามารถดูโน้ตเหล่านี้ได้โดยตรง โดยไม่คำนึงถึงการตั้งค่าการแสดงผลข้างต้น" localOnly: "การโพสต์ด้วย flag นี้จะไม่รวมโน้ตไปยังเซิร์ฟเวอร์อื่น ผู้ใช้บนเซิร์ฟเวอร์อื่นจะไม่สามารถดูโน้ตเหล่านี้ได้โดยตรง โดยไม่คำนึงถึงการตั้งค่าการแสดงผลข้างต้น"
_cw: _cw:
title: "คำเตือนเกี่ยวกับเนื้อหา" title: "คำเตือนเกี่ยวกับเนื้อหา"
description: "เนื้อหาที่เขียนด้วย “คำอธิบายประกอบ” จะแสดงแทนข้อความหลัก คลิก “ดูเพิ่มเติม” เพื่อแสดงข้อความเต็ม" description: "เนื้อหาที่เขียนใน “คำอธิบายประกอบ” จะแสดงแทนเนื้อหาหลัก ต้องคลิก “ดูเพิ่มเติม” เพื่อให้เนื้อหาหลักแสดง"
_exampleNote: _exampleNote:
cw: "นี่อาจจะทำให้คุณหิวอย่างแน่นอน!" cw: " ห้ามดู ระวังหิว"
note: "เพิ่งไปกินโดนัทเคลือบช็อคโกแลตมา 🍩😋" note: "เพิ่งไปกินโดนัทเคลือบช็อคโกแลตมา 🍩😋"
useCases: "ใช้สิ่งนี้เพื่อระบุโน้ตที่ต้องตามแนวทางปฏิบัติของเซิร์ฟเวอร์ หรือเพื่อควบคุมการสปอยล์และข้อความที่ละเอียดอ่อนด้วยตนเอง" useCases: "ใช้สิ่งนี้เพื่อระบุโน้ตที่ต้องตามแนวทางปฏิบัติของเซิร์ฟเวอร์ หรือเพื่อควบคุมการสปอยล์และข้อความที่ละเอียดอ่อนด้วยตนเอง"
_howToMakeAttachmentsSensitive: _howToMakeAttachmentsSensitive:
@ -1479,15 +1479,15 @@ _achievements:
title: "มือใหม่ III" title: "มือใหม่ III"
description: "เข้าสู่ระบบเป็นเวลารวม 15 วัน" description: "เข้าสู่ระบบเป็นเวลารวม 15 วัน"
_login30: _login30:
title: "มิสคิส์ I" title: "มิสคิส์ I"
description: "เข้าสู่ระบบเป็นเวลารวม 30 วัน" description: "เข้าสู่ระบบเป็นเวลารวม 30 วัน"
_login60: _login60:
title: "มิสคิส์ II" title: "มิสคิส์ II"
description: "เข้าสู่ระบบเป็นเวลารวม 60 วัน" description: "เข้าสู่ระบบเป็นเวลารวม 60 วัน"
_login100: _login100:
title: "มิสคิส์ III" title: "มิสคิส์ III"
description: "เข้าสู่ระบบเป็นเวลารวม 100 วัน" description: "เข้าสู่ระบบเป็นเวลารวม 100 วัน"
flavor: "มิสคิสต์หัวรุนแรง" flavor: "Violent Misskist (ทำไมเหมือนชื่อหนังสักเรื่องจังเลยนะ)"
_login200: _login200:
title: "ลูกค้าประจำ I" title: "ลูกค้าประจำ I"
description: "เข้าสู่ระบบเป็นเวลารวม 200 วัน" description: "เข้าสู่ระบบเป็นเวลารวม 200 วัน"
@ -2155,7 +2155,7 @@ _widgets:
serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์" serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์"
aiscript: " คอนโซล AiScript" aiscript: " คอนโซล AiScript"
aiscriptApp: "แอป AiScript" aiscriptApp: "แอป AiScript"
aichan: "ไอ" aichan: "藍 (ไอ)"
userList: "รายชื่อผู้ใช้" userList: "รายชื่อผู้ใช้"
_userList: _userList:
chooseList: "เลือกรายชื่อ" chooseList: "เลือกรายชื่อ"
@ -2197,7 +2197,7 @@ _visibility:
followersDescription: "เฉพาะผู้ติดตามเท่านั้นที่มองเห็นได้" followersDescription: "เฉพาะผู้ติดตามเท่านั้นที่มองเห็นได้"
specified: "ไดเร็ค" specified: "ไดเร็ค"
specifiedDescription: "ทำให้มองเห็นได้เฉพาะผู้ใช้ที่ระบุเท่านั้น" specifiedDescription: "ทำให้มองเห็นได้เฉพาะผู้ใช้ที่ระบุเท่านั้น"
disableFederation: "ไม่มีสหพันธ์" disableFederation: "การปิดใช้งานสหพันธ์"
disableFederationDescription: "อย่าส่งข้อมูลไปยังเซิร์ฟเวอร์อื่น" disableFederationDescription: "อย่าส่งข้อมูลไปยังเซิร์ฟเวอร์อื่น"
_postForm: _postForm:
replyPlaceholder: "ตอบกลับโน้ตนี้..." replyPlaceholder: "ตอบกลับโน้ตนี้..."
@ -2426,6 +2426,7 @@ _webhookSettings:
modifyWebhook: "แก้ไข Webhook" modifyWebhook: "แก้ไข Webhook"
name: "ชื่อ" name: "ชื่อ"
secret: "ความลับ" secret: "ความลับ"
trigger: "ทริกเกอร์"
active: "เปิดใช้งาน" active: "เปิดใช้งาน"
_events: _events:
follow: "เมื่อกำลังติดตามผู้ใช้" follow: "เมื่อกำลังติดตามผู้ใช้"

View File

@ -1665,6 +1665,7 @@ _achievements:
_bubbleGameDoubleExplodingHead: _bubbleGameDoubleExplodingHead:
title: "两个🤯" title: "两个🤯"
description: "你合成出了2个游戏里最大的Emoji" description: "你合成出了2个游戏里最大的Emoji"
flavor: ""
_role: _role:
new: "创建角色" new: "创建角色"
edit: "编辑角色" edit: "编辑角色"
@ -2315,6 +2316,7 @@ _pages:
eyeCatchingImageSet: "设置封面图片" eyeCatchingImageSet: "设置封面图片"
eyeCatchingImageRemove: "删除封面图片" eyeCatchingImageRemove: "删除封面图片"
chooseBlock: "添加块" chooseBlock: "添加块"
enterSectionTitle: "输入会话标题"
selectType: "选择类型" selectType: "选择类型"
contentBlocks: "内容" contentBlocks: "内容"
inputBlocks: "输入" inputBlocks: "输入"
@ -2498,6 +2500,10 @@ _moderationLogTypes:
createAbuseReportNotificationRecipient: "新建了举报通知" createAbuseReportNotificationRecipient: "新建了举报通知"
updateAbuseReportNotificationRecipient: "更新了举报通知" updateAbuseReportNotificationRecipient: "更新了举报通知"
deleteAbuseReportNotificationRecipient: "删除了举报通知" deleteAbuseReportNotificationRecipient: "删除了举报通知"
deleteAccount: "删除了账户"
deletePage: "删除了页面"
deleteFlash: "删除了 Play"
deleteGalleryPost: "删除了图库稿件"
_fileViewer: _fileViewer:
title: "文件信息" title: "文件信息"
type: "文件类型" type: "文件类型"

View File

@ -1967,7 +1967,7 @@ _soundSettings:
driveFileTypeWarnDescription: "請選擇音效檔案" driveFileTypeWarnDescription: "請選擇音效檔案"
driveFileDurationWarn: "音效太長了" driveFileDurationWarn: "音效太長了"
driveFileDurationWarnDescription: "使用長音效檔可能會影響 Misskey 的使用體驗。仍要使用此檔案嗎?" driveFileDurationWarnDescription: "使用長音效檔可能會影響 Misskey 的使用體驗。仍要使用此檔案嗎?"
driveFileError: "無法載入語音。請設定" driveFileError: "無法載入語音。請更設定"
_ago: _ago:
future: "未來" future: "未來"
justNow: "剛剛" justNow: "剛剛"
@ -2316,6 +2316,7 @@ _pages:
eyeCatchingImageSet: "設定封面影像" eyeCatchingImageSet: "設定封面影像"
eyeCatchingImageRemove: "刪除封面影像" eyeCatchingImageRemove: "刪除封面影像"
chooseBlock: "新增方塊" chooseBlock: "新增方塊"
enterSectionTitle: "輸入區段的標題"
selectType: "選擇類型" selectType: "選擇類型"
contentBlocks: "內容" contentBlocks: "內容"
inputBlocks: "輸入" inputBlocks: "輸入"
@ -2439,6 +2440,7 @@ _webhookSettings:
_systemEvents: _systemEvents:
abuseReport: "當使用者檢舉時" abuseReport: "當使用者檢舉時"
abuseReportResolved: "當處理了使用者的檢舉時" abuseReportResolved: "當處理了使用者的檢舉時"
userCreated: "使用者被新增時"
deleteConfirm: "請問是否要刪除 Webhook" deleteConfirm: "請問是否要刪除 Webhook"
_abuseReport: _abuseReport:
_notificationRecipient: _notificationRecipient:
@ -2498,6 +2500,10 @@ _moderationLogTypes:
createAbuseReportNotificationRecipient: "建立接收檢舉的通知對象" createAbuseReportNotificationRecipient: "建立接收檢舉的通知對象"
updateAbuseReportNotificationRecipient: "更新接收檢舉的通知對象" updateAbuseReportNotificationRecipient: "更新接收檢舉的通知對象"
deleteAbuseReportNotificationRecipient: "刪除接收檢舉的通知對象" deleteAbuseReportNotificationRecipient: "刪除接收檢舉的通知對象"
deleteAccount: "刪除帳戶"
deletePage: "刪除頁面"
deleteFlash: "刪除 Play"
deleteGalleryPost: "刪除相簿的貼文"
_fileViewer: _fileViewer:
title: "檔案詳細資訊" title: "檔案詳細資訊"
type: "檔案類型 " type: "檔案類型 "
@ -2632,4 +2638,5 @@ _mediaControls:
_contextMenu: _contextMenu:
title: "內容功能表" title: "內容功能表"
app: "應用程式" app: "應用程式"
appWithShift: "Shift 鍵應用程式"
native: "瀏覽器的使用者介面" native: "瀏覽器的使用者介面"

View File

@ -1,6 +1,6 @@
{ {
"name": "sharkey", "name": "sharkey",
"version": "2024.7.0-rc", "version": "2024.8.0-rc",
"codename": "shonk", "codename": "shonk",
"repository": { "repository": {
"type": "git", "type": "git",
@ -61,7 +61,7 @@
"glob": "11.0.0" "glob": "11.0.0"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "2.0.2", "@misskey-dev/eslint-plugin": "2.0.3",
"@types/node": "20.14.12", "@types/node": "20.14.12",
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/eslint-plugin": "7.17.0",
"@typescript-eslint/parser": "7.17.0", "@typescript-eslint/parser": "7.17.0",

View File

@ -122,7 +122,7 @@
"form-data": "4.0.0", "form-data": "4.0.0",
"glob": "10.3.10", "glob": "10.3.10",
"got": "14.4.2", "got": "14.4.2",
"happy-dom": "10.0.3", "happy-dom": "15.6.1",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"htmlescape": "1.1.1", "htmlescape": "1.1.1",
"http-link-header": "1.1.3", "http-link-header": "1.1.3",

View File

@ -4,12 +4,15 @@
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository } from '@/models/_.js'; import { Not, IsNull } from 'typeorm';
import type { FollowingsRepository, MiUser, UsersRepository } from '@/models/_.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { UserSuspendService } from '@/core/UserSuspendService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@Injectable() @Injectable()
export class DeleteAccountService { export class DeleteAccountService {
@ -17,9 +20,14 @@ export class DeleteAccountService {
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
private userSuspendService: UserSuspendService, @Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
private userEntityService: UserEntityService,
private apRendererService: ApRendererService,
private queueService: QueueService, private queueService: QueueService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private moderationLogService: ModerationLogService,
) { ) {
} }
@ -27,16 +35,52 @@ export class DeleteAccountService {
public async deleteAccount(user: { public async deleteAccount(user: {
id: string; id: string;
host: string | null; host: string | null;
}): Promise<void> { }, moderator?: MiUser): Promise<void> {
const _user = await this.usersRepository.findOneByOrFail({ id: user.id }); const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
if (_user.isRoot) throw new Error('cannot delete a root account'); if (_user.isRoot) throw new Error('cannot delete a root account');
if (moderator != null) {
this.moderationLogService.log(moderator, 'deleteAccount', {
userId: user.id,
userUsername: _user.username,
userHost: user.host,
});
}
// 物理削除する前にDelete activityを送信する // 物理削除する前にDelete activityを送信する
await this.userSuspendService.doPostSuspend(user).catch(e => {}); if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにDelete配信
const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user));
const queue: string[] = [];
const followings = await this.followingsRepository.find({
where: [
{ followerSharedInbox: Not(IsNull()) },
{ followeeSharedInbox: Not(IsNull()) },
],
select: ['followerSharedInbox', 'followeeSharedInbox'],
});
const inboxes = followings.map(x => x.followerSharedInbox ?? x.followeeSharedInbox);
for (const inbox of inboxes) {
if (inbox != null && !queue.includes(inbox)) queue.push(inbox);
}
for (const inbox of queue) {
this.queueService.deliver(user, content, inbox, true);
}
this.queueService.createDeleteAccountJob(user, { this.queueService.createDeleteAccountJob(user, {
soft: false, soft: false,
}); });
} else {
// リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する
this.queueService.createDeleteAccountJob(user, {
soft: true,
});
}
await this.usersRepository.update(user.id, { await this.usersRepository.update(user.id, {
isDeleted: true, isDeleted: true,

View File

@ -12,7 +12,7 @@ import FFmpeg from 'fluent-ffmpeg';
import isSvg from 'is-svg'; import isSvg from 'is-svg';
import probeImageSize from 'probe-image-size'; import probeImageSize from 'probe-image-size';
import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
import { encode } from 'blurhash'; import * as blurhash from 'blurhash';
import { LoggerService } from '@/core/LoggerService.js'; import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -283,7 +283,7 @@ export class FileInfoService {
} }
/** /**
* Calculate average color of image * Calculate blurhash string of image
*/ */
@bindThis @bindThis
private getBlurhash(path: string, type: string): Promise<string> { private getBlurhash(path: string, type: string): Promise<string> {
@ -298,7 +298,7 @@ export class FileInfoService {
let hash; let hash;
try { try {
hash = encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5); hash = blurhash.encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5);
} catch (e) { } catch (e) {
return reject(e); return reject(e);
} }

View File

@ -9,7 +9,8 @@ import type { ModerationLogsRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { ModerationLogPayloads, moderationLogTypes } from '@/types.js'; import type { ModerationLogPayloads } from '@/types.js';
import { moderationLogTypes } from '@/types.js';
@Injectable() @Injectable()
export class ModerationLogService { export class ModerationLogService {

View File

@ -281,192 +281,6 @@ export class NoteCreateService implements OnApplicationShutdown {
data.visibility = 'home'; data.visibility = 'home';
} }
if (this.isRenote(data)) {
switch (data.renote.visibility) {
case 'public':
// public noteは無条件にrenote可能
break;
case 'home':
// home noteはhome以下にrenote可能
if (data.visibility === 'public') {
data.visibility = 'home';
}
break;
case 'followers':
// 他人のfollowers noteはreject
if (data.renote.userId !== user.id) {
throw new Error('Renote target is not public or home');
}
// Renote対象がfollowersならfollowersにする
data.visibility = 'followers';
break;
case 'specified':
// specified / direct noteはreject
throw new Error('Renote target is not public or home');
}
}
// Check blocking
if (this.isRenote(data) && !this.isQuote(data)) {
if (data.renote.userHost === null) {
if (data.renote.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
if (blocked) {
throw new Error('blocked');
}
}
}
}
// 返信対象がpublicではないならhomeにする
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
data.visibility = 'home';
}
// ローカルのみをRenoteしたらローカルのみにする
if (data.renote && data.renote.localOnly && data.channel == null) {
data.localOnly = true;
}
// ローカルのみにリプライしたらローカルのみにする
if (data.reply && data.reply.localOnly && data.channel == null) {
data.localOnly = true;
}
if (data.text) {
if (data.text.length > DB_MAX_NOTE_TEXT_LENGTH) {
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
}
data.text = data.text.trim();
if (data.text === '') {
data.text = null;
}
} else {
data.text = null;
}
let tags = data.apHashtags;
let emojis = data.apEmojis;
let mentionedUsers = data.apMentions;
// Parse MFM if needed
if (!tags || !emojis || !mentionedUsers) {
const tokens = (data.text ? mfm.parse(data.text)! : []);
const cwTokens = data.cw ? mfm.parse(data.cw)! : [];
const choiceTokens = data.poll && data.poll.choices
? concat(data.poll.choices.map(choice => mfm.parse(choice)!))
: [];
const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens);
tags = data.apHashtags ?? extractHashtags(combinedTokens);
emojis = data.apEmojis ?? extractCustomEmojisFromMfm(combinedTokens);
mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens);
}
tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32);
if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {
mentionedUsers.push(await this.usersRepository.findOneByOrFail({ id: data.reply!.userId }));
}
if (data.visibility === 'specified') {
if (data.visibleUsers == null) throw new Error('invalid param');
for (const u of data.visibleUsers) {
if (!mentionedUsers.some(x => x.id === u.id)) {
mentionedUsers.push(u);
}
}
if (data.reply && !data.visibleUsers.some(x => x.id === data.reply!.userId)) {
data.visibleUsers.push(await this.usersRepository.findOneByOrFail({ id: data.reply!.userId }));
}
}
if (user.host && !data.cw) {
await this.federatedInstanceService.fetch(user.host).then(async i => {
if (i.isNSFW) {
data.cw = 'Instance is marked as NSFW';
}
});
}
if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) {
throw new IdentifiableError('9f466dab-c856-48cd-9e65-ff90ff750580', 'Note contains too many mentions');
}
const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
setImmediate('post created', { signal: this.#shutdownController.signal }).then(
() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!),
() => { /* aborted, ignore this */ },
);
return note;
}
@bindThis
public async import(user: {
id: MiUser['id'];
username: MiUser['username'];
host: MiUser['host'];
isBot: MiUser['isBot'];
noindex: MiUser['noindex'];
}, data: Option, silent = false): Promise<MiNote> {
// チャンネル外にリプライしたら対象のスコープに合わせる
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
if (data.reply.channelId) {
data.channel = await this.channelsRepository.findOneBy({ id: data.reply.channelId });
} else {
data.channel = null;
}
}
// チャンネル内にリプライしたら対象のスコープに合わせる
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
if (data.reply && (data.channel == null) && data.reply.channelId) {
data.channel = await this.channelsRepository.findOneBy({ id: data.reply.channelId });
}
if (data.createdAt == null) data.createdAt = new Date();
if (data.visibility == null) data.visibility = 'public';
if (data.localOnly == null) data.localOnly = false;
if (data.channel != null) data.visibility = 'public';
if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true;
const meta = await this.metaService.fetch();
if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = meta.sensitiveWords;
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home';
}
}
const hasProhibitedWords = await this.checkProhibitedWordsContain({
cw: data.cw,
text: data.text,
pollChoices: data.poll?.choices,
}, meta.prohibitedWords);
if (hasProhibitedWords) {
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
}
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
data.visibility = 'home';
}
if (data.renote) { if (data.renote) {
switch (data.renote.visibility) { switch (data.renote.visibility) {
case 'public': case 'public':
@ -576,6 +390,14 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
} }
if (user.host && !data.cw) {
await this.federatedInstanceService.fetch(user.host).then(async i => {
if (i.isNSFW) {
data.cw = 'Instance is marked as NSFW';
}
});
}
if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) { if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) {
throw new IdentifiableError('9f466dab-c856-48cd-9e65-ff90ff750580', 'Note contains too many mentions'); throw new IdentifiableError('9f466dab-c856-48cd-9e65-ff90ff750580', 'Note contains too many mentions');
} }
@ -583,13 +405,24 @@ export class NoteCreateService implements OnApplicationShutdown {
const note = await this.insertNote(user, data, tags, emojis, mentionedUsers); const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
setImmediate('post created', { signal: this.#shutdownController.signal }).then( setImmediate('post created', { signal: this.#shutdownController.signal }).then(
() => this.postNoteImported(note, user, data, silent, tags!, mentionedUsers!), () => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!),
() => { /* aborted, ignore this */ }, () => { /* aborted, ignore this */ },
); );
return note; return note;
} }
@bindThis
public async import(user: {
id: MiUser['id'];
username: MiUser['username'];
host: MiUser['host'];
isBot: MiUser['isBot'];
noindex: MiUser['noindex'];
}, data: Option): Promise<MiNote> {
return this.create(user, data, true);
}
@bindThis @bindThis
private async insertNote(user: { id: MiUser['id']; host: MiUser['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { private async insertNote(user: { id: MiUser['id']; host: MiUser['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) {
const insert = new MiNote({ const insert = new MiNote({
@ -707,7 +540,7 @@ export class NoteCreateService implements OnApplicationShutdown {
const meta = await this.metaService.fetch(); const meta = await this.metaService.fetch();
this.notesChart.update(note, true); this.notesChart.update(note, true);
if (meta.enableChartsForRemoteUser || (user.host == null)) { if (note.visibility !== 'specified' && (meta.enableChartsForRemoteUser || (user.host == null))) {
this.perUserNotesChart.update(user, note, true); this.perUserNotesChart.update(user, note, true);
} }
@ -967,105 +800,6 @@ export class NoteCreateService implements OnApplicationShutdown {
if (!user.noindex) this.index(note); if (!user.noindex) this.index(note);
} }
@bindThis
private async postNoteImported(note: MiNote, user: {
id: MiUser['id'];
username: MiUser['username'];
host: MiUser['host'];
isBot: MiUser['isBot'];
noindex: MiUser['noindex'];
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
const meta = await this.metaService.fetch();
this.notesChart.update(note, true);
if (meta.enableChartsForRemoteUser || (user.host == null)) {
this.perUserNotesChart.update(user, note, true);
}
// Register host
if (this.userEntityService.isRemoteUser(user)) {
this.federatedInstanceService.fetch(user.host).then(async i => {
if (note.renote && note.text) {
this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
} else if (!note.renote) {
this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
}
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
this.instanceChart.updateNote(i.host, note, true);
}
});
}
if (data.renote && data.text) {
// Increment notes count (user)
this.incNotesCountOfUser(user);
} else if (!data.renote) {
// Increment notes count (user)
this.incNotesCountOfUser(user);
}
this.pushToTl(note, user);
this.antennaService.addNoteToAntennas(note, user);
if (data.reply) {
this.saveReply(data.reply, note);
}
if (data.reply == null) {
// TODO: キャッシュ
this.followingsRepository.findBy({
followeeId: user.id,
notify: 'normal',
}).then(followings => {
for (const following of followings) {
// TODO: ワードミュート考慮
this.notificationService.createNotification(following.followerId, 'note', {
noteId: note.id,
}, user.id);
}
});
}
if (data.renote && data.text == null && data.renote.userId !== user.id && !user.isBot) {
this.incRenoteCount(data.renote);
}
if (data.poll && data.poll.expiresAt) {
const delay = data.poll.expiresAt.getTime() - Date.now();
this.queueService.endedPollNotificationQueue.add(note.id, {
noteId: note.id,
}, {
delay,
removeOnComplete: true,
});
}
// Pack the note
const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
if (data.channel) {
this.channelsRepository.increment({ id: data.channel.id }, 'notesCount', 1);
this.channelsRepository.update(data.channel.id, {
lastNotedAt: new Date(),
});
this.notesRepository.countBy({
userId: user.id,
channelId: data.channel.id,
}).then(count => {
// この処理が行われるのはノート作成後なので、ノートが一つしかなかったら最初の投稿だと判断できる
// TODO: とはいえノートを削除して何回も投稿すればその分だけインクリメントされる雑さもあるのでどうにかしたい
if (count === 1) {
this.channelsRepository.increment({ id: data.channel!.id }, 'usersCount', 1);
}
});
}
// Register to search database
if (!user.noindex) this.index(note);
}
@bindThis @bindThis
private isRenote(note: Option): note is Option & { renote: MiNote } { private isRenote(note: Option): note is Option & { renote: MiNote } {
return note.renote != null; return note.renote != null;

View File

@ -99,7 +99,7 @@ export class NoteDeleteService {
this.deliverToConcerned(user, note, content); this.deliverToConcerned(user, note, content);
} }
// also deliever delete activity to cascaded notes // also deliver delete activity to cascaded notes
const federatedLocalCascadingNotes = (cascadingNotes).filter(note => !note.localOnly && note.userHost == null); // filter out local-only notes const federatedLocalCascadingNotes = (cascadingNotes).filter(note => !note.localOnly && note.userHost == null); // filter out local-only notes
for (const cascadingNote of federatedLocalCascadingNotes) { for (const cascadingNote of federatedLocalCascadingNotes) {
if (!cascadingNote.user) continue; if (!cascadingNote.user) continue;

View File

@ -6,6 +6,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import { reversiUpdateKeys } from 'misskey-js';
import * as Reversi from 'misskey-reversi'; import * as Reversi from 'misskey-reversi';
import { IsNull, LessThan, MoreThan } from 'typeorm'; import { IsNull, LessThan, MoreThan } from 'typeorm';
import type { import type {
@ -399,7 +400,33 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
} }
@bindThis @bindThis
public async updateSettings(gameId: MiReversiGame['id'], user: MiUser, key: string, value: any) { public isValidReversiUpdateKey(key: unknown): key is typeof reversiUpdateKeys[number] {
if (typeof key !== 'string') return false;
return (reversiUpdateKeys as string[]).includes(key);
}
@bindThis
public isValidReversiUpdateValue<K extends typeof reversiUpdateKeys[number]>(key: K, value: unknown): value is MiReversiGame[K] {
switch (key) {
case 'map':
return Array.isArray(value) && value.every(row => typeof row === 'string');
case 'bw':
return typeof value === 'string' && ['random', '1', '2'].includes(value);
case 'isLlotheo':
return typeof value === 'boolean';
case 'canPutEverywhere':
return typeof value === 'boolean';
case 'loopedBoard':
return typeof value === 'boolean';
case 'timeLimitForEachTurn':
return typeof value === 'number' && value >= 0;
default:
return false;
}
}
@bindThis
public async updateSettings<K extends typeof reversiUpdateKeys[number]>(gameId: MiReversiGame['id'], user: MiUser, key: K, value: MiReversiGame[K]) {
const game = await this.get(gameId); const game = await this.get(gameId);
if (game == null) throw new Error('game not found'); if (game == null) throw new Error('game not found');
if (game.isStarted) return; if (game.isStarted) return;
@ -407,10 +434,6 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
if ((game.user1Id === user.id) && game.user1Ready) return; if ((game.user1Id === user.id) && game.user1Ready) return;
if ((game.user2Id === user.id) && game.user2Ready) return; if ((game.user2Id === user.id) && game.user2Ready) return;
if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard', 'timeLimitForEachTurn'].includes(key)) return;
// TODO: より厳格なバリデーション
const updatedGame = { const updatedGame = {
...game, ...game,
[key]: value, [key]: value,

View File

@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Not, IsNull } from 'typeorm'; import { Not, IsNull } from 'typeorm';
import type { FollowingsRepository } from '@/models/_.js'; import type { FollowingsRepository, FollowRequestsRepository, UsersRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
@ -13,24 +13,75 @@ import { DI } from '@/di-symbols.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { RelationshipJobData } from '@/queue/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@Injectable() @Injectable()
export class UserSuspendService { export class UserSuspendService {
constructor( constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.followingsRepository) @Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository, private followingsRepository: FollowingsRepository,
@Inject(DI.followRequestsRepository)
private followRequestsRepository: FollowRequestsRepository,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private queueService: QueueService, private queueService: QueueService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private moderationLogService: ModerationLogService,
) { ) {
} }
@bindThis @bindThis
public async doPostSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise<void> { public async suspend(user: MiUser, moderator: MiUser): Promise<void> {
await this.usersRepository.update(user.id, {
isSuspended: true,
});
this.moderationLogService.log(moderator, 'suspend', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
});
(async () => {
await this.postSuspend(user).catch(e => {});
await this.unFollowAll(user).catch(e => {});
})();
}
@bindThis
public async unsuspend(user: MiUser, moderator: MiUser): Promise<void> {
await this.usersRepository.update(user.id, {
isSuspended: false,
});
this.moderationLogService.log(moderator, 'unsuspend', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
});
(async () => {
await this.postUnsuspend(user).catch(e => {});
})();
}
@bindThis
private async postSuspend(user: { id: MiUser['id']; host: MiUser['host'] }): Promise<void> {
this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
this.followRequestsRepository.delete({
followeeId: user.id,
});
this.followRequestsRepository.delete({
followerId: user.id,
});
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにDelete配信 // 知り得る全SharedInboxにDelete配信
const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user)); const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user));
@ -58,7 +109,7 @@ export class UserSuspendService {
} }
@bindThis @bindThis
public async doPostUnsuspend(user: MiUser): Promise<void> { private async postUnsuspend(user: MiUser): Promise<void> {
this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false }); this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false });
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
@ -86,4 +137,26 @@ export class UserSuspendService {
} }
} }
} }
@bindThis
private async unFollowAll(follower: MiUser) {
const followings = await this.followingsRepository.find({
where: {
followerId: follower.id,
followeeId: Not(IsNull()),
},
});
const jobs: RelationshipJobData[] = [];
for (const following of followings) {
if (following.followeeId && following.followerId) {
jobs.push({
from: { id: following.followerId },
to: { id: following.followeeId },
silent: true,
});
}
}
this.queueService.createUnfollowJob(jobs);
}
} }

View File

@ -6,6 +6,7 @@
import * as crypto from 'node:crypto'; import * as crypto from 'node:crypto';
import { URL } from 'node:url'; import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Window } from 'happy-dom';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
@ -182,7 +183,8 @@ export class ApRequestService {
* @param url URL to fetch * @param url URL to fetch
*/ */
@bindThis @bindThis
public async signedGet(url: string, user: { id: MiUser['id'] }): Promise<unknown> { public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise<unknown> {
const _followAlternate = followAlternate ?? true;
const keypair = await this.userKeypairService.getUserKeypair(user.id); const keypair = await this.userKeypairService.getUserKeypair(user.id);
const req = ApRequestCreator.createSignedGet({ const req = ApRequestCreator.createSignedGet({
@ -200,9 +202,52 @@ export class ApRequestService {
headers: req.request.headers, headers: req.request.headers,
}, { }, {
throwErrorWhenResponseNotOk: true, throwErrorWhenResponseNotOk: true,
validators: [validateContentTypeSetAsActivityPub],
}); });
//#region リクエスト先がhtmlかつactivity+jsonへのalternate linkタグがあるとき
const contentType = res.headers.get('content-type');
if ((contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' && _followAlternate === true) {
const html = await res.text();
const window = new Window({
settings: {
disableJavaScriptEvaluation: true,
disableJavaScriptFileLoading: true,
disableCSSFileLoading: true,
disableComputedStyleRendering: true,
handleDisabledFileLoadingAsSuccess: true,
navigation: {
disableMainFrameNavigation: true,
disableChildFrameNavigation: true,
disableChildPageNavigation: true,
disableFallbackToSetURL: true,
},
timer: {
maxTimeout: 0,
maxIntervalTime: 0,
maxIntervalIterations: 0,
},
},
});
const document = window.document;
try {
document.documentElement.innerHTML = html;
const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]');
if (alternate) {
const href = alternate.getAttribute('href');
if (href) {
return await this.signedGet(href, user, false);
}
}
} catch (e) {
// something went wrong parsing the HTML, ignore the whole thing
}
}
//#endregion
validateContentTypeSetAsActivityPub(res);
const finalUrl = res.url; // redirects may have been involved const finalUrl = res.url; // redirects may have been involved
const activity = await res.json() as IObject; const activity = await res.json() as IObject;

View File

@ -358,7 +358,7 @@ export class ApNoteService {
value, value,
object, object,
}); });
throw new Error('invalid note'); throw err;
} }
const note = object as IPost; const note = object as IPost;
@ -471,19 +471,19 @@ export class ApNoteService {
| { status: 'ok'; res: MiNote } | { status: 'ok'; res: MiNote }
| { status: 'permerror' | 'temperror' } | { status: 'permerror' | 'temperror' }
> => { > => {
if (!/^https?:/.test(uri)) return { status: 'permerror' }; if (typeof uri !== 'string' || !/^https?:/.test(uri)) return { status: 'permerror' };
try { try {
const res = await this.resolveNote(uri, { resolver }); const res = await this.resolveNote(uri, { resolver });
if (res == null) return { status: 'permerror' }; if (res == null) return { status: 'permerror' };
return { status: 'ok', res }; return { status: 'ok', res };
} catch (e) { } catch (e) {
return { return {
status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror', status: (e instanceof StatusError && !e.isRetryable) ? 'permerror' : 'temperror',
}; };
} }
}; };
const uris = unique([note._misskey_quote, note.quoteUrl, note.quoteUri].filter((x): x is string => typeof x === 'string')); const uris = unique([note._misskey_quote, note.quoteUrl, note.quoteUri].filter(x => x != null));
const results = await Promise.all(uris.map(tryResolveNote)); const results = await Promise.all(uris.map(tryResolveNote));
quote = results.filter((x): x is { status: 'ok', res: MiNote } => x.status === 'ok').map(x => x.res).at(0); quote = results.filter((x): x is { status: 'ok', res: MiNote } => x.status === 'ok').map(x => x.res).at(0);
@ -496,10 +496,10 @@ export class ApNoteService {
// vote // vote
if (reply && reply.hasPoll) { if (reply && reply.hasPoll) {
const replyPoll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
const tryCreateVote = async (name: string, index: number): Promise<null> => { const tryCreateVote = async (name: string, index: number): Promise<null> => {
if (replyPoll.expiresAt && Date.now() > new Date(replyPoll.expiresAt).getTime()) { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {
this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
} else if (index >= 0) { } else if (index >= 0) {
this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
@ -512,7 +512,7 @@ export class ApNoteService {
}; };
if (note.name) { if (note.name) {
return await tryCreateVote(note.name, replyPoll.choices.findIndex(x => x === note.name)); return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name));
} }
} }

View File

@ -48,7 +48,7 @@ import type { ApResolverService, Resolver } from '../ApResolverService.js';
import type { ApLoggerService } from '../ApLoggerService.js'; import type { ApLoggerService } from '../ApLoggerService.js';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports // eslint-disable-next-line @typescript-eslint/consistent-type-imports
import type { ApImageService } from './ApImageService.js'; import type { ApImageService } from './ApImageService.js';
import type { IActor, IObject } from '../type.js'; import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js';
const nameLength = 128; const nameLength = 128;
const summaryLength = 2048; const summaryLength = 2048;
@ -308,6 +308,21 @@ export class ApPersonService implements OnModuleInit {
const isBot = getApType(object) === 'Service' || getApType(object) === 'Application'; const isBot = getApType(object) === 'Service' || getApType(object) === 'Application';
const [followingVisibility, followersVisibility] = await Promise.all(
[
this.isPublicCollection(person.following, resolver),
this.isPublicCollection(person.followers, resolver),
].map((p): Promise<'public' | 'private'> => p
.then(isPublic => isPublic ? 'public' : 'private')
.catch(err => {
if (!(err instanceof StatusError) || err.isRetryable) {
this.logger.error('error occurred while fetching following/followers collection', { stack: err });
}
return 'private';
})
)
);
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
const url = getOneApHrefNullable(person.url); const url = getOneApHrefNullable(person.url);
@ -382,6 +397,8 @@ export class ApPersonService implements OnModuleInit {
description: _description, description: _description,
url, url,
fields, fields,
followingVisibility,
followersVisibility,
birthday: bday?.[0] ?? null, birthday: bday?.[0] ?? null,
location: person['vcard:Address'] ?? null, location: person['vcard:Address'] ?? null,
userHost: host, userHost: host,
@ -490,6 +507,23 @@ export class ApPersonService implements OnModuleInit {
const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32); const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32);
const [followingVisibility, followersVisibility] = await Promise.all(
[
this.isPublicCollection(person.following, resolver),
this.isPublicCollection(person.followers, resolver),
].map((p): Promise<'public' | 'private' | undefined> => p
.then(isPublic => isPublic ? 'public' : 'private')
.catch(err => {
if (!(err instanceof StatusError) || err.isRetryable) {
this.logger.error('error occurred while fetching following/followers collection', { stack: err });
// Do not update the visibiility on transient errors.
return undefined;
}
return 'private';
})
)
);
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
const url = getOneApHrefNullable(person.url); const url = getOneApHrefNullable(person.url);
@ -561,6 +595,8 @@ export class ApPersonService implements OnModuleInit {
url, url,
fields, fields,
description: _description, description: _description,
followingVisibility,
followersVisibility,
birthday: bday?.[0] ?? null, birthday: bday?.[0] ?? null,
location: person['vcard:Address'] ?? null, location: person['vcard:Address'] ?? null,
listenbrainz: person.listenbrainz ?? null, listenbrainz: person.listenbrainz ?? null,
@ -733,4 +769,16 @@ export class ApPersonService implements OnModuleInit {
return 'ok'; return 'ok';
} }
@bindThis
private async isPublicCollection(collection: string | ICollection | IOrderedCollection | undefined, resolver: Resolver): Promise<boolean> {
if (collection) {
const resolved = await resolver.resolveCollection(collection);
if (resolved.first || (resolved as ICollection).items || (resolved as IOrderedCollection).orderedItems) {
return true;
}
}
return false;
}
} }

View File

@ -100,13 +100,15 @@ export interface IActivity extends IObject {
export interface ICollection extends IObject { export interface ICollection extends IObject {
type: 'Collection'; type: 'Collection';
totalItems: number; totalItems: number;
items: ApObject; first?: IObject | string;
items?: ApObject;
} }
export interface IOrderedCollection extends IObject { export interface IOrderedCollection extends IObject {
type: 'OrderedCollection'; type: 'OrderedCollection';
totalItems: number; totalItems: number;
orderedItems: ApObject; first?: IObject | string;
orderedItems?: ApObject;
} }
export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event']; export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event'];

View File

@ -49,6 +49,7 @@ export class FlashEntityService {
title: flash.title, title: flash.title,
summary: flash.summary, summary: flash.summary,
script: flash.script, script: flash.script,
visibility: flash.visibility,
likedCount: flash.likedCount, likedCount: flash.likedCount,
isLiked: meId ? await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }) : undefined, isLiked: meId ? await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }) : undefined,
}); });

View File

@ -490,12 +490,12 @@ export class UserEntityService implements OnModuleInit {
const mastoapi = !isDetailed ? opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null; const mastoapi = !isDetailed ? opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null;
const followingCount = profile == null ? null : const followingCount = profile == null ? null :
(profile.followingVisibility === 'public') || isMe ? user.followingCount : (profile.followingVisibility === 'public') || isMe || iAmModerator ? user.followingCount :
(profile.followingVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount : (profile.followingVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
null; null;
const followersCount = profile == null ? null : const followersCount = profile == null ? null :
(profile.followersVisibility === 'public') || isMe ? user.followersCount : (profile.followersVisibility === 'public') || isMe || iAmModerator ? user.followersCount :
(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : (profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null; null;

View File

@ -72,6 +72,10 @@ export class RedisKVCache<T> {
/** /**
* fetcherを呼び出して結果をキャッシュ& * fetcherを呼び出して結果をキャッシュ&
* This awaits the call to Redis to ensure that the write succeeded, which is important for a few reasons:
* * Other code uses this to synchronize changes between worker processes. A failed write can internally de-sync the cluster.
* * Without an `await`, consecutive calls could race. An unlucky race could result in the older write overwriting the newer value.
* * Not awaiting here makes the entire cache non-consistent. The prevents many possible uses.
*/ */
@bindThis @bindThis
public async fetch(key: string): Promise<T> { public async fetch(key: string): Promise<T> {
@ -172,6 +176,10 @@ export class RedisSingleCache<T> {
/** /**
* fetcherを呼び出して結果をキャッシュ& * fetcherを呼び出して結果をキャッシュ&
* This awaits the call to Redis to ensure that the write succeeded, which is important for a few reasons:
* * Other code uses this to synchronize changes between worker processes. A failed write can internally de-sync the cluster.
* * Without an `await`, consecutive calls could race. An unlucky race could result in the older write overwriting the newer value.
* * Not awaiting here makes the entire cache non-consistent. The prevents many possible uses.
*/ */
@bindThis @bindThis
public async fetch(): Promise<T> { public async fetch(): Promise<T> {

View File

@ -6,3 +6,7 @@
export type JsonValue = JsonArray | JsonObject | string | number | boolean | null; export type JsonValue = JsonArray | JsonObject | string | number | boolean | null;
export type JsonObject = {[K in string]?: JsonValue}; export type JsonObject = {[K in string]?: JsonValue};
export type JsonArray = JsonValue[]; export type JsonArray = JsonValue[];
export function isJsonObject(value: JsonValue | undefined): value is JsonObject {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

View File

@ -104,7 +104,3 @@ export function toArray<T>(x: T | T[] | undefined): T[] {
export function toSingle<T>(x: T | T[] | undefined): T | undefined { export function toSingle<T>(x: T | T[] | undefined): T | undefined {
return Array.isArray(x) ? x[0] : x; return Array.isArray(x) ? x[0] : x;
} }
export function toSingleLast<T>(x: T | T[] | undefined): T | undefined {
return Array.isArray(x) ? x.at(-1) : x;
}

View File

@ -44,6 +44,11 @@ export const packedFlashSchema = {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
}, },
visibility: {
type: 'string',
optional: false, nullable: false,
enum: ['private', 'public'],
},
likedCount: { likedCount: {
type: 'number', type: 'number',
optional: false, nullable: true, optional: false, nullable: true,

View File

@ -7,9 +7,9 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js'; import type { UsersRepository } from '@/models/_.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { UserSuspendService } from '@/core/UserSuspendService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
@ -33,9 +33,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
private userEntityService: UserEntityService, private deleteAccoountService: DeleteAccountService,
private queueService: QueueService,
private userSuspendService: UserSuspendService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId }); const user = await this.usersRepository.findOneBy({ id: ps.userId });
@ -48,22 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('cannot delete a root account'); throw new Error('cannot delete a root account');
} }
if (this.userEntityService.isLocalUser(user)) { await this.deleteAccoountService.deleteAccount(user);
// 物理削除する前にDelete activityを送信する
await this.userSuspendService.doPostSuspend(user).catch(err => {});
this.queueService.createDeleteAccountJob(user, {
soft: false,
});
} else {
this.queueService.createDeleteAccountJob(user, {
soft: true, // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する
});
}
await this.usersRepository.update(user.id, {
isDeleted: true,
});
}); });
} }
} }

View File

@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = { export const meta = {
tags: ['admin', 'role'], tags: ['admin', 'role'],
@ -33,12 +34,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor( constructor(
private metaService: MetaService, private metaService: MetaService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private moderationLogService: ModerationLogService,
) { ) {
super(meta, paramDef, async (ps) => { super(meta, paramDef, async (ps, me) => {
const before = await this.metaService.fetch(true);
await this.metaService.update({ await this.metaService.update({
policies: ps.policies, policies: ps.policies,
}); });
this.globalEventService.publishInternalEvent('policiesUpdated', ps.policies);
const after = await this.metaService.fetch(true);
this.globalEventService.publishInternalEvent('policiesUpdated', after.policies);
this.moderationLogService.log(me, 'updateServerSettings', {
before: before.policies,
after: after.policies,
});
}); });
} }
} }

View File

@ -3,18 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { IsNull, Not } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository, FollowingsRepository } from '@/models/_.js'; import type { UsersRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import type { RelationshipJobData } from '@/queue/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { UserSuspendService } from '@/core/UserSuspendService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { QueueService } from '@/core/QueueService.js';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
@ -38,13 +32,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
private userSuspendService: UserSuspendService, private userSuspendService: UserSuspendService,
private roleService: RoleService, private roleService: RoleService,
private moderationLogService: ModerationLogService,
private queueService: QueueService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId }); const user = await this.usersRepository.findOneBy({ id: ps.userId });
@ -57,42 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('cannot suspend moderator account'); throw new Error('cannot suspend moderator account');
} }
await this.usersRepository.update(user.id, { await this.userSuspendService.suspend(user, me);
isSuspended: true,
});
this.moderationLogService.log(me, 'suspend', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
});
(async () => {
await this.userSuspendService.doPostSuspend(user).catch(e => {});
await this.unFollowAll(user).catch(e => {});
})();
});
}
@bindThis
private async unFollowAll(follower: MiUser) {
const followings = await this.followingsRepository.find({
where: {
followerId: follower.id,
followeeId: Not(IsNull()),
},
});
const jobs: RelationshipJobData[] = [];
for (const following of followings) {
if (following.followeeId && following.followerId) {
jobs.push({
from: { id: following.followerId },
to: { id: following.followeeId },
silent: true,
}); });
} }
} }
this.queueService.createUnfollowJob(jobs);
}
}

View File

@ -6,7 +6,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js'; import type { UsersRepository } from '@/models/_.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { UserSuspendService } from '@/core/UserSuspendService.js'; import { UserSuspendService } from '@/core/UserSuspendService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -33,7 +32,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
private userSuspendService: UserSuspendService, private userSuspendService: UserSuspendService,
private moderationLogService: ModerationLogService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId }); const user = await this.usersRepository.findOneBy({ id: ps.userId });
@ -42,17 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('user not found'); throw new Error('user not found');
} }
await this.usersRepository.update(user.id, { await this.userSuspendService.unsuspend(user, me);
isSuspended: false,
});
this.moderationLogService.log(me, 'unsuspend', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
});
this.userSuspendService.doPostUnsuspend(user);
}); });
} }
} }

View File

@ -4,9 +4,11 @@
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { FlashsRepository } from '@/models/_.js'; import type { FlashsRepository, UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -44,17 +46,35 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor( constructor(
@Inject(DI.flashsRepository) @Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository, private flashsRepository: FlashsRepository,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private moderationLogService: ModerationLogService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const flash = await this.flashsRepository.findOneBy({ id: ps.flashId }); const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
if (flash == null) { if (flash == null) {
throw new ApiError(meta.errors.noSuchFlash); throw new ApiError(meta.errors.noSuchFlash);
} }
if (flash.userId !== me.id) {
if (!await this.roleService.isModerator(me) && flash.userId !== me.id) {
throw new ApiError(meta.errors.accessDenied); throw new ApiError(meta.errors.accessDenied);
} }
await this.flashsRepository.delete(flash.id); await this.flashsRepository.delete(flash.id);
if (flash.userId !== me.id) {
const user = await this.usersRepository.findOneByOrFail({ id: flash.userId });
this.moderationLogService.log(me, 'deleteFlash', {
flashId: flash.id,
flashUserId: flash.userId,
flashUserUsername: user.username,
flash,
});
}
}); });
} }
} }

View File

@ -5,8 +5,10 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import type { GalleryPostsRepository } from '@/models/_.js'; import type { GalleryPostsRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../../error.js'; import { ApiError } from '../../../error.js';
export const meta = { export const meta = {
@ -22,6 +24,12 @@ export const meta = {
code: 'NO_SUCH_POST', code: 'NO_SUCH_POST',
id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5', id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5',
}, },
accessDenied: {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: 'c86e09de-1c48-43ac-a435-1c7e42ed4496',
},
}, },
} as const; } as const;
@ -38,18 +46,35 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor( constructor(
@Inject(DI.galleryPostsRepository) @Inject(DI.galleryPostsRepository)
private galleryPostsRepository: GalleryPostsRepository, private galleryPostsRepository: GalleryPostsRepository,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private moderationLogService: ModerationLogService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const post = await this.galleryPostsRepository.findOneBy({ const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId });
id: ps.postId,
userId: me.id,
});
if (post == null) { if (post == null) {
throw new ApiError(meta.errors.noSuchPost); throw new ApiError(meta.errors.noSuchPost);
} }
if (!await this.roleService.isModerator(me) && post.userId !== me.id) {
throw new ApiError(meta.errors.accessDenied);
}
await this.galleryPostsRepository.delete(post.id); await this.galleryPostsRepository.delete(post.id);
if (post.userId !== me.id) {
const user = await this.usersRepository.findOneByOrFail({ id: post.userId });
this.moderationLogService.log(me, 'deleteGalleryPost', {
postId: post.id,
postUserId: post.userId,
postUserUsername: user.username,
post,
});
}
}); });
} }
} }

View File

@ -4,9 +4,11 @@
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { PagesRepository } from '@/models/_.js'; import type { PagesRepository, UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -44,17 +46,35 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor( constructor(
@Inject(DI.pagesRepository) @Inject(DI.pagesRepository)
private pagesRepository: PagesRepository, private pagesRepository: PagesRepository,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private moderationLogService: ModerationLogService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); const page = await this.pagesRepository.findOneBy({ id: ps.pageId });
if (page == null) { if (page == null) {
throw new ApiError(meta.errors.noSuchPage); throw new ApiError(meta.errors.noSuchPage);
} }
if (page.userId !== me.id) {
if (!await this.roleService.isModerator(me) && page.userId !== me.id) {
throw new ApiError(meta.errors.accessDenied); throw new ApiError(meta.errors.accessDenied);
} }
await this.pagesRepository.delete(page.id); await this.pagesRepository.delete(page.id);
if (page.userId !== me.id) {
const user = await this.usersRepository.findOneByOrFail({ id: page.userId });
this.moderationLogService.log(me, 'deletePage', {
pageId: page.id,
pageUserId: page.userId,
pageUserUsername: user.username,
page,
});
}
}); });
} }
} }

View File

@ -11,6 +11,7 @@ import { QueryService } from '@/core/QueryService.js';
import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -81,6 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private utilityService: UtilityService, private utilityService: UtilityService,
private followingEntityService: FollowingEntityService, private followingEntityService: FollowingEntityService,
private queryService: QueryService, private queryService: QueryService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy(ps.userId != null const user = await this.usersRepository.findOneBy(ps.userId != null
@ -93,6 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
if (profile.followersVisibility !== 'public' && !await this.roleService.isModerator(me)) {
if (profile.followersVisibility === 'private') { if (profile.followersVisibility === 'private') {
if (me == null || (me.id !== user.id)) { if (me == null || (me.id !== user.id)) {
throw new ApiError(meta.errors.forbidden); throw new ApiError(meta.errors.forbidden);
@ -112,6 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
} }
} }
} }
}
const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
.andWhere('following.followeeId = :userId', { userId: user.id }) .andWhere('following.followeeId = :userId', { userId: user.id })

View File

@ -12,6 +12,7 @@ import { QueryService } from '@/core/QueryService.js';
import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js'; import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -90,6 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private utilityService: UtilityService, private utilityService: UtilityService,
private followingEntityService: FollowingEntityService, private followingEntityService: FollowingEntityService,
private queryService: QueryService, private queryService: QueryService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy(ps.userId != null const user = await this.usersRepository.findOneBy(ps.userId != null
@ -102,6 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
if (profile.followingVisibility !== 'public' && !await this.roleService.isModerator(me)) {
if (profile.followingVisibility === 'private') { if (profile.followingVisibility === 'private') {
if (me == null || (me.id !== user.id)) { if (me == null || (me.id !== user.id)) {
throw new ApiError(meta.errors.forbidden); throw new ApiError(meta.errors.forbidden);
@ -121,6 +124,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
} }
} }
} }
}
const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
.andWhere('following.followerId = :userId', { userId: user.id }) .andWhere('following.followerId = :userId', { userId: user.id })

View File

@ -14,7 +14,8 @@ import { CacheService } from '@/core/CacheService.js';
import { MiFollowing, MiUserProfile } from '@/models/_.js'; import { MiFollowing, MiUserProfile } from '@/models/_.js';
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js'; import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
import type { JsonObject } from '@/misc/json-value.js'; import { isJsonObject } from '@/misc/json-value.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import type { ChannelsService } from './ChannelsService.js'; import type { ChannelsService } from './ChannelsService.js';
import type { EventEmitter } from 'events'; import type { EventEmitter } from 'events';
import type Channel from './channel.js'; import type Channel from './channel.js';
@ -149,8 +150,6 @@ export default class Connection {
const { type, body } = obj; const { type, body } = obj;
if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
switch (type) { switch (type) {
case 'readNotification': this.onReadNotification(body); break; case 'readNotification': this.onReadNotification(body); break;
case 'subNote': this.onSubscribeNote(body); break; case 'subNote': this.onSubscribeNote(body); break;
@ -191,7 +190,8 @@ export default class Connection {
} }
@bindThis @bindThis
private readNote(body: JsonObject) { private readNote(body: JsonValue | undefined) {
if (!isJsonObject(body)) return;
const id = body.id; const id = body.id;
const note = this.cachedNotes.find(n => n.id === id); const note = this.cachedNotes.find(n => n.id === id);
@ -203,7 +203,7 @@ export default class Connection {
} }
@bindThis @bindThis
private onReadNotification(payload: JsonObject) { private onReadNotification(payload: JsonValue | undefined) {
this.notificationService.readAllNotification(this.user!.id); this.notificationService.readAllNotification(this.user!.id);
} }
@ -211,7 +211,8 @@ export default class Connection {
* 稿 * 稿
*/ */
@bindThis @bindThis
private onSubscribeNote(payload: JsonObject) { private onSubscribeNote(payload: JsonValue | undefined) {
if (!isJsonObject(payload)) return;
if (!payload.id || typeof payload.id !== 'string') return; if (!payload.id || typeof payload.id !== 'string') return;
const current = this.subscribingNotes[payload.id] ?? 0; const current = this.subscribingNotes[payload.id] ?? 0;
@ -227,7 +228,8 @@ export default class Connection {
* 稿 * 稿
*/ */
@bindThis @bindThis
private onUnsubscribeNote(payload: JsonObject) { private onUnsubscribeNote(payload: JsonValue | undefined) {
if (!isJsonObject(payload)) return;
if (!payload.id || typeof payload.id !== 'string') return; if (!payload.id || typeof payload.id !== 'string') return;
const current = this.subscribingNotes[payload.id]; const current = this.subscribingNotes[payload.id];
@ -265,12 +267,13 @@ export default class Connection {
* *
*/ */
@bindThis @bindThis
private onChannelConnectRequested(payload: JsonObject) { private onChannelConnectRequested(payload: JsonValue | undefined) {
if (!isJsonObject(payload)) return;
const { channel, id, params, pong } = payload; const { channel, id, params, pong } = payload;
if (typeof id !== 'string') return; if (typeof id !== 'string') return;
if (typeof channel !== 'string') return; if (typeof channel !== 'string') return;
if (typeof pong !== 'boolean' && typeof pong !== 'undefined' && pong !== null) return; if (typeof pong !== 'boolean' && typeof pong !== 'undefined' && pong !== null) return;
if (typeof params !== 'undefined' && (typeof params !== 'object' || params === null || Array.isArray(params))) return; if (typeof params !== 'undefined' && !isJsonObject(params)) return;
this.connectChannel(id, params, channel, pong ?? undefined); this.connectChannel(id, params, channel, pong ?? undefined);
} }
@ -278,7 +281,8 @@ export default class Connection {
* *
*/ */
@bindThis @bindThis
private onChannelDisconnectRequested(payload: JsonObject) { private onChannelDisconnectRequested(payload: JsonValue | undefined) {
if (!isJsonObject(payload)) return;
const { id } = payload; const { id } = payload;
if (typeof id !== 'string') return; if (typeof id !== 'string') return;
this.disconnectChannel(id); this.disconnectChannel(id);
@ -350,7 +354,8 @@ export default class Connection {
* @param data * @param data
*/ */
@bindThis @bindThis
private onChannelMessageRequested(data: JsonObject) { private onChannelMessageRequested(data: JsonValue | undefined) {
if (!isJsonObject(data)) return;
if (typeof data.id !== 'string') return; if (typeof data.id !== 'string') return;
if (typeof data.type !== 'string') return; if (typeof data.type !== 'string') return;
if (typeof data.body === 'undefined') return; if (typeof data.body === 'undefined') return;

View File

@ -6,6 +6,7 @@
import Xev from 'xev'; import Xev from 'xev';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { isJsonObject } from '@/misc/json-value.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js'; import Channel, { type MiChannelService } from '../channel.js';
@ -36,7 +37,7 @@ class QueueStatsChannel extends Channel {
public onMessage(type: string, body: JsonValue) { public onMessage(type: string, body: JsonValue) {
switch (type) { switch (type) {
case 'requestLog': case 'requestLog':
if (typeof body !== 'object' || body === null || Array.isArray(body)) return; if (!isJsonObject(body)) return;
if (typeof body.id !== 'string') return; if (typeof body.id !== 'string') return;
if (typeof body.length !== 'number') return; if (typeof body.length !== 'number') return;
ev.once(`queueStatsLog:${body.id}`, statsLog => { ev.once(`queueStatsLog:${body.id}`, statsLog => {

View File

@ -9,8 +9,10 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { ReversiService } from '@/core/ReversiService.js'; import { ReversiService } from '@/core/ReversiService.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js'; import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { isJsonObject } from '@/misc/json-value.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js'; import Channel, { type MiChannelService } from '../channel.js';
import { reversiUpdateKeys } from 'misskey-js';
class ReversiGameChannel extends Channel { class ReversiGameChannel extends Channel {
public readonly chName = 'reversiGame'; public readonly chName = 'reversiGame';
@ -44,16 +46,17 @@ class ReversiGameChannel extends Channel {
this.ready(body); this.ready(body);
break; break;
case 'updateSettings': case 'updateSettings':
if (typeof body !== 'object' || body === null || Array.isArray(body)) return; if (!isJsonObject(body)) return;
if (typeof body.key !== 'string') return; if (!this.reversiService.isValidReversiUpdateKey(body.key)) return;
if (typeof body.value !== 'object' || body.value === null || Array.isArray(body.value)) return; if (!this.reversiService.isValidReversiUpdateValue(body.key, body.value)) return;
this.updateSettings(body.key, body.value); this.updateSettings(body.key, body.value);
break; break;
case 'cancel': case 'cancel':
this.cancelGame(); this.cancelGame();
break; break;
case 'putStone': case 'putStone':
if (typeof body !== 'object' || body === null || Array.isArray(body)) return; if (!isJsonObject(body)) return;
if (typeof body.pos !== 'number') return; if (typeof body.pos !== 'number') return;
if (typeof body.id !== 'string') return; if (typeof body.id !== 'string') return;
this.putStone(body.pos, body.id); this.putStone(body.pos, body.id);
@ -63,7 +66,7 @@ class ReversiGameChannel extends Channel {
} }
@bindThis @bindThis
private async updateSettings(key: string, value: JsonObject) { private async updateSettings<K extends typeof reversiUpdateKeys[number]>(key: K, value: MiReversiGame[K]) {
if (this.user == null) return; if (this.user == null) return;
this.reversiService.updateSettings(this.gameId!, this.user, key, value); this.reversiService.updateSettings(this.gameId!, this.user, key, value);

View File

@ -6,6 +6,7 @@
import Xev from 'xev'; import Xev from 'xev';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { isJsonObject } from '@/misc/json-value.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js'; import Channel, { type MiChannelService } from '../channel.js';
@ -36,7 +37,7 @@ class ServerStatsChannel extends Channel {
public onMessage(type: string, body: JsonValue) { public onMessage(type: string, body: JsonValue) {
switch (type) { switch (type) {
case 'requestLog': case 'requestLog':
if (typeof body !== 'object' || body === null || Array.isArray(body)) return; if (!isJsonObject(body)) return;
ev.once(`serverStatsLog:${body.id}`, statsLog => { ev.once(`serverStatsLog:${body.id}`, statsLog => {
this.send('statsLog', statsLog); this.send('statsLog', statsLog);
}); });

View File

@ -32,6 +32,7 @@ html
meta(property='og:site_name' content= instanceName || 'Sharkey') meta(property='og:site_name' content= instanceName || 'Sharkey')
meta(property='instance_url' content= instanceUrl) meta(property='instance_url' content= instanceUrl)
meta(name='viewport' content='width=device-width, initial-scale=1') meta(name='viewport' content='width=device-width, initial-scale=1')
meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no')
link(rel='icon' href= icon || '/favicon.ico') link(rel='icon' href= icon || '/favicon.ico')
link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png') link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
link(rel='manifest' href='/manifest.json') link(rel='manifest' href='/manifest.json')

View File

@ -98,6 +98,10 @@ export const moderationLogTypes = [
'createAbuseReportNotificationRecipient', 'createAbuseReportNotificationRecipient',
'updateAbuseReportNotificationRecipient', 'updateAbuseReportNotificationRecipient',
'deleteAbuseReportNotificationRecipient', 'deleteAbuseReportNotificationRecipient',
'deleteAccount',
'deletePage',
'deleteFlash',
'deleteGalleryPost',
] as const; ] as const;
export type ModerationLogPayloads = { export type ModerationLogPayloads = {
@ -321,6 +325,29 @@ export type ModerationLogPayloads = {
recipientId: string; recipientId: string;
recipient: any; recipient: any;
}; };
deleteAccount: {
userId: string;
userUsername: string;
userHost: string | null;
};
deletePage: {
pageId: string;
pageUserId: string;
pageUserUsername: string;
page: any;
};
deleteFlash: {
flashId: string;
flashUserId: string;
flashUserUsername: string;
flash: any;
};
deleteGalleryPost: {
postId: string;
postUserId: string;
postUserUsername: string;
post: any;
};
}; };
export type Serialized<T> = { export type Serialized<T> = {

View File

@ -20,7 +20,8 @@ import { CoreModule } from '@/core/CoreModule.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { LoggerService } from '@/core/LoggerService.js'; import { LoggerService } from '@/core/LoggerService.js';
import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.js'; import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.js';
import { MiMeta, MiNote } from '@/models/_.js'; import { MiMeta, MiNote, UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { secureRndstr } from '@/misc/secure-rndstr.js'; import { secureRndstr } from '@/misc/secure-rndstr.js';
import { DownloadService } from '@/core/DownloadService.js'; import { DownloadService } from '@/core/DownloadService.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
@ -86,6 +87,7 @@ async function createRandomRemoteUser(
} }
describe('ActivityPub', () => { describe('ActivityPub', () => {
let userProfilesRepository: UserProfilesRepository;
let imageService: ApImageService; let imageService: ApImageService;
let noteService: ApNoteService; let noteService: ApNoteService;
let personService: ApPersonService; let personService: ApPersonService;
@ -127,6 +129,8 @@ describe('ActivityPub', () => {
await app.init(); await app.init();
app.enableShutdownHooks(); app.enableShutdownHooks();
userProfilesRepository = app.get(DI.userProfilesRepository);
noteService = app.get<ApNoteService>(ApNoteService); noteService = app.get<ApNoteService>(ApNoteService);
personService = app.get<ApPersonService>(ApPersonService); personService = app.get<ApPersonService>(ApPersonService);
rendererService = app.get<ApRendererService>(ApRendererService); rendererService = app.get<ApRendererService>(ApRendererService);
@ -205,6 +209,53 @@ describe('ActivityPub', () => {
}); });
}); });
describe('Collection visibility', () => {
test('Public following/followers', async () => {
const actor = createRandomActor();
actor.following = {
id: `${actor.id}/following`,
type: 'OrderedCollection',
totalItems: 0,
first: `${actor.id}/following?page=1`,
};
actor.followers = `${actor.id}/followers`;
resolver.register(actor.id, actor);
resolver.register(actor.followers, {
id: actor.followers,
type: 'OrderedCollection',
totalItems: 0,
first: `${actor.followers}?page=1`,
});
const user = await personService.createPerson(actor.id, resolver);
const userProfile = await userProfilesRepository.findOneByOrFail({ userId: user.id });
assert.deepStrictEqual(userProfile.followingVisibility, 'public');
assert.deepStrictEqual(userProfile.followersVisibility, 'public');
});
test('Private following/followers', async () => {
const actor = createRandomActor();
actor.following = {
id: `${actor.id}/following`,
type: 'OrderedCollection',
totalItems: 0,
// first: …
};
actor.followers = `${actor.id}/followers`;
resolver.register(actor.id, actor);
//resolver.register(actor.followers, { … });
const user = await personService.createPerson(actor.id, resolver);
const userProfile = await userProfilesRepository.findOneByOrFail({ userId: user.id });
assert.deepStrictEqual(userProfile.followingVisibility, 'private');
assert.deepStrictEqual(userProfile.followersVisibility, 'private');
});
});
describe('Renderer', () => { describe('Renderer', () => {
test('Render an announce with visibility: followers', () => { test('Render an announce with visibility: followers', () => {
rendererService.renderAnnounce('https://example.com/notes/00example', { rendererService.renderAnnounce('https://example.com/notes/00example', {

View File

@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { AISCRIPT_VERSION } from '@syuilo/aiscript';
import type { entities } from 'misskey-js' import type { entities } from 'misskey-js'
export function abuseUserReport() { export function abuseUserReport() {
@ -114,6 +115,40 @@ export function file(isSensitive = false) {
}; };
} }
const script = `/// @ ${AISCRIPT_VERSION}
var name = ""
Ui:render([
Ui:C:textInput({
label: "Your name"
onInput: @(v) { name = v }
})
Ui:C:button({
text: "Hello"
onClick: @() {
Mk:dialog(null, \`Hello, {name}!\`)
}
})
])
`;
export function flash(): entities.Flash {
return {
id: 'someflashid',
createdAt: '2016-12-28T22:49:51.000Z',
updatedAt: '2016-12-28T22:49:51.000Z',
userId: 'someuserid',
user: userLite(),
title: 'Some Play title',
summary: 'Some Play summary',
script,
visibility: 'public',
likedCount: 0,
isLiked: false,
};
}
export function folder(id = 'somefolderid', name = 'Some Folder', parentId: string | null = null): entities.DriveFolder { export function folder(id = 'somefolderid', name = 'Some Folder', parentId: string | null = null): entities.DriveFolder {
return { return {
id, id,

View File

@ -398,6 +398,7 @@ function toStories(component: string): Promise<string> {
glob('src/components/global/Mk*.vue'), glob('src/components/global/Mk*.vue'),
glob('src/components/global/RouterView.vue'), glob('src/components/global/RouterView.vue'),
glob('src/components/Mk[A-E]*.vue'), glob('src/components/Mk[A-E]*.vue'),
glob('src/components/MkFlashPreview.vue'),
glob('src/components/MkGalleryPostPreview.vue'), glob('src/components/MkGalleryPostPreview.vue'),
glob('src/components/MkSignupServerRules.vue'), glob('src/components/MkSignupServerRules.vue'),
glob('src/components/MkUserSetupDialog.vue'), glob('src/components/MkUserSetupDialog.vue'),

View File

@ -29,7 +29,7 @@
"@syuilo/aiscript": "0.19.0", "@syuilo/aiscript": "0.19.0",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.1.0", "@vitejs/plugin-vue": "5.1.0",
"@vue/compiler-sfc": "3.4.34", "@vue/compiler-sfc": "3.4.37",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11",
"astring": "1.8.6", "astring": "1.8.6",
"broadcast-channel": "7.0.0", "broadcast-channel": "7.0.0",
@ -73,7 +73,7 @@
"uuid": "10.0.0", "uuid": "10.0.0",
"v-code-diff": "1.12.0", "v-code-diff": "1.12.0",
"vite": "5.3.5", "vite": "5.3.5",
"vue": "3.4.34", "vue": "3.4.37",
"vuedraggable": "next" "vuedraggable": "next"
}, },
"devDependencies": { "devDependencies": {
@ -112,7 +112,7 @@
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/eslint-plugin": "7.17.0",
"@typescript-eslint/parser": "7.17.0", "@typescript-eslint/parser": "7.17.0",
"@vitest/coverage-v8": "1.6.0", "@vitest/coverage-v8": "1.6.0",
"@vue/runtime-core": "3.4.34", "@vue/runtime-core": "3.4.37",
"acorn": "8.12.1", "acorn": "8.12.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "13.13.1", "cypress": "13.13.1",

View File

@ -230,17 +230,18 @@ export async function mainBoot() {
claimAchievement('client60min'); claimAchievement('client60min');
}, 1000 * 60 * 60); }, 1000 * 60 * 60);
const lastUsed = miLocalStorage.getItem('lastUsed'); // 邪魔
if (lastUsed) { //const lastUsed = miLocalStorage.getItem('lastUsed');
const lastUsedDate = parseInt(lastUsed, 10); //if (lastUsed) {
// 二時間以上前なら // const lastUsedDate = parseInt(lastUsed, 10);
if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) { // // 二時間以上前なら
toast(i18n.tsx.welcomeBackWithName({ // if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) {
name: $i.name || $i.username, // toast(i18n.tsx.welcomeBackWithName({
}), true); // name: $i.name || $i.username,
} // }), true);
} // }
miLocalStorage.setItem('lastUsed', Date.now().toString()); //}
//miLocalStorage.setItem('lastUsed', Date.now().toString());
const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt'); const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo'); const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');

View File

@ -39,7 +39,7 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
const props = defineProps<{ const props = defineProps<{
user: Misskey.entities.UserDetailed; user: Misskey.entities.UserLite;
initialComment?: string; initialComment?: string;
}>(); }>();

View File

@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { StoryObj } from '@storybook/vue3';
import MkFlashPreview from './MkFlashPreview.vue';
import { flash } from './../../.storybook/fakes.js';
export const Public = {
render(args) {
return {
components: {
MkFlashPreview,
},
setup() {
return {
args,
};
},
computed: {
props() {
return {
...this.args,
};
},
},
template: '<MkFlashPreview v-bind="props" />',
};
},
args: {
flash: {
...flash(),
visibility: 'public',
},
},
parameters: {
layout: 'fullscreen',
},
decorators: [
() => ({
template: '<div style="display: flex; align-items: center; justify-content: center; height: 100vh"><div style="max-width: 700px; width: 100%; margin: 3rem"><story/></div></div>',
}),
],
} satisfies StoryObj<typeof MkFlashPreview>;
export const Private = {
...Public,
args: {
flash: {
...flash(),
visibility: 'private',
},
},
} satisfies StoryObj<typeof MkFlashPreview>;

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkA :to="`/play/${flash.id}`" class="vhpxefrk _panel"> <MkA :to="`/play/${flash.id}`" class="vhpxefrk _panel" :class="[{ gray: flash.visibility === 'private' }]">
<article> <article>
<header> <header>
<h1 :title="flash.title">{{ flash.title }}</h1> <h1 :title="flash.title">{{ flash.title }}</h1>
@ -22,11 +22,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { } from 'vue';
import * as Misskey from 'misskey-js';
import { userName } from '@/filters/user.js'; import { userName } from '@/filters/user.js';
const props = defineProps<{ const props = defineProps<{
//flash: Misskey.entities.Flash; flash: Misskey.entities.Flash;
flash: any;
}>(); }>();
</script> </script>
@ -91,6 +91,12 @@ const props = defineProps<{
} }
} }
&:global(.gray) {
--c: var(--bg);
background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
background-size: 16px 16px;
}
@media (max-width: 700px) { @media (max-width: 700px) {
} }

View File

@ -234,6 +234,7 @@ import { host } from '@/config.js';
import { isEnabledUrlPreview } from '@/instance.js'; import { isEnabledUrlPreview } from '@/instance.js';
import { type Keymap } from '@/scripts/hotkey.js'; import { type Keymap } from '@/scripts/hotkey.js';
import { focusPrev, focusNext } from '@/scripts/focus.js'; import { focusPrev, focusNext } from '@/scripts/focus.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -285,14 +286,7 @@ if (noteViewInterruptors.length > 0) {
}); });
} }
const isRenote = ( const isRenote = Misskey.note.isPureRenote(note.value);
note.value.renote != null &&
note.value.reply == null &&
note.value.text == null &&
note.value.cw == null &&
note.value.fileIds && note.value.fileIds.length === 0 &&
note.value.poll == null
);
const rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>(); const menuButton = shallowRef<HTMLElement>();
@ -303,7 +297,7 @@ const reactButton = shallowRef<HTMLElement>();
const quoteButton = shallowRef<HTMLElement>(); const quoteButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>();
const likeButton = shallowRef<HTMLElement>(); const likeButton = shallowRef<HTMLElement>();
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>(); const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const isMyRenote = $i && ($i.id === note.value.userId); const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(defaultStore.state.uncollapseCW); const showContent = ref(defaultStore.state.uncollapseCW);

View File

@ -266,6 +266,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
import { isEnabledUrlPreview } from '@/instance.js'; import { isEnabledUrlPreview } from '@/instance.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
import { type Keymap } from '@/scripts/hotkey.js'; import { type Keymap } from '@/scripts/hotkey.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
@ -299,14 +300,7 @@ if (noteViewInterruptors.length > 0) {
}); });
} }
const isRenote = ( const isRenote = Misskey.note.isPureRenote(note.value);
note.value.renote != null &&
note.value.reply == null &&
note.value.text == null &&
note.value.cw == null &&
note.value.fileIds && note.value.fileIds.length === 0 &&
note.value.poll == null
);
const rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>(); const menuButton = shallowRef<HTMLElement>();
@ -317,7 +311,7 @@ const reactButton = shallowRef<HTMLElement>();
const quoteButton = shallowRef<HTMLElement>(); const quoteButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>();
const likeButton = shallowRef<HTMLElement>(); const likeButton = shallowRef<HTMLElement>();
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>(); const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const isMyRenote = $i && ($i.id === note.value.userId); const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(defaultStore.state.uncollapseCW); const showContent = ref(defaultStore.state.uncollapseCW);

View File

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div :class="$style.preview"> <div :class="$style.preview">
<div :class="$style.preview__content1"> <div>
<MkInput v-model="text"> <MkInput v-model="text">
<template #label>Text</template> <template #label>Text</template>
</MkInput> </MkInput>

View File

@ -4,11 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkA v-adaptive-bg :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" class="_panel" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }"> <MkA :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }">
<div :class="$style.title"> <template v-if="forModeration">
<span :class="$style.icon"> <i v-if="role.isPublic" class="ti ti-world" :class="$style.icon" style="color: var(--success)"></i>
<i v-else class="ti ti-lock" :class="$style.icon" style="color: var(--warn)"></i>
</template>
<div v-adaptive-bg class="_panel" :class="$style.body">
<div :class="$style.bodyTitle">
<span :class="$style.bodyIcon">
<template v-if="role.iconUrl"> <template v-if="role.iconUrl">
<img :class="$style.badge" :src="role.iconUrl"/> <img :class="$style.bodyBadge" :src="role.iconUrl"/>
</template> </template>
<template v-else> <template v-else>
<i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--accent);"></i> <i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--accent);"></i>
@ -16,13 +22,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else class="ti ti-user" style="opacity: 0.7;"></i> <i v-else class="ti ti-user" style="opacity: 0.7;"></i>
</template> </template>
</span> </span>
<span :class="$style.name">{{ role.name }}</span> <span :class="$style.bodyName">{{ role.name }}</span>
<template v-if="detailed"> <template v-if="detailed">
<span v-if="role.target === 'manual'" :class="$style.users">{{ role.usersCount }} users</span> <span v-if="role.target === 'manual'" :class="$style.bodyUsers">{{ role.usersCount }} users</span>
<span v-else-if="role.target === 'conditional'" :class="$style.users">({{ i18n.ts._role.conditional }})</span> <span v-else-if="role.target === 'conditional'" :class="$style.bodyUsers">? users</span>
</template> </template>
</div> </div>
<div :class="$style.description">{{ role.description }}</div> <div :class="$style.bodyDescription">{{ role.description }}</div>
</div>
</MkA> </MkA>
</template> </template>
@ -42,34 +49,44 @@ const props = withDefaults(defineProps<{
<style lang="scss" module> <style lang="scss" module>
.root { .root {
display: block;
padding: 16px 20px;
border-left: solid 6px var(--color);
}
.title {
display: flex; display: flex;
align-items: center;
} }
.icon { .icon {
margin: 0 12px;
}
.body {
display: block;
padding: 16px 20px;
flex: 1;
border-left: solid 6px var(--color);
}
.bodyTitle {
display: flex;
}
.bodyIcon {
margin-right: 8px; margin-right: 8px;
} }
.badge { .bodyBadge {
height: 1.3em; height: 1.3em;
vertical-align: -20%; vertical-align: -20%;
} }
.name { .bodyName {
font-weight: bold; font-weight: bold;
} }
.users { .bodyUsers {
margin-left: auto; margin-left: auto;
opacity: 0.7; opacity: 0.7;
} }
.description { .bodyDescription {
opacity: 0.7; opacity: 0.7;
font-size: 85%; font-size: 85%;
} }

View File

@ -234,6 +234,7 @@ import { host } from '@/config.js';
import { isEnabledUrlPreview } from '@/instance.js'; import { isEnabledUrlPreview } from '@/instance.js';
import { type Keymap } from '@/scripts/hotkey.js'; import { type Keymap } from '@/scripts/hotkey.js';
import { focusPrev, focusNext } from '@/scripts/focus.js'; import { focusPrev, focusNext } from '@/scripts/focus.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -285,14 +286,7 @@ if (noteViewInterruptors.length > 0) {
}); });
} }
const isRenote = ( const isRenote = Misskey.note.isPureRenote(note.value);
note.value.renote != null &&
note.value.reply == null &&
note.value.text == null &&
note.value.cw == null &&
note.value.fileIds && note.value.fileIds.length === 0 &&
note.value.poll == null
);
const rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>(); const menuButton = shallowRef<HTMLElement>();
@ -303,7 +297,7 @@ const reactButton = shallowRef<HTMLElement>();
const quoteButton = shallowRef<HTMLElement>(); const quoteButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>();
const likeButton = shallowRef<HTMLElement>(); const likeButton = shallowRef<HTMLElement>();
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>(); const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const isMyRenote = $i && ($i.id === note.value.userId); const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(defaultStore.state.uncollapseCW); const showContent = ref(defaultStore.state.uncollapseCW);

View File

@ -274,6 +274,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
import { isEnabledUrlPreview } from '@/instance.js'; import { isEnabledUrlPreview } from '@/instance.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
import { type Keymap } from '@/scripts/hotkey.js'; import { type Keymap } from '@/scripts/hotkey.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
@ -307,14 +308,7 @@ if (noteViewInterruptors.length > 0) {
}); });
} }
const isRenote = ( const isRenote = Misskey.note.isPureRenote(note.value);
note.value.renote != null &&
note.value.reply == null &&
note.value.text == null &&
note.value.cw == null &&
note.value.fileIds && note.value.fileIds.length === 0 &&
note.value.poll == null
);
const rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
const noteEl = shallowRef<HTMLElement>(); const noteEl = shallowRef<HTMLElement>();
@ -326,7 +320,7 @@ const reactButton = shallowRef<HTMLElement>();
const quoteButton = shallowRef<HTMLElement>(); const quoteButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>();
const likeButton = shallowRef<HTMLElement>(); const likeButton = shallowRef<HTMLElement>();
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>(); const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const isMyRenote = $i && ($i.id === note.value.userId); const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(defaultStore.state.uncollapseCW); const showContent = ref(defaultStore.state.uncollapseCW);

View File

@ -21,7 +21,7 @@ import { host as hostRaw } from '@/config.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
defineProps<{ defineProps<{
user: Misskey.entities.User; user: Misskey.entities.UserLite;
detail?: boolean; detail?: boolean;
}>(); }>();

View File

@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</div> </div>
<MkInfo v-if="user.username.includes('.')">{{ i18n.ts.isSystemAccount }}</MkInfo> <MkInfo v-if="['instance.actor', 'relay.actor'].includes(user.username)">{{ i18n.ts.isSystemAccount }}</MkInfo>
<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink> <FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>

View File

@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div ref="el" class="hiyeyicy" :class="{ wide: !narrow }"> <div ref="el" class="hiyeyicy" :class="{ wide: !narrow }">
<div v-if="!narrow || currentPage?.route.name == null" class="nav"> <div v-if="!narrow || currentPage?.route.name == null" class="nav">
<MkSpacer :contentMax="700" :marginMin="16"> <MkSpacer :contentMax="700" :marginMin="16">
<div class="lxpfedzu"> <div class="lxpfedzu _gaps">
<div class="banner"> <div class="banner">
<img :src="instance.iconUrl || '/favicon.ico'" alt="" class="icon"/> <img :src="instance.iconUrl || '/favicon.ico'" alt="" class="icon"/>
</div> </div>
@ -62,10 +62,10 @@ const narrow = ref(false);
const view = ref(null); const view = ref(null);
const el = ref<HTMLDivElement | null>(null); const el = ref<HTMLDivElement | null>(null);
const pageProps = ref({}); const pageProps = ref({});
let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail); const noMaintainerInformation = computed(() => isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail));
let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableMcaptcha && !instance.enableTurnstile; const noBotProtection = computed(() => !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile && !instance.enableMcaptcha);
let noEmailServer = !instance.enableEmail; const noEmailServer = computed(() => !instance.enableEmail);
let noInquiryUrl = isEmpty(instance.inquiryUrl); const noInquiryUrl = computed(() => isEmpty(instance.inquiryUrl));
const thereIsUnresolvedAbuseReport = ref(false); const thereIsUnresolvedAbuseReport = ref(false);
const pendingUserApprovals = ref(false); const pendingUserApprovals = ref(false);
const currentPage = computed(() => router.currentRef.value.child); const currentPage = computed(() => router.currentRef.value.child);
@ -250,25 +250,22 @@ const menuDef = computed(() => [{
}], }],
}]); }]);
watch(narrow.value, () => {
if (currentPage.value?.route.name == null && !narrow.value) {
router.push('/admin/overview');
}
});
onMounted(() => { onMounted(() => {
if (el.value != null) {
ro.observe(el.value); ro.observe(el.value);
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD; narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
}
if (currentPage.value?.route.name == null && !narrow.value) { if (currentPage.value?.route.name == null && !narrow.value) {
router.push('/admin/overview'); router.replace('/admin/overview');
} }
}); });
onActivated(() => { onActivated(() => {
if (el.value != null) {
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD; narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
}
if (currentPage.value?.route.name == null && !narrow.value) { if (currentPage.value?.route.name == null && !narrow.value) {
router.push('/admin/overview'); router.replace('/admin/overview');
} }
}); });

View File

@ -21,13 +21,13 @@ SPDX-License-Identifier: AGPL-3.0-only
].includes(log.type), ].includes(log.type),
[$style.logYellow]: [ [$style.logYellow]: [
'markSensitiveDriveFile', 'markSensitiveDriveFile',
'resetPassword' 'resetPassword',
'suspendRemoteInstance',
].includes(log.type), ].includes(log.type),
[$style.logRed]: [ [$style.logRed]: [
'suspend', 'suspend',
'approve', 'approve',
'deleteRole', 'deleteRole',
'suspendRemoteInstance',
'deleteGlobalAnnouncement', 'deleteGlobalAnnouncement',
'deleteUserAnnouncement', 'deleteUserAnnouncement',
'deleteCustomEmoji', 'deleteCustomEmoji',
@ -37,6 +37,10 @@ SPDX-License-Identifier: AGPL-3.0-only
'deleteAvatarDecoration', 'deleteAvatarDecoration',
'deleteSystemWebhook', 'deleteSystemWebhook',
'deleteAbuseReportNotificationRecipient', 'deleteAbuseReportNotificationRecipient',
'deleteAccount',
'deletePage',
'deleteFlash',
'deleteGalleryPost',
].includes(log.type) ].includes(log.type)
}" }"
>{{ i18n.ts._moderationLogTypes[log.type] }}</b> >{{ i18n.ts._moderationLogTypes[log.type] }}</b>
@ -74,6 +78,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="log.type === 'createAbuseReportNotificationRecipient'">: {{ log.info.recipient.name }}</span> <span v-else-if="log.type === 'createAbuseReportNotificationRecipient'">: {{ log.info.recipient.name }}</span>
<span v-else-if="log.type === 'updateAbuseReportNotificationRecipient'">: {{ log.info.before.name }}</span> <span v-else-if="log.type === 'updateAbuseReportNotificationRecipient'">: {{ log.info.before.name }}</span>
<span v-else-if="log.type === 'deleteAbuseReportNotificationRecipient'">: {{ log.info.recipient.name }}</span> <span v-else-if="log.type === 'deleteAbuseReportNotificationRecipient'">: {{ log.info.recipient.name }}</span>
<span v-else-if="log.type === 'deleteAccount'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'deletePage'">: @{{ log.info.pageUserUsername }}</span>
<span v-else-if="log.type === 'deleteFlash'">: @{{ log.info.flashUserUsername }}</span>
<span v-else-if="log.type === 'deleteGalleryPost'">: @{{ log.info.postUserUsername }}</span>
</template> </template>
<template #icon> <template #icon>
<MkAvatar :user="log.user" :class="$style.avatar"/> <MkAvatar :user="log.user" :class="$style.avatar"/>
@ -148,7 +156,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</template> </template>
<template v-else-if="log.type === 'updateRemoteInstanceNote'"> <template v-else-if="log.type === 'updateRemoteInstanceNote'">
<div>{{ i18n.ts.user }}: {{ log.info.userId }}</div>
<div :class="$style.diff"> <div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/> <CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
</div> </div>

View File

@ -34,11 +34,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSelect> </MkSelect>
</div> </div>
<div :class="$style.inputs"> <div :class="$style.inputs">
<MkInput v-model="searchUsername" style="flex: 1;" type="text" :spellcheck="false" @update:modelValue="$refs.users.reload()"> <MkInput v-model="searchUsername" style="flex: 1;" type="text" :spellcheck="false">
<template #prefix>@</template> <template #prefix>@</template>
<template #label>{{ i18n.ts.username }}</template> <template #label>{{ i18n.ts.username }}</template>
</MkInput> </MkInput>
<MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()"> <MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params.origin === 'local'">
<template #prefix>@</template> <template #prefix>@</template>
<template #label>{{ i18n.ts.host }}</template> <template #label>{{ i18n.ts.host }}</template>
</MkInput> </MkInput>

View File

@ -369,7 +369,6 @@ const props = defineProps<{
}>(); }>();
const flash = ref<Misskey.entities.Flash | null>(null); const flash = ref<Misskey.entities.Flash | null>(null);
const visibility = ref<'private' | 'public'>('public');
if (props.id) { if (props.id) {
flash.value = await misskeyApi('flash/show', { flash.value = await misskeyApi('flash/show', {
@ -380,6 +379,7 @@ if (props.id) {
const title = ref(flash.value?.title ?? 'New Play'); const title = ref(flash.value?.title ?? 'New Play');
const summary = ref(flash.value?.summary ?? ''); const summary = ref(flash.value?.summary ?? '');
const permissions = ref(flash.value?.permissions ?? []); const permissions = ref(flash.value?.permissions ?? []);
const visibility = ref<'private' | 'public'>(flash.value?.visibility ?? 'public');
const script = ref(flash.value?.script ?? PRESET_DEFAULT); const script = ref(flash.value?.script ?? PRESET_DEFAULT);
function selectPreset(ev: MouseEvent) { function selectPreset(ev: MouseEvent) {

View File

@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-else v-tooltip="i18n.ts.like" asLike class="button" rounded @click="like()"><i class="ti ti-heart"></i><span v-if="flash?.likedCount && flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton> <MkButton v-else v-tooltip="i18n.ts.like" asLike class="button" rounded @click="like()"><i class="ti ti-heart"></i><span v-if="flash?.likedCount && flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton>
<MkButton v-tooltip="i18n.ts.copyLink" class="button" rounded @click="copyLink"><i class="ti ti-link ti-fw"></i></MkButton> <MkButton v-tooltip="i18n.ts.copyLink" class="button" rounded @click="copyLink"><i class="ti ti-link ti-fw"></i></MkButton>
<MkButton v-tooltip="i18n.ts.share" class="button" rounded @click="share"><i class="ti ti-share ti-fw"></i></MkButton> <MkButton v-tooltip="i18n.ts.share" class="button" rounded @click="share"><i class="ti ti-share ti-fw"></i></MkButton>
<MkButton v-if="$i && $i.id !== flash.user.id" class="button" rounded @mousedown="showMenu"><i class="ti ti-dots ti-fw"></i></MkButton>
</div> </div>
</div> </div>
</div> </div>
@ -61,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onDeactivated, onUnmounted, Ref, ref, watch, shallowRef } from 'vue'; import { computed, onDeactivated, onUnmounted, Ref, ref, watch, shallowRef, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { Interpreter, Parser, values } from '@syuilo/aiscript'; import { Interpreter, Parser, values } from '@syuilo/aiscript';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
@ -79,6 +80,7 @@ import { defaultStore } from '@/store.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.js'; import { isSupportShare } from '@/scripts/navigator.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { MenuItem } from '@/types/menu';
import { pleaseLogin } from '@/scripts/please-login.js'; import { pleaseLogin } from '@/scripts/please-login.js';
const props = defineProps<{ const props = defineProps<{
@ -229,6 +231,53 @@ async function run() {
} }
} }
function reportAbuse() {
if (!flash.value) return;
const pageUrl = `${url}/play/${flash.value.id}`;
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
user: flash.value.user,
initialComment: `Play: ${pageUrl}\n-----\n`,
}, {
closed: () => dispose(),
});
}
function showMenu(ev: MouseEvent) {
if (!flash.value) return;
const menu: MenuItem[] = [
...($i && $i.id !== flash.value.userId ? [
{
icon: 'ti ti-exclamation-circle',
text: i18n.ts.reportAbuse,
action: reportAbuse,
},
...($i.isModerator || $i.isAdmin ? [
{
type: 'divider' as const,
},
{
icon: 'ti ti-trash',
text: i18n.ts.delete,
danger: true,
action: () => os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
}).then(({ canceled }) => {
if (canceled || !flash.value) return;
os.apiWithDialog('flash/delete', { flashId: flash.value.id });
}),
},
] : []),
] : []),
];
os.popupMenu(menu, ev.currentTarget ?? ev.target);
}
function reset() { function reset() {
if (aiscript.value) aiscript.value.abort(); if (aiscript.value) aiscript.value.abort();
started.value = false; started.value = false;

View File

@ -31,6 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></button> <button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></button>
<button v-tooltip="i18n.ts.copyLink" v-click-anime class="_button" @click="copyLink"><i class="ti ti-link ti-fw"></i></button> <button v-tooltip="i18n.ts.copyLink" v-click-anime class="_button" @click="copyLink"><i class="ti ti-link ti-fw"></i></button>
<button v-if="isSupportShare()" v-tooltip="i18n.ts.share" v-click-anime class="_button" @click="share"><i class="ti ti-share ti-fw"></i></button> <button v-if="isSupportShare()" v-tooltip="i18n.ts.share" v-click-anime class="_button" @click="share"><i class="ti ti-share ti-fw"></i></button>
<button v-if="$i && $i.id !== post.user.id" v-click-anime class="_button" @mousedown="showMenu"><i class="ti ti-dots ti-fw"></i></button>
</div> </div>
</div> </div>
<div class="user"> <div class="user">
@ -62,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, ref } from 'vue'; import { computed, watch, ref, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -79,6 +80,7 @@ import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.js'; import { isSupportShare } from '@/scripts/navigator.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
import { MenuItem } from '@/types/menu';
const router = useRouter(); const router = useRouter();
@ -153,13 +155,56 @@ function edit() {
router.push(`/gallery/${post.value.id}/edit`); router.push(`/gallery/${post.value.id}/edit`);
} }
function reportAbuse() {
if (!post.value) return;
const pageUrl = `${url}/gallery/${post.value.id}`;
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
user: post.value.user,
initialComment: `Post: ${pageUrl}\n-----\n`,
}, {
closed: () => dispose(),
});
}
function showMenu(ev: MouseEvent) {
if (!post.value) return;
const menu: MenuItem[] = [
...($i && $i.id !== post.value.userId ? [
{
icon: 'ti ti-exclamation-circle',
text: i18n.ts.reportAbuse,
action: reportAbuse,
},
...($i.isModerator || $i.isAdmin ? [
{
type: 'divider' as const,
},
{
icon: 'ti ti-trash',
text: i18n.ts.delete,
danger: true,
action: () => os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
}).then(({ canceled }) => {
if (canceled || !post.value) return;
os.apiWithDialog('gallery/posts/delete', { postId: post.value.id });
}),
},
] : []),
] : []),
];
os.popupMenu(menu, ev.currentTarget ?? ev.target);
}
watch(() => props.postId, fetchPost, { immediate: true }); watch(() => props.postId, fetchPost, { immediate: true });
const headerActions = computed(() => [{ const headerActions = computed(() => []);
icon: 'ti ti-pencil',
text: i18n.ts.edit,
handler: edit,
}]);
const headerTabs = computed(() => []); const headerTabs = computed(() => []);

View File

@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { i18n } from '@/i18n.js';
export function getPageBlockList() {
return [
{ value: 'section', text: i18n.ts._pages.blocks.section },
{ value: 'text', text: i18n.ts._pages.blocks.text },
{ value: 'image', text: i18n.ts._pages.blocks.image },
{ value: 'note', text: i18n.ts._pages.blocks.note },
];
}

View File

@ -29,6 +29,7 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { deepClone } from '@/scripts/clone.js'; import { deepClone } from '@/scripts/clone.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { getPageBlockList } from '@/pages/page-editor/common.js';
const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue')); const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue'));
@ -53,11 +54,9 @@ watch(children, () => {
deep: true, deep: true,
}); });
const getPageBlockList = inject<(any) => any>('getPageBlockList');
async function rename() { async function rename() {
const { canceled, result: title } = await os.inputText({ const { canceled, result: title } = await os.inputText({
title: 'Enter title', title: i18n.ts._pages.enterSectionTitle,
default: props.modelValue.title, default: props.modelValue.title,
}); });
if (canceled) return; if (canceled) return;

View File

@ -76,6 +76,7 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { mainRouter } from '@/router/main.js'; import { mainRouter } from '@/router/main.js';
import { getPageBlockList } from '@/pages/page-editor/common.js';
const props = defineProps<{ const props = defineProps<{
initPageId?: string; initPageId?: string;
@ -100,7 +101,6 @@ const alignCenter = ref(false);
const hideTitleWhenPinned = ref(false); const hideTitleWhenPinned = ref(false);
provide('readonly', readonly.value); provide('readonly', readonly.value);
provide('getPageBlockList', getPageBlockList);
watch(eyeCatchingImageId, async () => { watch(eyeCatchingImageId, async () => {
if (eyeCatchingImageId.value == null) { if (eyeCatchingImageId.value == null) {
@ -215,15 +215,6 @@ async function add() {
content.value.push({ id, type }); content.value.push({ id, type });
} }
function getPageBlockList() {
return [
{ value: 'section', text: i18n.ts._pages.blocks.section },
{ value: 'text', text: i18n.ts._pages.blocks.text },
{ value: 'image', text: i18n.ts._pages.blocks.image },
{ value: 'note', text: i18n.ts._pages.blocks.note },
];
}
function setEyeCatchingImage(img) { function setEyeCatchingImage(img) {
selectFile(img.currentTarget ?? img.target, null).then(file => { selectFile(img.currentTarget ?? img.target, null).then(file => {
eyeCatchingImageId.value = file.id; eyeCatchingImageId.value = file.id;

View File

@ -62,8 +62,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-else v-tooltip="i18n.ts._pages.like" class="button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton> <MkButton v-else v-tooltip="i18n.ts._pages.like" class="button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton>
</div> </div>
<div :class="$style.other"> <div :class="$style.other">
<MkA v-if="page.userId === $i?.id" v-tooltip="i18n.ts._pages.editThisPage" :to="`/pages/edit/${page.id}`" class="_button" :class="$style.generalActionButton"><i class="ti ti-pencil ti-fw"></i></MkA>
<button v-tooltip="i18n.ts.copyLink" class="_button" :class="$style.generalActionButton" @click="copyLink"><i class="ti ti-link ti-fw"></i></button> <button v-tooltip="i18n.ts.copyLink" class="_button" :class="$style.generalActionButton" @click="copyLink"><i class="ti ti-link ti-fw"></i></button>
<button v-tooltip="i18n.ts.share" class="_button" :class="$style.generalActionButton" @click="share"><i class="ti ti-share ti-fw"></i></button> <button v-tooltip="i18n.ts.share" class="_button" :class="$style.generalActionButton" @click="share"><i class="ti ti-share ti-fw"></i></button>
<button v-if="$i" v-click-anime class="_button" :class="$style.generalActionButton" @mousedown="showMenu"><i class="ti ti-dots ti-fw"></i></button>
</div> </div>
</div> </div>
<div :class="$style.pageUser"> <div :class="$style.pageUser">
@ -78,14 +80,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<div><i class="ti ti-clock"></i> {{ i18n.ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div> <div><i class="ti ti-clock"></i> {{ i18n.ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div>
<div v-if="page.createdAt != page.updatedAt"><i class="ti ti-clock-edit"></i> {{ i18n.ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div> <div v-if="page.createdAt != page.updatedAt"><i class="ti ti-clock-edit"></i> {{ i18n.ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div>
</div> </div>
<div :class="$style.pageLinks">
<MkA v-if="!$i || $i.id !== page.userId" :to="`/@${username}/pages/${pageName}/view-source`" class="link">{{ i18n.ts._pages.viewSource }}</MkA>
<template v-if="$i && $i.id === page.userId">
<MkA :to="`/pages/edit/${page.id}`" class="link">{{ i18n.ts._pages.editThisPage }}</MkA>
<button v-if="$i.pinnedPageId === page.id" class="link _textButton" @click="pin(false)">{{ i18n.ts.unpin }}</button>
<button v-else class="link _textButton" @click="pin(true)">{{ i18n.ts.pin }}</button>
</template>
</div>
</div> </div>
<MkAd :prefer="['horizontal', 'horizontal-big']"/> <MkAd :prefer="['horizontal', 'horizontal-big']"/>
<MkContainer :max-height="300" :foldable="true" class="other"> <MkContainer :max-height="300" :foldable="true" class="other">
@ -104,7 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, ref } from 'vue'; import { computed, watch, ref, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XPage from '@/components/page/page.vue'; import XPage from '@/components/page/page.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
@ -126,6 +120,10 @@ import { isSupportShare } from '@/scripts/navigator.js';
import { instance } from '@/instance.js'; import { instance } from '@/instance.js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { useRouter } from '@/router/supplier.js';
import { MenuItem } from '@/types/menu';
const router = useRouter();
const props = defineProps<{ const props = defineProps<{
pageName: string; pageName: string;
@ -242,6 +240,69 @@ function pin(pin) {
}); });
} }
function reportAbuse() {
if (!page.value) return;
const pageUrl = `${url}/@${props.username}/pages/${props.pageName}`;
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
user: page.value.user,
initialComment: `Page: ${pageUrl}\n-----\n`,
}, {
closed: () => dispose(),
});
}
function showMenu(ev: MouseEvent) {
if (!page.value) return;
const menu: MenuItem[] = [
...($i && $i.id === page.value.userId ? [
{
icon: 'ti ti-code',
text: i18n.ts._pages.viewSource,
action: () => router.push(`/@${props.username}/pages/${props.pageName}/view-source`),
},
...($i.pinnedPageId === page.value.id ? [{
icon: 'ti ti-pinned-off',
text: i18n.ts.unpin,
action: () => pin(false),
}] : [{
icon: 'ti ti-pin',
text: i18n.ts.pin,
action: () => pin(true),
}]),
] : []),
...($i && $i.id !== page.value.userId ? [
{
icon: 'ti ti-exclamation-circle',
text: i18n.ts.reportAbuse,
action: reportAbuse,
},
...($i.isModerator || $i.isAdmin ? [
{
type: 'divider' as const,
},
{
icon: 'ti ti-trash',
text: i18n.ts.delete,
danger: true,
action: () => os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
}).then(({ canceled }) => {
if (canceled || !page.value) return;
os.apiWithDialog('pages/delete', { pageId: page.value.id });
}),
},
] : []),
] : []),
];
os.popupMenu(menu, ev.currentTarget ?? ev.target);
}
watch(() => path.value, fetchPage, { immediate: true }); watch(() => path.value, fetchPage, { immediate: true });
const headerActions = computed(() => []); const headerActions = computed(() => []);

View File

@ -197,9 +197,6 @@ const menuDef = computed(() => [{
}], }],
}]); }]);
watch(narrow, () => {
});
onMounted(() => { onMounted(() => {
ro.observe(el.value); ro.observe(el.value);

View File

@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, provide, shallowRef, ref } from 'vue'; import { computed, watch, provide, shallowRef, ref, onMounted, onActivated } from 'vue';
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue'; import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
import MkTimeline from '@/components/MkTimeline.vue'; import MkTimeline from '@/components/MkTimeline.vue';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
@ -54,15 +54,18 @@ import { deepMerge } from '@/scripts/merge.js';
import { MenuItem } from '@/types/menu.js'; import { MenuItem } from '@/types/menu.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js'; import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
import type { BasicTimelineType } from '@/timelines.js';
provide('shouldOmitHeaderTitle', true); provide('shouldOmitHeaderTitle', true);
const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>(); const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>();
const rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
type TimelinePageSrc = BasicTimelineType | `list:${string}`;
const queue = ref(0); const queue = ref(0);
const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global'); const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global');
const src = computed<'home' | 'local' | 'social' | 'global' | 'bubble' | `list:${string}`>({ const src = computed<TimelinePageSrc>({
get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value), get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value),
set: (x) => saveSrc(x), set: (x) => saveSrc(x),
}); });
@ -201,7 +204,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
os.popupMenu(items, ev.currentTarget ?? ev.target); os.popupMenu(items, ev.currentTarget ?? ev.target);
} }
function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global' | 'bubble' | `list:${string}`): void { function saveSrc(newSrc: TimelinePageSrc): void {
const out = deepMerge({ src: newSrc }, defaultStore.state.tl); const out = deepMerge({ src: newSrc }, defaultStore.state.tl);
if (newSrc.startsWith('userList:')) { if (newSrc.startsWith('userList:')) {
@ -242,6 +245,19 @@ function closeTutorial(): void {
defaultStore.set('timelineTutorials', before); defaultStore.set('timelineTutorials', before);
} }
function switchTlIfNeeded() {
if (isBasicTimeline(src.value) && !isAvailableBasicTimeline(src.value)) {
src.value = availableBasicTimelines()[0];
}
}
onMounted(() => {
switchTlIfNeeded();
});
onActivated(() => {
switchTlIfNeeded();
});
const headerActions = computed(() => { const headerActions = computed(() => {
const tmp = [ const tmp = [
{ {

View File

@ -16,21 +16,57 @@ function containsFocusTrappedElements(el: HTMLElement): boolean {
}); });
} }
function getZIndex(el: HTMLElement): number {
const zIndex = parseInt(window.getComputedStyle(el).zIndex || '0', 10);
if (isNaN(zIndex)) {
return 0;
}
return zIndex;
}
function getHighestZIndexElement(): { el: HTMLElement; zIndex: number; } | null {
let highestZIndexElement: HTMLElement | null = null;
let highestZIndex = -Infinity;
focusTrapElements.forEach((el) => {
const zIndex = getZIndex(el);
if (zIndex > highestZIndex) {
highestZIndex = zIndex;
highestZIndexElement = el;
}
});
return highestZIndexElement == null ? null : {
el: highestZIndexElement,
zIndex: highestZIndex,
};
}
function releaseFocusTrap(el: HTMLElement): void { function releaseFocusTrap(el: HTMLElement): void {
focusTrapElements.delete(el); focusTrapElements.delete(el);
if (el.inert === true) { if (el.inert === true) {
el.inert = false; el.inert = false;
} }
const highestZIndexElement = getHighestZIndexElement();
if (el.parentElement != null && el !== document.body) { if (el.parentElement != null && el !== document.body) {
el.parentElement.childNodes.forEach((siblingNode) => { el.parentElement.childNodes.forEach((siblingNode) => {
const siblingEl = getHTMLElementOrNull(siblingNode); const siblingEl = getHTMLElementOrNull(siblingNode);
if (!siblingEl) return; if (!siblingEl) return;
if (siblingEl !== el && (focusTrapElements.has(siblingEl) || containsFocusTrappedElements(siblingEl) || focusTrapElements.size === 0)) { if (
siblingEl !== el &&
(
highestZIndexElement == null ||
siblingEl === highestZIndexElement.el ||
siblingEl.contains(highestZIndexElement.el)
)
) {
siblingEl.inert = false; siblingEl.inert = false;
} else if ( } else if (
focusTrapElements.size > 0 && highestZIndexElement != null &&
!containsFocusTrappedElements(siblingEl) && siblingEl !== highestZIndexElement.el &&
!focusTrapElements.has(siblingEl) && !siblingEl.contains(highestZIndexElement.el) &&
!ignoreElements.includes(siblingEl.tagName.toLowerCase()) !ignoreElements.includes(siblingEl.tagName.toLowerCase())
) { ) {
siblingEl.inert = true; siblingEl.inert = true;
@ -45,9 +81,29 @@ function releaseFocusTrap(el: HTMLElement): void {
export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEls: boolean, parent: true): void; export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEls: boolean, parent: true): void;
export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEls?: boolean, parent?: false): { release: () => void; }; export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEls?: boolean, parent?: false): { release: () => void; };
export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEls = false, parent = false): { release: () => void; } | void { export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEls = false, parent = false): { release: () => void; } | void {
const highestZIndexElement = getHighestZIndexElement();
const highestZIndex = highestZIndexElement == null ? -Infinity : highestZIndexElement.zIndex;
const zIndex = getZIndex(el);
// If the element has a lower z-index than the highest z-index element, focus trap the highest z-index element instead
// Focus trapping for this element will be done in the release function
if (!parent && zIndex < highestZIndex) {
focusTrapElements.add(el);
if (highestZIndexElement) {
focusTrap(highestZIndexElement.el, hasInteractionWithOtherFocusTrappedEls);
}
return {
release: () => {
releaseFocusTrap(el);
},
};
}
if (el.inert === true) { if (el.inert === true) {
el.inert = false; el.inert = false;
} }
if (el.parentElement != null && el !== document.body) { if (el.parentElement != null && el !== document.body) {
el.parentElement.childNodes.forEach((siblingNode) => { el.parentElement.childNodes.forEach((siblingNode) => {
const siblingEl = getHTMLElementOrNull(siblingNode); const siblingEl = getHTMLElementOrNull(siblingNode);

View File

@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as Misskey from 'misskey-js';
export function getAppearNote(note: Misskey.entities.Note) {
return Misskey.note.isPureRenote(note) ? note.renote : note;
}

View File

@ -20,6 +20,7 @@ import { clipsCache, favoritedChannelsCache } from '@/cache.js';
import { MenuItem } from '@/types/menu.js'; import { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue'; import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { isSupportShare } from '@/scripts/navigator.js'; import { isSupportShare } from '@/scripts/navigator.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
export async function getNoteClipMenu(props: { export async function getNoteClipMenu(props: {
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -34,14 +35,7 @@ export async function getNoteClipMenu(props: {
} }
} }
const isRenote = ( const appearNote = getAppearNote(props.note);
props.note.renote != null &&
props.note.text == null &&
props.note.fileIds.length === 0 &&
props.note.poll == null
);
const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note;
const clips = await clipsCache.fetch(); const clips = await clipsCache.fetch();
const menu: MenuItem[] = [...clips.map(clip => ({ const menu: MenuItem[] = [...clips.map(clip => ({
@ -175,14 +169,7 @@ export function getNoteMenu(props: {
isDeleted: Ref<boolean>; isDeleted: Ref<boolean>;
currentClip?: Misskey.entities.Clip; currentClip?: Misskey.entities.Clip;
}) { }) {
const isRenote = ( const appearNote = getAppearNote(props.note);
props.note.renote != null &&
props.note.text == null &&
props.note.fileIds.length === 0 &&
props.note.poll == null
);
const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note;
const cleanups = [] as (() => void)[]; const cleanups = [] as (() => void)[];
@ -270,6 +257,7 @@ export function getNoteMenu(props: {
} }
async function unclip(): Promise<void> { async function unclip(): Promise<void> {
if (!props.currentClip) return;
os.apiWithDialog('clips/remove-note', { clipId: props.currentClip.id, noteId: appearNote.id }); os.apiWithDialog('clips/remove-note', { clipId: props.currentClip.id, noteId: appearNote.id });
props.isDeleted.value = true; props.isDeleted.value = true;
} }
@ -289,8 +277,8 @@ export function getNoteMenu(props: {
function share(): void { function share(): void {
navigator.share({ navigator.share({
title: i18n.tsx.noteOf({ user: appearNote.user.name }), title: i18n.tsx.noteOf({ user: appearNote.user.name ?? appearNote.user.username }),
text: appearNote.text, text: appearNote.text ?? '',
url: `${url}/notes/${appearNote.id}`, url: `${url}/notes/${appearNote.id}`,
}); });
} }
@ -543,14 +531,7 @@ export function getRenoteMenu(props: {
renoteButton: ShallowRef<HTMLElement | undefined>; renoteButton: ShallowRef<HTMLElement | undefined>;
mock?: boolean; mock?: boolean;
}) { }) {
const isRenote = ( const appearNote = getAppearNote(props.note);
props.note.renote != null &&
props.note.text == null &&
props.note.fileIds.length === 0 &&
props.note.poll == null
);
const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note;
const channelRenoteItems: MenuItem[] = []; const channelRenoteItems: MenuItem[] = [];
const normalRenoteItems: MenuItem[] = []; const normalRenoteItems: MenuItem[] = [];

View File

@ -7,7 +7,7 @@ import * as Misskey from 'misskey-js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
export function isFollowingVisibleForMe(user: Misskey.entities.UserDetailed): boolean { export function isFollowingVisibleForMe(user: Misskey.entities.UserDetailed): boolean {
if ($i && $i.id === user.id) return true; if ($i && ($i.id === user.id || $i.isAdmin || $i.isModerator)) return true;
if (user.followingVisibility === 'private') return false; if (user.followingVisibility === 'private') return false;
if (user.followingVisibility === 'followers' && !user.isFollowing) return false; if (user.followingVisibility === 'followers' && !user.isFollowing) return false;
@ -15,7 +15,7 @@ export function isFollowingVisibleForMe(user: Misskey.entities.UserDetailed): bo
return true; return true;
} }
export function isFollowersVisibleForMe(user: Misskey.entities.UserDetailed): boolean { export function isFollowersVisibleForMe(user: Misskey.entities.UserDetailed): boolean {
if ($i && $i.id === user.id) return true; if ($i && ($i.id === user.id || $i.isAdmin || $i.isModerator)) return true;
if (user.followersVisibility === 'private') return false; if (user.followersVisibility === 'private') return false;
if (user.followersVisibility === 'followers' && !user.isFollowing) return false; if (user.followersVisibility === 'followers' && !user.isFollowing) return false;

View File

@ -1203,6 +1203,7 @@ declare namespace entities {
export { export {
ID, ID,
DateString, DateString,
PureRenote,
PageEvent, PageEvent,
ModerationLog, ModerationLog,
ServerStats, ServerStats,
@ -2329,6 +2330,9 @@ type ISigninHistoryRequest = operations['i___signin-history']['requestBody']['co
// @public (undocumented) // @public (undocumented)
type ISigninHistoryResponse = operations['i___signin-history']['responses']['200']['content']['application/json']; type ISigninHistoryResponse = operations['i___signin-history']['responses']['200']['content']['application/json'];
// @public (undocumented)
function isPureRenote(note: Note): note is PureRenote;
// @public (undocumented) // @public (undocumented)
type IUnpinRequest = operations['i___unpin']['requestBody']['content']['application/json']; type IUnpinRequest = operations['i___unpin']['requestBody']['content']['application/json'];
@ -2515,6 +2519,9 @@ type ModerationLog = {
} | { } | {
type: 'unsetUserAvatar'; type: 'unsetUserAvatar';
info: ModerationLogPayloads['unsetUserAvatar']; info: ModerationLogPayloads['unsetUserAvatar'];
} | {
type: 'unsetUserBanner';
info: ModerationLogPayloads['unsetUserBanner'];
} | { } | {
type: 'createSystemWebhook'; type: 'createSystemWebhook';
info: ModerationLogPayloads['createSystemWebhook']; info: ModerationLogPayloads['createSystemWebhook'];
@ -2533,10 +2540,22 @@ type ModerationLog = {
} | { } | {
type: 'deleteAbuseReportNotificationRecipient'; type: 'deleteAbuseReportNotificationRecipient';
info: ModerationLogPayloads['deleteAbuseReportNotificationRecipient']; info: ModerationLogPayloads['deleteAbuseReportNotificationRecipient'];
} | {
type: 'deleteAccount';
info: ModerationLogPayloads['deleteAccount'];
} | {
type: 'deletePage';
info: ModerationLogPayloads['deletePage'];
} | {
type: 'deleteFlash';
info: ModerationLogPayloads['deleteFlash'];
} | {
type: 'deleteGalleryPost';
info: ModerationLogPayloads['deleteGalleryPost'];
}); });
// @public (undocumented) // @public (undocumented)
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient"]; export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient", "deleteAccount", "deletePage", "deleteFlash", "deleteGalleryPost"];
// @public (undocumented) // @public (undocumented)
type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json']; type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
@ -2565,6 +2584,13 @@ type MyAppsResponse = operations['my___apps']['responses']['200']['content']['ap
// @public (undocumented) // @public (undocumented)
type Note = components['schemas']['Note']; type Note = components['schemas']['Note'];
declare namespace note {
export {
isPureRenote
}
}
export { note }
// @public (undocumented) // @public (undocumented)
type NoteFavorite = components['schemas']['NoteFavorite']; type NoteFavorite = components['schemas']['NoteFavorite'];
@ -2826,6 +2852,15 @@ type PinnedUsersResponse = operations['pinned-users']['responses']['200']['conte
// @public (undocumented) // @public (undocumented)
type PromoReadRequest = operations['promo___read']['requestBody']['content']['application/json']; type PromoReadRequest = operations['promo___read']['requestBody']['content']['application/json'];
// Warning: (ae-forgotten-export) The symbol "AllNullRecord" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "NonNullableRecord" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
type PureRenote = Omit<Note, 'renote' | 'renoteId' | 'reply' | 'replyId' | 'text' | 'cw' | 'files' | 'fileIds' | 'poll'> & AllNullRecord<Pick<Note, 'reply' | 'replyId' | 'text' | 'cw' | 'poll'>> & {
files: [];
fileIds: [];
} & NonNullableRecord<Pick<Note, 'renote' | 'renoteId'>>;
// @public (undocumented) // @public (undocumented)
type QueueCount = components['schemas']['QueueCount']; type QueueCount = components['schemas']['QueueCount'];
@ -2905,6 +2940,9 @@ type ReversiShowGameResponse = operations['reversi___show-game']['responses']['2
// @public (undocumented) // @public (undocumented)
type ReversiSurrenderRequest = operations['reversi___surrender']['requestBody']['content']['application/json']; type ReversiSurrenderRequest = operations['reversi___surrender']['requestBody']['content']['application/json'];
// @public (undocumented)
export const reversiUpdateKeys: ["map", "bw", "isLlotheo", "canPutEverywhere", "loopedBoard", "timeLimitForEachTurn"];
// @public (undocumented) // @public (undocumented)
type ReversiVerifyRequest = operations['reversi___verify']['requestBody']['content']['application/json']; type ReversiVerifyRequest = operations['reversi___verify']['requestBody']['content']['application/json'];
@ -3305,7 +3343,7 @@ type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['
// Warnings were encountered during analysis: // Warnings were encountered during analysis:
// //
// src/entities.ts:35:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/entities.ts:49:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
// src/streaming.types.ts:236:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:236:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
// src/streaming.types.ts:246:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:246:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts

View File

@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "misskey-js", "name": "misskey-js",
"version": "2024.7.0", "version": "2024.8.0",
"description": "Misskey SDK for JavaScript", "description": "Misskey SDK for JavaScript",
"license": "MIT", "license": "MIT",
"main": "./built/index.js", "main": "./built/index.js",

View File

@ -4816,6 +4816,8 @@ export type components = {
title: string; title: string;
summary: string; summary: string;
script: string; script: string;
/** @enum {string} */
visibility: 'private' | 'public';
likedCount: number | null; likedCount: number | null;
isLiked?: boolean; isLiked?: boolean;
}; };

View File

@ -1,11 +1,19 @@
import type { operations } from './autogen/types.js'; import type { operations } from './autogen/types.js';
import type { import type {
AbuseReportNotificationRecipient, Ad, AbuseReportNotificationRecipient,
Ad,
Announcement, Announcement,
EmojiDetailed, InviteCode, EmojiDetailed,
Flash,
GalleryPost,
InviteCode,
MetaDetailed, MetaDetailed,
Note, Note,
Role, SystemWebhook, UserLite, Page,
Role,
ReversiGameDetailed,
SystemWebhook,
UserLite,
} from './autogen/models.js'; } from './autogen/models.js';
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned', 'edited'] as const; export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned', 'edited'] as const;
@ -155,6 +163,10 @@ export const moderationLogTypes = [
'createAbuseReportNotificationRecipient', 'createAbuseReportNotificationRecipient',
'updateAbuseReportNotificationRecipient', 'updateAbuseReportNotificationRecipient',
'deleteAbuseReportNotificationRecipient', 'deleteAbuseReportNotificationRecipient',
'deleteAccount',
'deletePage',
'deleteFlash',
'deleteGalleryPost',
] as const; ] as const;
// See: packages/backend/src/core/ReversiService.ts@L410 // See: packages/backend/src/core/ReversiService.ts@L410
@ -165,7 +177,7 @@ export const reversiUpdateKeys = [
'canPutEverywhere', 'canPutEverywhere',
'loopedBoard', 'loopedBoard',
'timeLimitForEachTurn', 'timeLimitForEachTurn',
] as const; ] as const satisfies (keyof ReversiGameDetailed)[];
export type ReversiUpdateKey = typeof reversiUpdateKeys[number]; export type ReversiUpdateKey = typeof reversiUpdateKeys[number];
@ -398,4 +410,27 @@ export type ModerationLogPayloads = {
recipientId: string; recipientId: string;
recipient: AbuseReportNotificationRecipient; recipient: AbuseReportNotificationRecipient;
}; };
deleteAccount: {
userId: string;
userUsername: string;
userHost: string | null;
};
deletePage: {
pageId: string;
pageUserId: string;
pageUserUsername: string;
page: Page;
};
deleteFlash: {
flashId: string;
flashUserId: string;
flashUserUsername: string;
flash: Flash;
};
deleteGalleryPost: {
postId: string;
postUserId: string;
postUserUsername: string;
post: GalleryPost;
};
}; };

View File

@ -3,6 +3,7 @@ import {
Announcement, Announcement,
EmojiDetailed, EmojiDetailed,
MeDetailed, MeDetailed,
Note,
Page, Page,
Role, Role,
RolePolicies, RolePolicies,
@ -16,6 +17,19 @@ export * from './autogen/models.js';
export type ID = string; export type ID = string;
export type DateString = string; export type DateString = string;
type NonNullableRecord<T> = {
[P in keyof T]-?: NonNullable<T[P]>;
};
type AllNullRecord<T> = {
[P in keyof T]: null;
};
export type PureRenote =
Omit<Note, 'renote' | 'renoteId' | 'reply' | 'replyId' | 'text' | 'cw' | 'files' | 'fileIds' | 'poll'>
& AllNullRecord<Pick<Note, 'reply' | 'replyId' | 'text' | 'cw' | 'poll'>>
& { files: []; fileIds: []; }
& NonNullableRecord<Pick<Note, 'renote' | 'renoteId'>>;
export type PageEvent = { export type PageEvent = {
pageId: Page['id']; pageId: Page['id'];
event: string; event: string;
@ -144,6 +158,9 @@ export type ModerationLog = {
} | { } | {
type: 'unsetUserAvatar'; type: 'unsetUserAvatar';
info: ModerationLogPayloads['unsetUserAvatar']; info: ModerationLogPayloads['unsetUserAvatar'];
} | {
type: 'unsetUserBanner';
info: ModerationLogPayloads['unsetUserBanner'];
} | { } | {
type: 'createSystemWebhook'; type: 'createSystemWebhook';
info: ModerationLogPayloads['createSystemWebhook']; info: ModerationLogPayloads['createSystemWebhook'];
@ -162,6 +179,18 @@ export type ModerationLog = {
} | { } | {
type: 'deleteAbuseReportNotificationRecipient'; type: 'deleteAbuseReportNotificationRecipient';
info: ModerationLogPayloads['deleteAbuseReportNotificationRecipient']; info: ModerationLogPayloads['deleteAbuseReportNotificationRecipient'];
} | {
type: 'deleteAccount';
info: ModerationLogPayloads['deleteAccount'];
} | {
type: 'deletePage';
info: ModerationLogPayloads['deletePage'];
} | {
type: 'deleteFlash';
info: ModerationLogPayloads['deleteFlash'];
} | {
type: 'deleteGalleryPost';
info: ModerationLogPayloads['deleteGalleryPost'];
}); });
export type ServerStats = { export type ServerStats = {

View File

@ -22,6 +22,7 @@ export const mutedNoteReasons = consts.mutedNoteReasons;
export const followingVisibilities = consts.followingVisibilities; export const followingVisibilities = consts.followingVisibilities;
export const followersVisibilities = consts.followersVisibilities; export const followersVisibilities = consts.followersVisibilities;
export const moderationLogTypes = consts.moderationLogTypes; export const moderationLogTypes = consts.moderationLogTypes;
export const reversiUpdateKeys = consts.reversiUpdateKeys;
// api extractor not supported yet // api extractor not supported yet
//export * as api from './api.js'; //export * as api from './api.js';
@ -29,4 +30,5 @@ export const moderationLogTypes = consts.moderationLogTypes;
import * as api from './api.js'; import * as api from './api.js';
import * as entities from './entities.js'; import * as entities from './entities.js';
import * as acct from './acct.js'; import * as acct from './acct.js';
export { api, entities, acct }; import * as note from './note.js';
export { api, entities, acct, note };

View File

@ -0,0 +1,12 @@
import type { Note, PureRenote } from './entities.js';
export function isPureRenote(note: Note): note is PureRenote {
return (
note.renote != null &&
note.reply == null &&
note.text == null &&
note.cw == null &&
(note.fileIds == null || note.fileIds.length === 0) &&
note.poll == null
);
}

View File

@ -11,7 +11,7 @@ describe('API', () => {
expectType<Misskey.entities.MetaResponse>(res); expectType<Misskey.entities.MetaResponse>(res);
}); });
test('conditional respose type (meta)', async () => { test('conditional response type (meta)', async () => {
const cli = new Misskey.api.APIClient({ const cli = new Misskey.api.APIClient({
origin: 'https://misskey.test', origin: 'https://misskey.test',
credential: 'TOKEN' credential: 'TOKEN'
@ -30,7 +30,7 @@ describe('API', () => {
expectType<Misskey.entities.MetaResponse>(res4); expectType<Misskey.entities.MetaResponse>(res4);
}); });
test('conditional respose type (users/show)', async () => { test('conditional response type (users/show)', async () => {
const cli = new Misskey.api.APIClient({ const cli = new Misskey.api.APIClient({
origin: 'https://misskey.test', origin: 'https://misskey.test',
credential: 'TOKEN' credential: 'TOKEN'

View File

@ -47,8 +47,8 @@ importers:
version: 5.5.4 version: 5.5.4
devDependencies: devDependencies:
'@misskey-dev/eslint-plugin': '@misskey-dev/eslint-plugin':
specifier: 2.0.2 specifier: 2.0.3
version: 2.0.2(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0))(eslint@9.8.0)(globals@15.8.0) version: 2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0))(eslint@9.8.0)(globals@15.8.0)
'@types/node': '@types/node':
specifier: 20.14.12 specifier: 20.14.12
version: 20.14.12 version: 20.14.12
@ -257,8 +257,8 @@ importers:
specifier: 14.4.2 specifier: 14.4.2
version: 14.4.2 version: 14.4.2
happy-dom: happy-dom:
specifier: 10.0.3 specifier: 15.6.1
version: 10.0.3 version: 15.6.1
hpagent: hpagent:
specifier: 1.2.0 specifier: 1.2.0
version: 1.2.0 version: 1.2.0
@ -737,10 +737,10 @@ importers:
version: 15.1.1 version: 15.1.1
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: 5.1.0 specifier: 5.1.0
version: 5.1.0(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) version: 5.1.0(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))
'@vue/compiler-sfc': '@vue/compiler-sfc':
specifier: 3.4.34 specifier: 3.4.37
version: 3.4.34 version: 3.4.37
aiscript-vscode: aiscript-vscode:
specifier: github:aiscript-dev/aiscript-vscode#v0.1.11 specifier: github:aiscript-dev/aiscript-vscode#v0.1.11
version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9 version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9
@ -866,16 +866,16 @@ importers:
version: 10.0.0 version: 10.0.0
v-code-diff: v-code-diff:
specifier: 1.12.0 specifier: 1.12.0
version: 1.12.0(vue@3.4.34(typescript@5.5.4)) version: 1.12.0(vue@3.4.37(typescript@5.5.4))
vite: vite:
specifier: 5.3.5 specifier: 5.3.5
version: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) version: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
vue: vue:
specifier: 3.4.34 specifier: 3.4.37
version: 3.4.34(typescript@5.5.4) version: 3.4.37(typescript@5.5.4)
vuedraggable: vuedraggable:
specifier: next specifier: next
version: 4.1.0(vue@3.4.34(typescript@5.5.4)) version: 4.1.0(vue@3.4.37(typescript@5.5.4))
devDependencies: devDependencies:
'@misskey-dev/summaly': '@misskey-dev/summaly':
specifier: 5.1.0 specifier: 5.1.0
@ -930,13 +930,13 @@ importers:
version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
'@storybook/vue3': '@storybook/vue3':
specifier: 8.2.6 specifier: 8.2.6
version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.4.34(typescript@5.5.4)) version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.4.37(typescript@5.5.4))
'@storybook/vue3-vite': '@storybook/vue3-vite':
specifier: 8.1.11 specifier: 8.1.11
version: 8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) version: 8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))
'@testing-library/vue': '@testing-library/vue':
specifier: 8.1.0 specifier: 8.1.0
version: 8.1.0(@vue/compiler-sfc@3.4.34)(@vue/server-renderer@3.4.34(vue@3.4.34(typescript@5.5.4)))(vue@3.4.34(typescript@5.5.4)) version: 8.1.0(@vue/compiler-sfc@3.4.37)(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))
'@types/escape-regexp': '@types/escape-regexp':
specifier: 0.0.3 specifier: 0.0.3
version: 0.0.3 version: 0.0.3
@ -983,8 +983,8 @@ importers:
specifier: 1.6.0 specifier: 1.6.0
version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)) version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))
'@vue/runtime-core': '@vue/runtime-core':
specifier: 3.4.34 specifier: 3.4.37
version: 3.4.34 version: 3.4.37
acorn: acorn:
specifier: 8.12.1 specifier: 8.12.1
version: 8.12.1 version: 8.12.1
@ -1696,18 +1696,10 @@ packages:
resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.23.4':
resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.24.7': '@babel/helper-string-parser@7.24.7':
resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.22.20':
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.24.7': '@babel/helper-validator-identifier@7.24.7':
resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -1740,16 +1732,6 @@ packages:
resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/parser@7.23.9':
resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/parser@7.24.5':
resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/parser@7.24.7': '@babel/parser@7.24.7':
resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@ -2260,14 +2242,6 @@ packages:
resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/types@7.23.5':
resolution: {integrity: sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==}
engines: {node: '>=6.9.0'}
'@babel/types@7.24.0':
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
engines: {node: '>=6.9.0'}
'@babel/types@7.24.7': '@babel/types@7.24.7':
resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -3382,8 +3356,8 @@ packages:
'@misskey-dev/browser-image-resizer@2024.1.0': '@misskey-dev/browser-image-resizer@2024.1.0':
resolution: {integrity: sha512-4EnO0zLW5NDtng3Gaz5MuT761uiuoOuplwX18wBqgj8w56LTU5BjLn/vbHwDIIe0j2gwqDYhMb7bDjmr1/Fomg==} resolution: {integrity: sha512-4EnO0zLW5NDtng3Gaz5MuT761uiuoOuplwX18wBqgj8w56LTU5BjLn/vbHwDIIe0j2gwqDYhMb7bDjmr1/Fomg==}
'@misskey-dev/eslint-plugin@2.0.2': '@misskey-dev/eslint-plugin@2.0.3':
resolution: {integrity: sha512-bnTqxCSP0CIN0xSpIGib13bz+K8/3e4h8OlQjuCPlhZF7oFwtn339EZM8yJkHg6gdfciV8KOr3gzlLyG3jiVEQ==} resolution: {integrity: sha512-Gd7chezh53Gq4BZ+Tw/uRi4t5DocXR+vTFuTSRpwrZjbyTk6tWMHLV0EtaGY/6GT+Q6WH3UMJYExsFyHFK+JPw==}
peerDependencies: peerDependencies:
'@eslint/compat': '>= 1' '@eslint/compat': '>= 1'
'@typescript-eslint/eslint-plugin': '>= 7' '@typescript-eslint/eslint-plugin': '>= 7'
@ -5538,29 +5512,26 @@ packages:
'@volar/typescript@2.4.0-alpha.18': '@volar/typescript@2.4.0-alpha.18':
resolution: {integrity: sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==} resolution: {integrity: sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==}
'@vue/compiler-core@3.4.29':
resolution: {integrity: sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==}
'@vue/compiler-core@3.4.31': '@vue/compiler-core@3.4.31':
resolution: {integrity: sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==} resolution: {integrity: sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==}
'@vue/compiler-core@3.4.34': '@vue/compiler-core@3.4.34':
resolution: {integrity: sha512-Z0izUf32+wAnQewjHu+pQf1yw00EGOmevl1kE+ljjjMe7oEfpQ+BI3/JNK7yMB4IrUsqLDmPecUrpj3mCP+yJQ==} resolution: {integrity: sha512-Z0izUf32+wAnQewjHu+pQf1yw00EGOmevl1kE+ljjjMe7oEfpQ+BI3/JNK7yMB4IrUsqLDmPecUrpj3mCP+yJQ==}
'@vue/compiler-dom@3.4.29': '@vue/compiler-core@3.4.37':
resolution: {integrity: sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==} resolution: {integrity: sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==}
'@vue/compiler-dom@3.4.31':
resolution: {integrity: sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==}
'@vue/compiler-dom@3.4.34': '@vue/compiler-dom@3.4.34':
resolution: {integrity: sha512-3PUOTS1h5cskdOJMExCu2TInXuM0j60DRPpSCJDqOCupCfUZCJoyQmKtRmA8EgDNZ5kcEE7vketamRZfrEuVDw==} resolution: {integrity: sha512-3PUOTS1h5cskdOJMExCu2TInXuM0j60DRPpSCJDqOCupCfUZCJoyQmKtRmA8EgDNZ5kcEE7vketamRZfrEuVDw==}
'@vue/compiler-sfc@3.4.34': '@vue/compiler-dom@3.4.37':
resolution: {integrity: sha512-x6lm0UrM03jjDXTPZgD9Ad8bIVD1ifWNit2EaWQIZB5CULr46+FbLQ5RpK7AXtDHGjx9rmvC7QRCTjsiGkAwRw==} resolution: {integrity: sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==}
'@vue/compiler-ssr@3.4.34': '@vue/compiler-sfc@3.4.37':
resolution: {integrity: sha512-8TDBcLaTrFm5rnF+Qm4BlliaopJgqJ28Nsrc80qazynm5aJO+Emu7y0RWw34L8dNnTRdcVBpWzJxhGYzsoVu4g==} resolution: {integrity: sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==}
'@vue/compiler-ssr@3.4.37':
resolution: {integrity: sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==}
'@vue/compiler-vue2@2.7.16': '@vue/compiler-vue2@2.7.16':
resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
@ -5584,22 +5555,19 @@ packages:
typescript: typescript:
optional: true optional: true
'@vue/reactivity@3.4.34': '@vue/reactivity@3.4.37':
resolution: {integrity: sha512-ua+Lo+wBRlBEX9TtgPOShE2JwIO7p6BTZ7t1KZVPoaBRfqbC7N3c8Mpzicx173fXxx5VXeU6ykiHo7WgLzJQDA==} resolution: {integrity: sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==}
'@vue/runtime-core@3.4.34': '@vue/runtime-core@3.4.37':
resolution: {integrity: sha512-PXhkiRPwcPGJ1BnyBZFI96GfInCVskd0HPNIAZn7i3YOmLbtbTZpB7/kDTwC1W7IqdGPkTVC63IS7J2nZs4Ebg==} resolution: {integrity: sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==}
'@vue/runtime-dom@3.4.34': '@vue/runtime-dom@3.4.37':
resolution: {integrity: sha512-dXqIe+RqFAK2Euak4UsvbIupalrhc67OuQKpD7HJ3W2fv8jlqvI7szfBCsAEcE8o/wyNpkloxB6J8viuF/E3gw==} resolution: {integrity: sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==}
'@vue/server-renderer@3.4.34': '@vue/server-renderer@3.4.37':
resolution: {integrity: sha512-GeyEUfMVRZMD/mZcNONEqg7MiU10QQ1DB3O/Qr6+8uXpbwdlmVgQ5Qs1/ZUAFX1X2UUtqMoGrDRbxdWfOJFT7Q==} resolution: {integrity: sha512-jZ5FAHDR2KBq2FsRUJW6GKDOAG9lUTX8aBEGq4Vf6B/35I9fPce66BornuwmqmKgfiSlecwuOb6oeoamYMohkg==}
peerDependencies: peerDependencies:
vue: 3.4.34 vue: 3.4.37
'@vue/shared@3.4.29':
resolution: {integrity: sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==}
'@vue/shared@3.4.31': '@vue/shared@3.4.31':
resolution: {integrity: sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==} resolution: {integrity: sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==}
@ -5607,6 +5575,9 @@ packages:
'@vue/shared@3.4.34': '@vue/shared@3.4.34':
resolution: {integrity: sha512-x5LmiRLpRsd9KTjAB8MPKf0CDPMcuItjP0gbNqFCIgL1I8iYp4zglhj9w9FPCdIbHG2M91RVeIbArFfFTz9I3A==} resolution: {integrity: sha512-x5LmiRLpRsd9KTjAB8MPKf0CDPMcuItjP0gbNqFCIgL1I8iYp4zglhj9w9FPCdIbHG2M91RVeIbArFfFTz9I3A==}
'@vue/shared@3.4.37':
resolution: {integrity: sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==}
'@vue/test-utils@2.4.1': '@vue/test-utils@2.4.1':
resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==} resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==}
peerDependencies: peerDependencies:
@ -6940,6 +6911,10 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
entities@5.0.0:
resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==}
engines: {node: '>=0.12'}
env-paths@2.2.1: env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -7696,6 +7671,10 @@ packages:
happy-dom@10.0.3: happy-dom@10.0.3:
resolution: {integrity: sha512-WkCP+Z5fX6U5PY+yHP3ElV5D9PoxRAHRWPFq3pG9rg/6Hjf5ak7dozAgSCywsTRUq2qfa8vV8OQvUy5pRXy8EQ==} resolution: {integrity: sha512-WkCP+Z5fX6U5PY+yHP3ElV5D9PoxRAHRWPFq3pG9rg/6Hjf5ak7dozAgSCywsTRUq2qfa8vV8OQvUy5pRXy8EQ==}
happy-dom@15.6.1:
resolution: {integrity: sha512-dsMHLsJHZYhXeExP47B2siAfKNVxptlwFss3/bq/9sG3iBt0P2WYFBq68JgMR5vB5gsN2Ev0feTTPD/+rosUNQ==}
engines: {node: '>=18.0.0'}
hard-rejection@2.1.0: hard-rejection@2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -10706,10 +10685,6 @@ packages:
sortablejs@1.14.0: sortablejs@1.14.0:
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
source-map-js@1.2.0: source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -11658,6 +11633,9 @@ packages:
vue-component-type-helpers@2.0.29: vue-component-type-helpers@2.0.29:
resolution: {integrity: sha512-58i+ZhUAUpwQ+9h5Hck0D+jr1qbYl4voRt5KffBx8qzELViQ4XdT/Tuo+mzq8u63teAG8K0lLaOiL5ofqW38rg==} resolution: {integrity: sha512-58i+ZhUAUpwQ+9h5Hck0D+jr1qbYl4voRt5KffBx8qzELViQ4XdT/Tuo+mzq8u63teAG8K0lLaOiL5ofqW38rg==}
vue-component-type-helpers@2.1.2:
resolution: {integrity: sha512-URuxnrOhO9lUG4LOAapGWBaa/WOLDzzyAbL+uKZqT7RS+PFy0cdXI2mUSh7GaMts6vtHaeVbGk7trd0FPJi65Q==}
vue-demi@0.14.7: vue-demi@0.14.7:
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -11700,8 +11678,8 @@ packages:
peerDependencies: peerDependencies:
typescript: '>=5.0.0' typescript: '>=5.0.0'
vue@3.4.34: vue@3.4.37:
resolution: {integrity: sha512-VZze05HWlA3ItreQ/ka7Sx7PoD0/3St8FEiSlSTVgb6l4hL+RjtP2/8g5WQBzZgyf8WG2f+g1bXzC7zggLhAJA==} resolution: {integrity: sha512-3vXvNfkKTBsSJ7JP+LyR7GBuwQuckbWvuwAid3xbqK9ppsKt/DUvfqgZ48fgOLEfpy1IacL5f8QhUVl77RaI7A==}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
peerDependenciesMeta: peerDependenciesMeta:
@ -12520,10 +12498,10 @@ snapshots:
'@babel/helper-compilation-targets': 7.22.15 '@babel/helper-compilation-targets': 7.22.15
'@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.5) '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.5)
'@babel/helpers': 7.23.5 '@babel/helpers': 7.23.5
'@babel/parser': 7.23.9 '@babel/parser': 7.24.7
'@babel/template': 7.22.15 '@babel/template': 7.22.15
'@babel/traverse': 7.23.5 '@babel/traverse': 7.23.5
'@babel/types': 7.23.5 '@babel/types': 7.24.7
convert-source-map: 2.0.0 convert-source-map: 2.0.0
debug: 4.3.5(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
gensync: 1.0.0-beta.2 gensync: 1.0.0-beta.2
@ -12652,7 +12630,7 @@ snapshots:
'@babel/helper-module-imports@7.22.15': '@babel/helper-module-imports@7.22.15':
dependencies: dependencies:
'@babel/types': 7.24.0 '@babel/types': 7.24.7
'@babel/helper-module-imports@7.24.7': '@babel/helper-module-imports@7.24.7':
dependencies: dependencies:
@ -12668,7 +12646,7 @@ snapshots:
'@babel/helper-module-imports': 7.22.15 '@babel/helper-module-imports': 7.22.15
'@babel/helper-simple-access': 7.22.5 '@babel/helper-simple-access': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-split-export-declaration': 7.22.6
'@babel/helper-validator-identifier': 7.22.20 '@babel/helper-validator-identifier': 7.24.7
'@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)':
dependencies: dependencies:
@ -12709,7 +12687,7 @@ snapshots:
'@babel/helper-simple-access@7.22.5': '@babel/helper-simple-access@7.22.5':
dependencies: dependencies:
'@babel/types': 7.24.0 '@babel/types': 7.24.7
'@babel/helper-simple-access@7.24.7': '@babel/helper-simple-access@7.24.7':
dependencies: dependencies:
@ -12727,18 +12705,14 @@ snapshots:
'@babel/helper-split-export-declaration@7.22.6': '@babel/helper-split-export-declaration@7.22.6':
dependencies: dependencies:
'@babel/types': 7.24.0 '@babel/types': 7.24.7
'@babel/helper-split-export-declaration@7.24.7': '@babel/helper-split-export-declaration@7.24.7':
dependencies: dependencies:
'@babel/types': 7.24.7 '@babel/types': 7.24.7
'@babel/helper-string-parser@7.23.4': {}
'@babel/helper-string-parser@7.24.7': {} '@babel/helper-string-parser@7.24.7': {}
'@babel/helper-validator-identifier@7.22.20': {}
'@babel/helper-validator-identifier@7.24.7': {} '@babel/helper-validator-identifier@7.24.7': {}
'@babel/helper-validator-option@7.23.5': {} '@babel/helper-validator-option@7.23.5': {}
@ -12758,7 +12732,7 @@ snapshots:
dependencies: dependencies:
'@babel/template': 7.22.15 '@babel/template': 7.22.15
'@babel/traverse': 7.23.5 '@babel/traverse': 7.23.5
'@babel/types': 7.23.5 '@babel/types': 7.24.7
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -12769,7 +12743,7 @@ snapshots:
'@babel/highlight@7.23.4': '@babel/highlight@7.23.4':
dependencies: dependencies:
'@babel/helper-validator-identifier': 7.22.20 '@babel/helper-validator-identifier': 7.24.7
chalk: 2.4.2 chalk: 2.4.2
js-tokens: 4.0.0 js-tokens: 4.0.0
@ -12780,14 +12754,6 @@ snapshots:
js-tokens: 4.0.0 js-tokens: 4.0.0
picocolors: 1.0.0 picocolors: 1.0.0
'@babel/parser@7.23.9':
dependencies:
'@babel/types': 7.24.0
'@babel/parser@7.24.5':
dependencies:
'@babel/types': 7.24.0
'@babel/parser@7.24.7': '@babel/parser@7.24.7':
dependencies: dependencies:
'@babel/types': 7.24.7 '@babel/types': 7.24.7
@ -13473,8 +13439,8 @@ snapshots:
'@babel/template@7.22.15': '@babel/template@7.22.15':
dependencies: dependencies:
'@babel/code-frame': 7.23.5 '@babel/code-frame': 7.23.5
'@babel/parser': 7.23.9 '@babel/parser': 7.24.7
'@babel/types': 7.24.0 '@babel/types': 7.24.7
'@babel/template@7.24.0': '@babel/template@7.24.0':
dependencies: dependencies:
@ -13496,8 +13462,8 @@ snapshots:
'@babel/helper-function-name': 7.23.0 '@babel/helper-function-name': 7.23.0
'@babel/helper-hoist-variables': 7.22.5 '@babel/helper-hoist-variables': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-split-export-declaration': 7.22.6
'@babel/parser': 7.23.9 '@babel/parser': 7.24.7
'@babel/types': 7.23.5 '@babel/types': 7.24.7
debug: 4.3.5(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
@ -13518,18 +13484,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@babel/types@7.23.5':
dependencies:
'@babel/helper-string-parser': 7.23.4
'@babel/helper-validator-identifier': 7.22.20
to-fast-properties: 2.0.0
'@babel/types@7.24.0':
dependencies:
'@babel/helper-string-parser': 7.23.4
'@babel/helper-validator-identifier': 7.22.20
to-fast-properties: 2.0.0
'@babel/types@7.24.7': '@babel/types@7.24.7':
dependencies: dependencies:
'@babel/helper-string-parser': 7.24.7 '@babel/helper-string-parser': 7.24.7
@ -14585,7 +14539,7 @@ snapshots:
'@misskey-dev/browser-image-resizer@2024.1.0': {} '@misskey-dev/browser-image-resizer@2024.1.0': {}
'@misskey-dev/eslint-plugin@2.0.2(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0))(eslint@9.8.0)(globals@15.8.0)': '@misskey-dev/eslint-plugin@2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0))(eslint@9.8.0)(globals@15.8.0)':
dependencies: dependencies:
'@eslint/compat': 1.1.1 '@eslint/compat': 1.1.1
'@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)
@ -16292,18 +16246,18 @@ snapshots:
dependencies: dependencies:
storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
'@storybook/vue3-vite@8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4))': '@storybook/vue3-vite@8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))':
dependencies: dependencies:
'@storybook/builder-vite': 8.1.11(encoding@0.1.13)(prettier@3.3.3)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)) '@storybook/builder-vite': 8.1.11(encoding@0.1.13)(prettier@3.3.3)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))
'@storybook/core-server': 8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4) '@storybook/core-server': 8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)
'@storybook/types': 8.1.11 '@storybook/types': 8.1.11
'@storybook/vue3': 8.1.11(encoding@0.1.13)(prettier@3.3.3)(vue@3.4.34(typescript@5.5.4)) '@storybook/vue3': 8.1.11(encoding@0.1.13)(prettier@3.3.3)(vue@3.4.37(typescript@5.5.4))
find-package-json: 1.2.0 find-package-json: 1.2.0
magic-string: 0.30.10 magic-string: 0.30.10
typescript: 5.5.4 typescript: 5.5.4
vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
vue-component-meta: 2.0.16(typescript@5.5.4) vue-component-meta: 2.0.16(typescript@5.5.4)
vue-docgen-api: 4.75.1(vue@3.4.34(typescript@5.5.4)) vue-docgen-api: 4.75.1(vue@3.4.37(typescript@5.5.4))
transitivePeerDependencies: transitivePeerDependencies:
- '@preact/preset-vite' - '@preact/preset-vite'
- bufferutil - bufferutil
@ -16316,24 +16270,24 @@ snapshots:
- vite-plugin-glimmerx - vite-plugin-glimmerx
- vue - vue
'@storybook/vue3@8.1.11(encoding@0.1.13)(prettier@3.3.3)(vue@3.4.34(typescript@5.5.4))': '@storybook/vue3@8.1.11(encoding@0.1.13)(prettier@3.3.3)(vue@3.4.37(typescript@5.5.4))':
dependencies: dependencies:
'@storybook/docs-tools': 8.1.11(encoding@0.1.13)(prettier@3.3.3) '@storybook/docs-tools': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
'@storybook/global': 5.0.0 '@storybook/global': 5.0.0
'@storybook/preview-api': 8.1.11 '@storybook/preview-api': 8.1.11
'@storybook/types': 8.1.11 '@storybook/types': 8.1.11
'@vue/compiler-core': 3.4.29 '@vue/compiler-core': 3.4.34
lodash: 4.17.21 lodash: 4.17.21
ts-dedent: 2.2.0 ts-dedent: 2.2.0
type-fest: 2.19.0 type-fest: 2.19.0
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
vue-component-type-helpers: 2.0.29 vue-component-type-helpers: 2.1.2
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- prettier - prettier
- supports-color - supports-color
'@storybook/vue3@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.4.34(typescript@5.5.4))': '@storybook/vue3@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.4.37(typescript@5.5.4))':
dependencies: dependencies:
'@storybook/components': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/components': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
'@storybook/global': 5.0.0 '@storybook/global': 5.0.0
@ -16345,8 +16299,8 @@ snapshots:
storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
ts-dedent: 2.2.0 ts-dedent: 2.2.0
type-fest: 2.19.0 type-fest: 2.19.0
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
vue-component-type-helpers: 2.0.29 vue-component-type-helpers: 2.1.2
'@swc/cli@0.3.12(@swc/core@1.6.6)(chokidar@3.5.3)': '@swc/cli@0.3.12(@swc/core@1.6.6)(chokidar@3.5.3)':
dependencies: dependencies:
@ -16574,14 +16528,14 @@ snapshots:
dependencies: dependencies:
'@testing-library/dom': 10.1.0 '@testing-library/dom': 10.1.0
'@testing-library/vue@8.1.0(@vue/compiler-sfc@3.4.34)(@vue/server-renderer@3.4.34(vue@3.4.34(typescript@5.5.4)))(vue@3.4.34(typescript@5.5.4))': '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.4.37)(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))':
dependencies: dependencies:
'@babel/runtime': 7.23.4 '@babel/runtime': 7.23.4
'@testing-library/dom': 9.3.4 '@testing-library/dom': 9.3.4
'@vue/test-utils': 2.4.1(@vue/server-renderer@3.4.34(vue@3.4.34(typescript@5.5.4)))(vue@3.4.34(typescript@5.5.4)) '@vue/test-utils': 2.4.1(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
optionalDependencies: optionalDependencies:
'@vue/compiler-sfc': 3.4.34 '@vue/compiler-sfc': 3.4.37
transitivePeerDependencies: transitivePeerDependencies:
- '@vue/server-renderer' - '@vue/server-renderer'
@ -17336,10 +17290,10 @@ snapshots:
'@ungap/structured-clone@1.2.0': {} '@ungap/structured-clone@1.2.0': {}
'@vitejs/plugin-vue@5.1.0(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4))': '@vitejs/plugin-vue@5.1.0(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))':
dependencies: dependencies:
vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
'@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))': '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))':
dependencies: dependencies:
@ -17414,14 +17368,6 @@ snapshots:
path-browserify: 1.0.1 path-browserify: 1.0.1
vscode-uri: 3.0.8 vscode-uri: 3.0.8
'@vue/compiler-core@3.4.29':
dependencies:
'@babel/parser': 7.24.7
'@vue/shared': 3.4.29
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.0
'@vue/compiler-core@3.4.31': '@vue/compiler-core@3.4.31':
dependencies: dependencies:
'@babel/parser': 7.24.7 '@babel/parser': 7.24.7
@ -17438,37 +17384,40 @@ snapshots:
estree-walker: 2.0.2 estree-walker: 2.0.2
source-map-js: 1.2.0 source-map-js: 1.2.0
'@vue/compiler-dom@3.4.29': '@vue/compiler-core@3.4.37':
dependencies: dependencies:
'@vue/compiler-core': 3.4.29 '@babel/parser': 7.24.7
'@vue/shared': 3.4.29 '@vue/shared': 3.4.37
entities: 5.0.0
'@vue/compiler-dom@3.4.31': estree-walker: 2.0.2
dependencies: source-map-js: 1.2.0
'@vue/compiler-core': 3.4.31
'@vue/shared': 3.4.31
'@vue/compiler-dom@3.4.34': '@vue/compiler-dom@3.4.34':
dependencies: dependencies:
'@vue/compiler-core': 3.4.34 '@vue/compiler-core': 3.4.34
'@vue/shared': 3.4.34 '@vue/shared': 3.4.34
'@vue/compiler-sfc@3.4.34': '@vue/compiler-dom@3.4.37':
dependencies:
'@vue/compiler-core': 3.4.37
'@vue/shared': 3.4.37
'@vue/compiler-sfc@3.4.37':
dependencies: dependencies:
'@babel/parser': 7.24.7 '@babel/parser': 7.24.7
'@vue/compiler-core': 3.4.34 '@vue/compiler-core': 3.4.37
'@vue/compiler-dom': 3.4.34 '@vue/compiler-dom': 3.4.37
'@vue/compiler-ssr': 3.4.34 '@vue/compiler-ssr': 3.4.37
'@vue/shared': 3.4.34 '@vue/shared': 3.4.37
estree-walker: 2.0.2 estree-walker: 2.0.2
magic-string: 0.30.10 magic-string: 0.30.10
postcss: 8.4.40 postcss: 8.4.40
source-map-js: 1.2.0 source-map-js: 1.2.0
'@vue/compiler-ssr@3.4.34': '@vue/compiler-ssr@3.4.37':
dependencies: dependencies:
'@vue/compiler-dom': 3.4.34 '@vue/compiler-dom': 3.4.37
'@vue/shared': 3.4.34 '@vue/shared': 3.4.37
'@vue/compiler-vue2@2.7.16': '@vue/compiler-vue2@2.7.16':
dependencies: dependencies:
@ -17480,8 +17429,8 @@ snapshots:
'@vue/language-core@2.0.16(typescript@5.5.4)': '@vue/language-core@2.0.16(typescript@5.5.4)':
dependencies: dependencies:
'@volar/language-core': 2.2.0 '@volar/language-core': 2.2.0
'@vue/compiler-dom': 3.4.31 '@vue/compiler-dom': 3.4.34
'@vue/shared': 3.4.31 '@vue/shared': 3.4.34
computeds: 0.0.1 computeds: 0.0.1
minimatch: 9.0.4 minimatch: 9.0.4
path-browserify: 1.0.1 path-browserify: 1.0.1
@ -17492,9 +17441,9 @@ snapshots:
'@vue/language-core@2.0.29(typescript@5.5.4)': '@vue/language-core@2.0.29(typescript@5.5.4)':
dependencies: dependencies:
'@volar/language-core': 2.4.0-alpha.18 '@volar/language-core': 2.4.0-alpha.18
'@vue/compiler-dom': 3.4.31 '@vue/compiler-dom': 3.4.34
'@vue/compiler-vue2': 2.7.16 '@vue/compiler-vue2': 2.7.16
'@vue/shared': 3.4.31 '@vue/shared': 3.4.34
computeds: 0.0.1 computeds: 0.0.1
minimatch: 9.0.4 minimatch: 9.0.4
muggle-string: 0.4.1 muggle-string: 0.4.1
@ -17502,41 +17451,41 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
'@vue/reactivity@3.4.34': '@vue/reactivity@3.4.37':
dependencies: dependencies:
'@vue/shared': 3.4.34 '@vue/shared': 3.4.37
'@vue/runtime-core@3.4.34': '@vue/runtime-core@3.4.37':
dependencies: dependencies:
'@vue/reactivity': 3.4.34 '@vue/reactivity': 3.4.37
'@vue/shared': 3.4.34 '@vue/shared': 3.4.37
'@vue/runtime-dom@3.4.34': '@vue/runtime-dom@3.4.37':
dependencies: dependencies:
'@vue/reactivity': 3.4.34 '@vue/reactivity': 3.4.37
'@vue/runtime-core': 3.4.34 '@vue/runtime-core': 3.4.37
'@vue/shared': 3.4.34 '@vue/shared': 3.4.37
csstype: 3.1.3 csstype: 3.1.3
'@vue/server-renderer@3.4.34(vue@3.4.34(typescript@5.5.4))': '@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4))':
dependencies: dependencies:
'@vue/compiler-ssr': 3.4.34 '@vue/compiler-ssr': 3.4.37
'@vue/shared': 3.4.34 '@vue/shared': 3.4.37
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
'@vue/shared@3.4.29': {}
'@vue/shared@3.4.31': {} '@vue/shared@3.4.31': {}
'@vue/shared@3.4.34': {} '@vue/shared@3.4.34': {}
'@vue/test-utils@2.4.1(@vue/server-renderer@3.4.34(vue@3.4.34(typescript@5.5.4)))(vue@3.4.34(typescript@5.5.4))': '@vue/shared@3.4.37': {}
'@vue/test-utils@2.4.1(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))':
dependencies: dependencies:
js-beautify: 1.14.9 js-beautify: 1.14.9
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
vue-component-type-helpers: 1.8.4 vue-component-type-helpers: 1.8.4
optionalDependencies: optionalDependencies:
'@vue/server-renderer': 3.4.34(vue@3.4.34(typescript@5.5.4)) '@vue/server-renderer': 3.4.37(vue@3.4.37(typescript@5.5.4))
'@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.19.11)': '@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.19.11)':
dependencies: dependencies:
@ -18019,7 +17968,7 @@ snapshots:
babel-walk@3.0.0-canary-5: babel-walk@3.0.0-canary-5:
dependencies: dependencies:
'@babel/types': 7.24.0 '@babel/types': 7.24.7
bail@2.0.2: {} bail@2.0.2: {}
@ -18572,8 +18521,8 @@ snapshots:
constantinople@4.0.1: constantinople@4.0.1:
dependencies: dependencies:
'@babel/parser': 7.24.5 '@babel/parser': 7.24.7
'@babel/types': 7.24.0 '@babel/types': 7.24.7
content-disposition@0.5.4: content-disposition@0.5.4:
dependencies: dependencies:
@ -18682,12 +18631,12 @@ snapshots:
css-tree@2.2.1: css-tree@2.2.1:
dependencies: dependencies:
mdn-data: 2.0.28 mdn-data: 2.0.28
source-map-js: 1.0.2 source-map-js: 1.2.0
css-tree@2.3.1: css-tree@2.3.1:
dependencies: dependencies:
mdn-data: 2.0.30 mdn-data: 2.0.30
source-map-js: 1.0.2 source-map-js: 1.2.0
css-what@6.1.0: {} css-what@6.1.0: {}
@ -19062,6 +19011,8 @@ snapshots:
entities@4.5.0: {} entities@4.5.0: {}
entities@5.0.0: {}
env-paths@2.2.1: {} env-paths@2.2.1: {}
envinfo@7.8.1: {} envinfo@7.8.1: {}
@ -20228,6 +20179,12 @@ snapshots:
whatwg-encoding: 2.0.0 whatwg-encoding: 2.0.0
whatwg-mimetype: 3.0.0 whatwg-mimetype: 3.0.0
happy-dom@15.6.1:
dependencies:
entities: 4.5.0
webidl-conversions: 7.0.0
whatwg-mimetype: 3.0.0
hard-rejection@2.1.0: {} hard-rejection@2.1.0: {}
has-bigints@1.0.2: {} has-bigints@1.0.2: {}
@ -21004,7 +20961,7 @@ snapshots:
'@babel/generator': 7.24.7 '@babel/generator': 7.24.7
'@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5) '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5)
'@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.5) '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.5)
'@babel/types': 7.24.0 '@babel/types': 7.24.7
'@jest/expect-utils': 29.7.0 '@jest/expect-utils': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
@ -21451,8 +21408,8 @@ snapshots:
magicast@0.3.4: magicast@0.3.4:
dependencies: dependencies:
'@babel/parser': 7.24.5 '@babel/parser': 7.24.7
'@babel/types': 7.24.0 '@babel/types': 7.24.7
source-map-js: 1.2.0 source-map-js: 1.2.0
mailcheck@1.1.1: {} mailcheck@1.1.1: {}
@ -23705,8 +23662,6 @@ snapshots:
sortablejs@1.14.0: {} sortablejs@1.14.0: {}
source-map-js@1.0.2: {}
source-map-js@1.2.0: {} source-map-js@1.2.0: {}
source-map-support@0.5.13: source-map-support@0.5.13:
@ -24487,14 +24442,14 @@ snapshots:
uuid@9.0.1: {} uuid@9.0.1: {}
v-code-diff@1.12.0(vue@3.4.34(typescript@5.5.4)): v-code-diff@1.12.0(vue@3.4.37(typescript@5.5.4)):
dependencies: dependencies:
diff: 5.1.0 diff: 5.1.0
diff-match-patch: 1.0.5 diff-match-patch: 1.0.5
highlight.js: 11.9.0 highlight.js: 11.9.0
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
vue-demi: 0.14.7(vue@3.4.34(typescript@5.5.4)) vue-demi: 0.14.7(vue@3.4.37(typescript@5.5.4))
vue-i18n: 9.13.1(vue@3.4.34(typescript@5.5.4)) vue-i18n: 9.13.1(vue@3.4.37(typescript@5.5.4))
v8-to-istanbul@9.2.0: v8-to-istanbul@9.2.0:
dependencies: dependencies:
@ -24642,24 +24597,26 @@ snapshots:
vue-component-type-helpers@2.0.29: {} vue-component-type-helpers@2.0.29: {}
vue-demi@0.14.7(vue@3.4.34(typescript@5.5.4)): vue-component-type-helpers@2.1.2: {}
dependencies:
vue: 3.4.34(typescript@5.5.4)
vue-docgen-api@4.75.1(vue@3.4.34(typescript@5.5.4)): vue-demi@0.14.7(vue@3.4.37(typescript@5.5.4)):
dependencies:
vue: 3.4.37(typescript@5.5.4)
vue-docgen-api@4.75.1(vue@3.4.37(typescript@5.5.4)):
dependencies: dependencies:
'@babel/parser': 7.24.7 '@babel/parser': 7.24.7
'@babel/types': 7.24.7 '@babel/types': 7.24.7
'@vue/compiler-dom': 3.4.29 '@vue/compiler-dom': 3.4.34
'@vue/compiler-sfc': 3.4.34 '@vue/compiler-sfc': 3.4.37
ast-types: 0.16.1 ast-types: 0.16.1
hash-sum: 2.0.0 hash-sum: 2.0.0
lru-cache: 8.0.4 lru-cache: 8.0.4
pug: 3.0.3 pug: 3.0.3
recast: 0.23.6 recast: 0.23.6
ts-map: 1.0.3 ts-map: 1.0.3
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.4.34(typescript@5.5.4)) vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.4.37(typescript@5.5.4))
vue-eslint-parser@9.4.3(eslint@9.8.0): vue-eslint-parser@9.4.3(eslint@9.8.0):
dependencies: dependencies:
@ -24674,16 +24631,16 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
vue-i18n@9.13.1(vue@3.4.34(typescript@5.5.4)): vue-i18n@9.13.1(vue@3.4.37(typescript@5.5.4)):
dependencies: dependencies:
'@intlify/core-base': 9.13.1 '@intlify/core-base': 9.13.1
'@intlify/shared': 9.13.1 '@intlify/shared': 9.13.1
'@vue/devtools-api': 6.6.1 '@vue/devtools-api': 6.6.1
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.4.34(typescript@5.5.4)): vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.4.37(typescript@5.5.4)):
dependencies: dependencies:
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
vue-template-compiler@2.7.14: vue-template-compiler@2.7.14:
dependencies: dependencies:
@ -24697,20 +24654,20 @@ snapshots:
semver: 7.6.0 semver: 7.6.0
typescript: 5.5.4 typescript: 5.5.4
vue@3.4.34(typescript@5.5.4): vue@3.4.37(typescript@5.5.4):
dependencies: dependencies:
'@vue/compiler-dom': 3.4.34 '@vue/compiler-dom': 3.4.37
'@vue/compiler-sfc': 3.4.34 '@vue/compiler-sfc': 3.4.37
'@vue/runtime-dom': 3.4.34 '@vue/runtime-dom': 3.4.37
'@vue/server-renderer': 3.4.34(vue@3.4.34(typescript@5.5.4)) '@vue/server-renderer': 3.4.37(vue@3.4.37(typescript@5.5.4))
'@vue/shared': 3.4.34 '@vue/shared': 3.4.37
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
vuedraggable@4.1.0(vue@3.4.34(typescript@5.5.4)): vuedraggable@4.1.0(vue@3.4.37(typescript@5.5.4)):
dependencies: dependencies:
sortablejs: 1.14.0 sortablejs: 1.14.0
vue: 3.4.34(typescript@5.5.4) vue: 3.4.37(typescript@5.5.4)
w3c-xmlserializer@5.0.0: w3c-xmlserializer@5.0.0:
dependencies: dependencies:
@ -24830,8 +24787,8 @@ snapshots:
with@7.0.2: with@7.0.2:
dependencies: dependencies:
'@babel/parser': 7.24.5 '@babel/parser': 7.24.7
'@babel/types': 7.24.0 '@babel/types': 7.24.7
assert-never: 1.2.1 assert-never: 1.2.1
babel-walk: 3.0.0-canary-5 babel-walk: 3.0.0-canary-5