1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-14 09:55:38 +02:00

Merge branch 'develop' into room

This commit is contained in:
syuilo
2026-03-15 16:38:33 +09:00
111 changed files with 3292 additions and 2748 deletions

View File

@@ -28,7 +28,7 @@
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.3",
"@rollup/pluginutils": "5.3.0",
"@sentry/vue": "10.38.0",
"@sentry/vue": "10.40.0",
"@syuilo/aiscript": "1.2.1",
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
"@twemoji/parser": "16.0.0",
@@ -43,13 +43,13 @@
"chartjs-chart-matrix": "3.0.0",
"chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.2.0",
"chromatic": "15.1.0",
"chromatic": "15.2.0",
"compare-versions": "6.1.1",
"cropperjs": "2.1.0",
"date-fns": "4.1.0",
"eventemitter3": "5.0.4",
"execa": "9.6.1",
"exifreader": "4.36.1",
"exifreader": "4.36.2",
"frontend-shared": "workspace:*",
"i18n": "workspace:*",
"icons-subsetter": "workspace:*",
@@ -59,7 +59,7 @@
"is-file-animated": "1.0.2",
"json5": "2.2.3",
"matter-js": "0.20.0",
"mediabunny": "1.34.2",
"mediabunny": "1.35.1",
"mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*",
@@ -68,38 +68,38 @@
"punycode.js": "2.3.1",
"qr-code-styling": "1.9.2",
"qr-scanner": "1.4.2",
"rollup": "4.57.1",
"sanitize-html": "2.17.0",
"rollup": "4.59.0",
"sanitize-html": "2.17.1",
"sass": "1.97.3",
"shiki": "3.22.0",
"shiki": "3.23.0",
"textarea-caret": "3.1.0",
"three": "0.182.0",
"three": "0.183.2",
"throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0",
"v-code-diff": "1.13.1",
"vite": "7.3.1",
"vue": "3.5.28",
"vue": "3.5.29",
"wanakana": "5.3.1"
},
"devDependencies": {
"@misskey-dev/summaly": "5.2.5",
"@storybook/addon-essentials": "8.6.15",
"@storybook/addon-interactions": "8.6.15",
"@storybook/addon-links": "10.2.8",
"@storybook/addon-mdx-gfm": "8.6.15",
"@storybook/addon-storysource": "8.6.15",
"@storybook/blocks": "8.6.15",
"@storybook/components": "8.6.15",
"@storybook/core-events": "8.6.15",
"@storybook/manager-api": "8.6.15",
"@storybook/preview-api": "8.6.15",
"@storybook/react": "10.2.8",
"@storybook/react-vite": "10.2.8",
"@storybook/test": "8.6.15",
"@storybook/theming": "8.6.15",
"@storybook/types": "8.6.15",
"@storybook/vue3": "10.2.8",
"@storybook/vue3-vite": "10.2.8",
"@storybook/addon-essentials": "8.6.17",
"@storybook/addon-interactions": "8.6.17",
"@storybook/addon-links": "10.2.13",
"@storybook/addon-mdx-gfm": "8.6.17",
"@storybook/addon-storysource": "8.6.17",
"@storybook/blocks": "8.6.17",
"@storybook/components": "8.6.17",
"@storybook/core-events": "8.6.17",
"@storybook/manager-api": "8.6.17",
"@storybook/preview-api": "8.6.17",
"@storybook/react": "10.2.13",
"@storybook/react-vite": "10.2.13",
"@storybook/test": "8.6.17",
"@storybook/theming": "8.6.17",
"@storybook/types": "8.6.17",
"@storybook/vue3": "10.2.13",
"@storybook/vue3-vite": "10.2.13",
"@tabler/icons-webfont": "3.35.0",
"@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0",
@@ -107,46 +107,46 @@
"@types/insert-text-at-cursor": "0.3.2",
"@types/matter-js": "0.20.2",
"@types/micromatch": "4.0.10",
"@types/node": "24.10.13",
"@types/node": "24.11.0",
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/sanitize-html": "2.16.0",
"@types/seedrandom": "3.0.8",
"@types/textarea-caret": "3.0.4",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@typescript-eslint/eslint-plugin": "8.55.0",
"@typescript-eslint/parser": "8.55.0",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@vitest/coverage-v8": "4.0.18",
"@vue/compiler-core": "3.5.28",
"acorn": "8.15.0",
"@vue/compiler-core": "3.5.29",
"acorn": "8.16.0",
"astring": "1.9.0",
"cross-env": "10.1.0",
"cypress": "15.10.0",
"cypress": "15.11.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.8.0",
"estree-walker": "3.0.3",
"happy-dom": "20.6.1",
"happy-dom": "20.7.0",
"intersection-observer": "0.12.2",
"magic-string": "0.30.21",
"micromatch": "4.0.8",
"minimatch": "10.2.2",
"minimatch": "10.2.4",
"msw": "2.12.10",
"msw-storybook-addon": "2.0.6",
"nodemon": "3.1.11",
"nodemon": "3.1.14",
"prettier": "3.8.1",
"react": "19.2.4",
"react-dom": "19.2.4",
"seedrandom": "3.0.5",
"start-server-and-test": "2.1.3",
"storybook": "10.2.8",
"start-server-and-test": "2.1.5",
"storybook": "10.2.13",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"tsx": "4.21.0",
"vite-plugin-glsl": "1.5.5",
"vite-plugin-turbosnap": "1.0.3",
"vitest": "4.0.18",
"vitest-fetch-mock": "0.4.5",
"vue-component-type-helpers": "3.2.4",
"vue-component-type-helpers": "3.2.5",
"vue-eslint-parser": "10.4.0",
"vue-tsc": "3.2.4"
"vue-tsc": "3.2.5"
}
}

