Skip to content

Commit bf634e5

Browse files
committed
Fix: 横長絵文字をいい感じに見れるようにする
1 parent f4b7ece commit bf634e5

7 files changed

Lines changed: 156 additions & 14 deletions

File tree

locales/ja-JP.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ emojiPicker: "絵文字ピッカー"
148148
pinnedEmojisForReactionSettingDescription: "リアクション時にピン留め表示する絵文字を設定できます"
149149
pinnedEmojisSettingDescription: "絵文字入力時にピン留め表示する絵文字を設定できます"
150150
emojiPickerDisplay: "ピッカーの表示"
151+
allowWideCustomEmojisInPicker: "絵文字ピッカーで横長のカスタム絵文字を広げて表示する"
151152
overwriteFromPinnedEmojisForReaction: "リアクション設定から上書きする"
152153
overwriteFromPinnedEmojis: "全般設定から上書きする"
153154
reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"

packages/frontend/src/components/MkEmojiPicker.section.vue

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ SPDX-License-Identifier: AGPL-3.0-only
1818
class="_button item"
1919
:disabled="disabledEmojis?.value.includes(emoji)"
2020
@pointerenter="(ev) => { emit('previewEnter', emoji, ev); computeButtonTitle(ev); }"
21-
@pointerleave="emit('previewLeave')"
21+
@pointerleave="previewLeave"
22+
@pointerdown="(ev) => emit('previewTouchStart', emoji, ev)"
23+
@pointerup="(ev) => emit('previewTouchEnd', ev)"
24+
@pointercancel="(ev) => emit('previewTouchCancel', ev)"
2225
@click="emit('chosen', emoji, $event)"
2326
>
2427
<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true" :fallbackToImage="true"/>
@@ -41,7 +44,10 @@ SPDX-License-Identifier: AGPL-3.0-only
4144
:customEmojiTree="child.children"
4245
@chosen="nestedChosen"
4346
@previewEnter="nestedPreviewEnter"
47+
@previewTouchStart="nestedPreviewTouchStart"
4448
@previewLeave="emit('previewLeave')"
49+
@previewTouchEnd="nestedPreviewTouchEnd"
50+
@previewTouchCancel="nestedPreviewTouchCancel"
4551
>
4652
{{ child.value || i18n.ts.other }}
4753
</MkEmojiPickerSection>
@@ -54,7 +60,10 @@ SPDX-License-Identifier: AGPL-3.0-only
5460
class="_button item"
5561
:disabled="disabledEmojis?.value.includes(emoji)"
5662
@pointerenter="(ev) => { emit('previewEnter', emoji, ev); computeButtonTitle(ev); }"
57-
@pointerleave="emit('previewLeave')"
63+
@pointerleave="previewLeave"
64+
@pointerdown="(ev) => emit('previewTouchStart', emoji, ev)"
65+
@pointerup="(ev) => emit('previewTouchEnd', ev)"
66+
@pointercancel="(ev) => emit('previewTouchCancel', ev)"
5867
@click="emit('chosen', emoji, $event)"
5968
>
6069
<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/>
@@ -84,7 +93,10 @@ const props = defineProps<{
8493
const emit = defineEmits<{
8594
(ev: 'chosen', v: string, event: PointerEvent): void;
8695
(ev: 'previewEnter', v: string, event: PointerEvent): void;
96+
(ev: 'previewTouchStart', v: string, event: PointerEvent): void;
8797
(ev: 'previewLeave'): void;
98+
(ev: 'previewTouchEnd', event: PointerEvent): void;
99+
(ev: 'previewTouchCancel', event: PointerEvent): void;
88100
}>();
89101
90102
const emojis = computed(() => Array.isArray(props.emojis) ? props.emojis : props.emojis.value);
@@ -105,4 +117,25 @@ function nestedChosen(emoji: string, ev: PointerEvent) {
105117
function nestedPreviewEnter(emoji: string, ev: PointerEvent) {
106118
emit('previewEnter', emoji, ev);
107119
}
120+
121+
function previewLeave(ev: PointerEvent) {
122+
if (ev.pointerType === 'touch') {
123+
emit('previewTouchCancel', ev);
124+
return;
125+
}
126+
127+
emit('previewLeave');
128+
}
129+
130+
function nestedPreviewTouchStart(emoji: string, ev: PointerEvent) {
131+
emit('previewTouchStart', emoji, ev);
132+
}
133+
134+
function nestedPreviewTouchEnd(ev: PointerEvent) {
135+
emit('previewTouchEnd', ev);
136+
}
137+
138+
function nestedPreviewTouchCancel(ev: PointerEvent) {
139+
emit('previewTouchCancel', ev);
140+
}
108141
</script>

packages/frontend/src/components/MkEmojiPicker.vue

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
44
-->
55

66
<template>
7-
<div class="omfetrab _popup" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
7+
<div class="omfetrab _popup" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow, allowWideCustomEmojis: prefer.s.allowWideCustomEmojisInPicker }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
88
<input
99
ref="searchEl"
1010
:value="q"
@@ -30,7 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only
3030
:title="emoji.name"
3131
tabindex="0"
3232
@pointerenter="(ev) => startPreview(`:${emoji.name}:`, ev)"
33-
@pointerleave="endPreview"
33+
@pointerleave="leavePreview"
34+
@pointerdown="(ev) => startTouchPreview(`:${emoji.name}:`, ev)"
35+
@pointerup="endTouchPreview"
36+
@pointercancel="cancelTouchPreview"
3437
@click="chosen(emoji, $event)"
3538
>
3639
<MkCustomEmoji class="emoji" :name="emoji.name" :fallbackToImage="true"/>
@@ -44,7 +47,10 @@ SPDX-License-Identifier: AGPL-3.0-only
4447
:title="emoji.name"
4548
tabindex="0"
4649
@pointerenter="(ev) => startPreview(emoji.char, ev)"
47-
@pointerleave="endPreview"
50+
@pointerleave="leavePreview"
51+
@pointerdown="(ev) => startTouchPreview(emoji.char, ev)"
52+
@pointerup="endTouchPreview"
53+
@pointercancel="cancelTouchPreview"
4854
@click="chosen(emoji, $event)"
4955
>
5056
<MkEmoji class="emoji" :emoji="emoji.char"/>
@@ -64,7 +70,10 @@ SPDX-License-Identifier: AGPL-3.0-only
6470
:title="emoji.name"
6571
tabindex="0"
6672
@pointerenter="(ev) => startPreview(`:${emoji.name}:`, ev)"
67-
@pointerleave="endPreview"
73+
@pointerleave="leavePreview"
74+
@pointerdown="(ev) => startTouchPreview(`:${emoji.name}:`, ev)"
75+
@pointerup="endTouchPreview"
76+
@pointercancel="cancelTouchPreview"
6877
@click="chosen(emoji, $event)"
6978
>
7079
<MkCustomEmoji class="emoji" :name="emoji.name" :normal="true"/>
@@ -83,7 +92,10 @@ SPDX-License-Identifier: AGPL-3.0-only
8392
:disabled="!canReact(emoji)"
8493
tabindex="0"
8594
@pointerenter="(ev) => { startPreview(getKey(emoji), ev); computeButtonTitle(ev); }"
86-
@pointerleave="endPreview"
95+
@pointerleave="leavePreview"
96+
@pointerdown="(ev) => startTouchPreview(getKey(emoji), ev)"
97+
@pointerup="endTouchPreview"
98+
@pointercancel="cancelTouchPreview"
8799
@click="chosen(emoji, $event)"
88100
>
89101
<MkCustomEmoji v-if="!emoji.hasOwnProperty('char')" class="emoji" :name="getKey(emoji)" :normal="true"/>
@@ -103,7 +115,10 @@ SPDX-License-Identifier: AGPL-3.0-only
103115
:disabled="!canReact(emoji)"
104116
:data-emoji="getKey(emoji)"
105117
@pointerenter="(ev) => { startPreview(getKey(emoji), ev); computeButtonTitle(ev); }"
106-
@pointerleave="endPreview"
118+
@pointerleave="leavePreview"
119+
@pointerdown="(ev) => startTouchPreview(getKey(emoji), ev)"
120+
@pointerup="endTouchPreview"
121+
@pointercancel="cancelTouchPreview"
107122
@click="chosen(emoji, $event)"
108123
>
109124
<MkCustomEmoji v-if="!emoji.hasOwnProperty('char')" class="emoji" :name="getKey(emoji)" :normal="true"/>
@@ -124,14 +139,17 @@ SPDX-License-Identifier: AGPL-3.0-only
124139
:customEmojiTree="child.children"
125140
@chosen="chosen"
126141
@previewEnter="(emoji: string, ev: PointerEvent) => startPreview(emoji, ev)"
142+
@previewTouchStart="(emoji: string, ev: PointerEvent) => startTouchPreview(emoji, ev)"
127143
@previewLeave="endPreview"
144+
@previewTouchEnd="endTouchPreview"
145+
@previewTouchCancel="cancelTouchPreview"
128146
>
129147
{{ child.value || i18n.ts.other }}
130148
</XSection>
131149
</div>
132150
<div v-once class="group">
133151
<header class="_acrylic">{{ i18n.ts.emoji }}</header>
134-
<XSection v-for="category in categories" :key="category" :emojis="emojiCharByCategory.get(category) ?? []" :hasChildSection="false" @chosen="chosen" @previewEnter="(emoji: string, ev: PointerEvent) => startPreview(emoji, ev)" @previewLeave="endPreview">{{ category }}</XSection>
152+
<XSection v-for="category in categories" :key="category" :emojis="emojiCharByCategory.get(category) ?? []" :hasChildSection="false" @chosen="chosen" @previewEnter="(emoji: string, ev: PointerEvent) => startPreview(emoji, ev)" @previewTouchStart="(emoji: string, ev: PointerEvent) => startTouchPreview(emoji, ev)" @previewLeave="endPreview" @previewTouchEnd="endTouchPreview" @previewTouchCancel="cancelTouchPreview">{{ category }}</XSection>
135153
</div>
136154
</div>
137155
<div class="tabs">
@@ -213,6 +231,7 @@ const emit = defineEmits<{
213231
let previewTimer: number | null = null;
214232
let previewPopupDispose: (() => void) | null = null;
215233
let closePreviewPopup: (() => void) | null = null;
234+
let suppressNextClick = false;
216235
217236
function clearPreviewTimer(): void {
218237
if (previewTimer !== null) {
@@ -253,25 +272,60 @@ function showPreviewPopup(emoji: string, anchorElement: HTMLElement): void {
253272
};
254273
}
255274
275+
function schedulePreview(emoji: string, target: HTMLElement, options: { suppressClickOnOpen?: boolean } = {}): void {
276+
clearPreviewTimer();
277+
hidePreviewPopup();
278+
279+
previewTimer = window.setTimeout(() => {
280+
showPreviewPopup(emoji, target);
281+
if (options.suppressClickOnOpen) suppressNextClick = true;
282+
}, PREVIEW_DELAY);
283+
}
284+
256285
function startPreview(emoji: string, ev: PointerEvent): void {
257286
if (ev.pointerType === 'touch') return;
258287
259288
const target = ev.currentTarget as HTMLElement | null;
260289
if (target == null) return;
261290
262-
clearPreviewTimer();
263-
hidePreviewPopup();
291+
schedulePreview(emoji, target);
292+
}
264293
265-
previewTimer = window.setTimeout(() => {
266-
showPreviewPopup(emoji, target);
267-
}, PREVIEW_DELAY);
294+
function startTouchPreview(emoji: string, ev: PointerEvent): void {
295+
if (ev.pointerType !== 'touch') return;
296+
297+
const target = ev.currentTarget as HTMLElement | null;
298+
if (target == null) return;
299+
300+
suppressNextClick = false;
301+
schedulePreview(emoji, target, { suppressClickOnOpen: true });
268302
}
269303
270304
function endPreview(): void {
271305
clearPreviewTimer();
272306
hidePreviewPopup();
273307
}
274308
309+
function endTouchPreview(ev: PointerEvent): void {
310+
if (ev.pointerType !== 'touch') return;
311+
endPreview();
312+
}
313+
314+
function cancelTouchPreview(ev: PointerEvent): void {
315+
if (ev.pointerType !== 'touch') return;
316+
suppressNextClick = false;
317+
endPreview();
318+
}
319+
320+
function leavePreview(ev: PointerEvent): void {
321+
if (ev.pointerType === 'touch') {
322+
cancelTouchPreview(ev);
323+
return;
324+
}
325+
326+
endPreview();
327+
}
328+
275329
const searchEl = useTemplateRef('searchEl');
276330
const emojisEl = useTemplateRef('emojisEl');
277331
@@ -640,6 +694,14 @@ function computeButtonTitle(ev: PointerEvent): void {
640694
}
641695
642696
function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: PointerEvent) {
697+
if (suppressNextClick) {
698+
ev?.preventDefault();
699+
ev?.stopPropagation();
700+
suppressNextClick = false;
701+
endPreview();
702+
return;
703+
}
704+
643705
endPreview();
644706
645707
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
@@ -906,6 +968,32 @@ defineExpose({
906968
}
907969
}
908970
971+
&.allowWideCustomEmojis {
972+
> .emojis {
973+
::v-deep(section) {
974+
> .body {
975+
display: flex;
976+
flex-wrap: wrap;
977+
align-content: flex-start;
978+
979+
> .item {
980+
aspect-ratio: auto;
981+
width: fit-content;
982+
height: var(--eachSize);
983+
min-width: var(--eachSize);
984+
contain: layout style;
985+
box-sizing: border-box;
986+
987+
> .emoji {
988+
width: auto;
989+
object-fit: unset;
990+
}
991+
}
992+
}
993+
}
994+
}
995+
}
996+
909997
> .search {
910998
width: 100%;
911999
padding: 12px;

packages/frontend/src/pages/settings/emoji-palette.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ SPDX-License-Identifier: AGPL-3.0-only
109109
</MkPreferenceContainer>
110110
</SearchMarker>
111111

112+
<SearchMarker :keywords="['emoji', 'picker', 'wide', 'width', 'custom']">
113+
<MkPreferenceContainer k="allowWideCustomEmojisInPicker">
114+
<MkSwitch v-model="allowWideCustomEmojisInPicker">
115+
<template #label><SearchLabel>{{ i18n.ts.allowWideCustomEmojisInPicker }}</SearchLabel></template>
116+
</MkSwitch>
117+
</MkPreferenceContainer>
118+
</SearchMarker>
119+
112120
<MkButton @click="previewPicker"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
113121
</div>
114122
</FormSection>
@@ -180,6 +188,7 @@ const emojiPickerHeightDef = [
180188
] as MkRadiosOption<number>[];
181189
182190
const emojiPickerStyle = prefer.model('emojiPickerStyle');
191+
const allowWideCustomEmojisInPicker = prefer.model('allowWideCustomEmojisInPicker');
183192
184193
const palettesSyncEnabled = ref(prefer.isSyncEnabled('emojiPalettes'));
185194

packages/frontend/src/preferences/def.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ export const PREF_DEF = definePreferences({
285285
emojiPickerStyle: {
286286
default: 'auto' as 'auto' | 'popup' | 'drawer',
287287
},
288+
allowWideCustomEmojisInPicker: {
289+
default: false,
290+
},
288291
squareAvatars: {
289292
default: false,
290293
},

packages/frontend/src/store.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ export const store = markRaw(new Pizzax('base', {
321321
where: 'device',
322322
default: 'auto' as 'auto' | 'popup' | 'drawer',
323323
},
324+
allowWideCustomEmojisInPicker: {
325+
where: 'device',
326+
default: false,
327+
},
324328
reportError: {
325329
where: 'device',
326330
default: false,

packages/i18n/src/autogen/locale.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,10 @@ export interface Locale extends ILocale {
602602
* ピッカーの表示
603603
*/
604604
"emojiPickerDisplay": string;
605+
/**
606+
* 絵文字ピッカーで横長のカスタム絵文字を広げて表示する
607+
*/
608+
"allowWideCustomEmojisInPicker": string;
605609
/**
606610
* リアクション設定から上書きする
607611
*/

0 commit comments

Comments
 (0)