mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-02 07:25:49 +02:00
Compare commits
5 Commits
renovate/m
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d9412b338 | ||
|
|
a23a72b015 | ||
|
|
93bd9d551d | ||
|
|
35d6c20828 | ||
|
|
7c9942f014 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,4 +1,16 @@
|
|||||||
## 2026.4.0
|
## Unreleased
|
||||||
|
|
||||||
|
### General
|
||||||
|
-
|
||||||
|
|
||||||
|
### Client
|
||||||
|
-
|
||||||
|
|
||||||
|
### Server
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
|
## 2026.5.0
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- Enhance: アバターデコレーションにカテゴリを設定できるように
|
- Enhance: アバターデコレーションにカテゴリを設定できるように
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2026.4.0-beta.2",
|
"version": "2026.5.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -63,10 +63,10 @@ type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
|||||||
class NotificationManager {
|
class NotificationManager {
|
||||||
private notifier: { id: MiUser['id']; };
|
private notifier: { id: MiUser['id']; };
|
||||||
private note: MiNote;
|
private note: MiNote;
|
||||||
private queue: {
|
private queue: Map<MiLocalUser['id'], {
|
||||||
target: MiLocalUser['id'];
|
target: MiLocalUser['id'];
|
||||||
reason: NotificationType;
|
reason: NotificationType;
|
||||||
}[];
|
}>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private mutingsRepository: MutingsRepository,
|
private mutingsRepository: MutingsRepository,
|
||||||
@@ -77,7 +77,7 @@ class NotificationManager {
|
|||||||
) {
|
) {
|
||||||
this.notifier = notifier;
|
this.notifier = notifier;
|
||||||
this.note = note;
|
this.note = note;
|
||||||
this.queue = [];
|
this.queue = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@@ -85,7 +85,7 @@ class NotificationManager {
|
|||||||
// 自分自身へは通知しない
|
// 自分自身へは通知しない
|
||||||
if (this.notifier.id === notifiee) return;
|
if (this.notifier.id === notifiee) return;
|
||||||
|
|
||||||
const exist = this.queue.find(x => x.target === notifiee);
|
const exist = this.queue.get(notifiee);
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
// 「メンションされているかつ返信されている」場合は、メンションとしての通知ではなく返信としての通知にする
|
// 「メンションされているかつ返信されている」場合は、メンションとしての通知ではなく返信としての通知にする
|
||||||
@@ -93,7 +93,7 @@ class NotificationManager {
|
|||||||
exist.reason = reason;
|
exist.reason = reason;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.queue.push({
|
this.queue.set(notifiee, {
|
||||||
reason: reason,
|
reason: reason,
|
||||||
target: notifiee,
|
target: notifiee,
|
||||||
});
|
});
|
||||||
@@ -102,25 +102,25 @@ class NotificationManager {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async notify() {
|
public async notify() {
|
||||||
if (this.queue.length === 0) {
|
if (this.queue.size === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetUserIds = this.queue.map(x => x.target);
|
let visibleUserIds: Set<MiUser['id']> | null;
|
||||||
let visibleUserIds: Set<string>;
|
|
||||||
|
|
||||||
switch (this.note.visibility) {
|
switch (this.note.visibility) {
|
||||||
case 'public':
|
case 'public':
|
||||||
case 'home':
|
case 'home':
|
||||||
visibleUserIds = new Set(targetUserIds);
|
visibleUserIds = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'specified':
|
case 'specified':
|
||||||
visibleUserIds = new Set(this.note.visibleUserIds.filter(id => targetUserIds.includes(id)));
|
visibleUserIds = new Set(this.note.visibleUserIds);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// TODO: フォロワー限定ノートにフォロワーではない人がメンションされた場合通知されるのが正しい挙動なのか確認(一部に挙動の不一致がありそう)。現状は通知されるためフィルタしない
|
// TODO: フォロワー限定ノートにフォロワーではない人がメンションされた場合通知されるのが正しい挙動なのか確認(一部に挙動の不一致がありそう)。現状は通知されるためフィルタしない
|
||||||
// case 'followers': {
|
// case 'followers': {
|
||||||
|
// const targetUserIds = this.queue.map(x => x.target);
|
||||||
// const followers = await this.followingsRepository.find({
|
// const followers = await this.followingsRepository.find({
|
||||||
// where: {
|
// where: {
|
||||||
// followeeId: this.note.userId,
|
// followeeId: this.note.userId,
|
||||||
@@ -138,8 +138,10 @@ class NotificationManager {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const x of this.queue) {
|
for (const x of this.queue.values()) {
|
||||||
if (!visibleUserIds.has(x.target)) {
|
const isVisibleToTarget = visibleUserIds === null || visibleUserIds.has(x.target);
|
||||||
|
|
||||||
|
if (!isVisibleToTarget) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export class InboxProcessorService implements OnApplicationShutdown {
|
|||||||
if (userExistenceCheckApId != null) {
|
if (userExistenceCheckApId != null) {
|
||||||
const user = await this.apDbResolverService.getUserFromApId(userExistenceCheckApId);
|
const user = await this.apDbResolverService.getUserFromApId(userExistenceCheckApId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new Bull.UnrecoverableError(`skip: user not found for delete activity. ${getApId(userExistenceCheckApId)}`);
|
return `skip: user not found for delete activity. ${getApId(userExistenceCheckApId)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,42 +6,42 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template>
|
<template>
|
||||||
<div :class="$style.root" class="_gaps_s">
|
<div :class="$style.root" class="_gaps_s">
|
||||||
<div
|
<div
|
||||||
v-for="item in props.items"
|
v-for="displayItem in displayItems"
|
||||||
:key="item.id"
|
:key="displayItem.item.id"
|
||||||
v-panel
|
v-panel
|
||||||
:class="[$style.item, { [$style.itemWaiting]: item.preprocessing, [$style.itemCompleted]: item.uploaded, [$style.itemFailed]: item.uploadFailed }]"
|
:class="[$style.item, { [$style.itemWaiting]: displayItem.item.preprocessing, [$style.itemCompleted]: displayItem.item.uploaded, [$style.itemFailed]: displayItem.item.uploadFailed }]"
|
||||||
:style="{
|
:style="{
|
||||||
'--p': item.progress != null ? `${item.progress.value / item.progress.max * 100}%` : '0%',
|
'--p': displayItem.item.progress != null ? `${displayItem.item.progress.value / displayItem.item.progress.max * 100}%` : '0%',
|
||||||
'--pp': item.preprocessProgress != null ? `${item.preprocessProgress * 100}%` : '100%',
|
'--pp': displayItem.item.preprocessProgress != null ? `${displayItem.item.preprocessProgress * 100}%` : '100%',
|
||||||
}"
|
}"
|
||||||
@contextmenu.prevent.stop="onContextmenu(item, $event)"
|
@contextmenu.prevent.stop="onContextmenu(displayItem.item, $event)"
|
||||||
>
|
>
|
||||||
<div :class="$style.itemInner">
|
<div :class="$style.itemInner">
|
||||||
<div :class="$style.itemActionWrapper">
|
<div :class="$style.itemActionWrapper">
|
||||||
<MkButton :iconOnly="true" rounded @click="emit('showMenu', item, $event)"><i class="ti ti-dots"></i></MkButton>
|
<MkButton :iconOnly="true" rounded @click="emit('showMenu', displayItem.item, $event)"><i class="ti ti-dots"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.itemThumbnail" :style="{ backgroundImage: `url(${ item.thumbnail })` }" @click="onThumbnailClick(item, $event)"></div>
|
<div :class="$style.itemThumbnail" :style="{ backgroundImage: `url(${ displayItem.item.thumbnail })` }" @click="onThumbnailClick(displayItem.item, $event)"></div>
|
||||||
<div :class="$style.itemBody">
|
<div :class="$style.itemBody">
|
||||||
<div>
|
<div>
|
||||||
<i v-if="item.isSensitive" style="color: var(--MI_THEME-warn); margin-right: 0.5em;" class="ti ti-eye-exclamation"></i>
|
<i v-if="displayItem.item.isSensitive" style="color: var(--MI_THEME-warn); margin-right: 0.5em;" class="ti ti-eye-exclamation"></i>
|
||||||
<MkCondensedLine :minScale="2 / 3">
|
<MkCondensedLine :minScale="2 / 3">
|
||||||
<span>{{ getUploadName(item).lastIndexOf('.') != -1 ? getUploadName(item).substring(0, getUploadName(item).lastIndexOf('.')) : getUploadName(item) }}</span>
|
<span>{{ displayItem.nameParts.baseName }}</span>
|
||||||
<span v-if="getUploadName(item).lastIndexOf('.') != -1" style="opacity: 0.5;">{{ getUploadName(item).substring(getUploadName(item).lastIndexOf('.')) }}</span>
|
<span v-if="displayItem.nameParts.extension != null" style="opacity: 0.5;">{{ displayItem.nameParts.extension }}</span>
|
||||||
</MkCondensedLine>
|
</MkCondensedLine>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.itemInfo">
|
<div :class="$style.itemInfo">
|
||||||
<span>{{ item.file.type }}</span>
|
<span>{{ displayItem.item.file.type }}</span>
|
||||||
<span v-if="item.compressedSize">({{ i18n.tsx._uploader.compressedToX({ x: bytes(item.compressedSize) }) }} = {{ i18n.tsx._uploader.savedXPercent({ x: Math.round((1 - item.compressedSize / item.file.size) * 100) }) }})</span>
|
<span v-if="displayItem.item.compressedSize">({{ i18n.tsx._uploader.compressedToX({ x: bytes(displayItem.item.compressedSize) }) }} = {{ i18n.tsx._uploader.savedXPercent({ x: Math.round((1 - displayItem.item.compressedSize / displayItem.item.file.size) * 100) }) }})</span>
|
||||||
<span v-else>{{ bytes(item.file.size) }}</span>
|
<span v-else>{{ bytes(displayItem.item.file.size) }}</span>
|
||||||
<span v-if="item.preprocessing">{{ i18n.ts.preprocessing }}<MkLoading inline em style="margin-left: 0.5em;"/></span>
|
<span v-if="displayItem.item.preprocessing">{{ i18n.ts.preprocessing }}<MkLoading inline em style="margin-left: 0.5em;"/></span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.itemIconWrapper">
|
<div :class="$style.itemIconWrapper">
|
||||||
<MkSystemIcon v-if="item.uploading" :class="$style.itemIcon" type="waiting"/>
|
<MkSystemIcon v-if="displayItem.item.uploading" :class="$style.itemIcon" type="waiting"/>
|
||||||
<MkSystemIcon v-else-if="item.uploaded" :class="$style.itemIcon" type="success"/>
|
<MkSystemIcon v-else-if="displayItem.item.uploaded" :class="$style.itemIcon" type="success"/>
|
||||||
<MkSystemIcon v-else-if="item.uploadFailed" :class="$style.itemIcon" type="error"/>
|
<MkSystemIcon v-else-if="displayItem.item.uploadFailed" :class="$style.itemIcon" type="error"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,6 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
import { isLink } from '@@/js/is-link.js';
|
import { isLink } from '@@/js/is-link.js';
|
||||||
import { getUploadName } from '@/composables/use-uploader.js';
|
import { getUploadName } from '@/composables/use-uploader.js';
|
||||||
import type { UploaderItem } from '@/composables/use-uploader.js';
|
import type { UploaderItem } from '@/composables/use-uploader.js';
|
||||||
@@ -60,11 +61,36 @@ const props = defineProps<{
|
|||||||
items: UploaderItem[];
|
items: UploaderItem[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const displayItems = computed(() => props.items.map(item => ({
|
||||||
|
item,
|
||||||
|
nameParts: getUploadNameParts(item),
|
||||||
|
})));
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'showMenu', item: UploaderItem, event: PointerEvent): void;
|
(ev: 'showMenu', item: UploaderItem, event: PointerEvent): void;
|
||||||
(ev: 'showMenuViaContextmenu', item: UploaderItem, event: PointerEvent): void;
|
(ev: 'showMenuViaContextmenu', item: UploaderItem, event: PointerEvent): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
function getUploadNameParts(item: UploaderItem): {
|
||||||
|
baseName: string;
|
||||||
|
extension: string | null;
|
||||||
|
} {
|
||||||
|
const name = getUploadName(item);
|
||||||
|
const extensionIndex = name.lastIndexOf('.');
|
||||||
|
|
||||||
|
if (extensionIndex === -1) {
|
||||||
|
return {
|
||||||
|
baseName: name,
|
||||||
|
extension: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseName: name.substring(0, extensionIndex),
|
||||||
|
extension: name.substring(extensionIndex),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function onContextmenu(item: UploaderItem, ev: PointerEvent) {
|
function onContextmenu(item: UploaderItem, ev: PointerEvent) {
|
||||||
if (ev.target && isLink(ev.target as HTMLElement)) return;
|
if (ev.target && isLink(ev.target as HTMLElement)) return;
|
||||||
if (window.getSelection()?.toString() !== '') return;
|
if (window.getSelection()?.toString() !== '') return;
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ export function popup<T extends Component>(
|
|||||||
|
|
||||||
const id = ++popupIdCount;
|
const id = ++popupIdCount;
|
||||||
const dispose = () => {
|
const dispose = () => {
|
||||||
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ?
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
popups.value = popups.value.filter(p => p.id !== id);
|
popups.value = popups.value.filter(p => p.id !== id);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { markRaw, shallowRef, ref, watch } from 'vue';
|
import { shallowRef, ref, watch } from 'vue';
|
||||||
import type MkEmojiPickerDialog__TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue';
|
||||||
import { popup, popupAsyncWithDialog } from '@/os.js';
|
import { popup } from '@/os.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,7 +15,6 @@ import { prefer } from '@/preferences.js';
|
|||||||
* 一度表示したダイアログを連続で使用できることが望ましいシーンでの利用が想定される。
|
* 一度表示したダイアログを連続で使用できることが望ましいシーンでの利用が想定される。
|
||||||
*/
|
*/
|
||||||
class EmojiPicker {
|
class EmojiPicker {
|
||||||
private loadedComponent: typeof MkEmojiPickerDialog__TypeReferenceOnly | null = null;
|
|
||||||
private emojisRef = ref<string[]>([]);
|
private emojisRef = ref<string[]>([]);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -23,15 +22,6 @@ class EmojiPicker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public init() {
|
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
watch([prefer.r.emojiPaletteForMain, prefer.r.emojiPalettes], () => {
|
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 ?? [];
|
this.emojisRef.value = prefer.s.emojiPaletteForMain == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForMain)?.emojis ?? [];
|
||||||
}, {
|
}, {
|
||||||
@@ -46,10 +36,8 @@ class EmojiPicker {
|
|||||||
) {
|
) {
|
||||||
const anchorRef = shallowRef(anchorElement);
|
const anchorRef = shallowRef(anchorElement);
|
||||||
|
|
||||||
if (this.loadedComponent) {
|
// defineAsyncComponentはiOS等でユーザーアクティベーションが失われてfocusが効かなくなるため使用不可
|
||||||
// コンポーネント解決済みのため同期的に popup() できる。
|
const { dispose } = popup(MkEmojiPickerDialog, {
|
||||||
// ユーザーアクティベーションコンテキストが維持されiOSでもfocusが機能する。
|
|
||||||
const { dispose } = popup(this.loadedComponent, {
|
|
||||||
anchorElement: anchorRef,
|
anchorElement: anchorRef,
|
||||||
pinnedEmojis: this.emojisRef,
|
pinnedEmojis: this.emojisRef,
|
||||||
asReactionPicker: false,
|
asReactionPicker: false,
|
||||||
@@ -63,29 +51,6 @@ class EmojiPicker {
|
|||||||
dispose();
|
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();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { markRaw, shallowRef, ref, watch } from 'vue';
|
import { shallowRef, ref, watch } from 'vue';
|
||||||
import type MkEmojiPickerDialog__TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue';
|
||||||
import { popup, popupAsyncWithDialog } from '@/os.js';
|
import { popup } from '@/os.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
class ReactionPicker {
|
class ReactionPicker {
|
||||||
private loadedComponent: typeof MkEmojiPickerDialog__TypeReferenceOnly | null = null;
|
|
||||||
private reactionsRef = ref<string[]>([]);
|
private reactionsRef = ref<string[]>([]);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -18,15 +17,6 @@ class ReactionPicker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public init() {
|
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], () => {
|
watch([prefer.r.emojiPaletteForReaction, prefer.r.emojiPalettes], () => {
|
||||||
this.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 ?? [];
|
||||||
}, {
|
}, {
|
||||||
@@ -43,10 +33,8 @@ class ReactionPicker {
|
|||||||
const anchorRef = shallowRef(anchorElement);
|
const anchorRef = shallowRef(anchorElement);
|
||||||
const targetNoteRef = ref(targetNote);
|
const targetNoteRef = ref(targetNote);
|
||||||
|
|
||||||
if (this.loadedComponent) {
|
// defineAsyncComponentはiOS等でユーザーアクティベーションが失われてfocusが効かなくなるため使用不可
|
||||||
// 通常パス: コンポーネント解決済みのため同期的に popup() できる。
|
const { dispose } = popup(MkEmojiPickerDialog, {
|
||||||
// ユーザーアクティベーションコンテキストが維持されiOSでもfocusが機能する。
|
|
||||||
const { dispose } = popup(this.loadedComponent, {
|
|
||||||
anchorElement: anchorRef,
|
anchorElement: anchorRef,
|
||||||
pinnedEmojis: this.reactionsRef,
|
pinnedEmojis: this.reactionsRef,
|
||||||
asReactionPicker: true,
|
asReactionPicker: true,
|
||||||
@@ -60,29 +48,6 @@ class ReactionPicker {
|
|||||||
dispose();
|
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();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2026.4.0-beta.2",
|
"version": "2026.5.0",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user