View File

@@ -109,6 +109,7 @@ function onDragstart(ev: DragEvent, item: T) {
// Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう
// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
// SEE: https://issues.chromium.org/issues/41150279
window.setTimeout(() => {
dragging.value = true;
}, 10);

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m">
<div v-if="Object.values(form).filter(item => typeof item.hidden !== 'boolean' || item.hidden === true).length > 0" class="_gaps_m">
<template v-for="v, k in form">
<template v-if="typeof v.hidden == 'function' ? v.hidden(values) : v.hidden"></template>
<MkInput v-else-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1" :manualSave="v.manualSave" @savingStateChange="(changed, invalid) => onSavingStateChange(k, changed, invalid)">

View File

@@ -233,7 +233,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
if (!useAnim) {
return genEl(token.children, scale);
}
return h(MkSparkle, {}, genEl(token.children, scale));
return h(MkSparkle, {}, { default: () => genEl(token.children, scale) });
}
case 'rotate': {
const degrees = safeParseFloat(token.props.args.deg) ?? 90;
@@ -363,7 +363,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
url: token.props.url,
rel: 'nofollow noopener',
navigationBehavior: props.linkNavigationBehavior,
}, genEl(token.children, scale, true))];
}, { default: () => genEl(token.children, scale, true) })];
}
case 'mention': {
@@ -381,7 +381,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`,
style: 'color:var(--MI_THEME-hashtag);',
behavior: props.linkNavigationBehavior,
}, `#${token.props.hashtag}`)];
}, { default: () => `#${token.props.hashtag}` })];
}
case 'blockCode': {

View File

@@ -82,8 +82,10 @@ export class Pizzax<T extends StateDef> {
this.r = {} as ReactiveState<T>;
for (const [k, v] of Object.entries(def) as [keyof T, T[keyof T]['default']][]) {
this.s[k] = v.default;
this.r[k] = ref(v.default);
// 参照渡しになるのを防ぐためclone
const defaultValue = deepClone(v.default);
this.s[k] = defaultValue;
this.r[k] = ref(defaultValue);
}
this.ready = this.init();
@@ -120,7 +122,8 @@ export class Pizzax<T extends StateDef> {
} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) {
this.r[k].value = this.s[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
} else {
this.r[k].value = this.s[k] = v.default;
// 参照渡しになるのを防ぐためclone
this.r[k].value = this.s[k] = deepClone(v.default);
}
}
@@ -148,7 +151,8 @@ export class Pizzax<T extends StateDef> {
this.r[k].value = this.s[k] = (kvs as Partial<T>)[k];
cache[k] = (kvs as Partial<T>)[k];
} else {
this.r[k].value = this.s[k] = v.default;
// 参照渡しになるのを防ぐためclone
this.r[k].value = this.s[k] = deepClone(v.default);
}
}
}
@@ -218,8 +222,10 @@ export class Pizzax<T extends StateDef> {
}
public reset(key: keyof T) {
this.set(key, this.def[key].default);
return this.def[key].default;
// 参照渡しになるのを防ぐためclone
const defaultValue = deepClone(this.def[key].default);
this.set(key, defaultValue);
return defaultValue;
}
/**

View File

@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<template #default="{ item, dragStart }">
<div :class="$style.item">
<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
<!-- divが無いとエラーになる -->
<RolesEditorFormula
:modelValue="item"
:dragStartCallback="dragStart"

View File

@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<template #default="{ item }">
<div>
<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
<!-- divが無いとエラーになる -->
<component :is="getComponent(item.type) as any" :modelValue="item" @update:modelValue="updateItem" @remove="() => removeItem(item)"/>
</div>
</template>

View File

@@ -51,10 +51,12 @@ import { enableAutoBackup, getPreferencesProfileMenu } from '@/preferences/utili
import { store } from '@/store.js';
import { signout } from '@/signout.js';
import { genSearchIndexes } from '@/utility/inapp-search.js';
import { enableStoragePersistence, storagePersisted, storagePersistenceSupported, skipStoragePersistence } from '@/utility/storage.js';
import { enableStoragePersistence, getStoragePersistenceStatusRef, storagePersistenceSupported, skipStoragePersistence } from '@/utility/storage.js';
const searchIndex = await import('search-index:settings').then(({ searchIndexes }) => genSearchIndexes(searchIndexes));
const storagePersisted = await getStoragePersistenceStatusRef();
const indexInfo = {
title: i18n.ts.settings,
icon: 'ti ti-settings',

View File

@@ -165,7 +165,7 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue';
import FormSlot from '@/components/form/slot.vue';
import * as os from '@/os.js';
import { enableStoragePersistence, storagePersisted, storagePersistenceSupported } from '@/utility/storage.js';
import { enableStoragePersistence, getStoragePersistenceStatusRef, storagePersistenceSupported } from '@/utility/storage.js';
import { ensureSignin } from '@/i.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
@@ -180,6 +180,8 @@ import { cloudBackup } from '@/preferences/utility.js';
const $i = ensureSignin();
const storagePersisted = await getStoragePersistenceStatusRef();
const reportError = prefer.model('reportError');
const enableCondensedLine = prefer.model('enableCondensedLine');
const skipNoteRender = prefer.model('skipNoteRender');

View File

@@ -14,6 +14,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { deepEqual } from '@/utility/deep-equal.js';
import { deepClone } from '@/utility/clone.js';
// NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない
@@ -122,7 +123,8 @@ export function getInitialPrefValue<K extends keyof PREF>(k: K): ValueOf<K> {
if (typeof _default === 'function') { // factory
return _default() as ValueOf<K>;
} else {
return _default as unknown as ValueOf<K>;
// 参照渡しになるのを防ぐためclone
return deepClone(_default as unknown as ValueOf<K>);
}
}

View File

@@ -10,6 +10,10 @@ const float PI = 3.141592653589793;
const float TWO_PI = 6.283185307179586;
const float HALF_PI = 1.5707963267948966;
const float goldenAngle = 2.39996323;
const int sampleCount = 256;
const float sampleCountF = float(sampleCount);
in vec2 in_uv;
uniform sampler2D in_texture;
uniform vec2 in_resolution;
@@ -18,7 +22,6 @@ uniform vec2 u_scale;
uniform bool u_ellipse;
uniform float u_angle;
uniform float u_radius;
uniform int u_samples;
out vec4 out_color;
float rand(vec2 value) {
@@ -51,17 +54,7 @@ void main() {
vec4 result = vec4(0.0);
float totalSamples = 0.0;
// Make blur radius resolution-independent by using a percentage of image size
float referenceSize = min(in_resolution.x, in_resolution.y);
float normalizedRadius = u_radius / 100.0;
float radiusPx = normalizedRadius * referenceSize;
vec2 texelSize = 1.0 / in_resolution;
int sampleCount = max(u_samples, 1);
float sampleCountF = float(sampleCount);
float jitter = rand(in_uv * in_resolution);
float goldenAngle = 2.39996323;
float jitter = rand(in_uv);
// Sample in a circular pattern to avoid axis-aligned artifacts
for (int i = 0; i < sampleCount; i++) {
@@ -69,15 +62,11 @@ void main() {
float radius = sqrt((fi + 0.5) / sampleCountF);
float theta = (fi + jitter) * goldenAngle;
vec2 direction = vec2(cos(theta), sin(theta));
vec2 offset = direction * (radiusPx * radius) * texelSize;
vec2 sampleUV = in_uv + offset;
if (sampleUV.x >= 0.0 && sampleUV.x <= 1.0 && sampleUV.y >= 0.0 && sampleUV.y <= 1.0) {
float weight = exp(-radius * radius * 4.0);
result += texture(in_texture, sampleUV) * weight;
totalSamples += weight;
}
vec2 offset = direction * (u_radius * radius);
float weight = exp(-radius * radius * 4.0);
result += texture(in_texture, in_uv + offset) * weight;
totalSamples += weight;
}
out_color = totalSamples > 0.0 ? result / totalSamples : texture(in_texture, in_uv);
out_color = result / totalSamples;
}

View File

@@ -24,7 +24,6 @@ export const fn = defineImageCompositorFunction<{
gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0);
gl.uniform1f(u.angle, params.angle / 2);
gl.uniform1f(u.radius, params.radius);
gl.uniform1i(u.samples, 256);
},
});
@@ -84,10 +83,10 @@ export const uiDefinition = {
radius: {
label: i18n.ts._imageEffector._fxProps.strength,
type: 'number',
default: 10.0,
default: 0.15,
min: 0.0,
max: 20.0,
step: 0.5,
max: 0.3,
step: 0.01,
},
},
} satisfies ImageEffectorUiDefinition<typeof fn>;

View File

@@ -14,12 +14,15 @@ uniform sampler2D in_texture;
uniform vec2 in_resolution;
uniform vec2 u_pos;
uniform float u_frequency;
uniform bool u_thresholdEnabled;
uniform float u_threshold;
uniform float u_outlineThickness;
uniform float u_maskSize;
uniform bool u_black;
out vec4 out_color;
float remap(float value, float inputMin, float inputMax, float outputMin, float outputMax) {
return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin));
}
void main() {
vec4 in_color = texture(in_texture, in_uv);
vec2 centeredUv = (in_uv - vec2(0.5, 0.5));
@@ -33,16 +36,19 @@ void main() {
float noiseY = (noiseUV.y + seed) * u_frequency;
float noise = (1.0 + snoise(vec3(noiseX, noiseY, time))) / 2.0;
float t = noise;
if (u_thresholdEnabled) t = t < u_threshold ? 1.0 : 0.0;
if (noise < u_threshold) {
out_color = in_color;
} else {
float n = remap(noise, u_threshold, 1.0, 0.0, 1.0);
// TODO: マスクの形自体も揺らぎを与える
float d = distance(uv * vec2(2.0, 2.0), u_pos * vec2(2.0, 2.0));
float mask = d < u_maskSize ? 0.0 : ((d - u_maskSize) * (1.0 + (u_maskSize * 2.0)));
out_color = vec4(
mix(in_color.r, u_black ? 0.0 : 1.0, t * mask),
mix(in_color.g, u_black ? 0.0 : 1.0, t * mask),
mix(in_color.b, u_black ? 0.0 : 1.0, t * mask),
in_color.a
);
// TODO: マスクの形自体も揺らぎを与える
float d = distance(uv * vec2(2.0, 2.0), u_pos * vec2(2.0, 2.0));
float mask = d < u_maskSize ? 0.0 : ((d - u_maskSize) * (1.0 + (u_maskSize * 2.0)));
out_color = vec4(
mix(in_color.r, n < u_outlineThickness ? 0.0 : 1.0, mask),
mix(in_color.g, n < u_outlineThickness ? 0.0 : 1.0, mask),
mix(in_color.b, n < u_outlineThickness ? 0.0 : 1.0, mask),
in_color.a
);
}
}

View File

@@ -12,20 +12,17 @@ export const fn = defineImageCompositorFunction<{
x: number;
y: number;
frequency: number;
smoothing: boolean;
threshold: number;
density: number;
outlineThickness: number;
maskSize: number;
black: boolean;
}>({
shader,
main: ({ gl, u, params }) => {
gl.uniform2f(u.pos, params.x / 2, params.y / 2);
gl.uniform1f(u.frequency, params.frequency * params.frequency);
// thresholdの調整が有効な間はsmoothingが利用できない
gl.uniform1i(u.thresholdEnabled, params.smoothing ? 0 : 1);
gl.uniform1f(u.threshold, params.threshold);
gl.uniform1f(u.threshold, 1.0 - params.density);
gl.uniform1f(u.outlineThickness, params.outlineThickness);
gl.uniform1f(u.maskSize, params.maskSize);
gl.uniform1i(u.black, params.black ? 1 : 0);
},
});
@@ -56,20 +53,22 @@ export const uiDefinition = {
max: 15.0,
step: 0.1,
},
smoothing: {
label: i18n.ts._imageEffector._fxProps.zoomLinesSmoothing,
caption: i18n.ts._imageEffector._fxProps.zoomLinesSmoothingDescription,
type: 'boolean',
default: false,
},
threshold: {
label: i18n.ts._imageEffector._fxProps.zoomLinesThreshold,
density: {
label: i18n.ts._imageEffector._fxProps.density,
type: 'number',
default: 0.5,
min: 0.0,
max: 1.0,
step: 0.01,
},
outlineThickness: {
label: i18n.ts._imageEffector._fxProps.zoomLinesOutlineThickness,
type: 'number',
default: 0.25,
min: 0.0,
max: 1.0,
step: 0.01,
},
maskSize: {
label: i18n.ts._imageEffector._fxProps.zoomLinesMaskSize,
type: 'number',
@@ -78,10 +77,5 @@ export const uiDefinition = {
max: 1.0,
step: 0.01,
},
black: {
label: i18n.ts._imageEffector._fxProps.zoomLinesBlack,
type: 'boolean',
default: false,
},
},
} satisfies ImageEffectorUiDefinition<typeof fn>;

View File

@@ -3,13 +3,21 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ref } from 'vue';
import { readonly, ref } from 'vue';
import * as os from '@/os.js';
import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
export const storagePersistenceSupported = window.isSecureContext && 'storage' in navigator;
export const storagePersisted = ref(storagePersistenceSupported ? await navigator.storage.persisted() : false);
const storagePersisted = ref(false);
export async function getStoragePersistenceStatusRef() {
if (storagePersistenceSupported) {
storagePersisted.value = await navigator.storage.persisted().catch(() => false);
}
return readonly(storagePersisted);
}
export async function enableStoragePersistence() {
if (!storagePersistenceSupported) return;

View File

@@ -30,7 +30,7 @@ import { useLowresTime } from '@/composables/use-lowres-time.js';
import { userPage, acct } from '@/filters/user.js';
const props = defineProps<{
item: Misskey.entities.UsersGetFollowingBirthdayUsersResponse[number];
item: Misskey.entities.UsersGetFollowingUsersByBirthdayResponse[number];
}>();
const now = useLowresTime();

View File

@@ -106,7 +106,7 @@ const end = computed(() => {
}
});
const birthdayUsersPaginator = markRaw(new Paginator('users/get-following-birthday-users', {
const birthdayUsersPaginator = markRaw(new Paginator('users/get-following-users-by-birthday', {
limit: 18,
offsetMode: true,
computedParams: computed(() => {