diff --git a/.devcontainer/compose.yml b/.devcontainer/compose.yml index d02d2a8f4a..501f78c814 100644 --- a/.devcontainer/compose.yml +++ b/.devcontainer/compose.yml @@ -28,7 +28,7 @@ services: db: restart: unless-stopped - image: postgres:15-alpine + image: postgres:18-alpine networks: - internal_network environment: diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.yml b/.github/ISSUE_TEMPLATE/01_bug-report.yml index 077855b5bf..fd68e602dd 100644 --- a/.github/ISSUE_TEMPLATE/01_bug-report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug-report.yml @@ -76,7 +76,7 @@ body: * Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment * Misskey: 2025.x.x * Node: 20.x.x - * PostgreSQL: 15.x.x + * PostgreSQL: 18.x.x * Redis: 7.x.x * OS and Architecture: Ubuntu 24.04.2 LTS aarch64 value: | diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index faf53f3b3d..a65b244ba1 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -38,7 +38,7 @@ jobs: services: postgres: - image: postgres:15 + image: postgres:18 ports: - 54312:5432 env: @@ -117,7 +117,7 @@ jobs: services: postgres: - image: postgres:15 + image: postgres:18 ports: - 54312:5432 env: @@ -165,7 +165,7 @@ jobs: services: postgres: - image: postgres:15 + image: postgres:18 ports: - 54312:5432 env: diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 4d9a475c45..3ccfb7e3e0 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -64,7 +64,7 @@ jobs: services: postgres: - image: postgres:15 + image: postgres:18 ports: - 54312:5432 env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e3beb4c31..eeb6e81019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,22 @@ -## 2025.11.1 +## Unreleased ### General - +### Client +- + +### Server +- + + +## 2025.11.1 + ### Client - Enhance: リアクションの受け入れ設定にキャプションを追加 #15921 - Fix: ページの内容がはみ出ることがある問題を修正 +- Fix: ナビゲーションバーを下に表示しているときに、項目数が多いと表示が崩れる問題を修正 - Fix: ヘッダーメニューのチャンネルの新規作成の項目でチャンネル作成ページに飛べない問題を修正 #16816 - Fix: ラジオボタンに空白の選択肢が表示される問題を修正 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1105) @@ -14,10 +24,15 @@ - Fix: 投稿フォームのリセットボタンで注釈がリセットされない問題を修正 - Fix: PlayのAiScriptバージョン判定(v0.x系・v1.x系の判定)が正しく動作しない問題を修正 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1129) +- Fix: フォロー申請をキャンセルする際の確認ダイアログの文言が不正確な問題を修正 +- Fix: 初回読み込み時にエラーになることがある問題を修正 +- Fix: お気に入りクリップの一覧表示が正しく動作しない問題を修正 +- Fix: AiScript Misskey 拡張APIにおいて、各種関数の引数で明示的に `null` が指定されている場合のハンドリングを修正 ### Server -- Enhance: `clips/my-favorites` APIがページネーションに対応しました - Enhance: メモリ使用量を削減しました +- Enhance: 依存関係の更新 +- Fix: ワードミュートの文字数計算を修正 - Fix: チャンネルのリアルタイム更新時に、ロックダウン設定にて非ログイン時にノートを表示しない設定にしている場合でもノートが表示されてしまう問題を修正 - Fix: DeepL APIのAPIキー指定方式変更に対応 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1096) diff --git a/chart/templates/Deployment.yml b/chart/templates/Deployment.yml index 3c73837801..280bf9a597 100644 --- a/chart/templates/Deployment.yml +++ b/chart/templates/Deployment.yml @@ -27,7 +27,7 @@ spec: ports: - containerPort: 3000 - name: postgres - image: postgres:15-alpine + image: postgres:18-alpine env: - name: POSTGRES_USER value: "example-misskey-user" diff --git a/compose.local-db.yml b/compose.local-db.yml index 3835cb23db..4703b16fc5 100644 --- a/compose.local-db.yml +++ b/compose.local-db.yml @@ -15,7 +15,7 @@ services: db: restart: always - image: postgres:15-alpine + image: postgres:18-alpine ports: - "5432:5432" env_file: diff --git a/compose_example.yml b/compose_example.yml index 336bd814a7..70de5bba7b 100644 --- a/compose_example.yml +++ b/compose_example.yml @@ -37,7 +37,7 @@ services: db: restart: always - image: postgres:15-alpine + image: postgres:18-alpine networks: - internal_network env_file: diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 1730bdbe79..59768668ef 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -83,6 +83,8 @@ files: "Fitxers" download: "Descarregar" driveFileDeleteConfirm: "Estàs segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer també seran esborrades." unfollowConfirm: "Segur que vols deixar de seguir a {name}?" +cancelFollowRequestConfirm: "Vols cancel·lar la teva sol·licitud de seguiment a {name}?" +rejectFollowRequestConfirm: "Vols rebutjar la sol·licitud de seguiment de {name}?" exportRequested: "Has sol·licitat una exportació de dades. Això pot trigar una estona. S'afegirà a la teva unitat de disc un cop estigui completada." importRequested: "Has sol·licitat una importació de dades. Això pot trigar una estona." lists: "Llistes" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 50c98488a3..147c60b0be 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -83,6 +83,8 @@ files: "Archivos" download: "Descargar" driveFileDeleteConfirm: "¿Desea borrar el archivo \"{name}\"? Las notas que tengan este archivo como adjunto serán eliminadas" unfollowConfirm: "¿Desea dejar de seguir a {name}?" +cancelFollowRequestConfirm: "¿Desea cancelar su solicitud de seguimiento a {name}?" +rejectFollowRequestConfirm: "¿Desea rechazar la solicitud de seguimiento de {name}?" exportRequested: "Has solicitado la exportación. Puede llevar un tiempo. Cuando termine la exportación, se añadirá al drive" importRequested: "Has solicitado la importación. Puede llevar un tiempo." lists: "Listas" diff --git a/locales/index.d.ts b/locales/index.d.ts index ab17f8b736..7f0efd22e8 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -350,6 +350,14 @@ export interface Locale extends ILocale { * {name}のフォローを解除しますか? */ "unfollowConfirm": ParameterizedString<"name">; + /** + * {name}へのフォロー申請をキャンセルしますか? + */ + "cancelFollowRequestConfirm": ParameterizedString<"name">; + /** + * {name}からのフォロー申請を拒否しますか? + */ + "rejectFollowRequestConfirm": ParameterizedString<"name">; /** * エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a1f9af8edc..8e4a52b68d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -83,6 +83,8 @@ files: "ファイル" download: "ダウンロード" driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。" unfollowConfirm: "{name}のフォローを解除しますか?" +cancelFollowRequestConfirm: "{name}へのフォロー申請をキャンセルしますか?" +rejectFollowRequestConfirm: "{name}からのフォロー申請を拒否しますか?" exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。" importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。" lists: "リスト" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 615e70af2f..5a70bffeef 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -83,6 +83,8 @@ files: "파일" download: "다운로드" driveFileDeleteConfirm: "‘{name}’ 파일을 삭제하시겠습니까? 이 파일을 사용하는 일부 콘텐츠도 삭제됩니다." unfollowConfirm: "{name}님을 언팔로우하시겠습니까?" +cancelFollowRequestConfirm: "{name}(으)로의 팔로우 신청을 취소하시겠습니까?" +rejectFollowRequestConfirm: "{name}(으)로부터의 팔로우 신청을 거부하시겠습니까?" exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다." importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다." lists: "리스트" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 2bb5a37895..fcad02f241 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -83,6 +83,8 @@ files: "文件" download: "下载" driveFileDeleteConfirm: "要删除「{name}」文件吗?附加此文件的帖子也会被删除。" unfollowConfirm: "要取消对 {name} 的关注吗?" +cancelFollowRequestConfirm: "要取消申请关注{name}吗?" +rejectFollowRequestConfirm: "要拒绝{name}的关注申请吗?" exportRequested: "导出请求已提交,这可能需要花一些时间,导出的文件将保存到网盘中。" importRequested: "导入请求已提交,这可能需要花一点时间。" lists: "列表" @@ -2557,13 +2559,13 @@ _poll: deadlineTime: "时间" duration: "期限" votesCount: "{n}票" - totalVotes: "总票数 {n}" + totalVotes: "总计{n}票" vote: "投票" - showResult: "显示结果" + showResult: "查看结果" voted: "已投票" closed: "已截止" remainingDays: "{d}天{h}小时后截止" - remainingHours: "{h} 小时 {m} 分后截止" + remainingHours: "{h}小时{m}分后截止" remainingMinutes: "{m}分{s}秒后截止" remainingSeconds: "{s}秒后截止" _visibility: diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index db9c1116c3..5227423d84 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -83,6 +83,8 @@ files: "檔案" download: "下載" driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此檔案的貼文也會跟著被刪除。" unfollowConfirm: "確定要取消追隨{name}嗎?" +cancelFollowRequestConfirm: "要取消向 {name} 送出的追隨申請嗎?" +rejectFollowRequestConfirm: "要拒絕來自 {name} 的追隨申請嗎?" exportRequested: "已請求匯出。這可能會花一點時間。匯出的檔案將會被放到雲端硬碟裡。" importRequested: "已請求匯入。這可能會花一點時間。" lists: "清單" diff --git a/package.json b/package.json index 4bedd4eefb..bcbd1315f9 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "2025.11.1-alpha.1", + "version": "2025.11.1", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@10.20.0", + "packageManager": "pnpm@10.22.0", "workspaces": [ "packages/frontend-shared", "packages/frontend", @@ -55,30 +55,30 @@ }, "dependencies": { "cssnano": "7.1.2", - "esbuild": "0.25.11", + "esbuild": "0.27.0", "execa": "9.6.0", "fast-glob": "3.3.3", - "glob": "11.0.3", + "glob": "13.0.0", "ignore-walk": "8.0.0", - "js-yaml": "4.1.0", + "js-yaml": "4.1.1", "postcss": "8.5.6", "tar": "7.5.2", - "terser": "5.44.0", + "terser": "5.44.1", "typescript": "5.9.3" }, "devDependencies": { - "@eslint/js": "9.39.0", - "@misskey-dev/eslint-plugin": "2.1.0", + "@eslint/js": "9.39.1", + "@misskey-dev/eslint-plugin": "2.2.0", "@types/js-yaml": "4.0.9", - "@types/node": "24.9.2", - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", + "@types/node": "24.10.1", + "@typescript-eslint/eslint-plugin": "8.47.0", + "@typescript-eslint/parser": "8.47.0", "cross-env": "10.1.0", - "cypress": "15.5.0", - "eslint": "9.39.0", + "cypress": "15.6.0", + "eslint": "9.39.1", "globals": "16.5.0", "ncp": "2.0.0", - "pnpm": "10.20.0", + "pnpm": "10.22.0", "start-server-and-test": "2.1.2" }, "optionalDependencies": { diff --git a/packages/backend/package.json b/packages/backend/package.json index 4d8f1d8b9a..72463a69b9 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -70,8 +70,8 @@ "utf-8-validate": "6.0.5" }, "dependencies": { - "@aws-sdk/client-s3": "3.933.0", - "@aws-sdk/lib-storage": "3.933.0", + "@aws-sdk/client-s3": "3.936.0", + "@aws-sdk/lib-storage": "3.936.0", "@discordapp/twemoji": "16.0.1", "@fastify/accepts": "5.0.3", "@fastify/cookie": "11.0.2", @@ -88,14 +88,14 @@ "@nestjs/core": "11.1.9", "@nestjs/testing": "11.1.9", "@peertube/http-signature": "1.7.0", - "@sentry/node": "10.25.0", - "@sentry/profiling-node": "10.25.0", + "@sentry/node": "10.26.0", + "@sentry/profiling-node": "10.26.0", "@simplewebauthn/server": "13.2.2", "@sinonjs/fake-timers": "15.0.0", "@smithy/node-http-handler": "4.4.5", - "@swc/cli": "0.7.8", + "@swc/cli": "0.7.9", "@swc/core": "1.15.2", - "@twemoji/parser": "17.0.1", + "@twemoji/parser": "16.0.0", "@types/redis-info": "3.0.3", "accepts": "1.3.8", "ajv": "8.17.1", @@ -114,10 +114,10 @@ "content-disposition": "1.0.0", "date-fns": "4.1.0", "deep-email-validator": "0.1.21", - "fastify": "5.6.1", + "fastify": "5.6.2", "fastify-raw-body": "5.0.0", "feed": "5.1.0", - "file-type": "21.1.0", + "file-type": "21.1.1", "fluent-ffmpeg": "2.1.3", "form-data": "4.0.5", "got": "14.6.4", @@ -161,7 +161,7 @@ "qrcode": "1.5.4", "random-seed": "0.3.0", "ratelimiter": "3.4.1", - "re2": "1.22.1", + "re2": "1.22.3", "redis-info": "3.1.0", "reflect-metadata": "0.2.2", "rename": "1.0.4", @@ -190,7 +190,7 @@ "devDependencies": { "@jest/globals": "29.7.0", "@nestjs/platform-express": "11.1.9", - "@sentry/vue": "10.25.0", + "@sentry/vue": "10.26.0", "@simplewebauthn/types": "12.0.0", "@swc/jest": "0.2.39", "@types/accepts": "1.3.7", diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index d1fb3858db..5ec362fb34 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -41,7 +41,7 @@ function greet() { //#endregion console.log(' Misskey is an open-source decentralized microblogging platform.'); - console.log(chalk.rgb(255, 136, 0)(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo')); + console.log(chalk.rgb(255, 136, 0)(' If you like Misskey, please consider donating to support dev. https://misskey-hub.net/docs/donate/')); console.log(''); console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`); diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 23ab8082ed..7a005400bb 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -7,11 +7,11 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Injectable } from '@nestjs/common'; -import * as nsfw from 'nsfwjs'; import si from 'systeminformation'; import { Mutex } from 'async-mutex'; import fetch from 'node-fetch'; import { bindThis } from '@/decorators.js'; +import type { NSFWJS, PredictionType } from 'nsfwjs'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -21,7 +21,7 @@ let isSupportedCpu: undefined | boolean = undefined; @Injectable() export class AiService { - private model: nsfw.NSFWJS; + private model: NSFWJS; private modelLoadMutex: Mutex = new Mutex(); constructor( @@ -29,7 +29,7 @@ export class AiService { } @bindThis - public async detectSensitive(source: string | Buffer): Promise { + public async detectSensitive(source: string | Buffer): Promise { try { if (isSupportedCpu === undefined) { isSupportedCpu = await this.computeIsSupportedCpu(); @@ -44,6 +44,7 @@ export class AiService { tf.env().global.fetch = fetch; if (this.model == null) { + const nsfw = await import('nsfwjs'); await this.modelLoadMutex.runExclusive(async () => { if (this.model == null) { this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 }); diff --git a/packages/backend/src/server/api/endpoints/clips/my-favorites.ts b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts index 057b567312..44719592d1 100644 --- a/packages/backend/src/server/api/endpoints/clips/my-favorites.ts +++ b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts @@ -5,7 +5,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { QueryService } from '@/core/QueryService.js'; import type { ClipFavoritesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; @@ -31,11 +30,6 @@ export const meta = { export const paramDef = { type: 'object', properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, }, required: [], } as const; @@ -46,16 +40,14 @@ export default class extends Endpoint { // eslint- @Inject(DI.clipFavoritesRepository) private clipFavoritesRepository: ClipFavoritesRepository, - private queryService: QueryService, private clipEntityService: ClipEntityService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.clipFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + const query = this.clipFavoritesRepository.createQueryBuilder('favorite') .andWhere('favorite.userId = :meId', { meId: me.id }) .leftJoinAndSelect('favorite.clip', 'clip'); const favorites = await query - .limit(ps.limit) .getMany(); return this.clipEntityService.packMany(favorites.map(x => x.clip!), me); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 5c7958fc1c..113a09cb14 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -295,8 +295,20 @@ export default class extends Endpoint { // eslint- if (ps.chatScope !== undefined) updates.chatScope = ps.chatScope; function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) { - // TODO: ちゃんと数える - const length = JSON.stringify(mutedWords).length; + const count = (arr: (string[] | string)[]) => { + let length = 0; + for (const item of arr) { + if (typeof item === 'string') { + length += item.length; + } else if (Array.isArray(item)) { + for (const subItem of item) { + length += subItem.length; + } + } + } + return length; + }; + const length = count(mutedWords); if (length > limit) { throw new ApiError(meta.errors.tooManyMutedWords); } diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/create.ts b/packages/backend/src/server/api/endpoints/notes/drafts/create.ts index 880f4964a1..efb5ee01d1 100644 --- a/packages/backend/src/server/api/endpoints/notes/drafts/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/drafts/create.ts @@ -135,6 +135,18 @@ export const meta = { code: 'CANNOT_RENOTE_TO_EXTERNAL', id: 'ed1952ac-2d26-4957-8b30-2deda76bedf7', }, + + scheduledAtRequired: { + message: 'scheduledAt is required when isActuallyScheduled is true.', + code: 'SCHEDULED_AT_REQUIRED', + id: '15e28a55-e74c-4d65-89b7-8880cdaaa87d', + }, + + scheduledAtMustBeInFuture: { + message: 'scheduledAt must be in the future.', + code: 'SCHEDULED_AT_MUST_BE_IN_FUTURE', + id: 'e4bed6c9-017e-4934-aed0-01c22cc60ec1', + }, }, limit: { @@ -252,6 +264,10 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility); case 'c3275f19-4558-4c59-83e1-4f684b5fab66': throw new ApiError(meta.errors.tooManyScheduledNotes); + case '94a89a43-3591-400a-9c17-dd166e71fdfa': + throw new ApiError(meta.errors.scheduledAtRequired); + case 'b34d0c1b-996f-4e34-a428-c636d98df457': + throw new ApiError(meta.errors.scheduledAtMustBeInFuture); default: throw err; } diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/update.ts b/packages/backend/src/server/api/endpoints/notes/drafts/update.ts index 9a2e2ca415..2900e0cb0d 100644 --- a/packages/backend/src/server/api/endpoints/notes/drafts/update.ts +++ b/packages/backend/src/server/api/endpoints/notes/drafts/update.ts @@ -165,6 +165,18 @@ export const meta = { code: 'TOO_MANY_SCHEDULED_NOTES', id: '02f5df79-08ae-4a33-8524-f1503c8f6212', }, + + scheduledAtRequired: { + message: 'scheduledAt is required when isActuallyScheduled is true.', + code: 'SCHEDULED_AT_REQUIRED', + id: 'fe9737d5-cc41-498c-af9d-149207307530', + }, + + scheduledAtMustBeInFuture: { + message: 'scheduledAt must be in the future.', + code: 'SCHEDULED_AT_MUST_BE_IN_FUTURE', + id: 'ed1a6673-d0d1-4364-aaae-9bf3f139cbc5', + }, }, limit: { @@ -295,6 +307,10 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.containsTooManyMentions); case 'bacdf856-5c51-4159-b88a-804fa5103be5': throw new ApiError(meta.errors.tooManyScheduledNotes); + case '94a89a43-3591-400a-9c17-dd166e71fdfa': + throw new ApiError(meta.errors.scheduledAtRequired); + case 'b34d0c1b-996f-4e34-a428-c636d98df457': + throw new ApiError(meta.errors.scheduledAtMustBeInFuture); default: throw err; } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 3cd83efa1a..f9d904f3cd 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -50,7 +50,7 @@ import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntitySer import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; import { ClientLoggerService } from './ClientLoggerService.js'; -import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify'; +import type { FastifyError, FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -918,7 +918,7 @@ export class ClientServerService { return await renderBase(reply); }); - fastify.setErrorHandler(async (error, request, reply) => { + fastify.setErrorHandler(async (error, request, reply) => { const errId = randomUUID(); this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${error.message}`, { path: request.routeOptions.url, diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index a76c75fe5c..46b365a9c7 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -28,7 +28,7 @@ html meta(name='theme-color-orig' content= themeColor || '#86b300') meta(property='og:site_name' content= instanceName || 'Misskey') 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, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover') meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no') link(rel='icon' href= icon || '/favicon.ico') link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png') diff --git a/packages/backend/test-federation/compose.tpl.yml b/packages/backend/test-federation/compose.tpl.yml index 3d2ed21337..92b986736d 100644 --- a/packages/backend/test-federation/compose.tpl.yml +++ b/packages/backend/test-federation/compose.tpl.yml @@ -95,7 +95,7 @@ services: retries: 20 db: - image: postgres:15-alpine + image: postgres:18-alpine env_file: - ./.config/docker.env volumes: diff --git a/packages/backend/test/compose.yml b/packages/backend/test/compose.yml index 6593fc33dd..fe96616fc0 100644 --- a/packages/backend/test/compose.yml +++ b/packages/backend/test/compose.yml @@ -5,7 +5,7 @@ services: - "127.0.0.1:56312:6379" dbtest: - image: postgres:15 + image: postgres:18 ports: - "127.0.0.1:54312:5432" environment: diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index fec83c2433..fe9a217ee8 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -506,10 +506,10 @@ describe('クリップ', () => { }); }; - const myFavorites = async (parameters: Misskey.entities.ClipsMyFavoritesRequest, request: Partial> = {}): Promise => { + const myFavorites = async (request: Partial> = {}): Promise => { return successfulApiCall({ endpoint: 'clips/my-favorites', - parameters, + parameters: {}, user: alice, ...request, }); @@ -562,9 +562,8 @@ describe('クリップ', () => { await favorite({ clipId: clip.id }); } - const favorited = await myFavorites({ - limit: 30, - }); + // pagenationはない。全部一気にとれる。 + const favorited = await myFavorites(); assert.strictEqual(favorited.length, clips.length); for (const clip of favorited) { assert.strictEqual(clip.favoritedCount, 1); @@ -618,7 +617,7 @@ describe('クリップ', () => { const clip = await show({ clipId: aliceClip.id }); assert.strictEqual(clip.favoritedCount, 0); assert.strictEqual(clip.isFavorited, false); - assert.deepStrictEqual(await myFavorites({}), []); + assert.deepStrictEqual(await myFavorites(), []); }); test.each([ @@ -652,13 +651,13 @@ describe('クリップ', () => { test('を取得できる。', async () => { await favorite({ clipId: aliceClip.id }); - const favorited = await myFavorites({}); + const favorited = await myFavorites(); assert.deepStrictEqual(favorited, [await show({ clipId: aliceClip.id })]); }); test('を取得したとき他人のお気に入りは含まない。', async () => { await favorite({ clipId: aliceClip.id }); - const favorited = await myFavorites({}, { user: bob }); + const favorited = await myFavorites({ user: bob }); assert.deepStrictEqual(favorited, []); }); }); diff --git a/packages/frontend-builder/package.json b/packages/frontend-builder/package.json index 5e1ddee1ec..ef5c8e0367 100644 --- a/packages/frontend-builder/package.json +++ b/packages/frontend-builder/package.json @@ -11,15 +11,15 @@ }, "devDependencies": { "@types/estree": "1.0.8", - "@types/node": "24.9.2", - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", - "rollup": "4.52.5", + "@types/node": "24.10.1", + "@typescript-eslint/eslint-plugin": "8.47.0", + "@typescript-eslint/parser": "8.47.0", + "rollup": "4.53.3", "typescript": "5.9.3" }, "dependencies": { "estree-walker": "3.0.3", "magic-string": "0.30.21", - "vite": "7.1.11" + "vite": "7.2.2" } } diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index c5bcb13cf7..7bfd32686c 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -15,8 +15,8 @@ "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.3.0", "@twemoji/parser": "16.0.0", - "@vitejs/plugin-vue": "6.0.1", - "@vue/compiler-sfc": "3.5.22", + "@vitejs/plugin-vue": "6.0.2", + "@vue/compiler-sfc": "3.5.24", "astring": "1.9.0", "buraha": "0.0.1", "estree-walker": "3.0.3", @@ -26,16 +26,16 @@ "mfm-js": "0.25.0", "misskey-js": "workspace:*", "punycode.js": "2.3.1", - "rollup": "4.52.5", - "sass": "1.93.3", - "shiki": "3.14.0", + "rollup": "4.53.3", + "sass": "1.94.1", + "shiki": "3.15.0", "tinycolor2": "1.6.0", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", "typescript": "5.9.3", "uuid": "13.0.0", - "vite": "7.1.11", - "vue": "3.5.22" + "vite": "7.2.2", + "vue": "3.5.24" }, "devDependencies": { "@misskey-dev/summaly": "5.2.5", @@ -43,14 +43,14 @@ "@testing-library/vue": "8.1.0", "@types/estree": "1.0.8", "@types/micromatch": "4.0.10", - "@types/node": "24.9.2", + "@types/node": "24.10.1", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", + "@typescript-eslint/eslint-plugin": "8.47.0", + "@typescript-eslint/parser": "8.47.0", "@vitest/coverage-v8": "3.2.4", - "@vue/runtime-core": "3.5.22", + "@vue/runtime-core": "3.5.24", "acorn": "8.15.0", "cross-env": "10.1.0", "eslint-plugin-import": "2.32.0", @@ -59,14 +59,14 @@ "happy-dom": "20.0.10", "intersection-observer": "0.12.2", "micromatch": "4.0.8", - "msw": "2.11.6", - "nodemon": "3.1.10", + "msw": "2.12.2", + "nodemon": "3.1.11", "prettier": "3.6.2", "start-server-and-test": "2.1.2", "tsx": "4.20.6", "vite-plugin-turbosnap": "1.0.3", - "vue-component-type-helpers": "3.1.2", + "vue-component-type-helpers": "3.1.4", "vue-eslint-parser": "10.2.0", - "vue-tsc": "3.1.2" + "vue-tsc": "3.1.4" } } diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index 52feb3401b..af4a6cb67e 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -21,12 +21,12 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/node": "24.9.2", - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", - "esbuild": "0.25.11", + "@types/node": "24.10.1", + "@typescript-eslint/eslint-plugin": "8.47.0", + "@typescript-eslint/parser": "8.47.0", + "esbuild": "0.27.0", "eslint-plugin-vue": "10.5.1", - "nodemon": "3.1.10", + "nodemon": "3.1.11", "typescript": "5.9.3", "vue-eslint-parser": "10.2.0" }, @@ -35,6 +35,6 @@ ], "dependencies": { "misskey-js": "workspace:*", - "vue": "3.5.22" + "vue": "3.5.24" } } diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 776822978d..b6906e130a 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -24,12 +24,12 @@ "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.3.0", - "@sentry/vue": "10.22.0", - "@syuilo/aiscript": "1.1.2", + "@sentry/vue": "10.26.0", + "@syuilo/aiscript": "1.2.0", "@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0", "@twemoji/parser": "16.0.0", - "@vitejs/plugin-vue": "6.0.1", - "@vue/compiler-sfc": "3.5.22", + "@vitejs/plugin-vue": "6.0.2", + "@vue/compiler-sfc": "3.5.24", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "analytics": "0.8.19", "astring": "1.9.0", @@ -41,7 +41,7 @@ "chartjs-chart-matrix": "3.0.0", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.2.0", - "chromatic": "13.3.3", + "chromatic": "13.3.4", "compare-versions": "6.1.1", "cropperjs": "2.1.0", "date-fns": "4.1.0", @@ -58,7 +58,7 @@ "json5": "2.2.3", "magic-string": "0.30.21", "matter-js": "0.20.0", - "mediabunny": "1.24.2", + "mediabunny": "1.25.0", "mfm-js": "0.25.0", "misskey-bubble-game": "workspace:*", "misskey-js": "workspace:*", @@ -67,21 +67,21 @@ "punycode.js": "2.3.1", "qr-code-styling": "1.9.2", "qr-scanner": "1.4.2", - "rollup": "4.52.5", + "rollup": "4.53.3", "sanitize-html": "2.17.0", - "sass": "1.93.3", - "shiki": "3.14.0", + "sass": "1.94.1", + "shiki": "3.15.0", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", - "three": "0.181.0", + "three": "0.181.2", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", "typescript": "5.9.3", "v-code-diff": "1.13.1", - "vite": "7.1.11", - "vue": "3.5.22", + "vite": "7.2.2", + "vue": "3.5.24", "vuedraggable": "next", "wanakana": "5.3.1" }, @@ -110,21 +110,21 @@ "@types/estree": "1.0.8", "@types/matter-js": "0.20.2", "@types/micromatch": "4.0.10", - "@types/node": "24.9.2", + "@types/node": "24.10.1", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/sanitize-html": "2.16.0", "@types/seedrandom": "3.0.8", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", + "@typescript-eslint/eslint-plugin": "8.47.0", + "@typescript-eslint/parser": "8.47.0", "@vitest/coverage-v8": "3.2.4", - "@vue/compiler-core": "3.5.22", - "@vue/runtime-core": "3.5.22", + "@vue/compiler-core": "3.5.24", + "@vue/runtime-core": "3.5.24", "acorn": "8.15.0", "cross-env": "10.1.0", - "cypress": "15.5.0", + "cypress": "15.6.0", "eslint-plugin-import": "2.32.0", "eslint-plugin-vue": "10.5.1", "fast-glob": "3.3.3", @@ -132,9 +132,9 @@ "intersection-observer": "0.12.2", "micromatch": "4.0.8", "minimatch": "10.1.1", - "msw": "2.11.6", + "msw": "2.12.2", "msw-storybook-addon": "2.0.6", - "nodemon": "3.1.10", + "nodemon": "3.1.11", "prettier": "3.6.2", "react": "19.2.0", "react-dom": "19.2.0", @@ -147,8 +147,8 @@ "vite-plugin-turbosnap": "1.0.3", "vitest": "3.2.4", "vitest-fetch-mock": "0.4.5", - "vue-component-type-helpers": "3.1.2", + "vue-component-type-helpers": "3.1.4", "vue-eslint-parser": "10.2.0", - "vue-tsc": "3.1.2" + "vue-tsc": "3.1.4" } } diff --git a/packages/frontend/src/aiscript/api.ts b/packages/frontend/src/aiscript/api.ts index dc84925375..3a476787fe 100644 --- a/packages/frontend/src/aiscript/api.ts +++ b/packages/frontend/src/aiscript/api.ts @@ -40,29 +40,77 @@ export function createAiScriptEnv(opts: { storageKey: string, token?: string }) CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value), LOCALE: values.STR(lang), SERVER_URL: values.STR(url), - 'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => { - utils.assertString(title); - utils.assertString(text); - if (type != null) { - assertStringAndIsIn(type, DIALOG_TYPES); + 'Mk:dialog': values.FN_NATIVE(async ([_title, _text, _type]) => { + let title: string | undefined = undefined; + let text: string | undefined = undefined; + let type: typeof DIALOG_TYPES[number] = 'info'; + + if (_title != null) { + if (utils.isString(_title)) { + title = _title.value; + } else { + utils.assertNull(_title); + } } + + if (_text != null) { + if (utils.isString(_text)) { + text = _text.value; + } else { + utils.assertNull(_text); + } + } + + if (_type != null) { + if (utils.isString(_type)) { + assertStringAndIsIn(_type, DIALOG_TYPES); + type = _type.value; + } else { + utils.assertNull(_type); + } + } + await os.alert({ - type: type ? type.value : 'info', - title: title.value, - text: text.value, + type, + title, + text, }); return values.NULL; }), - 'Mk:confirm': values.FN_NATIVE(async ([title, text, type]) => { - utils.assertString(title); - utils.assertString(text); - if (type != null) { - assertStringAndIsIn(type, DIALOG_TYPES); + 'Mk:confirm': values.FN_NATIVE(async ([_title, _text, _type]) => { + let title: string | undefined = undefined; + let text: string | undefined = undefined; + let type: typeof DIALOG_TYPES[number] = 'question'; + + if (_title != null) { + if (utils.isString(_title)) { + title = _title.value; + } else { + utils.assertNull(_title); + } } + + if (_text != null) { + if (utils.isString(_text)) { + text = _text.value; + } else { + utils.assertNull(_text); + } + } + + if (_type != null) { + if (utils.isString(_type)) { + assertStringAndIsIn(_type, DIALOG_TYPES); + type = _type.value; + } else { + utils.assertNull(_type); + } + } + const confirm = await os.confirm({ - type: type ? type.value : 'question', - title: title.value, - text: text.value, + type, + title, + text, }); return confirm.canceled ? values.FALSE : values.TRUE; }), @@ -76,15 +124,23 @@ export function createAiScriptEnv(opts: { storageKey: string, token?: string }) if (ep.value.includes('://') || ep.value.includes('..')) { throw new errors.AiScriptRuntimeError('invalid endpoint'); } - if (token) { + + let actualToken: string | null = null; + if (token != null && !utils.isNull(token)) { utils.assertString(token); // バグがあればundefinedもあり得るため念のため - if (typeof token.value !== 'string') throw new Error('invalid token'); + if (typeof token.value !== 'string') throw new errors.AiScriptRuntimeError('invalid token'); + actualToken = token.value; } - const actualToken: string | null = token?.value ?? opts.token ?? null; + + if (actualToken == null) { + actualToken = opts.token ?? null; + } + if (param == null) { throw new errors.AiScriptRuntimeError('expected param'); } + utils.assertObject(param); return misskeyApi(ep.value as keyof Misskey.Endpoints, utils.valToJs(param) as object, actualToken).then(res => { return utils.jsToVal(res); diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index f9783cb65c..2b522d3f10 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { computed, watch, version as vueVersion } from 'vue'; +import { watch, version as vueVersion } from 'vue'; import { compareVersions } from 'compare-versions'; import { version, lang, apiUrl, isSafeMode } from '@@/js/config.js'; import defaultLightTheme from '@@/themes/l-light.json5'; @@ -15,11 +15,11 @@ import directives from '@/directives/index.js'; import components from '@/components/index.js'; import { applyTheme } from '@/theme.js'; import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js'; -import { updateI18n, i18n } from '@/i18n.js'; +import { i18n } from '@/i18n.js'; import { refreshCurrentAccount, login } from '@/accounts.js'; import { store } from '@/store.js'; import { fetchInstance, instance } from '@/instance.js'; -import { deviceKind, updateDeviceKind } from '@/utility/device-kind.js'; +import { updateDeviceKind } from '@/utility/device-kind.js'; import { reloadChannel } from '@/utility/unison-reload.js'; import { getUrlWithoutLoginId } from '@/utility/login-id.js'; import { getAccountFromId } from '@/utility/get-account-from-id.js'; @@ -109,13 +109,6 @@ export async function common(createVue: () => Promise>) { else window.location.reload(); }); - // If mobile, insert the viewport meta tag - if (['smartphone', 'tablet'].includes(deviceKind)) { - const viewport = window.document.getElementsByName('viewport').item(0); - viewport.setAttribute('content', - `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); - } - //#region Set lang attr const html = window.document.documentElement; html.setAttribute('lang', lang); @@ -160,8 +153,15 @@ export async function common(createVue: () => Promise>) { }); //#endregion + if (!isSafeMode) { + // TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア + if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme)); + if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme)); + } + // NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため) // NOTE: この処理は必ずダークモード判定処理より後に来ること(初回のテーマ適用のため) + // NOTE: この処理は必ずサーバーテーマ適用処理より後に来ること(二重applyTheme発火を防ぐため) // see: https://github.com/misskey-dev/misskey/issues/16562 watch(store.r.darkMode, (darkMode) => { const theme = (() => { @@ -178,26 +178,17 @@ export async function common(createVue: () => Promise>) { window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light'; if (!isSafeMode) { - const darkTheme = prefer.model('darkTheme'); - const lightTheme = prefer.model('lightTheme'); - - watch(darkTheme, (theme) => { + watch(prefer.r.darkTheme, (theme) => { if (store.s.darkMode) { applyTheme(theme ?? defaultDarkTheme); } }); - watch(lightTheme, (theme) => { + watch(prefer.r.lightTheme, (theme) => { if (!store.s.darkMode) { applyTheme(theme ?? defaultLightTheme); } }); - - fetchInstanceMetaPromise.then(() => { - // TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア - if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme)); - if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme)); - }); } watch(prefer.r.overridedDeviceKind, (kind) => { diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index c7361a19c6..ba21fe82e4 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -102,6 +102,21 @@ async function onClick() { await misskeyApi('following/delete', { userId: props.user.id, }); + } else if (hasPendingFollowRequestFromYou.value) { + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.tsx.cancelFollowRequestConfirm({ name: props.user.name || props.user.username }), + }); + + if (canceled) { + wait.value = false; + return; + } + + await misskeyApi('following/requests/cancel', { + userId: props.user.id, + }); + hasPendingFollowRequestFromYou.value = false; } else { if (prefer.s.alwaysConfirmFollow) { const { canceled } = await os.confirm({ @@ -115,41 +130,34 @@ async function onClick() { } } - if (hasPendingFollowRequestFromYou.value) { - await misskeyApi('following/requests/cancel', { - userId: props.user.id, - }); - hasPendingFollowRequestFromYou.value = false; - } else { - await misskeyApi('following/create', { - userId: props.user.id, - withReplies: prefer.s.defaultFollowWithReplies, - }); - emit('update:user', { - ...props.user, - withReplies: prefer.s.defaultFollowWithReplies, - }); - hasPendingFollowRequestFromYou.value = true; + await misskeyApi('following/create', { + userId: props.user.id, + withReplies: prefer.s.defaultFollowWithReplies, + }); + emit('update:user', { + ...props.user, + withReplies: prefer.s.defaultFollowWithReplies, + }); + hasPendingFollowRequestFromYou.value = true; - if ($i == null) { - wait.value = false; - return; - } + if ($i == null) { + wait.value = false; + return; + } - claimAchievement('following1'); + claimAchievement('following1'); - if ($i.followingCount >= 10) { - claimAchievement('following10'); - } - if ($i.followingCount >= 50) { - claimAchievement('following50'); - } - if ($i.followingCount >= 100) { - claimAchievement('following100'); - } - if ($i.followingCount >= 300) { - claimAchievement('following300'); - } + if ($i.followingCount >= 10) { + claimAchievement('following10'); + } + if ($i.followingCount >= 50) { + claimAchievement('following50'); + } + if ($i.followingCount >= 100) { + claimAchievement('following100'); + } + if ($i.followingCount >= 300) { + claimAchievement('following300'); } } } catch (err) { diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue index 35e259a571..ba24d7abc6 100644 --- a/packages/frontend/src/pages/follow-requests.vue +++ b/packages/frontend/src/pages/follow-requests.vue @@ -63,14 +63,28 @@ function accept(user: Misskey.entities.UserLite) { }); } -function reject(user: Misskey.entities.UserLite) { - os.apiWithDialog('following/requests/reject', { userId: user.id }).then(() => { +async function reject(user: Misskey.entities.UserLite) { + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.tsx.rejectFollowRequestConfirm({ name: user.name || user.username }), + }); + + if (canceled) return; + + await os.apiWithDialog('following/requests/reject', { userId: user.id }).then(() => { paginator.reload(); }); } -function cancel(user: Misskey.entities.UserLite) { - os.apiWithDialog('following/requests/cancel', { userId: user.id }).then(() => { +async function cancel(user: Misskey.entities.UserLite) { + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.tsx.cancelFollowRequestConfirm({ name: user.name || user.username }), + }); + + if (canceled) return; + + await os.apiWithDialog('following/requests/cancel', { userId: user.id }).then(() => { paginator.reload(); }); } diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue index f48dc5be4d..45faae48be 100644 --- a/packages/frontend/src/pages/my-clips/index.vue +++ b/packages/frontend/src/pages/my-clips/index.vue @@ -43,6 +43,8 @@ const paginator = markRaw(new Paginator('clips/list', { })); const favoritesPaginator = markRaw(new Paginator('clips/my-favorites', { + // ページネーションに対応していない + noPaging: true, })); async function create() { diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 2f2107d9ed..8d1d33977d 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -234,7 +234,7 @@ export const PREF_DEF = definePreferences({ default: false, }, disableShowingAnimatedImages: { - default: prefersReducedMotion, + default: false, }, emojiStyle: { default: 'twemoji', // twemoji / fluentEmoji / native diff --git a/packages/frontend/src/theme.ts b/packages/frontend/src/theme.ts index 2fca4e79ae..fc3f5f55f2 100644 --- a/packages/frontend/src/theme.ts +++ b/packages/frontend/src/theme.ts @@ -158,6 +158,8 @@ export function applyTheme(theme: Theme, persist = true) { // 様々な理由により startViewTransition は失敗することがある // ref. https://github.com/misskey-dev/misskey/issues/16562 + // FIXME: viewTransitonエラーはtry~catch貫通してそうな気配がする + console.error(err); window.document.documentElement.classList.remove('_themeChanging_'); diff --git a/packages/frontend/src/ui/_common_/navbar-h.vue b/packages/frontend/src/ui/_common_/navbar-h.vue index b025dd4858..64da4647b6 100644 --- a/packages/frontend/src/ui/_common_/navbar-h.vue +++ b/packages/frontend/src/ui/_common_/navbar-h.vue @@ -4,40 +4,40 @@ SPDX-License-Identifier: AGPL-3.0-only -->