diff --git a/locales/index.d.ts b/locales/index.d.ts index 77b9d3d84..f30481828 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -9795,6 +9795,26 @@ export interface Locale extends ILocale { * 対局がキャンセルされました */ "gameCanceled": string; + /** + * 開始時に対局をタイムラインに投稿 + */ + "shareToTlTheGameWhenStart": string; + /** + * 対局を開始しました! #MisskeyReversi + */ + "iStartedAGame": string; + /** + * 相手が設定を変更しました + */ + "opponentHasSettingsChanged": string; + /** + * 変則許可 (完全フリー) + */ + "allowIrregularRules": string; + /** + * 変則なし + */ + "disallowIrregularRules": string; }; "_offlineScreen": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 609d2a8fe..c7e1bc77c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2611,6 +2611,11 @@ _reversi: freeMatch: "フリーマッチ" lookingForPlayer: "対戦相手を探しています" gameCanceled: "対局がキャンセルされました" + shareToTlTheGameWhenStart: "開始時に対局をタイムラインに投稿" + iStartedAGame: "対局を開始しました! #MisskeyReversi" + opponentHasSettingsChanged: "相手が設定を変更しました" + allowIrregularRules: "変則許可 (完全フリー)" + disallowIrregularRules: "変則なし" _offlineScreen: title: "オフライン - サーバーに接続できません" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 74dcf5f81..719812bfd 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -380,8 +380,11 @@ hcaptcha: "hCaptcha(キャプチャ)" enableHcaptcha: "hCaptcha(キャプチャ)をつけとく" hcaptchaSiteKey: "サイトキー" hcaptchaSecretKey: "シークレットキー" +mcaptcha: "mCaptcha" +enableMcaptcha: "hCaptcha(キャプチャ)をつけとく" mcaptchaSiteKey: "サイトキー" mcaptchaSecretKey: "シークレットキー" +mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL" recaptcha: "reCAPTCHA" enableRecaptcha: "reCAPTCHA(リキャプチャ)を有効にする" recaptchaSiteKey: "サイトキー" @@ -629,6 +632,7 @@ medium: "中" small: "小" generateAccessToken: "アクセストークンの発行" permission: "権限" +adminPermission: "管理者権限" enableAll: "全部使えるようにする" disableAll: "全部使えへんようにする" tokenRequested: "アカウントへのアクセス許してやったらどうや" @@ -1055,6 +1059,8 @@ limitWidthOfReaction: "ツッコミの最大横幅を制限して、ちっさく noteIdOrUrl: "ノートIDかURL" video: "動画" videos: "動画" +audio: "音声" +audioFiles: "音声" dataSaver: "データケチケチ" accountMigration: "アカウントのお引っ越し" accountMoved: "このユーザーはさらのアカウントに引っ越したで:" @@ -1187,7 +1193,25 @@ seasonalScreenEffect: "季節にあった画面の動き" decorate: "デコる" addMfmFunction: "装飾つける" enableQuickAddMfmFunction: "ややこしいMFMのピッカーを出す" +bubbleGame: "バブルゲーム" +sfx: "効果音" +soundWillBePlayed: "サウンドが再生されるで" +showReplay: "リプレイ見る" +replay: "リプレイ" +replaying: "リプレイ中" +ranking: "ランキング" lastNDays: "直近{n}日" +backToTitle: "タイトルへ" +hemisphere: "住んでる地域" +withSensitive: "センシティブなファイルを含むノートを表示" +userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿" +enableHorizontalSwipe: "スワイプしてタブを切り替える" +_bubbleGame: + howToPlay: "遊び方" + _howToPlay: + section1: "位置を調整してハコにモノを落とすで。" + section2: "同じもんがくっついたら別のやつになって、スコアがもらえるで。" + section3: "モノがハコからあふれたらゲームオーバーや。ハコからあふれんようにしながらモノを融合させてハイスコアを目指しいや!" _announcement: forExistingUsers: "もうおるユーザーのみ" forExistingUsersDescription: "オンにしたらこのお知らせができた時点でおる人らにだけお知らせが行くで。切ったらこの知らせが行ったあとにアカウント作った人にもちゃんとお知らせが行くで。" @@ -1558,6 +1582,13 @@ _achievements: _tutorialCompleted: title: "Misskeyひよっこ講座 修了証" description: "チュートリアル全部やった" + _bubbleGameExplodingHead: + title: "🤯" + description: "バブルゲームで最も大きいモノを出した" + _bubbleGameDoubleExplodingHead: + title: "ダブル🤯" + description: "バブルゲームで最も大きいモノを2つ同時に出した" + flavor: "これくらいの おべんとばこに 🤯 🤯 ちょっとつめて" _role: new: "ロールの作成" edit: "ロールの編集" @@ -2410,6 +2441,51 @@ _dataSaver: _code: title: "コードハイライト" description: "MFMとかでコードハイライト記法が使われてるとき、タップするまで読み込まれへんくなるで。コードハイライトではハイライトする言語ごとにその決めてるファイルを読む必要はあんねんな。けどな、それは自動で読み込まれなくなるから、通信量を少なくできることができるねん。" +_hemisphere: + N: "北半球" + S: "南半球" + caption: "一部のクライアント設定で、季節を判定するのに使用するで。" _reversi: + reversi: "リバーシ" + gameSettings: "対局の設定" + chooseBoard: "ボードを選択" + blackOrWhite: "先行/後攻" + blackIs: "{name}が黒(先行)" + rules: "ルール" + thisGameIsStartedSoon: "対局、そろそろ開始されるで。" + waitingForOther: "相手の準備が完了するのを待ってんで。" + waitingForMe: "あんさんの準備が完了すんのを待ってんで" + waitingBoth: "準備してなー" + ready: "準備完了" + cancelReady: "準備を再開" + opponentTurn: "相手のターンやで" + myTurn: "あんさんのターンや" + turnOf: "{name}のターンやで" + pastTurnOf: "{name}のターン" + surrender: "投了" + surrendered: "投了により" + timeout: "時間切れ" + drawn: "引き分け" + won: "{name}の勝ち" + black: "黒" + white: "白" total: "合計" + turnCount: "{count}ターン目" + myGames: "自分の対局" + allGames: "みんなの対局" + ended: "終了" + playing: "対局中" + isLlotheo: "石の少ない方が勝ち(ロセオ)" + loopedMap: "ループマップ" + canPutEverywhere: "どこでも置けるモード" + timeLimitForEachTurn: "1ターンの時間制限" + freeMatch: "フリーマッチ" + lookingForPlayer: "対戦相手を探してるで" + gameCanceled: "対局がキャンセルされたわ" + shareToTlTheGameWhenStart: "初めの時に対局をタイムラインに投稿するで" + iStartedAGame: "対局し始めたで! #MisskeyReversi" + opponentHasSettingsChanged: "相手が設定変えたで" +_offlineScreen: + title: "オフライン - サーバーに接続できひんで" + header: "サーバーに接続できへんわ" diff --git a/packages/backend/migration/1706081514499-reversi-6.js b/packages/backend/migration/1706081514499-reversi-6.js new file mode 100644 index 000000000..de870be44 --- /dev/null +++ b/packages/backend/migration/1706081514499-reversi-6.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class Reversi61706081514499 { + name = 'Reversi61706081514499' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "reversi_game" ADD "noIrregularRules" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "noIrregularRules"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index a9ada8391..7ffd2e6fe 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -81,13 +81,13 @@ "@fastify/view": "8.2.0", "@misskey-dev/sharp-read-bmp": "^1.1.1", "@misskey-dev/summaly": "^5.0.3", - "@nestjs/common": "10.2.10", - "@nestjs/core": "10.2.10", - "@nestjs/testing": "10.2.10", + "@nestjs/common": "10.3.1", + "@nestjs/core": "10.3.1", + "@nestjs/testing": "10.3.1", "@peertube/http-signature": "1.7.0", "@simplewebauthn/server": "9.0.0", "@sinonjs/fake-timers": "11.2.2", - "@smithy/node-http-handler": "2.1.10", + "@smithy/node-http-handler": "2.3.1", "@swc/cli": "0.1.65", "@swc/core": "1.3.105", "@twemoji/parser": "15.0.0", @@ -98,7 +98,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.2", - "bullmq": "5.1.4", + "bullmq": "5.1.5", "cacheable-lookup": "7.0.0", "cbor": "9.0.1", "chalk": "5.3.0", @@ -186,7 +186,7 @@ "devDependencies": { "@jest/globals": "29.7.0", "@misskey-dev/eslint-plugin": "1.0.0", - "@nestjs/platform-express": "10.3.0", + "@nestjs/platform-express": "10.3.1", "@simplewebauthn/types": "9.0.0", "@swc/jest": "0.2.31", "@types/accepts": "1.3.7", @@ -204,7 +204,7 @@ "@types/jsrsasign": "10.5.12", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "20.11.5", + "@types/node": "20.11.6", "@types/nodemailer": "6.4.14", "@types/oauth": "0.9.4", "@types/oauth2orize": "1.11.3", diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts index db702f23a..8fbe61140 100644 --- a/packages/backend/src/core/ReversiService.ts +++ b/packages/backend/src/core/ReversiService.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { ModuleRef } from '@nestjs/core'; import * as Reversi from 'misskey-reversi'; +import { LessThan, MoreThan } from 'typeorm'; import type { MiReversiGame, ReversiGamesRepository, @@ -23,7 +24,7 @@ import { Serialized } from '@/types.js'; import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js'; import type { OnModuleInit } from '@nestjs/common'; -const MATCHING_TIMEOUT_MS = 1000 * 15; // 15sec +const INVITATION_TIMEOUT_MS = 1000 * 20; // 20sec @Injectable() export class ReversiService implements OnModuleInit { @@ -84,44 +85,82 @@ export class ReversiService implements OnModuleInit { map: game.map, bw: game.bw, crc32: game.crc32, + noIrregularRules: game.noIrregularRules, } satisfies Partial; } @bindThis - public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise { + public async matchSpecificUser(me: MiUser, targetUser: MiUser, multiple = false): Promise { if (targetUser.id === me.id) { throw new Error('You cannot match yourself.'); } + if (!multiple) { + // 既にマッチしている対局が無いか探す(3分以内) + const games = await this.reversiGamesRepository.find({ + where: [ + { id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, user2Id: targetUser.id, isStarted: false }, + { id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: targetUser.id, user2Id: me.id, isStarted: false }, + ], + relations: ['user1', 'user2'], + order: { id: 'DESC' }, + }); + if (games.length > 0) { + return games[0]; + } + } + + //#region 相手から既に招待されてないか確認 const invitations = await this.redisClient.zrange( `reversi:matchSpecific:${me.id}`, - Date.now() - MATCHING_TIMEOUT_MS, + Date.now() - INVITATION_TIMEOUT_MS, '+inf', 'BYSCORE'); if (invitations.includes(targetUser.id)) { await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, targetUser.id); - const game = await this.matched(targetUser.id, me.id); - - return game; - } else { - this.redisClient.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id); - - this.globalEventService.publishReversiStream(targetUser.id, 'invited', { - user: await this.userEntityService.pack(me, targetUser), + const game = await this.matched(targetUser.id, me.id, { + noIrregularRules: false, }); - return null; + return game; } + //#endregion + + const redisPipeline = this.redisClient.pipeline(); + redisPipeline.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id); + redisPipeline.expire(`reversi:matchSpecific:${targetUser.id}`, 120, 'NX'); + await redisPipeline.exec(); + + this.globalEventService.publishReversiStream(targetUser.id, 'invited', { + user: await this.userEntityService.pack(me, targetUser), + }); + + return null; } @bindThis - public async matchAnyUser(me: MiUser): Promise { + public async matchAnyUser(me: MiUser, options: { noIrregularRules: boolean }, multiple = false): Promise { + if (!multiple) { + // 既にマッチしている対局が無いか探す(3分以内) + const games = await this.reversiGamesRepository.find({ + where: [ + { id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, isStarted: false }, + { id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user2Id: me.id, isStarted: false }, + ], + relations: ['user1', 'user2'], + order: { id: 'DESC' }, + }); + if (games.length > 0) { + return games[0]; + } + } + //#region まず自分宛ての招待を探す const invitations = await this.redisClient.zrange( `reversi:matchSpecific:${me.id}`, - Date.now() - MATCHING_TIMEOUT_MS, + Date.now() - INVITATION_TIMEOUT_MS, '+inf', 'BYSCORE'); @@ -129,7 +168,9 @@ export class ReversiService implements OnModuleInit { const invitorId = invitations[Math.floor(Math.random() * invitations.length)]; await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, invitorId); - const game = await this.matched(invitorId, me.id); + const game = await this.matched(invitorId, me.id, { + noIrregularRules: false, + }); return game; } @@ -137,23 +178,35 @@ export class ReversiService implements OnModuleInit { const matchings = await this.redisClient.zrange( 'reversi:matchAny', - Date.now() - MATCHING_TIMEOUT_MS, - '+inf', - 'BYSCORE'); + 0, + 2, // 自分自身のIDが入っている場合もあるので2つ取得 + 'REV'); - const userIds = matchings.filter(id => id !== me.id); + const items = matchings.filter(id => !id.startsWith(me.id)); - if (userIds.length > 0) { - // pick random - const matchedUserId = userIds[Math.floor(Math.random() * userIds.length)]; + if (items.length > 0) { + const [matchedUserId, option] = items[0].split(':'); - await this.redisClient.zrem('reversi:matchAny', me.id, matchedUserId); + await this.redisClient.zrem('reversi:matchAny', + me.id, + matchedUserId, + me.id + ':noIrregularRules', + matchedUserId + ':noIrregularRules'); - const game = await this.matched(matchedUserId, me.id); + const game = await this.matched(matchedUserId, me.id, { + noIrregularRules: options.noIrregularRules || option === 'noIrregularRules', + }); return game; } else { - await this.redisClient.zadd('reversi:matchAny', Date.now(), me.id); + const redisPipeline = this.redisClient.pipeline(); + if (options.noIrregularRules) { + redisPipeline.zadd('reversi:matchAny', Date.now(), me.id + ':noIrregularRules'); + } else { + redisPipeline.zadd('reversi:matchAny', Date.now(), me.id); + } + redisPipeline.expire('reversi:matchAny', 15, 'NX'); + await redisPipeline.exec(); return null; } } @@ -165,7 +218,15 @@ export class ReversiService implements OnModuleInit { @bindThis public async matchAnyUserCancel(user: MiUser) { - await this.redisClient.zrem('reversi:matchAny', user.id); + await this.redisClient.zrem('reversi:matchAny', user.id, user.id + ':noIrregularRules'); + } + + @bindThis + public async cleanOutdatedGames() { + await this.reversiGamesRepository.delete({ + id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 10)), + isStarted: false, + }); } @bindThis @@ -219,7 +280,7 @@ export class ReversiService implements OnModuleInit { } @bindThis - private async matched(parentId: MiUser['id'], childId: MiUser['id']): Promise { + private async matched(parentId: MiUser['id'], childId: MiUser['id'], options: { noIrregularRules: boolean; }): Promise { const game = await this.reversiGamesRepository.insert({ id: this.idService.gen(), user1Id: parentId, @@ -232,6 +293,7 @@ export class ReversiService implements OnModuleInit { map: Reversi.maps.eighteight.data, bw: 'random', isLlotheo: false, + noIrregularRules: options.noIrregularRules, }).then(x => this.reversiGamesRepository.findOneOrFail({ where: { id: x.identifiers[0].id }, relations: ['user1', 'user2'], @@ -333,7 +395,7 @@ export class ReversiService implements OnModuleInit { public async getInvitations(user: MiUser): Promise { const invitations = await this.redisClient.zrange( `reversi:matchSpecific:${user.id}`, - Date.now() - MATCHING_TIMEOUT_MS, + Date.now() - INVITATION_TIMEOUT_MS, '+inf', 'BYSCORE'); return invitations; diff --git a/packages/backend/src/core/entities/ReversiGameEntityService.ts b/packages/backend/src/core/entities/ReversiGameEntityService.ts index 6f90b8d69..b37d87b45 100644 --- a/packages/backend/src/core/entities/ReversiGameEntityService.ts +++ b/packages/backend/src/core/entities/ReversiGameEntityService.ts @@ -60,6 +60,7 @@ export class ReversiGameEntityService { canPutEverywhere: game.canPutEverywhere, loopedBoard: game.loopedBoard, timeLimitForEachTurn: game.timeLimitForEachTurn, + noIrregularRules: game.noIrregularRules, logs: game.logs, map: game.map, }); @@ -108,6 +109,7 @@ export class ReversiGameEntityService { canPutEverywhere: game.canPutEverywhere, loopedBoard: game.loopedBoard, timeLimitForEachTurn: game.timeLimitForEachTurn, + noIrregularRules: game.noIrregularRules, }); } diff --git a/packages/backend/src/models/ReversiGame.ts b/packages/backend/src/models/ReversiGame.ts index 0363f4d41..59e0c8723 100644 --- a/packages/backend/src/models/ReversiGame.ts +++ b/packages/backend/src/models/ReversiGame.ts @@ -113,6 +113,11 @@ export class MiReversiGame { }) public bw: string; + @Column('boolean', { + default: false, + }) + public noIrregularRules: boolean; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/models/json-schema/reversi-game.ts b/packages/backend/src/models/json-schema/reversi-game.ts index f8a5e7451..ff4c78eeb 100644 --- a/packages/backend/src/models/json-schema/reversi-game.ts +++ b/packages/backend/src/models/json-schema/reversi-game.ts @@ -82,6 +82,10 @@ export const packedReversiGameLiteSchema = { type: 'string', optional: false, nullable: false, }, + noIrregularRules: { + type: 'boolean', + optional: false, nullable: false, + }, isLlotheo: { type: 'boolean', optional: false, nullable: false, @@ -196,6 +200,10 @@ export const packedReversiGameDetailedSchema = { type: 'string', optional: false, nullable: false, }, + noIrregularRules: { + type: 'boolean', + optional: false, nullable: false, + }, isLlotheo: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index e252c5d8a..17b6c8ba0 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -11,6 +11,7 @@ import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import type { Config } from '@/config.js'; +import { ReversiService } from '@/core/ReversiService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -32,6 +33,7 @@ export class CleanProcessorService { private roleAssignmentsRepository: RoleAssignmentsRepository, private queueLoggerService: QueueLoggerService, + private reversiService: ReversiService, private idService: IdService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('clean'); @@ -65,6 +67,8 @@ export class CleanProcessorService { }); } + this.reversiService.cleanOutdatedGames(); + this.logger.succ('Cleaned.'); } } diff --git a/packages/backend/src/server/api/endpoints/reversi/games.ts b/packages/backend/src/server/api/endpoints/reversi/games.ts index 0be18020a..6143bf680 100644 --- a/packages/backend/src/server/api/endpoints/reversi/games.ts +++ b/packages/backend/src/server/api/endpoints/reversi/games.ts @@ -43,7 +43,6 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId) - .andWhere('game.isStarted = TRUE') .innerJoinAndSelect('game.user1', 'user1') .innerJoinAndSelect('game.user2', 'user2'); @@ -53,6 +52,8 @@ export default class extends Endpoint { // eslint- .where('game.user1Id = :userId', { userId: me.id }) .orWhere('game.user2Id = :userId', { userId: me.id }); })); + } else { + query.andWhere('game.isStarted = TRUE'); } const games = await query.take(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/reversi/match.ts b/packages/backend/src/server/api/endpoints/reversi/match.ts index 043082ec9..00d144dff 100644 --- a/packages/backend/src/server/api/endpoints/reversi/match.ts +++ b/packages/backend/src/server/api/endpoints/reversi/match.ts @@ -37,6 +37,8 @@ export const paramDef = { type: 'object', properties: { userId: { type: 'string', format: 'misskey:id', nullable: true }, + noIrregularRules: { type: 'boolean', default: false }, + multiple: { type: 'boolean', default: false }, }, required: [], } as const; @@ -56,7 +58,9 @@ export default class extends Endpoint { // eslint- throw err; }) : null; - const game = target ? await this.reversiService.matchSpecificUser(me, target) : await this.reversiService.matchAnyUser(me); + const game = target + ? await this.reversiService.matchSpecificUser(me, target, ps.multiple) + : await this.reversiService.matchAnyUser(me, { noIrregularRules: ps.noIrregularRules }, ps.multiple); if (game == null) return; diff --git a/packages/frontend/assets/reversi/logo.png b/packages/frontend/assets/reversi/logo.png index 05c8f40e3..724a311ea 100644 Binary files a/packages/frontend/assets/reversi/logo.png and b/packages/frontend/assets/reversi/logo.png differ diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 939b98389..64ee45a07 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -40,7 +40,7 @@ "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "10.3.1", + "chromatic": "10.5.0", "compare-versions": "6.1.0", "cropperjs": "2.0.0-beta.4", "date-fns": "2.30.0", @@ -51,7 +51,6 @@ "insert-text-at-cursor": "0.3.0", "is-file-animated": "1.0.2", "json5": "2.2.3", - "lodash.defaultsdeep": "4.6.1", "matter-js": "0.19.0", "mfm-js": "0.24.0", "misskey-bubble-game": "workspace:*", @@ -101,10 +100,9 @@ "@testing-library/vue": "8.0.1", "@types/escape-regexp": "0.0.3", "@types/estree": "1.0.5", - "@types/lodash.defaultsdeep": "4.6.9", "@types/matter-js": "0.19.6", "@types/micromatch": "4.0.6", - "@types/node": "20.11.5", + "@types/node": "20.11.6", "@types/punycode": "2.1.3", "@types/sanitize-html": "2.9.5", "@types/throttle-debounce": "5.0.2", @@ -125,7 +123,7 @@ "happy-dom": "10.0.3", "intersection-observer": "0.12.2", "micromatch": "4.0.5", - "msw": "2.1.3", + "msw": "2.1.4", "msw-storybook-addon": "1.10.0", "nodemon": "3.0.3", "prettier": "3.2.4", @@ -137,7 +135,7 @@ "vite-plugin-turbosnap": "1.0.3", "vitest": "0.34.6", "vitest-fetch-mock": "0.2.2", - "vue-eslint-parser": "9.4.1", + "vue-eslint-parser": "9.4.2", "vue-tsc": "1.8.27" } } diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue index 55bb4b13b..67d32c505 100644 --- a/packages/frontend/src/components/MkHorizontalSwipe.vue +++ b/packages/frontend/src/components/MkHorizontalSwipe.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only