fix(frontend): mfmFunctionPickerを使用して挿入する際のハンドリングを改善 (#17018)

* fix(frontend): mfmFunctionPickerを使用して絵文字を挿入する際のハンドリングを改善

* fix

* Update MkPostForm.vue

* Update Changelog

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
かっこかり 2026-01-08 21:08:27 +09:00 committed by GitHub
parent cd973b252a
commit ece4efcefe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 40 additions and 41 deletions

View File

@ -17,6 +17,7 @@
- Fix: ドライブクリーナーでファイルを削除しても画面に反映されない問題を修正 #16061
- Fix: 非ログイン時にログインを求めるダイアログが表示された後にダイアログのぼかしが解除されず操作不能になることがある問題を修正
- Fix: ドライブのソートが「登録日(昇順)」の場合に正しく動作しない問題を修正
- Fix: 高度なMFMのピッカーを使用する際の挙動を改善
- Fix: 管理画面でアーカイブ済のお知らせを表示した際にアクティブなお知らせが多い旨の警告が出る問題を修正
- Fix: ファイルタブのセンシティブメディアを開く際に確認ダイアログを出す設定が適用されない問題を修正
- Fix: 2月29日を誕生日に設定している場合、閏年以外は3月1日を誕生日として扱うように修正

View File

@ -1166,17 +1166,41 @@ async function insertEmoji(ev: MouseEvent) {
},
() => {
textAreaReadOnly.value = false;
nextTick(() => focus());
nextTick(() => {
if (textareaEl.value) {
textareaEl.value.focus();
textareaEl.value.setSelectionRange(pos, posEnd);
}
});
},
);
}
async function insertMfmFunction(ev: MouseEvent) {
if (textareaEl.value == null) return;
let pos = textareaEl.value.selectionStart ?? 0;
let posEnd = textareaEl.value.selectionEnd ?? text.value.length;
mfmFunctionPicker(
ev.currentTarget ?? ev.target,
textareaEl.value,
text,
(tag) => {
if (pos === posEnd) {
text.value = `${text.value.substring(0, pos)}$[${tag} ]${text.value.substring(pos)}`;
pos += tag.length + 3;
posEnd = pos;
} else {
text.value = `${text.value.substring(0, pos)}$[${tag} ${text.value.substring(pos, posEnd)}]${text.value.substring(posEnd)}`;
pos += tag.length + 3;
posEnd = pos;
}
},
() => {
nextTick(() => {
if (textareaEl.value) {
textareaEl.value.focus();
textareaEl.value.setSelectionRange(pos, posEnd);
}
});
},
);
}

View File

@ -654,6 +654,7 @@ export function popupMenu(items: (MenuItem | null)[], anchorElement?: HTMLElemen
align?: string;
width?: number;
onClosing?: () => void;
onClosed?: () => void;
}): Promise<void> {
if (!(anchorElement instanceof HTMLElement)) {
anchorElement = null;
@ -672,6 +673,7 @@ export function popupMenu(items: (MenuItem | null)[], anchorElement?: HTMLElemen
resolve();
dispose();
returnFocusTo = null;
options?.onClosed?.();
},
closing: () => {
options?.onClosing?.();

View File

@ -3,55 +3,27 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { nextTick } from 'vue';
import { MFM_TAGS } from '@@/js/const.js';
import type { Ref } from 'vue';
import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
/**
* MFMの装飾のリストを表示する
*/
export function mfmFunctionPicker(anchorElement: HTMLElement | EventTarget | null, textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) {
export function mfmFunctionPicker(anchorElement: HTMLElement | EventTarget | null, onChosen: (tag: string) => void, onClosed?: () => void) {
os.popupMenu([{
text: i18n.ts.addMfmFunction,
type: 'label',
}, ...getFunctionList(textArea, textRef)], anchorElement);
}
function getFunctionList(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>): MenuItem[] {
return MFM_TAGS.map(tag => ({
}, ...MFM_TAGS.map(tag => ({
text: tag,
icon: 'ti ti-icons',
action: () => add(textArea, textRef, tag),
}));
}
function add(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>, type: string) {
const caretStart: number = textArea.selectionStart as number;
const caretEnd: number = textArea.selectionEnd as number;
MFM_TAGS.forEach(tag => {
if (type === tag) {
if (caretStart === caretEnd) {
// 単純にFunctionを追加
const trimmedText = `${textRef.value.substring(0, caretStart)}$[${type} ]${textRef.value.substring(caretEnd)}`;
textRef.value = trimmedText;
} else {
// 選択範囲を囲むようにFunctionを追加
const trimmedText = `${textRef.value.substring(0, caretStart)}$[${type} ${textRef.value.substring(caretStart, caretEnd)}]${textRef.value.substring(caretEnd)}`;
textRef.value = trimmedText;
}
}
});
const nextCaretStart: number = caretStart + 3 + type.length;
const nextCaretEnd: number = caretEnd + 3 + type.length;
// キャレットを戻す
nextTick(() => {
textArea.focus();
textArea.setSelectionRange(nextCaretStart, nextCaretEnd);
action: () => {
onChosen(tag);
},
}))], anchorElement, {
onClosed: () => {
if (onClosed) onClosed();
},
});
}