Compare commits

..

19 Commits

Author SHA1 Message Date
syuilo 6d00645bc7 fix(frontend): iPadOSのPWAでアプリを切り替えた際にウィジェット表示ボタンが消滅する問題を修正 2025-12-18 20:27:12 +09:00
syuilo baeed4bc80 perf(backend): lazy load systeminformation
systeminformationを必要とする機能を有効にしていないサーバーで無駄に読み込まれることが無いように
2025-12-18 20:05:20 +09:00
syuilo dba44daf9c 🎨 and refactor 2025-12-18 15:40:40 +09:00
syuilo 46e6dd99d1 chore: remove beta label from some features 2025-12-18 15:15:07 +09:00
syuilo f48af7f73b 🎨 2025-12-18 14:55:19 +09:00
syuilo 834e8b4c24 fix(frontend): デッキUIでメニュー位置を下にしているとプロファイル削除ボタンが表示されないのを修正 2025-12-18 14:55:16 +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
renovate[bot] 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
syuilo 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
28 changed files with 459 additions and 408 deletions

View File

@ -116,14 +116,13 @@ port: 3000
# Incorrect configuration can cause issues such as difficulty signing in, # Incorrect configuration can cause issues such as difficulty signing in,
# so please configure your settings carefully. # so please configure your settings carefully.
# #
#trustProxy: [ #trustProxy:
# '10.0.0.0/8' # - '10.0.0.0/8'
# '172.16.0.0/12' # - '172.16.0.0/12'
# '192.168.0.0/16' # - '192.168.0.0/16'
# '127.0.0.1/32' # - '127.0.0.1/32'
# '::1/128' # - '::1/128'
# 'fc00::/7' # - 'fc00::/7'
#]
# ┌──────────────────────────┐ # ┌──────────────────────────┐
#───┘ PostgreSQL configuration └──────────────────────────────── #───┘ PostgreSQL configuration └────────────────────────────────

View File

@ -6,13 +6,13 @@ v2025.12.0で行われた「configの`trustProxy`のデフォルト値を`false`
**セキュリティを向上させるためには適切な設定を行うことを推奨しますが、間違った設定値を入れると上述のような不具合の原因となりますので、慎重に行ってください。** **セキュリティを向上させるためには適切な設定を行うことを推奨しますが、間違った設定値を入れると上述のような不具合の原因となりますので、慎重に行ってください。**
### General ### General
- - 依存関係の更新
### Client ### Client
- - Enhance: デッキのUI説明を追加
- Fix: バージョン表記のないPlayが正しく動作しない問題を修正
### Server バージョン表記のないものは v0.x 系として実行されます。v1.x 系で動作させたい場合は必ずバージョン表記を含めてください。
- - Fix: デッキUIでメニュー位置を下にしているとプロファイル削除ボタンが表示されないのを修正
## 2025.12.1 ## 2025.12.1

View File

