1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-06-04 01:54:10 +02:00

Merge branch 'develop' into room

This commit is contained in:
syuilo
2026-04-30 16:44:38 +09:00
8 changed files with 139 additions and 138 deletions

View File

@@ -5,6 +5,7 @@
### Client
- Enhance: チャンネル指定リノートでリノート先のチャンネルに移動できるように
- Enhance: ベータ版でのアップデート時のダイアログの更新情報リンクをGitHubのReleasesページに遷移するようにし、正しく閲覧できるように
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
- Fix: ドライブへの画像アップロード時にファイル名の変更が無視される不具合を修正
- Fix: 連合が無効化されたサーバーで一部の設定項目が空欄で表示される問題を修正

View File

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

View File

@@ -66,7 +66,7 @@
"punycode.js": "2.3.1",
"qr-code-styling": "1.9.2",
"qr-scanner": "1.4.2",
"sanitize-html": "2.17.2",
"sanitize-html": "2.17.3",
"shiki": "4.0.2",
"textarea-caret": "3.1.0",
"three": "0.183.2",

View File

@@ -5,9 +5,9 @@
// TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
import { markRaw, ref, defineAsyncComponent, nextTick, effectScope, isRef, shallowReactive, watch } from 'vue';
import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
import * as Misskey from 'misskey-js';
import type { Component, MaybeRef, ShallowReactive } from 'vue';
import type { Component, MaybeRef } from 'vue';
import type { ComponentEmit, ComponentProps as CP } from 'vue-component-type-helpers';
import type { Form, GetFormResultType } from '@/utility/form.js';
import type { MenuItem } from '@/types/menu.js';
@@ -145,7 +145,7 @@ let popupIdCount = 0;
export const popups = ref<{
id: number;
component: Component;
props: ShallowReactive<Record<string, any>>;
props: Record<string, any>;
events: Record<string, any>;
}[]>([]);
@@ -183,32 +183,6 @@ type ComponentEmitsObject<C extends Component, IE = OverloadToUnion<ComponentEmi
: (...args: any[]) => void;
}>;
// ref をそのまま保持せず popup 側の reactive props に同期するようにして、スコープをまたいでリアクティビティが切れるのを防止する
function normalizePopupProps<T extends Record<string, any>>(props: T): {
resolvedProps: ShallowReactive<T>;
stopSync: () => void;
} {
const resolvedProps = shallowReactive<T>({} as T) as T; // shallowReactiveの返り値はreadonlyだが、実際には書き換えるので元の型で扱う
const scope = effectScope();
scope.run(() => {
for (const [key, value] of Object.entries(props)) {
if (isRef(value)) {
watch(value, (resolvedValue) => {
resolvedProps[key as keyof T] = resolvedValue as T[keyof T];
}, { immediate: true });
} else {
resolvedProps[key as keyof T] = value;
}
}
});
return {
resolvedProps: resolvedProps as ShallowReactive<T>,
stopSync: () => scope.stop(),
};
}
// NOTE: ジェネリック型つきのコンポーネントでは、emitsの型推論がうまく働かない型変数を取り出すことはできないため
// NOTE: emitsがOverloadToUnionで対応しているオーバーロードの数を超える場合は、OverloadToUnionの個数を増やせばOK
export function popup<T extends Component>(
@@ -219,24 +193,20 @@ export function popup<T extends Component>(
markRaw(component);
const id = ++popupIdCount;
const { resolvedProps, stopSync } = normalizePopupProps(props);
let disposed = false;
const dispose = () => {
if (disposed) return;
disposed = true;
stopSync();
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ
nextTick(() => {
popups.value = popups.value.filter(p => p.id !== id);
});
};
popups.value.push({
const state = {
component,
props: resolvedProps,
props,
events,
id,
});
};
popups.value.push(state);
return {
dispose,

View File

@@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineAsyncComponent, ref, watch } from 'vue';
import type { Ref } from 'vue';
import { popup } from '@/os.js';
import { markRaw, shallowRef, ref, watch } from 'vue';
import type MkEmojiPickerDialog__TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
import { popup, popupAsyncWithDialog } from '@/os.js';
import { prefer } from '@/preferences.js';
/**
@@ -15,53 +15,77 @@ import { prefer } from '@/preferences.js';
* 一度表示したダイアログを連続で使用できることが望ましいシーンでの利用が想定される。
*/
class EmojiPicker {
private anchorElement: Ref<HTMLElement | null> = ref(null);
private manualShowing = ref(false);
private onChosen?: (emoji: string) => void;
private onClosed?: () => void;
private loadedComponent: typeof MkEmojiPickerDialog__TypeReferenceOnly | null = null;
private emojisRef = ref<string[]>([]);
constructor() {
// nop
}
public async init() {
const emojisRef = ref<string[]>([]);
watch([prefer.r.emojiPaletteForMain, prefer.r.emojiPalettes], () => {
emojisRef.value = prefer.s.emojiPaletteForMain == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForMain)?.emojis ?? [];
}, {
immediate: true,
public init() {
// コンポーネントをプリロードしてキャッシュしておく。
// iOS PWA では await を挟むとユーザーアクティベーションが失われfocusが効かなくなるため、
// show() 呼び出し時には同期的に popup() できるよう事前にコンポーネントを解決しておく。
import('@/components/MkEmojiPickerDialog.vue').then(m => {
this.loadedComponent = markRaw(m.default);
}).catch(err => {
console.error('[EmojiPicker] Failed to preload MkEmojiPickerDialog:', err);
});
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
anchorElement: this.anchorElement,
pinnedEmojis: emojisRef,
asReactionPicker: false,
manualShowing: this.manualShowing,
choseAndClose: false,
watch([prefer.r.emojiPaletteForMain, prefer.r.emojiPalettes], () => {
this.emojisRef.value = prefer.s.emojiPaletteForMain == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForMain)?.emojis ?? [];
}, {
done: emoji => {
if (this.onChosen) this.onChosen(emoji);
},
close: () => {
this.manualShowing.value = false;
},
closed: () => {
this.anchorElement.value = null;
if (this.onClosed) this.onClosed();
},
immediate: true,
});
}
public show(
anchorElement: HTMLElement,
onChosen?: EmojiPicker['onChosen'],
onClosed?: EmojiPicker['onClosed'],
onChosen?: (emoji: string) => void,
onClosed?: () => void,
) {
this.anchorElement.value = anchorElement;
this.manualShowing.value = true;
this.onChosen = onChosen;
this.onClosed = onClosed;
const anchorRef = shallowRef(anchorElement);
if (this.loadedComponent) {
// コンポーネント解決済みのため同期的に popup() できる。
// ユーザーアクティベーションコンテキストが維持されiOSでもfocusが機能する。
const { dispose } = popup(this.loadedComponent, {
anchorElement: anchorRef,
pinnedEmojis: this.emojisRef,
asReactionPicker: false,
choseAndClose: false,
}, {
done: (emoji: string) => {
if (onChosen) onChosen(emoji);
},
closed: () => {
if (onClosed) onClosed();
dispose();
},
});
} else {
// フォールバック: 初回タップがプリロード完了前
popupAsyncWithDialog(
import('@/components/MkEmojiPickerDialog.vue').then(m => {
this.loadedComponent = markRaw(m.default);
return this.loadedComponent;
}),
{
anchorElement: anchorRef,
pinnedEmojis: this.emojisRef,
asReactionPicker: false,
choseAndClose: false,
},
{
done: (emoji: string) => {
if (onChosen) onChosen(emoji);
},
closed: () => {
if (onClosed) onClosed();
},
},
);
}
}
}

View File

@@ -4,57 +4,85 @@
*/
import * as Misskey from 'misskey-js';
import { defineAsyncComponent, ref, watch } from 'vue';
import type { Ref } from 'vue';
import { popup } from '@/os.js';
import { markRaw, shallowRef, ref, watch } from 'vue';
import type MkEmojiPickerDialog__TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
import { popup, popupAsyncWithDialog } from '@/os.js';
import { prefer } from '@/preferences.js';
class ReactionPicker {
private anchorElement: Ref<HTMLElement | null> = ref(null);
private manualShowing = ref(false);
private targetNote: Ref<Misskey.entities.Note | null> = ref(null);
private onChosen?: (reaction: string) => void;
private onClosed?: () => void;
private loadedComponent: typeof MkEmojiPickerDialog__TypeReferenceOnly | null = null;
private reactionsRef = ref<string[]>([]);
constructor() {
// nop
}
public async init() {
const reactionsRef = ref<string[]>([]);
public init() {
// コンポーネントをプリロードしてキャッシュしておく。
// iOS PWA では await を挟むとユーザーアクティベーションが失われfocusが効かなくなるため、
// show() 呼び出し時には同期的に popup() できるよう事前にコンポーネントを解決しておく。
import('@/components/MkEmojiPickerDialog.vue').then(m => {
this.loadedComponent = markRaw(m.default);
}).catch(err => {
console.error('[ReactionPicker] Failed to preload MkEmojiPickerDialog:', err);
});
watch([prefer.r.emojiPaletteForReaction, prefer.r.emojiPalettes], () => {
reactionsRef.value = prefer.s.emojiPaletteForReaction == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForReaction)?.emojis ?? [];
this.reactionsRef.value = prefer.s.emojiPaletteForReaction == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForReaction)?.emojis ?? [];
}, {
immediate: true,
});
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
anchorElement: this.anchorElement,
pinnedEmojis: reactionsRef,
asReactionPicker: true,
targetNote: this.targetNote,
manualShowing: this.manualShowing,
}, {
done: reaction => {
if (this.onChosen) this.onChosen(reaction);
},
close: () => {
this.manualShowing.value = false;
},
closed: () => {
this.anchorElement.value = null;
if (this.onClosed) this.onClosed();
},
});
}
public show(anchorElement: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
this.anchorElement.value = anchorElement;
this.targetNote.value = targetNote;
this.manualShowing.value = true;
this.onChosen = onChosen;
this.onClosed = onClosed;
public show(
anchorElement: HTMLElement | null,
targetNote: Misskey.entities.Note | null,
onChosen?: (reaction: string) => void,
onClosed?: () => void,
) {
const anchorRef = shallowRef(anchorElement);
const targetNoteRef = ref(targetNote);
if (this.loadedComponent) {
// 通常パス: コンポーネント解決済みのため同期的に popup() できる。
// ユーザーアクティベーションコンテキストが維持されiOSでもfocusが機能する。
const { dispose } = popup(this.loadedComponent, {
anchorElement: anchorRef,
pinnedEmojis: this.reactionsRef,
asReactionPicker: true,
targetNote: targetNoteRef,
}, {
done: (reaction: string) => {
if (onChosen) onChosen(reaction);
},
closed: () => {
if (onClosed) onClosed();
dispose();
},
});
} else {
// フォールバック: 初回タップがプリロード完了前
popupAsyncWithDialog(
import('@/components/MkEmojiPickerDialog.vue').then(m => {
this.loadedComponent = markRaw(m.default);
return this.loadedComponent;
}),
{
anchorElement: anchorRef,
pinnedEmojis: this.reactionsRef,
asReactionPicker: true,
targetNote: targetNoteRef,
},
{
done: (reaction: string) => {
if (onChosen) onChosen(reaction);
},
closed: () => {
if (onClosed) onClosed();
},
},
);
}
}
}

View File

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

26
pnpm-lock.yaml generated
View File

@@ -759,8 +759,8 @@ importers:
specifier: 1.4.2
version: 1.4.2
sanitize-html:
specifier: 2.17.2
version: 2.17.2
specifier: 2.17.3
version: 2.17.3
shiki:
specifier: 4.0.2
version: 4.0.2
@@ -9161,10 +9161,6 @@ packages:
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.5.9:
resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==}
engines: {node: ^10 || ^12 || >=14}
@@ -9628,9 +9624,6 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
sanitize-html@2.17.2:
resolution: {integrity: sha512-EnffJUl46VE9uvZ0XeWzObHLurClLlT12gsOk1cHyP2Ol1P0BnBnsXmShlBmWVJM+dKieQI68R0tsPY5m/B+Jg==}
sanitize-html@2.17.3:
resolution: {integrity: sha512-Kn4srCAo2+wZyvCNKCSyB2g8RQ8IkX/gQs2uqoSRNu5t9I2qvUyAVvRDiFUVAiX3N3PNuwStY0eNr+ooBHVWEg==}
@@ -20729,12 +20722,6 @@ snapshots:
postcss-value-parser@4.2.0: {}
postcss@8.5.8:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
postcss@8.5.9:
dependencies:
nanoid: 3.3.11
@@ -21306,15 +21293,6 @@ snapshots:
safer-buffer@2.1.2: {}
sanitize-html@2.17.2:
dependencies:
deepmerge: 4.3.1
escape-string-regexp: 4.0.0
htmlparser2: 10.1.0
is-plain-object: 5.0.0
parse-srcset: 1.0.2
postcss: 8.5.8
sanitize-html@2.17.3:
dependencies:
deepmerge: 4.3.1