diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 25d9cfc1fb..514abdfb20 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -5,7 +5,7 @@
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/node:1": {
- "version": "22.11.0"
+ "version": "22.15.0"
},
"ghcr.io/devcontainers-extra/features/pnpm:2": {
"version": "10.10.0"
diff --git a/.node-version b/.node-version
index 7af24b7ddb..b8ffd70759 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-22.11.0
+22.15.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 42fbf8a17a..b3655536b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
## 2025.5.0
+### Note
+- DockerのNode.jsが22.15.0に更新されました
+
### General
-
@@ -9,6 +12,7 @@
- Enhance: タイムラインのパフォーマンスを向上
- Fix: 一部のブラウザでアコーディオンメニューのアニメーションが動作しない問題を修正
- Fix: ダイアログのお知らせが画面からはみ出ることがある問題を修正
+- Fix: ユーザーポップアップでエラーが生じてもインジケーターが表示され続けてしまう問題を修正
### Server
- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775`
@@ -21,6 +25,8 @@
- Fix: チャンネルのフォロー一覧の結果が一部正しくないのを修正 (#12175)
- Fix: ファイルをアップロードした際にファイル名が常に untitled になる問題を修正
- Fix: ファイルのアップロードに失敗することがある問題を修正
+ - 投稿フォーム上で画像のクロップを行うと、`Invalid Param.`エラーでノートが投稿出来なくなる問題も解決されます。
+ - この事象によって既にノートが投稿出来ない状態になっている場合は、投稿フォーム右上のメニューから、下書きデータの「リセット」を行ってください。
## 2025.4.1
diff --git a/Dockerfile b/Dockerfile
index 9d5596f1f1..aafaa9dc6e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.4
-ARG NODE_VERSION=22.11.0-bookworm
+ARG NODE_VERSION=22.15.0-bookworm
# build assets & compile TypeScript
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 8d723825f3..b331f64330 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -1425,6 +1425,7 @@ _settings:
ifOff: "Quan es desactiva"
enableSyncThemesBetweenDevices: "Sincronitzar els temes instal·lats entre dispositius"
enablePullToRefresh: "Lliscar i actualitzar "
+ enablePullToRefresh_description: "Amb el ratolí, llisca mentre prems la roda."
_chat:
showSenderName: "Mostrar el nom del remitent"
sendOnEnter: "Introdueix per enviar"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 71a442a187..e2312b0422 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1348,6 +1348,7 @@ readonly: "Read only"
goToDeck: "Return to Deck"
federationJobs: "Federation Jobs"
driveAboutTip: "In Drive, a list of files you've uploaded in the past will be displayed.
\nYou can reuse these files when attaching them to notes, or you can upload files in advance to post later.
\nBe careful when deleting a file, as it will not be available in all places where it was used (such as notes, pages, avatars, banners, etc.).
\nYou can also create folders to organize your files."
+scrollToClose: "Scroll to close"
_chat:
noMessagesYet: "No messages yet"
newMessage: "New message"
@@ -1425,6 +1426,7 @@ _settings:
ifOff: "When turned off"
enableSyncThemesBetweenDevices: "Synchronize installed themes across devices"
enablePullToRefresh: "Pull to Refresh"
+ enablePullToRefresh_description: "When using a mouse, drag while pressing in the scrolling wheel."
_chat:
showSenderName: "Show sender's name"
sendOnEnter: "Press Enter to send"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index a57d26c113..1c15fd48d1 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -1348,6 +1348,7 @@ readonly: "唯讀"
goToDeck: "回去甲板"
federationJobs: "聯邦通訊作業"
driveAboutTip: "在「雲端硬碟」中,會顯示過去上傳的檔案列表。
\n可以在附加到貼文時重新利用,或者事先上傳之後再用於發布。
\n請注意,刪除檔案後,之前使用過該檔案的所有地方(貼文、頁面、大頭貼、橫幅等)也會一併無法顯示。
\n也可以建立資料夾來整理檔案。"
+scrollToClose: "用滾輪關閉"
_chat:
noMessagesYet: "尚無訊息"
newMessage: "新訊息"
@@ -1425,6 +1426,7 @@ _settings:
ifOff: "關閉時"
enableSyncThemesBetweenDevices: "在裝置之間同步已安裝的主題"
enablePullToRefresh: "下拉更新"
+ enablePullToRefresh_description: "使用滑鼠,按下並拖曳滾輪。"
_chat:
showSenderName: "顯示發送者的名稱"
sendOnEnter: "按下 Enter 發送訊息"
diff --git a/package.json b/package.json
index e10f4e9461..308cfe0a23 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "misskey",
- "version": "2025.5.0-alpha.0",
+ "version": "2025.5.0-alpha.1",
"codename": "nasubi",
"repository": {
"type": "git",
diff --git a/packages/backend/eslint.config.js b/packages/backend/eslint.config.js
index ae7b2baf49..d15a703ba2 100644
--- a/packages/backend/eslint.config.js
+++ b/packages/backend/eslint.config.js
@@ -1,4 +1,5 @@
import tsParser from '@typescript-eslint/parser';
+import globals from 'globals';
import sharedConfig from '../shared/eslint.config.js';
export default [
@@ -6,6 +7,13 @@ export default [
{
ignores: ['**/node_modules', 'built', '@types/**/*', 'migration'],
},
+ {
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ },
+ },
+ },
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
diff --git a/packages/backend/jest.js b/packages/backend/jest.js
new file mode 100644
index 0000000000..0cb2c2ab77
--- /dev/null
+++ b/packages/backend/jest.js
@@ -0,0 +1,20 @@
+#!/usr/bin/env node
+import child_process from 'node:child_process';
+import path from 'node:path';
+import url from 'node:url';
+
+import semver from 'semver';
+
+const __filename = url.fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const args = [];
+args.push(...[
+ ...semver.satisfies(process.version, '^20.17.0 || ^22.0.0') ? ['--no-experimental-require-module'] : [],
+ '--experimental-vm-modules',
+ '--experimental-import-meta-resolve',
+ path.join(__dirname, 'node_modules/jest/bin/jest.js'),
+ ...process.argv.slice(2),
+]);
+
+child_process.spawn(process.execPath, args, { stdio: 'inherit' });
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 3c6dcc6523..71f301d430 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -22,12 +22,12 @@
"typecheck": "tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit",
"eslint": "eslint --quiet \"{src,test-federation}/**/*.ts\"",
"lint": "pnpm typecheck && pnpm eslint",
- "jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
- "jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs",
- "jest:fed": "node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.fed.cjs",
- "jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs",
- "jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs",
- "jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
+ "jest": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.cjs",
+ "jest:e2e": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.e2e.cjs",
+ "jest:fed": "node ./jest.js --forceExit --config jest.config.fed.cjs",
+ "jest-and-coverage": "cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.unit.cjs",
+ "jest-and-coverage:e2e": "cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.e2e.cjs",
+ "jest-clear": "cross-env NODE_ENV=test node ./jest.js --clearCache",
"test": "pnpm jest",
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
"test:fed": "pnpm jest:fed",
diff --git a/packages/backend/test-federation/compose.yml b/packages/backend/test-federation/compose.yml
index 6c010e0089..bd0ac15a31 100644
--- a/packages/backend/test-federation/compose.yml
+++ b/packages/backend/test-federation/compose.yml
@@ -50,6 +50,10 @@ services:
source: ../jest.config.fed.cjs
target: /misskey/packages/backend/jest.config.fed.cjs
read_only: true
+ - type: bind
+ source: ../jest.js
+ target: /misskey/packages/backend/jest.js
+ read_only: true
- type: bind
source: ../../misskey-js/built
target: /misskey/packages/misskey-js/built
diff --git a/packages/frontend/src/components/MkSwiper.vue b/packages/frontend/src/components/MkSwiper.vue
index 1d0ffaea11..b66bfb0e9d 100644
--- a/packages/frontend/src/components/MkSwiper.vue
+++ b/packages/frontend/src/components/MkSwiper.vue
@@ -53,12 +53,12 @@ const MIN_SWIPE_DISTANCE = 20;
// スワイプ時の動作を発火する最小の距離
const SWIPE_DISTANCE_THRESHOLD = 70;
-// スワイプを中断するY方向の移動距離
-const SWIPE_ABORT_Y_THRESHOLD = 75;
-
// スワイプできる最大の距離
const MAX_SWIPE_DISTANCE = 120;
+// スワイプ方向を判定する角度の許容範囲(度数)
+const SWIPE_DIRECTION_ANGLE_THRESHOLD = 50;
+
// ▲ しきい値 ▲ //
let startScreenX: number | null = null;
@@ -69,6 +69,7 @@ const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === t
const pullDistance = ref(0);
const isSwipingForClass = ref(false);
let swipeAborted = false;
+let swipeDirectionLocked: 'horizontal' | 'vertical' | null = null;
function touchStart(event: TouchEvent) {
if (!prefer.r.enableHorizontalSwipe.value) return;
@@ -79,6 +80,7 @@ function touchStart(event: TouchEvent) {
startScreenX = event.touches[0].screenX;
startScreenY = event.touches[0].screenY;
+ swipeDirectionLocked = null; // スワイプ方向をリセット
}
function touchMove(event: TouchEvent) {
@@ -95,15 +97,24 @@ function touchMove(event: TouchEvent) {
let distanceX = event.touches[0].screenX - startScreenX;
let distanceY = event.touches[0].screenY - startScreenY;
- if (Math.abs(distanceY) > SWIPE_ABORT_Y_THRESHOLD) {
- swipeAborted = true;
+ // スワイプ方向をロック
+ if (!swipeDirectionLocked) {
+ const angle = Math.abs(Math.atan2(distanceY, distanceX) * (180 / Math.PI));
+ if (angle > 90 - SWIPE_DIRECTION_ANGLE_THRESHOLD && angle < 90 + SWIPE_DIRECTION_ANGLE_THRESHOLD) {
+ swipeDirectionLocked = 'vertical';
+ } else {
+ swipeDirectionLocked = 'horizontal';
+ }
+ }
+ // 縦方向のスワイプの場合は中断
+ if (swipeDirectionLocked === 'vertical') {
+ swipeAborted = true;
pullDistance.value = 0;
isSwiping.value = false;
window.setTimeout(() => {
isSwipingForClass.value = false;
}, 400);
-
return;
}
@@ -164,6 +175,8 @@ function touchEnd(event: TouchEvent) {
window.setTimeout(() => {
isSwipingForClass.value = false;
}, 400);
+
+ swipeDirectionLocked = null; // スワイプ方向をリセット
}
/** 横スワイプに関与する可能性のある要素を調べる */
@@ -190,7 +203,7 @@ watch(tabModel, (newTab, oldTab) => {
const newIndex = props.tabs.findIndex(tab => tab.key === newTab);
const oldIndex = props.tabs.findIndex(tab => tab.key === oldTab);
- if (oldIndex >= 0 && newIndex && oldIndex < newIndex) {
+ if (oldIndex >= 0 && newIndex >= 0 && oldIndex < newIndex) {
transitionName.value = 'swipeAnimationLeft';
} else {
transitionName.value = 'swipeAnimationRight';
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue
index 3bd2a2ffae..2a423bfa55 100644
--- a/packages/frontend/src/components/MkUserPopup.vue
+++ b/packages/frontend/src/components/MkUserPopup.vue
@@ -12,7 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-only
appear @afterLeave="emit('closed')"
>