@ -2890,6 +2890,15 @@ _deck:
usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります" usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります"
flexible: "幅を自動調整" flexible: "幅を自動調整"
enableSyncBetweenDevicesForProfiles: "プロファイル情報のデバイス間同期を有効にする" enableSyncBetweenDevicesForProfiles: "プロファイル情報のデバイス間同期を有効にする"
showHowToUse: "UIの説明を見る"
_howToUse:
addColumn_title: "カラム追加"
addColumn_description: "カラムの種類を選んで追加できます。"
settings_title: "UI設定"
settings_description: "デッキUIの詳細設定を行えます。"
switchProfile_title: "プロファイル切り替え"
switchProfile_description: "UIのレイアウトをプロファイルとして保存し、いつでも切り替えられるようにできます。"
_columns: _columns:
main: "メイン" main: "メイン"

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.12.2-alpha.0", "version": "2025.12.2-beta.2",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -71,11 +71,11 @@
"utf-8-validate": "6.0.5" "utf-8-validate": "6.0.5"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.947.0", "@aws-sdk/client-s3": "3.948.0",
"@aws-sdk/lib-storage": "3.947.0", "@aws-sdk/lib-storage": "3.948.0",
"@discordapp/twemoji": "16.0.1", "@discordapp/twemoji": "16.0.1",
"@fastify/accepts": "5.0.4", "@fastify/accepts": "5.0.4",
"@fastify/cors": "11.1.0", "@fastify/cors": "11.2.0",
"@fastify/express": "4.0.2", "@fastify/express": "4.0.2",
"@fastify/http-proxy": "11.4.1", "@fastify/http-proxy": "11.4.1",
"@fastify/multipart": "9.3.0", "@fastify/multipart": "9.3.0",
@ -166,7 +166,7 @@
"slacc": "0.0.10", "slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"systeminformation": "5.27.12", "systeminformation": "5.27.14",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.5", "tmp": "0.2.5",
"tsc-alias": "1.8.16", "tsc-alias": "1.8.16",
@ -207,7 +207,7 @@
"@types/rename": "1.0.7", "@types/rename": "1.0.7",
"@types/sanitize-html": "2.16.0", "@types/sanitize-html": "2.16.0",
"@types/semver": "7.7.1", "@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/sinonjs__fake-timers": "15.0.1",
"@types/supertest": "6.0.3", "@types/supertest": "6.0.3",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",

View File

@ -7,7 +7,6 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import si from 'systeminformation';
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -84,6 +83,7 @@ export class AiService {
@bindThis @bindThis
private async getCpuFlags(): Promise<string[]> { private async getCpuFlags(): Promise<string[]> {
const si = await import('systeminformation');
const str = await si.cpuFlags(); const str = await si.cpuFlags();
return str.split(/\s+/); return str.split(/\s+/);
} }

View File

@ -4,13 +4,12 @@
*/ */
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import si from 'systeminformation';
import Xev from 'xev'; import Xev from 'xev';
import * as osUtils from 'os-utils'; import * as osUtils from 'os-utils';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { OnApplicationShutdown } from '@nestjs/common';
import { MiMeta } from '@/models/_.js'; import { MiMeta } from '@/models/_.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { OnApplicationShutdown } from '@nestjs/common';
const ev = new Xev(); const ev = new Xev();
@ -97,12 +96,14 @@ function cpuUsage(): Promise<number> {
// MEMORY STAT // MEMORY STAT
async function mem() { async function mem() {
const si = await import('systeminformation');
const data = await si.mem(); const data = await si.mem();
return data; return data;
} }
// NETWORK STAT // NETWORK STAT
async function net() { async function net() {
const si = await import('systeminformation');
const iface = await si.networkInterfaceDefault(); const iface = await si.networkInterfaceDefault();
const data = await si.networkStats(iface); const data = await si.networkStats(iface);
return data[0]; return data[0];
@ -110,5 +111,6 @@ async function net() {
// FS STAT // FS STAT
async function fs() { async function fs() {
const si = await import('systeminformation');
return await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 })); return await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
} }

View File

@ -4,15 +4,11 @@
*/ */
import * as os from 'node:os'; import * as os from 'node:os';
import sysUtils from 'systeminformation';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
export async function showMachineInfo(parentLogger: Logger) { export async function showMachineInfo(parentLogger: Logger) {
const logger = parentLogger.createSubLogger('machine'); const logger = parentLogger.createSubLogger('machine');
logger.debug(`Hostname: ${os.hostname()}`); logger.debug(`Hostname: ${os.hostname()}`);
logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`); logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`);
const mem = await sysUtils.mem(); logger.debug(`CPU: ${os.cpus().length} core MEM: ${(os.totalmem() / 1024 / 1024 / 1024).toFixed(1)}GB (available: ${(os.freemem() / 1024 / 1024 / 1024).toFixed(1)}GB)`);
const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1);
const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1);
logger.debug(`CPU: ${os.cpus().length} core MEM: ${totalmem}GB (available: ${availmem}GB)`);
} }

View File

