Compare commits

...
Author SHA1 Message Date
kakkokari-gtyih c13856e700 fix types 2025-12-17 13:42:10 +09:00
kakkokari-gtyih 143296afed fix: queryとbirthdayを同時に受け付けないように 2025-12-17 13:24:07 +09:00
kakkokari-gtyih 999d965715 Merge remote-tracking branch 'msky/develop' into copilot/add-filter-for-followers 2025-12-17 13:21:13 +09:00
github-actions[bot] 7ef0c96758 Bump version to 2025.12.2-beta.2 2025-12-17 03:31:55 +00:00
syuilo b10074e939 enhance(frontend): add deck tour 2025-12-17 12:27:55 +09:00
260dbd150b fix(deps): update dependency systeminformation to v5.27.14 [security] [ci skip] (#17003)
* fix(deps): update dependency systeminformation to v5.27.14 [security]

* update whitelist to force update systeminformation package

* bump other dependencies to fix dep error

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-12-17 09:36:15 +09:00
syuilo 79cbbcfe0f Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-12-17 09:08:25 +09:00
syuilo c893f85864 Update example.yml 2025-12-17 09:08:22 +09:00
syuiloandかっこかり 24d4ffa2ec Update CHANGELOG.md
Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
2025-12-17 09:07:51 +09:00
github-actions[bot] 0b931daefd Bump version to 2025.12.2-beta.1 2025-12-16 14:00:25 +00:00
かっこかり cc05d93194 fix(frontend): まれに設定変更のタブ間同期に失敗する問題を修正 (#16991) 2025-12-16 22:56:57 +09:00
かっこかり 90345591bb fix(frontend): 無限スクロールできる箇所の調整 (#17000) 2025-12-16 22:50:26 +09:00
github-actions[bot] 730227f353 Bump version to 2025.12.2-beta.0 2025-12-16 12:31:04 +00:00
syuilo 4acb37ee9d Update CHANGELOG.md 2025-12-16 21:23:51 +09:00
syuilo 7025769c69 fix(frontend): バージョン表記のないPlayが正しく動作しない問題を修正
Fix #16996
2025-12-16 21:23:23 +09:00
syuilo 1a4ef8769f Update CHANGELOG.md 2025-12-16 20:08:01 +09:00
github-actions[bot] 055cd0c250 Bump version to 2025.12.2-alpha.0 2025-12-16 11:00:42 +00:00
syuilo d35ddc77d2 enhance(backend): request ip が localhost だった場合、レートリミットをスキップ & 警告を出すように 2025-12-16 19:56:44 +09:00
syuilo 8d871a58e3 Update CHANGELOG.md 2025-12-16 19:55:31 +09:00
syuilo 99b0b436e0 Update example.yml 2025-12-16 19:55:16 +09:00
syuilo e3d5b95672 Revert "Merge commit from fork"
This reverts commit 5512898463.

see https://github.com/misskey-dev/misskey/issues/16994
2025-12-16 14:21:39 +09:00
copilot-swe-agent[bot]andkakkokari-gtyih f40a74449d feat: add query parameter to users/following and users/followers APIs for filtering
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
2025-11-29 09:52:07 +00:00
copilot-swe-agent[bot] 6591199c05 Initial plan 2025-11-29 09:40:10 +00:00
24 changed files with 534 additions and 394 deletions
+11 -2
View File
@@ -110,10 +110,19 @@ port: 3000
# Changes how the server interpret the origin IP of the request.
#
# Any format supported by Fastify is accepted.
# Default: do not trust any proxies (i.e. trustProxy: false)
# Default: trust all proxies (i.e. trustProxy: true)
# See: https://fastify.dev/docs/latest/reference/server/#trustproxy
# To improve security, we recommend that you configure your settings appropriately.
# Incorrect configuration can cause issues such as difficulty signing in,
# so please configure your settings carefully.
#
# trustProxy: false
#trustProxy:
# - '10.0.0.0/8'
# - '172.16.0.0/12'
# - '192.168.0.0/16'
# - '127.0.0.1/32'
# - '::1/128'
# - 'fc00::/7'
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
+10 -6
View File
@@ -1,13 +1,17 @@
## Unreleased
## 2025.12.2
### Note
v2025.12.0で行われた「configの`trustProxy`のデフォルト値を`false`に変更」について、正しく環境に応じた設定を行わないとサインインが困難になるといった状態を緩和するために、以前のデフォルト値に戻す暫定対応を行いました。
**セキュリティを向上させるためには適切な設定を行うことを推奨しますが、間違った設定値を入れると上述のような不具合の原因となりますので、慎重に行ってください。**
### General
-
- 依存関係の更新
### Client
-
### Server
-
- Enhance: デッキのUI説明を追加
- Fix: バージョン表記のないPlayが正しく動作しない問題を修正
バージョン表記のないものは v0.x 系として実行されます。v1.x 系で動作させたい場合は必ずバージョン表記を含めてください。
## 2025.12.1
+9
View File
@@ -2890,6 +2890,15 @@ _deck:
usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります"
flexible: "幅を自動調整"
enableSyncBetweenDevicesForProfiles: "プロファイル情報のデバイス間同期を有効にする"
showHowToUse: "UIの説明を見る"
_howToUse:
addColumn_title: "カラム追加"
addColumn_description: "カラムの種類を選んで追加できます。"
settings_title: "UI設定"
settings_description: "デッキUIの詳細設定を行えます。"
switchProfile_title: "プロファイル切り替え"
switchProfile_description: "UIのレイアウトをプロファイルとして保存し、いつでも切り替えられるようにできます。"
_columns:
main: "メイン"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.12.1",
"version": "2025.12.2-beta.2",
"codename": "nasubi",
"repository": {
"type": "git",
+5 -5
View File
@@ -71,11 +71,11 @@
"utf-8-validate": "6.0.5"
},
"dependencies": {
"@aws-sdk/client-s3": "3.947.0",
"@aws-sdk/lib-storage": "3.947.0",
"@aws-sdk/client-s3": "3.948.0",
"@aws-sdk/lib-storage": "3.948.0",
"@discordapp/twemoji": "16.0.1",
"@fastify/accepts": "5.0.4",
"@fastify/cors": "11.1.0",
"@fastify/cors": "11.2.0",
"@fastify/express": "4.0.2",
"@fastify/http-proxy": "11.4.1",
"@fastify/multipart": "9.3.0",
@@ -166,7 +166,7 @@
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"systeminformation": "5.27.12",
"systeminformation": "5.27.14",
"tinycolor2": "1.6.0",
"tmp": "0.2.5",
"tsc-alias": "1.8.16",
@@ -207,7 +207,7 @@
"@types/rename": "1.0.7",
"@types/sanitize-html": "2.16.0",
"@types/semver": "7.7.1",
"@types/simple-oauth2": "5.0.7",
"@types/simple-oauth2": "5.0.8",
"@types/sinonjs__fake-timers": "15.0.1",
"@types/supertest": "6.0.3",
"@types/tinycolor2": "1.4.6",
+1 -1
View File
@@ -75,7 +75,7 @@ export class ServerService implements OnApplicationShutdown {
@bindThis
public async launch(): Promise<void> {
const fastify = Fastify({
trustProxy: this.config.trustProxy ?? false,
trustProxy: this.config.trustProxy ?? true,
logger: false,
});
this.#fastify = fastify;
@@ -313,12 +313,16 @@ export class ApiCallService implements OnApplicationShutdown {
}
if (ep.meta.limit) {
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
let limitActor: string;
let limitActor: string | null;
if (user) {
limitActor = user.id;
} else {
limitActor = getIpHash(request.ip);
if (request.ip === '::1' || request.ip === '127.0.0.1') {
console.warn('request ip is localhost, maybe caused by misconfiguration of trustProxy or reverse proxy');
limitActor = null;
} else {
limitActor = getIpHash(request.ip);
}
}
const limit = Object.assign({}, ep.meta.limit);
@@ -330,7 +334,7 @@ export class ApiCallService implements OnApplicationShutdown {
// TODO: 毎リクエスト計算するのもあれだしキャッシュしたい
const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1;
if (factor > 0) {
if (limitActor != null && factor > 0) {
// Rate limit
const rateLimit = await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor);
if (rateLimit != null) {
@@ -89,17 +89,21 @@ export class SigninApiService {
return { error };
}
if (request.ip === '::1' || request.ip === '127.0.0.1') {
console.warn('request ip is localhost, maybe caused by misconfiguration of trustProxy or reverse proxy');
} else {
// not more than 1 attempt per second and not more than 10 attempts per hour
const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
if (rateLimit != null) {
reply.code(429);
return {
error: {
message: 'Too many failed attempts to sign in. Try again later.',
code: 'TOO_MANY_AUTHENTICATION_FAILURES',
id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
},
};
const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
if (rateLimit != null) {
reply.code(429);
return {
error: {
message: 'Too many failed attempts to sign in. Try again later.',
code: 'TOO_MANY_AUTHENTICATION_FAILURES',
id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
},
};
}
}
if (typeof username !== 'string') {
@@ -84,19 +84,23 @@ export class SigninWithPasskeyApiService {
return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' });
};
try {
if (request.ip === '::1' || request.ip === '127.0.0.1') {
console.warn('request ip is localhost, maybe caused by misconfiguration of trustProxy or reverse proxy');
} else {
try {
// Not more than 1 API call per 250ms and not more than 100 attempts per 30min
// NOTE: 1 Sign-in require 2 API calls
await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip));
} catch (err) {
reply.code(429);
return {
error: {
message: 'Too many failed attempts to sign in. Try again later.',
code: 'TOO_MANY_AUTHENTICATION_FAILURES',
id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
},
};
await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip));
} catch (err) {
reply.code(429);
return {
error: {
message: 'Too many failed attempts to sign in. Try again later.',
code: 'TOO_MANY_AUTHENTICATION_FAILURES',
id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
},
};
}
}
// Initiate Passkey Auth challenge with context
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { IsNull } from 'typeorm';
import { Brackets, IsNull } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
@@ -12,6 +12,8 @@ import { FollowingEntityService } from '@/core/entities/FollowingEntityService.j
import { UtilityService } from '@/core/UtilityService.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -79,6 +81,7 @@ export const paramDef = {
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
query: { type: 'string', nullable: true },
},
},
],
@@ -100,6 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private followingEntityService: FollowingEntityService,
private queryService: QueryService,
private roleService: RoleService,
private userEntityService: UserEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy('userId' in ps
@@ -138,6 +142,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.andWhere('following.followeeId = :userId', { userId: user.id })
.innerJoinAndSelect('following.follower', 'follower');
if (ps.query) {
const searchQuery = ps.query;
const isUsername = searchQuery.startsWith('@') && !searchQuery.includes(' ') && searchQuery.indexOf('@', 1) === -1;
query.andWhere(new Brackets(qb => {
qb.where('follower.name ILIKE :query', { query: '%' + sqlLikeEscape(searchQuery) + '%' });
if (isUsername) {
qb.orWhere('follower.usernameLower LIKE :username', { username: sqlLikeEscape(searchQuery.replace('@', '').toLowerCase()) + '%' });
} else if (this.userEntityService.validateLocalUsername(searchQuery)) {
qb.orWhere('follower.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(searchQuery.toLowerCase()) + '%' });
}
}));
}
const followings = await query
.limit(ps.limit)
.getMany();
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { IsNull } from 'typeorm';
import { Brackets, IsNull } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/_.js';
import { birthdaySchema } from '@/models/User.js';
@@ -13,6 +13,8 @@ import { FollowingEntityService } from '@/core/entities/FollowingEntityService.j
import { UtilityService } from '@/core/UtilityService.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -86,9 +88,21 @@ export const paramDef = {
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
birthday: { ...birthdaySchema, nullable: true },
},
},
{
oneOf: [{
type: 'object',
properties: {
query: { type: 'string' },
},
}, {
type: 'object',
properties: {
birthday: { ...birthdaySchema, nullable: true },
},
}],
},
],
} as const;
@@ -108,6 +122,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private followingEntityService: FollowingEntityService,
private queryService: QueryService,
private roleService: RoleService,
private userEntityService: UserEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy('userId' in ps
@@ -146,7 +161,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.andWhere('following.followerId = :userId', { userId: user.id })
.innerJoinAndSelect('following.followee', 'followee');
if (ps.birthday) {
// query takes priority over birthday
if ('query' in ps && ps.query != null) {
const searchQuery = ps.query;
const isUsername = searchQuery.startsWith('@') && !searchQuery.includes(' ') && searchQuery.indexOf('@', 1) === -1;
query.andWhere(new Brackets(qb => {
qb.where('followee.name ILIKE :query', { query: '%' + sqlLikeEscape(searchQuery) + '%' });
if (isUsername) {
qb.orWhere('followee.usernameLower LIKE :username', { username: sqlLikeEscape(searchQuery.replace('@', '').toLowerCase()) + '%' });
} else if (this.userEntityService.validateLocalUsername(searchQuery)) {
qb.orWhere('followee.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(searchQuery.toLowerCase()) + '%' });
}
}));
} else if ('birthday' in ps && ps.birthday != null) {
try {
const birthday = ps.birthday.substring(5, 10);
const birthdayUserQuery = this.userProfilesRepository.createQueryBuilder('user_profile');
+3 -3
View File
@@ -26,7 +26,7 @@
"misskey-js": "workspace:*",
"punycode.js": "2.3.1",
"rollup": "4.53.3",
"sass": "1.95.0",
"sass": "1.95.1",
"shiki": "3.19.0",
"tinycolor2": "1.6.0",
"uuid": "13.0.0",
@@ -61,8 +61,8 @@
"tsx": "4.21.0",
"typescript": "5.9.3",
"vite-plugin-turbosnap": "1.0.3",
"vue-component-type-helpers": "3.1.7",
"vue-component-type-helpers": "3.1.8",
"vue-eslint-parser": "10.2.0",
"vue-tsc": "3.1.7"
"vue-tsc": "3.1.8"
}
}
+9 -9
View File
@@ -55,7 +55,7 @@
"is-file-animated": "1.0.2",
"json5": "2.2.3",
"matter-js": "0.20.0",
"mediabunny": "1.25.7",
"mediabunny": "1.25.8",
"mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*",
@@ -66,7 +66,7 @@
"qr-scanner": "1.4.2",
"rollup": "4.53.3",
"sanitize-html": "2.17.0",
"sass": "1.95.0",
"sass": "1.95.1",
"shiki": "3.19.0",
"textarea-caret": "3.1.0",
"three": "0.181.2",
@@ -82,7 +82,7 @@
"@misskey-dev/summaly": "5.2.5",
"@storybook/addon-essentials": "8.6.14",
"@storybook/addon-interactions": "8.6.14",
"@storybook/addon-links": "10.1.4",
"@storybook/addon-links": "10.1.5",
"@storybook/addon-mdx-gfm": "8.6.14",
"@storybook/addon-storysource": "8.6.14",
"@storybook/blocks": "8.6.14",
@@ -90,13 +90,13 @@
"@storybook/core-events": "8.6.14",
"@storybook/manager-api": "8.6.14",
"@storybook/preview-api": "8.6.14",
"@storybook/react": "10.1.4",
"@storybook/react-vite": "10.1.4",
"@storybook/react": "10.1.5",
"@storybook/react-vite": "10.1.5",
"@storybook/test": "8.6.14",
"@storybook/theming": "8.6.14",
"@storybook/types": "8.6.14",
"@storybook/vue3": "10.1.4",
"@storybook/vue3-vite": "10.1.4",
"@storybook/vue3": "10.1.5",
"@storybook/vue3-vite": "10.1.5",
"@tabler/icons-webfont": "3.35.0",
"@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0",
@@ -133,7 +133,7 @@
"react-dom": "19.2.1",
"seedrandom": "3.0.5",
"start-server-and-test": "2.1.3",
"storybook": "10.1.4",
"storybook": "10.1.5",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"tsx": "4.21.0",
"typescript": "5.9.3",
@@ -141,7 +141,7 @@
"vite-plugin-turbosnap": "1.0.3",
"vitest": "4.0.15",
"vitest-fetch-mock": "0.4.5",
"vue-component-type-helpers": "3.1.7",
"vue-component-type-helpers": "3.1.8",
"vue-eslint-parser": "10.2.0",
"vue-tsc": "3.1.8"
}
+2 -2
View File
@@ -193,7 +193,7 @@ function start() {
}
function getIsLegacy(version: string | null): boolean {
if (version == null) return false;
if (version == null) return true;
try {
return compareVersions(version, '1.0.0') < 0;
} catch {
@@ -206,7 +206,7 @@ async function run() {
if (!flash.value) return;
const version = utils.getLangVersion(flash.value.script);
const isLegacy = version != null && getIsLegacy(version);
const isLegacy = getIsLegacy(version);
const { Interpreter, Parser, values } = isLegacy ? (await import('@syuilo/aiscript-0-19-0') as any) : await import('@syuilo/aiscript');
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<SearchMarker :keywords="['signin', 'login', 'history', 'log']">
<FormSection>
<template #label><SearchLabel>{{ i18n.ts.signinHistory }}</SearchLabel></template>
<MkPagination :paginator="paginator" withControl>
<MkPagination :paginator="paginator" withControl :forceDisableInfiniteScroll="true">
<template #default="{items}">
<div>
<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
@@ -25,11 +25,16 @@ const props = defineProps<{
type: 'following' | 'followers';
}>();
const followingPaginator = markRaw(new Paginator('users/following', {
const followingPaginator = markRaw(new Paginator<'users/following', {
req: Misskey.entities.UsersFollowingRequest & { query?: string; };
res: Misskey.entities.UsersFollowingResponse;
}>('users/following', {
limit: 20,
computedParams: computed(() => ({
userId: props.user.id,
})),
canSearch: true,
searchParamName: 'query',
}));
const followersPaginator = markRaw(new Paginator('users/followers', {
@@ -37,6 +42,8 @@ const followersPaginator = markRaw(new Paginator('users/followers', {
computedParams: computed(() => ({
userId: props.user.id,
})),
canSearch: true,
searchParamName: 'query',
}));
</script>
+12 -9
View File
@@ -257,20 +257,23 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
this.rewriteRawState(key, v);
this.emit('committed', {
key,
value: v,
oldValue: this.s[key],
});
const record = this.getMatchedRecordOf(key);
const _save = () => {
this.save();
this.emit('committed', {
key,
value: v,
oldValue: this.s[key],
});
};
if (parseScope(record[0]).account == null && isAccountDependentKey(key) && currentAccount != null) {
this.profile.preferences[key].push([makeScope({
server: host,
account: currentAccount.id,
}), v, {}]);
this.save();
_save();
return;
}
@@ -278,12 +281,12 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
this.profile.preferences[key].push([makeScope({
server: host,
}), v, {}]);
this.save();
_save();
return;
}
record[1] = v;
this.save();
_save();
if (record[2].sync) {
// awaitの必要なし
+1
View File
@@ -12,6 +12,7 @@ export const TIPS = [
'clips',
'userLists',
'postForm',
'deck',
'tl.home',
'tl.local',
'tl.social',
+41 -7
View File
@@ -38,36 +38,39 @@ SPDX-License-Identifier: AGPL-3.0-only
@headerWheel="onWheel"
/>
</section>
<div v-if="layout.length === 0" class="_panel" :class="$style.onboarding">
<div v-if="layout.length === 0" class="_panel _gaps" :class="$style.onboarding">
<div>{{ i18n.ts._deck.introduction }}</div>
<div>{{ i18n.ts._deck.introduction2 }}</div>
<MkInfo v-if="!store.r.tips.value.deck" closable @close="closeTip('deck')">
<button class="_textButton" @click="showTour">{{ i18n.ts._deck.showHowToUse }}</button>
</MkInfo>
</div>
</div>
<div v-if="prefer.r['deck.menuPosition'].value === 'right'" :class="$style.sideMenu">
<div :class="$style.sideMenuTop">
<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button>
<button ref="swicthProfileButtonEl" v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button>
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
</div>
<div :class="$style.sideMenuMiddle">
<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.sideMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
<button ref="addColumnButtonEl" v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.sideMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
</div>
<div :class="$style.sideMenuBottom">
<button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.sideMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button>
<button ref="settingsButtonEl" v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.sideMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button>
</div>
</div>
</div>
<div v-if="prefer.r['deck.menuPosition'].value === 'bottom'" :class="$style.bottomMenu">
<div :class="$style.bottomMenuLeft">
<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.bottomMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button>
<button ref="swicthProfileButtonEl" v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.bottomMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button>
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.bottomMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
</div>
<div :class="$style.bottomMenuMiddle">
<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.bottomMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
<button ref="addColumnButtonEl" v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.bottomMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
</div>
<div :class="$style.bottomMenuRight">
<button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.bottomMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button>
<button ref="settingsButtonEl" v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.bottomMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button>
</div>
</div>
@@ -96,6 +99,7 @@ import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
import { deviceKind } from '@/utility/device-kind.js';
import { prefer } from '@/preferences.js';
import { store } from '@/store.js';
import XMainColumn from '@/ui/deck/main-column.vue';
import XTlColumn from '@/ui/deck/tl-column.vue';
import XAntennaColumn from '@/ui/deck/antenna-column.vue';
@@ -107,10 +111,13 @@ import XMentionsColumn from '@/ui/deck/mentions-column.vue';
import XDirectColumn from '@/ui/deck/direct-column.vue';
import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue';
import XChatColumn from '@/ui/deck/chat-column.vue';
import MkInfo from '@/components/MkInfo.vue';
import { mainRouter } from '@/router.js';
import { columns, layout, columnTypes, switchProfileMenu, addColumn as addColumnToStore, deleteProfile as deleteProfile_ } from '@/deck.js';
import { shouldSuggestRestoreBackup } from '@/preferences/utility.js';
import { shouldSuggestReload } from '@/utility/reload-suggest.js';
import { startTour } from '@/utility/tour.js';
import { closeTip } from '@/tips.js';
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
@@ -163,6 +170,9 @@ function showSettings() {
}
const columnsEl = useTemplateRef('columnsEl');
const addColumnButtonEl = useTemplateRef('addColumnButtonEl');
const settingsButtonEl = useTemplateRef('settingsButtonEl');
const swicthProfileButtonEl = useTemplateRef('swicthProfileButtonEl');
const addColumn = async (ev) => {
const { canceled, result: column } = await os.select({
@@ -218,6 +228,30 @@ async function deleteProfile() {
os.success();
}
function showTour() {
if (addColumnButtonEl.value == null ||
settingsButtonEl.value == null ||
swicthProfileButtonEl.value == null) {
return;
}
startTour([{
element: addColumnButtonEl.value,
title: i18n.ts._deck._howToUse.addColumn_title,
description: i18n.ts._deck._howToUse.addColumn_description,
}, {
element: settingsButtonEl.value,
title: i18n.ts._deck._howToUse.settings_title,
description: i18n.ts._deck._howToUse.settings_description,
}, {
element: swicthProfileButtonEl.value,
title: i18n.ts._deck._howToUse.switchProfile_title,
description: i18n.ts._deck._howToUse.switchProfile_description,
}]).then(() => {
closeTip('deck');
});
}
window.document.documentElement.style.overflowY = 'hidden';
window.document.documentElement.style.scrollBehavior = 'auto';
</script>
+30
View File
@@ -10936,6 +10936,36 @@ export interface Locale extends ILocale {
* プロファイル情報のデバイス間同期を有効にする
*/
"enableSyncBetweenDevicesForProfiles": string;
/**
* UIの説明を見る
*/
"showHowToUse": string;
"_howToUse": {
/**
* カラム追加
*/
"addColumn_title": string;
/**
* カラムの種類を選んで追加できます。
*/
"addColumn_description": string;
/**
* UI設定
*/
"settings_title": string;
/**
* デッキUIの詳細設定を行えます。
*/
"settings_description": string;
/**
* プロファイル切り替え
*/
"switchProfile_title": string;
/**
* UIのレイアウトをプロファイルとして保存し、いつでも切り替えられるようにできます。
*/
"switchProfile_description": string;
};
"_columns": {
/**
* メイン
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2025.12.1",
"version": "2025.12.2-beta.2",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
+5 -1
View File
@@ -34768,6 +34768,7 @@ export interface operations {
untilDate?: number;
/** @default 10 */
limit?: number;
query?: string | null;
};
};
};
@@ -34847,8 +34848,11 @@ export interface operations {
untilDate?: number;
/** @default 10 */
limit?: number;
} & ({
query?: string;
} | {
birthday?: string | null;
};
});
};
};
responses: {
+294 -314
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -35,5 +35,4 @@ ignorePatchFailures: false
minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack
minimumReleaseAgeExclude:
- '@syuilo/aiscript'
- vue-tsc
- '@vue/language-core'
- systeminformation # 脆弱性対応。そのうち消すこと