Merge branch 'develop' into enh-instanceticker-alt

This commit is contained in:
かっこかり 2025-05-08 11:53:05 +09:00 committed by GitHub
commit bcbec10c78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 181 additions and 63 deletions

View File

@ -1,13 +1,22 @@
## 2025.5.0
### Note
- DockerのNode.jsが22.15.0に更新されました
## Unreleased
### General
-
### Client
- Feat: マウスでもタイムラインを引っ張って更新できるように
-
### Server
-
## 2025.5.0
### Note
- DockerのNode.jsが22.15.0に更新されました
### Client
- Feat: マウスで中ボタンドラッグによりタイムラインを引っ張って更新できるように
- アクセシビリティ設定からオフにすることもできます
- Enhance: タイムラインのパフォーマンスを向上
- Enhance: バックアップされた設定のプロファイルを削除できるように

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.5.0-rc.0",
"version": "2025.5.0",
"codename": "nasubi",
"repository": {
"type": "git",

View File

@ -11,18 +11,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div
v-else-if="!input && !select"
:class="[$style.icon, {
[$style.type_success]: type === 'success',
[$style.type_error]: type === 'error',
[$style.type_warning]: type === 'warning',
[$style.type_info]: type === 'info',
}]"
:class="[$style.icon]"
>
<i v-if="type === 'success'" :class="$style.iconInner" class="ti ti-check"></i>
<i v-else-if="type === 'error'" :class="$style.iconInner" class="ti ti-circle-x"></i>
<i v-else-if="type === 'warning'" :class="$style.iconInner" class="ti ti-alert-triangle"></i>
<i v-else-if="type === 'info'" :class="$style.iconInner" class="ti ti-info-circle"></i>
<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i>
<MkSystemIcon v-if="type === 'success'" :class="$style.iconInner" style="width: 45px;" type="success"/>
<MkSystemIcon v-else-if="type === 'error'" :class="$style.iconInner" style="width: 45px;" type="error"/>
<MkSystemIcon v-else-if="type === 'warning'" :class="$style.iconInner" style="width: 45px;" type="warn"/>
<MkSystemIcon v-else-if="type === 'info'" :class="$style.iconInner" style="width: 45px;" type="info"/>
<MkSystemIcon v-else-if="type === 'question'" :class="$style.iconInner" style="width: 45px;" type="question"/>
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
</div>
<header v-if="title" :class="$style.title" class="_selectable"><Mfm :text="title"/></header>
@ -202,22 +197,6 @@ function onInputKeydown(evt: KeyboardEvent) {
margin: 0 auto;
}
.type_info {
color: #55c4dd;
}
.type_success {
color: var(--MI_THEME-success);
}
.type_error {
color: var(--MI_THEME-error);
}
.type_warning {
color: var(--MI_THEME-warn);
}
.title {
margin: 0 0 8px 0;
font-weight: bold;

View File

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
<div :class="[$style.root, { [$style.warn]: type === 'notFound', [$style.error]: type === 'error' }]" class="_gaps_m">
<div :class="[$style.root, { [$style.warn]: type === 'notFound', [$style.error]: type === 'error' }]" class="_gaps">
<img v-if="type === 'empty' && instance.infoImageUrl" :src="instance.infoImageUrl" draggable="false" :class="$style.img"/>
<MkSystemIcon v-else-if="type === 'empty'" type="info" :class="$style.icon"/>
<img v-if="type === 'notFound' && instance.notFoundImageUrl" :src="instance.notFoundImageUrl" draggable="false" :class="$style.img"/>
@ -46,8 +46,8 @@ const props = defineProps<{
}
.icon {
width: 50px;
height: 50px;
width: 65px;
height: 65px;
margin: 0 auto;
}
</style>

View File

@ -4,20 +4,29 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<svg v-if="type === 'info'" :class="[$style.icon, $style.info]" viewBox="0 0 128 128">
<path d="M64,92L64,56" style="--l:40;" :class="[$style.line, $style.anim]"/>
<circle cx="64" cy="36" r="4" :class="[$style.fill]"/>
<circle cx="64" cy="64" r="56" style="--l:400;" :class="[$style.line, $style.anim]"/>
<svg v-if="type === 'info'" :class="[$style.icon, $style.info]" viewBox="0 0 160 160">
<path d="M80,108L80,72" style="--l:37;" :class="[$style.line, $style.animLine]"/>
<path d="M80,52L80,52" :class="[$style.line, $style.animFade]"/>
<circle cx="80" cy="80" r="56" style="--l:350;" :class="[$style.line, $style.animCircle]"/>
</svg>
<svg v-else-if="type === 'question'" :class="[$style.icon, $style.question]" viewBox="0 0 128 128">
<path d="M63.991,76L64,68C73.333,68 80,60.333 80,52C80,43.667 73.336,36 64,36C54.664,36 47.983,43.664 47.983,52" style="--l:85;" :class="[$style.line, $style.anim]"/>
<circle cx="64" cy="92" r="4" :class="[$style.fill]"/>
<circle cx="64" cy="64" r="56" style="--l:400;" :class="[$style.line, $style.anim]"/>
<svg v-else-if="type === 'question'" :class="[$style.icon, $style.question]" viewBox="0 0 160 160">
<path d="M80,92L79.991,84C88.799,83.98 96,76.962 96,68C96,59.038 88.953,52 79.991,52C71.03,52 64,59.038 64,68" style="--l:85;" :class="[$style.line, $style.animLine]"/>
<path d="M80,108L80,108" :class="[$style.line, $style.animFade]"/>
<circle cx="80" cy="80" r="56" style="--l:350;" :class="[$style.line, $style.animCircle]"/>
</svg>
<svg v-else-if="type === 'error'" :class="[$style.icon, $style.error]" viewBox="0 0 128 128">
<path d="M47.029,47.029L80.971,80.971" style="--l:55;--duration:0.3s;" :class="[$style.line, $style.anim]"/>
<path d="M80.971,47.029L47.029,80.971" style="--l:55;--duration:0.3s;--delay:0.2s;" :class="[$style.line, $style.anim]"/>
<circle cx="64" cy="64" r="56" style="--l:400;" :class="[$style.line, $style.anim]"/>
<svg v-else-if="type === 'success'" :class="[$style.icon, $style.success]" viewBox="0 0 160 160">
<path d="M62,80L74,92L98,68" style="--l:50;" :class="[$style.line, $style.animLine]"/>
<circle cx="80" cy="80" r="56" style="--l:350;" :class="[$style.line, $style.animCircleSuccess]"/>
</svg>
<svg v-else-if="type === 'warn'" :class="[$style.icon, $style.warn]" viewBox="0 0 160 160">
<path d="M80,64L80,88" style="--l:27;" :class="[$style.line, $style.animLine]"/>
<path d="M80,108L80,108" :class="[$style.line, $style.animFade]"/>
<path d="M92,28L144,116C148.709,124.65 144.083,135.82 136,136L24,136C15.917,135.82 11.291,124.65 16,116L68,28C73.498,19.945 86.771,19.945 92,28Z" style="--l:395;" :class="[$style.line, $style.animLine]"/>
</svg>
<svg v-else-if="type === 'error'" :class="[$style.icon, $style.error]" viewBox="0 0 160 160">
<path d="M63,63L96,96" style="--l:47;--duration:0.3s;" :class="[$style.line, $style.animLine]"/>
<path d="M96,63L63,96" style="--l:47;--duration:0.3s;--delay:0.2s;" :class="[$style.line, $style.animLine]"/>
<circle cx="80" cy="80" r="56" style="--l:350;" :class="[$style.line, $style.animCircle]"/>
</svg>
</template>
@ -25,18 +34,25 @@ SPDX-License-Identifier: AGPL-3.0-only
import {} from 'vue';
const props = defineProps<{
type: 'info' | 'question' | 'warn' | 'error';
type: 'info' | 'question' | 'success' | 'warn' | 'error';
}>();
</script>
<style lang="scss" module>
.icon {
color: var(--MI_THEME-accent);
stroke-linecap: round;
stroke-linejoin: round;
&.info {
color: var(--MI_THEME-accent);
}
&.question {
color: var(--MI_THEME-warn);
color: var(--MI_THEME-fg);
}
&.success {
color: var(--MI_THEME-success);
}
&.warn {
@ -52,25 +68,69 @@ const props = defineProps<{
fill: none;
stroke: currentColor;
stroke-width: 8px;
shape-rendering: geometricPrecision;
}
.fill {
fill: currentColor;
}
.anim {
.animLine {
stroke-dasharray: var(--l);
stroke-dashoffset: var(--l);
animation: line-animation var(--duration, 0.5s) ease-out 1 forwards;
animation: line var(--duration, 0.5s) cubic-bezier(0,0,.25,1) 1 forwards;
animation-delay: var(--delay, 0s);
}
@keyframes line-animation {
.animCircle {
stroke-dasharray: var(--l);
stroke-dashoffset: var(--l);
animation: line var(--duration, 0.5s) cubic-bezier(0,0,.25,1) 1 forwards;
animation-delay: var(--delay, 0s);
transform-origin: center;
transform: rotate(-90deg);
}
.animCircleSuccess {
stroke-dasharray: var(--l);
stroke-dashoffset: var(--l);
animation: circleSuccess var(--duration, 0.5s) cubic-bezier(0,0,.25,1) 1 forwards;
animation-delay: var(--delay, 0s);
transform-origin: center;
}
.animFade {
opacity: 0;
animation: fade-in var(--duration, 0.5s) cubic-bezier(0,0,.25,1) 1 forwards;
animation-delay: var(--delay, 0s);
}
@keyframes line {
0% {
stroke-dashoffset: var(--l);
opacity: 0;
}
100% {
stroke-dashoffset: 0;
opacity: 1;
}
}
@keyframes circleSuccess {
0% {
stroke-dashoffset: var(--l);
opacity: 0;
transform: rotate(-90deg);
}
100% {
stroke-dashoffset: 0;
opacity: 1;
transform: rotate(90deg);
}
}
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>

View File

@ -0,0 +1,66 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<PageWithHeader>
<div class="_spacer" style="--MI_SPACER-w: 600px;">
<div class="_gaps_m">
<MkResult v-if="resultType === 'empty'" type="empty"/>
<MkResult v-if="resultType === 'notFound'" type="notFound"/>
<MkResult v-if="resultType === 'error'" type="error"/>
<MkSelect
v-model="resultType" :items="[
{ label: 'empty', value: 'empty' },
{ label: 'notFound', value: 'notFound' },
{ label: 'error', value: 'error' },
]"
></MkSelect>
<MkSystemIcon v-if="iconType === 'info'" type="info" style="width: 150px;"/>
<MkSystemIcon v-if="iconType === 'question'" type="question" style="width: 150px;"/>
<MkSystemIcon v-if="iconType === 'success'" type="success" style="width: 150px;"/>
<MkSystemIcon v-if="iconType === 'warn'" type="warn" style="width: 150px;"/>
<MkSystemIcon v-if="iconType === 'error'" type="error" style="width: 150px;"/>
<MkSelect
v-model="iconType" :items="[
{ label: 'info', value: 'info' },
{ label: 'question', value: 'question' },
{ label: 'success', value: 'success' },
{ label: 'warn', value: 'warn' },
{ label: 'error', value: 'error' },
]"
></MkSelect>
<div class="_buttons">
<MkButton @click="os.alert({ type: 'error', title: 'Error', text: 'error' })">Error</MkButton>
<MkButton @click="os.alert({ type: 'warning', title: 'Warning', text: 'warning' })">Warning</MkButton>
<MkButton @click="os.alert({ type: 'info', title: 'Info', text: 'info' })">Info</MkButton>
<MkButton @click="os.alert({ type: 'success', title: 'Success', text: 'success' })">Success</MkButton>
<MkButton @click="os.alert({ type: 'question', title: 'Question', text: 'question' })">Question</MkButton>
</div>
</div>
</div>
</PageWithHeader>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { definePage } from '@/page.js';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkLink from '@/components/MkLink.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
const resultType = ref('empty');
const iconType = ref('info');
definePage(() => ({
title: 'DEBUG ROOM',
icon: 'ti ti-help-circle',
}));
</script>

View File

@ -86,7 +86,7 @@ const storageProvider: StorageProvider = {
});
},
cloudGets: async (ctx) => {
cloudGetBulk: async (ctx) => {
// TODO: 値の取得を1つのリクエストで済ませたい(バックエンド側でAPIの新設が必要)
const fetchings = ctx.needs.map(need => storageProvider.cloudGet(need).then(res => [need.key, res] as const));
const cloudDatas = await Promise.all(fetchings);

View File

@ -79,7 +79,7 @@ export type PreferencesProfile = {
export type StorageProvider = {
save: (ctx: { profile: PreferencesProfile; }) => void;
cloudGets: <K extends keyof PREF>(ctx: { needs: { key: K; scope: Scope; }[] }) => Promise<Partial<Record<K, ValueOf<K>>>>;
cloudGetBulk: <K extends keyof PREF>(ctx: { needs: { key: K; scope: Scope; }[] }) => Promise<Partial<Record<K, ValueOf<K>>>>;
cloudGet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; }) => Promise<{ value: ValueOf<K>; } | null>;
cloudSet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; value: ValueOf<K>; }) => Promise<void>;
};
@ -240,7 +240,7 @@ export class PreferencesManager {
}
}
const cloudValues = await this.storageProvider.cloudGets({ needs });
const cloudValues = await this.storageProvider.cloudGetBulk({ needs });
for (const _key in PREF_DEF) {
const key = _key as keyof PREF;

View File

@ -140,7 +140,7 @@ function importProfile() {
export async function cloudBackup() {
if ($i == null) return;
if (!canAutoBackup()) {
throw new Error('Profile name is not set');
throw new Error('cannot auto backup for this profile');
}
await misskeyApi('i/registry/set', {

View File

@ -591,6 +591,10 @@ export const ROUTE_DEF = [{
path: '/reversi/g/:gameId',
component: page(() => import('@/pages/reversi/game.vue')),
loginRequired: false,
}, {
path: '/debug',
component: page(() => import('@/pages/debug.vue')),
loginRequired: false,
}, {
// テスト用リダイレクト設定。ログイン中ユーザのプロフィールにリダイレクトする
path: '/redirect-test',

View File

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