@ -4,7 +4,6 @@
*/ */
import * as os from 'node:os'; import * as os from 'node:os';
import si from 'systeminformation';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
@ -112,6 +111,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) { ) {
super(meta, paramDef, async () => { super(meta, paramDef, async () => {
const si = await import('systeminformation');
const memStats = await si.mem(); const memStats = await si.mem();
const fsStats = await si.fsSize(); const fsStats = await si.fsSize();
const netInterface = await si.networkInterfaceDefault(); const netInterface = await si.networkInterfaceDefault();

View File

@ -4,7 +4,6 @@
*/ */
import * as os from 'node:os'; import * as os from 'node:os';
import si from 'systeminformation';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { MiMeta } from '@/models/_.js'; import { MiMeta } from '@/models/_.js';
@ -93,6 +92,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}, },
}; };
const si = await import('systeminformation');
const memStats = await si.mem(); const memStats = await si.mem();
const fsStats = await si.fsSize(); const fsStats = await si.fsSize();

View File

@ -26,7 +26,7 @@
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.53.3", "rollup": "4.53.3",
"sass": "1.95.0", "sass": "1.95.1",
"shiki": "3.19.0", "shiki": "3.19.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"uuid": "13.0.0", "uuid": "13.0.0",
@ -61,8 +61,8 @@
"tsx": "4.21.0", "tsx": "4.21.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"vite-plugin-turbosnap": "1.0.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-eslint-parser": "10.2.0",
"vue-tsc": "3.1.7" "vue-tsc": "3.1.8"
} }
} }

View File

@ -55,7 +55,7 @@
"is-file-animated": "1.0.2", "is-file-animated": "1.0.2",
"json5": "2.2.3", "json5": "2.2.3",
"matter-js": "0.20.0", "matter-js": "0.20.0",
"mediabunny": "1.25.7", "mediabunny": "1.25.8",
"mfm-js": "0.25.0", "mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*", "misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
@ -66,7 +66,7 @@
"qr-scanner": "1.4.2", "qr-scanner": "1.4.2",
"rollup": "4.53.3", "rollup": "4.53.3",
"sanitize-html": "2.17.0", "sanitize-html": "2.17.0",
"sass": "1.95.0", "sass": "1.95.1",
"shiki": "3.19.0", "shiki": "3.19.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.181.2", "three": "0.181.2",
@ -82,7 +82,7 @@
"@misskey-dev/summaly": "5.2.5", "@misskey-dev/summaly": "5.2.5",
"@storybook/addon-essentials": "8.6.14", "@storybook/addon-essentials": "8.6.14",
"@storybook/addon-interactions": "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-mdx-gfm": "8.6.14",
"@storybook/addon-storysource": "8.6.14", "@storybook/addon-storysource": "8.6.14",
"@storybook/blocks": "8.6.14", "@storybook/blocks": "8.6.14",
@ -90,13 +90,13 @@
"@storybook/core-events": "8.6.14", "@storybook/core-events": "8.6.14",
"@storybook/manager-api": "8.6.14", "@storybook/manager-api": "8.6.14",
"@storybook/preview-api": "8.6.14", "@storybook/preview-api": "8.6.14",
"@storybook/react": "10.1.4", "@storybook/react": "10.1.5",
"@storybook/react-vite": "10.1.4", "@storybook/react-vite": "10.1.5",
"@storybook/test": "8.6.14", "@storybook/test": "8.6.14",
"@storybook/theming": "8.6.14", "@storybook/theming": "8.6.14",
"@storybook/types": "8.6.14", "@storybook/types": "8.6.14",
"@storybook/vue3": "10.1.4", "@storybook/vue3": "10.1.5",
"@storybook/vue3-vite": "10.1.4", "@storybook/vue3-vite": "10.1.5",
"@tabler/icons-webfont": "3.35.0", "@tabler/icons-webfont": "3.35.0",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0", "@types/canvas-confetti": "1.9.0",
@ -133,7 +133,7 @@
"react-dom": "19.2.1", "react-dom": "19.2.1",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"start-server-and-test": "2.1.3", "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", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"tsx": "4.21.0", "tsx": "4.21.0",
"typescript": "5.9.3", "typescript": "5.9.3",
@ -141,7 +141,7 @@
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "4.0.15", "vitest": "4.0.15",
"vitest-fetch-mock": "0.4.5", "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-eslint-parser": "10.2.0",
"vue-tsc": "3.1.8" "vue-tsc": "3.1.8"
} }

View File

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div <div
:class="[$style.root, { [$style.modal]: modal, _popup: modal }]" :class="[$style.root]"
@dragover.stop="onDragover" @dragover.stop="onDragover"
@dragenter="onDragenter" @dragenter="onDragenter"
@dragleave="onDragleave" @dragleave="onDragleave"
@ -114,7 +114,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef, onUnmounted } from 'vue'; import { watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef, onUnmounted } from 'vue';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor'; import insertTextAtCursor from 'insert-text-at-cursor';
@ -161,8 +161,6 @@ import { closeTip } from '@/tips.js';
const $i = ensureSignin(); const $i = ensureSignin();
const modal = inject(DI.inModal, false);
const props = withDefaults(defineProps<PostFormProps & { const props = withDefaults(defineProps<PostFormProps & {
fixed?: boolean; fixed?: boolean;
autofocus?: boolean; autofocus?: boolean;
@ -1447,13 +1445,6 @@ defineExpose({
.root { .root {
position: relative; position: relative;
container-type: inline-size; container-type: inline-size;
&.modal {
width: 100%;
max-width: 520px;
overflow-x: clip;
overflow-y: auto;
}
} }
//#region header //#region header
@ -1722,7 +1713,8 @@ html[data-color-scheme=light] .preview {
min-width: 100%; min-width: 100%;
width: 100%; width: 100%;
min-height: 90px; min-height: 90px;
height: 100%; max-height: 500px;
field-sizing: content;
} }
.textCount { .textCount {

View File

@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPostForm <MkPostForm
ref="form" ref="form"
:class="$style.form" :class="$style.form"
class="_popup"
v-bind="props" v-bind="props"
autofocus autofocus
freezeAfterPosted freezeAfterPosted
@ -73,7 +74,8 @@ function onModalClosed() {
<style lang="scss" module> <style lang="scss" module>
.form { .form {
max-height: 100%; width: 100%;
max-width: 520px;
margin: 0 auto auto auto; margin: 0 auto auto auto;
} }
</style> </style>

View File

@ -122,7 +122,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<SearchMarker> <SearchMarker>
<MkFolder :defaultOpen="true"> <MkFolder :defaultOpen="true">
<template #icon><SearchIcon><i class="ti ti-bolt"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-bolt"></i></SearchIcon></template>
<template #label><SearchLabel>Misskey® Reactions Boost Technology (RBT)</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template> <template #label><SearchLabel>Misskey® Reactions Boost Technology (RBT)</SearchLabel></template>
<template v-if="rbtForm.savedState.enableReactionsBuffering" #suffix>Enabled</template> <template v-if="rbtForm.savedState.enableReactionsBuffering" #suffix>Enabled</template>
<template v-else #suffix>Disabled</template> <template v-else #suffix>Disabled</template>
<template v-if="rbtForm.modified.value" #footer> <template v-if="rbtForm.modified.value" #footer>

View File

@ -193,7 +193,7 @@ function start() {
} }
function getIsLegacy(version: string | null): boolean { function getIsLegacy(version: string | null): boolean {
if (version == null) return false; if (version == null) return true;
try { try {
return compareVersions(version, '1.0.0') < 0; return compareVersions(version, '1.0.0') < 0;
} catch { } catch {
@ -206,7 +206,7 @@ async function run() {
if (!flash.value) return; if (!flash.value) return;
const version = utils.getLangVersion(flash.value.script); 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'); const { Interpreter, Parser, values } = isLegacy ? (await import('@syuilo/aiscript-0-19-0') as any) : await import('@syuilo/aiscript');

View File

@ -90,7 +90,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<SearchMarker :keywords="['lockdown']"> <SearchMarker :keywords="['lockdown']">
<FormSection> <FormSection>
<template #label><SearchLabel>{{ i18n.ts.lockdown }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template> <template #label><SearchLabel>{{ i18n.ts.lockdown }}</SearchLabel></template>
<div class="_gaps_m"> <div class="_gaps_m">
<SearchMarker :keywords="['login', 'signin']"> <SearchMarker :keywords="['login', 'signin']">
@ -213,9 +213,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import type { MkSelectItem } from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import type { MkSelectItem } from '@/components/MkSelect.vue';
import FormSection from '@/components/form/section.vue'; import FormSection from '@/components/form/section.vue';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';

View File

@ -107,7 +107,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<SearchMarker :keywords="['follow', 'message']"> <SearchMarker :keywords="['follow', 'message']">
<MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false"> <MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false">
<template #label><SearchLabel>{{ i18n.ts._profile.followedMessage }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template> <template #label><SearchLabel>{{ i18n.ts._profile.followedMessage }}</SearchLabel></template>
<template #caption> <template #caption>
<div><SearchText>{{ i18n.ts._profile.followedMessageDescription }}</SearchText></div> <div><SearchText>{{ i18n.ts._profile.followedMessageDescription }}</SearchText></div>
<div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div> <div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div>

View File

@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<SearchMarker :keywords="['signin', 'login', 'history', 'log']"> <SearchMarker :keywords="['signin', 'login', 'history', 'log']">
<FormSection> <FormSection>
<template #label><SearchLabel>{{ i18n.ts.signinHistory }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.signinHistory }}</SearchLabel></template>
<MkPagination :paginator="paginator" withControl> <MkPagination :paginator="paginator" withControl :forceDisableInfiniteScroll="true">
<template #default="{items}"> <template #default="{items}">
<div> <div>
<div v-for="item in items" :key="item.id" v-panel class="timnmucd"> <div v-for="item in items" :key="item.id" v-panel class="timnmucd">

View File

@ -257,20 +257,23 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
this.rewriteRawState(key, v); this.rewriteRawState(key, v);
this.emit('committed', {
key,
value: v,
oldValue: this.s[key],
});
const record = this.getMatchedRecordOf(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) { if (parseScope(record[0]).account == null && isAccountDependentKey(key) && currentAccount != null) {
this.profile.preferences[key].push([makeScope({ this.profile.preferences[key].push([makeScope({
server: host, server: host,
account: currentAccount.id, account: currentAccount.id,
}), v, {}]); }), v, {}]);
this.save(); _save();
return; return;
} }
@ -278,12 +281,12 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
this.profile.preferences[key].push([makeScope({ this.profile.preferences[key].push([makeScope({
server: host, server: host,
}), v, {}]); }), v, {}]);
this.save(); _save();
return; return;
} }
record[1] = v; record[1] = v;
this.save(); _save();
if (record[2].sync) { if (record[2].sync) {
// awaitの必要なし // awaitの必要なし

View File

@ -12,6 +12,7 @@ export const TIPS = [
'clips', 'clips',
'userLists', 'userLists',
'postForm', 'postForm',
'deck',
'tl.home', 'tl.home',
'tl.local', 'tl.local',
'tl.social', 'tl.social',

View File

@ -14,6 +14,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-if="store.r.realtimeMode.value" class="ti ti-bolt ti-fw"></i> <i v-if="store.r.realtimeMode.value" class="ti ti-bolt ti-fw"></i>
<i v-else class="ti ti-bolt-off ti-fw"></i> <i v-else class="ti ti-bolt-off ti-fw"></i>
</button> </button>
<button v-if="!iconOnly && showWidgetButton" v-tooltip.noDelay.right="i18n.ts.widgets" class="_button" :class="[$style.widget]" @click="() => emit('widgetButtonClick')">
<i class="ti ti-apps ti-fw"></i>
</button>
</div> </div>
<div :class="$style.middle"> <div :class="$style.middle">
<MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact> <MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact>
@ -51,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkA> </MkA>
</div> </div>
<div :class="$style.bottom"> <div :class="$style.bottom">
<button v-if="showWidgetButton" v-tooltip.noDelay.right="i18n.ts.widgets" class="_button" :class="[$style.widget]" @click="() => emit('widgetButtonClick')"> <button v-if="iconOnly && showWidgetButton" v-tooltip.noDelay.right="i18n.ts.widgets" class="_button" :class="[$style.widget]" @click="() => emit('widgetButtonClick')">
<i class="ti ti-apps ti-fw"></i> <i class="ti ti-apps ti-fw"></i>
</button> </button>
<button v-if="iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode"> <button v-if="iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode">
@ -436,6 +439,12 @@ function menuEdit() {
} }
} }
.widget {
display: inline-block;
width: var(--top-height);
margin-left: auto;
}
.bottom { .bottom {
position: sticky; position: sticky;
bottom: 0; bottom: 0;

View File

@ -38,36 +38,39 @@ SPDX-License-Identifier: AGPL-3.0-only
@headerWheel="onWheel" @headerWheel="onWheel"
/> />
</section> </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.introduction }}</div>
<div>{{ i18n.ts._deck.introduction2 }}</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> </div>
<div v-if="prefer.r['deck.menuPosition'].value === 'right'" :class="$style.sideMenu"> <div v-if="prefer.r['deck.menuPosition'].value === 'right'" :class="$style.sideMenu">
<div :class="$style.sideMenuTop"> <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> <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>
<div :class="$style.sideMenuMiddle"> <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>
<div :class="$style.sideMenuBottom"> <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>
</div> </div>
<div v-if="prefer.r['deck.menuPosition'].value === 'bottom'" :class="$style.bottomMenu"> <div v-if="prefer.r['deck.menuPosition'].value === 'bottom'" :class="$style.bottomMenu">
<div :class="$style.bottomMenuLeft"> <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.top="`${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> <button v-tooltip.noDelay.top="i18n.ts._deck.deleteProfile" :class="$style.bottomMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
</div> </div>
<div :class="$style.bottomMenuMiddle"> <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.top="i18n.ts._deck.addColumn" :class="$style.bottomMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
</div> </div>
<div :class="$style.bottomMenuRight"> <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.top="i18n.ts.settings" :class="$style.bottomMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings-2"></i></button>
</div> </div>
</div> </div>
@ -96,6 +99,7 @@ import { $i } from '@/i.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { deviceKind } from '@/utility/device-kind.js'; import { deviceKind } from '@/utility/device-kind.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { store } from '@/store.js';
import XMainColumn from '@/ui/deck/main-column.vue'; import XMainColumn from '@/ui/deck/main-column.vue';
import XTlColumn from '@/ui/deck/tl-column.vue'; import XTlColumn from '@/ui/deck/tl-column.vue';
import XAntennaColumn from '@/ui/deck/antenna-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 XDirectColumn from '@/ui/deck/direct-column.vue';
import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue'; import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue';
import XChatColumn from '@/ui/deck/chat-column.vue'; import XChatColumn from '@/ui/deck/chat-column.vue';
import MkInfo from '@/components/MkInfo.vue';
import { mainRouter } from '@/router.js'; import { mainRouter } from '@/router.js';
import { columns, layout, columnTypes, switchProfileMenu, addColumn as addColumnToStore, deleteProfile as deleteProfile_ } from '@/deck.js'; import { columns, layout, columnTypes, switchProfileMenu, addColumn as addColumnToStore, deleteProfile as deleteProfile_ } from '@/deck.js';
import { shouldSuggestRestoreBackup } from '@/preferences/utility.js'; import { shouldSuggestRestoreBackup } from '@/preferences/utility.js';
import { shouldSuggestReload } from '@/utility/reload-suggest.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 XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue')); const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
@ -163,6 +170,9 @@ function showSettings() {
} }
const columnsEl = useTemplateRef('columnsEl'); const columnsEl = useTemplateRef('columnsEl');
const addColumnButtonEl = useTemplateRef('addColumnButtonEl');
const settingsButtonEl = useTemplateRef('settingsButtonEl');
const swicthProfileButtonEl = useTemplateRef('swicthProfileButtonEl');
const addColumn = async (ev) => { const addColumn = async (ev) => {
const { canceled, result: column } = await os.select({ const { canceled, result: column } = await os.select({
@ -218,6 +228,30 @@ async function deleteProfile() {
os.success(); 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.overflowY = 'hidden';
window.document.documentElement.style.scrollBehavior = 'auto'; window.document.documentElement.style.scrollBehavior = 'auto';
</script> </script>
@ -345,7 +379,7 @@ window.document.documentElement.style.scrollBehavior = 'auto';
} }
.bottomMenuButton { .bottomMenuButton {
display: block; display: inline-block;
height: 100%; height: 100%;
aspect-ratio: 1; aspect-ratio: 1;
} }

View File

@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XTitlebar v-if="prefer.r.showTitlebar.value" style="flex-shrink: 0;"/> <XTitlebar v-if="prefer.r.showTitlebar.value" style="flex-shrink: 0;"/>
<div :class="$style.nonTitlebarArea"> <div :class="$style.nonTitlebarArea">
<XSidebar v-if="!isMobile" :class="$style.sidebar" :showWidgetButton="!isDesktop" @widgetButtonClick="widgetsShowing = true"/> <XSidebar v-if="!isMobile" :class="$style.sidebar" :showWidgetButton="!showWidgetsSide" @widgetButtonClick="widgetsShowing = true"/>
<div :class="[$style.contents, !isMobile && prefer.r.showTitlebar.value ? $style.withSidebarAndTitlebar : null]" @contextmenu.stop="onContextmenu"> <div :class="[$style.contents, !isMobile && prefer.r.showTitlebar.value ? $style.withSidebarAndTitlebar : null]" @contextmenu.stop="onContextmenu">
<div> <div>
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XMobileFooterMenu v-if="isMobile" ref="navFooter" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/> <XMobileFooterMenu v-if="isMobile" ref="navFooter" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
</div> </div>
<div v-if="isDesktop && !pageMetadata?.needWideArea" :class="$style.widgets"> <div v-if="showWidgetsSide && !pageMetadata?.needWideArea" :class="$style.widgets">
<XWidgets/> <XWidgets/>
</div> </div>
</div> </div>
@ -64,7 +64,8 @@ const DESKTOP_THRESHOLD = 1100;
const MOBILE_THRESHOLD = 500; const MOBILE_THRESHOLD = 500;
// UI deviceKind === 'desktop' // UI deviceKind === 'desktop'
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD); const showWidgetsSide = window.innerWidth >= DESKTOP_THRESHOLD;
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD); const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD; isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
@ -102,14 +103,6 @@ if (window.innerWidth > 1024) {
} }
} }
onMounted(() => {
if (!isDesktop.value) {
window.addEventListener('resize', () => {
if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
}, { passive: true });
}
});
const onContextmenu = (ev) => { const onContextmenu = (ev) => {
if (isLink(ev.target)) return; if (isLink(ev.target)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;

View File

@ -10936,6 +10936,36 @@ export interface Locale extends ILocale {
* *
*/ */
"enableSyncBetweenDevicesForProfiles": string; "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": { "_columns": {
/** /**
* *

View File

@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "misskey-js", "name": "misskey-js",
"version": "2025.12.2-alpha.0", "version": "2025.12.2-beta.2",
"description": "Misskey SDK for JavaScript", "description": "Misskey SDK for JavaScript",
"license": "MIT", "license": "MIT",
"main": "./built/index.js", "main": "./built/index.js",

File diff suppressed because it is too large Load Diff

View File

@ -35,5 +35,4 @@ ignorePatchFailures: false
minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack
minimumReleaseAgeExclude: minimumReleaseAgeExclude:
- '@syuilo/aiscript' - '@syuilo/aiscript'
- vue-tsc - systeminformation # 脆弱性対応。そのうち消すこと
- '@vue/language-core'