feat: complete `:emoji:` to unicode emoji
This commit is contained in:
parent
e8ce14ecd6
commit
94069e7441
|
@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span class="name">{{ hashtag }}</span>
|
<span class="name">{{ hashtag }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ol v-else-if="type === 'emoji' && emojis.length > 0" ref="suggests" :class="$style.list">
|
<ol v-else-if="type === 'emoji' || type === 'emojiComplete' && emojis.length > 0" ref="suggests" :class="$style.list">
|
||||||
<li v-for="emoji in emojis" :key="emoji.emoji" :class="$style.item" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
|
<li v-for="emoji in emojis" :key="emoji.emoji" :class="$style.item" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
|
||||||
<MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji"/>
|
<MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji"/>
|
||||||
<MkEmoji v-else :emoji="emoji.emoji" :class="$style.emoji"/>
|
<MkEmoji v-else :emoji="emoji.emoji" :class="$style.emoji"/>
|
||||||
|
@ -67,10 +67,16 @@ export type CompleteInfo = {
|
||||||
payload: string;
|
payload: string;
|
||||||
query: string;
|
query: string;
|
||||||
},
|
},
|
||||||
|
// `:emo` -> `:emoji:` or some unicode emoji
|
||||||
emoji: {
|
emoji: {
|
||||||
payload: string;
|
payload: string;
|
||||||
query: string;
|
query: string;
|
||||||
},
|
},
|
||||||
|
// like emoji but for `:emoji:` -> unicode emoji
|
||||||
|
emojiComplete: {
|
||||||
|
payload: string;
|
||||||
|
query: string;
|
||||||
|
},
|
||||||
mfmTag: {
|
mfmTag: {
|
||||||
payload: string;
|
payload: string;
|
||||||
query: string;
|
query: string;
|
||||||
|
@ -98,8 +104,7 @@ type EmojiDef = {
|
||||||
|
|
||||||
const lib = emojilist.filter(x => x.category !== 'flags');
|
const lib = emojilist.filter(x => x.category !== 'flags');
|
||||||
|
|
||||||
const emojiDb = computed(() => {
|
const unicodeEmojiDB = computed(() => {
|
||||||
//#region Unicode Emoji
|
|
||||||
const char2path = defaultStore.reactiveState.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
|
const char2path = defaultStore.reactiveState.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
|
||||||
|
|
||||||
const unicodeEmojiDB: EmojiDef[] = lib.map(x => ({
|
const unicodeEmojiDB: EmojiDef[] = lib.map(x => ({
|
||||||
|
@ -122,6 +127,12 @@ const emojiDb = computed(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
unicodeEmojiDB.sort((a, b) => a.name.length - b.name.length);
|
unicodeEmojiDB.sort((a, b) => a.name.length - b.name.length);
|
||||||
|
|
||||||
|
return unicodeEmojiDB;
|
||||||
|
});
|
||||||
|
|
||||||
|
const emojiDb = computed(() => {
|
||||||
|
//#region Unicode Emoji
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Custom Emoji
|
//#region Custom Emoji
|
||||||
|
@ -149,7 +160,7 @@ const emojiDb = computed(() => {
|
||||||
customEmojiDB.sort((a, b) => a.name.length - b.name.length);
|
customEmojiDB.sort((a, b) => a.name.length - b.name.length);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
return markRaw([...customEmojiDB, ...unicodeEmojiDB]);
|
return markRaw([...customEmojiDB, ...unicodeEmojiDB.value]);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -171,7 +182,7 @@ type PropsType<T extends keyof CompleteInfo> = {
|
||||||
//const props = defineProps<PropsType<keyof CompleteInfo>>();
|
//const props = defineProps<PropsType<keyof CompleteInfo>>();
|
||||||
// ↑と同じだけど↓にしないとdiscriminated unionにならない。
|
// ↑と同じだけど↓にしないとdiscriminated unionにならない。
|
||||||
// https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions
|
// https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions
|
||||||
const props = defineProps<PropsType<'user'> | PropsType<'hashtag'> | PropsType<'emoji'> | PropsType<'mfmTag'> | PropsType<'mfmParam'>>();
|
const props = defineProps<PropsType<'user'> | PropsType<'hashtag'> | PropsType<'emoji'> | PropsType<'emojiComplete'> | PropsType<'mfmTag'> | PropsType<'mfmParam'>>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
<T extends keyof CompleteInfo>(event: 'done', value: { type: T; value: CompleteInfo[T]['payload'] }): void;
|
<T extends keyof CompleteInfo>(event: 'done', value: { type: T; value: CompleteInfo[T]['payload'] }): void;
|
||||||
|
@ -194,7 +205,7 @@ const zIndex = os.claimZIndex('high');
|
||||||
function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T]['payload']) {
|
function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T]['payload']) {
|
||||||
emit('done', { type, value });
|
emit('done', { type, value });
|
||||||
emit('closed');
|
emit('closed');
|
||||||
if (type === 'emoji') {
|
if (type === 'emoji' || type === 'emojiComplete') {
|
||||||
let recents = defaultStore.state.recentlyUsedEmojis;
|
let recents = defaultStore.state.recentlyUsedEmojis;
|
||||||
recents = recents.filter((emoji: any) => emoji !== value);
|
recents = recents.filter((emoji: any) => emoji !== value);
|
||||||
recents.unshift(value);
|
recents.unshift(value);
|
||||||
|
@ -281,6 +292,14 @@ function exec() {
|
||||||
}
|
}
|
||||||
|
|
||||||
emojis.value = emojiAutoComplete(props.q, emojiDb.value);
|
emojis.value = emojiAutoComplete(props.q, emojiDb.value);
|
||||||
|
} else if (props.type === 'emojiComplete') {
|
||||||
|
if (!props.q || props.q === '') {
|
||||||
|
// 最近使った絵文字をサジェスト
|
||||||
|
emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => unicodeEmojiDB.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emojis.value = emojiAutoComplete(props.q, unicodeEmojiDB.value);
|
||||||
} else if (props.type === 'mfmTag') {
|
} else if (props.type === 'mfmTag') {
|
||||||
if (!props.q || props.q === '') {
|
if (!props.q || props.q === '') {
|
||||||
mfmTags.value = MFM_TAGS;
|
mfmTags.value = MFM_TAGS;
|
||||||
|
|
|
@ -99,6 +99,8 @@ export class Autocomplete {
|
||||||
const isMfmParam = mfmParamIndex !== -1 && text.split(/\$\[[a-zA-Z]+/).pop()?.includes('.');
|
const isMfmParam = mfmParamIndex !== -1 && text.split(/\$\[[a-zA-Z]+/).pop()?.includes('.');
|
||||||
const isMfmTag = mfmTagIndex !== -1 && !isMfmParam;
|
const isMfmTag = mfmTagIndex !== -1 && !isMfmParam;
|
||||||
const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
|
const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
|
||||||
|
// :ok:などを🆗にするたいおぷ
|
||||||
|
const isEmojiCompleteToUnicode = !isEmoji && emojiIndex === text.length - 1;
|
||||||
|
|
||||||
let opened = false;
|
let opened = false;
|
||||||
|
|
||||||
|
@ -129,6 +131,14 @@ export class Autocomplete {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isEmojiCompleteToUnicode && !opened && this.onlyType.includes('emoji')) {
|
||||||
|
const emoji = text.substring(text.lastIndexOf(':', text.length - 2) + 1, text.length - 1);
|
||||||
|
if (!emoji.includes(' ')) {
|
||||||
|
this.open('emojiComplete', emoji);
|
||||||
|
opened = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isMfmTag && !opened && this.onlyType.includes('mfmTag')) {
|
if (isMfmTag && !opened && this.onlyType.includes('mfmTag')) {
|
||||||
const mfmTag = text.substring(mfmTagIndex + 1);
|
const mfmTag = text.substring(mfmTagIndex + 1);
|
||||||
if (!mfmTag.includes(' ')) {
|
if (!mfmTag.includes(' ')) {
|
||||||
|
@ -272,6 +282,22 @@ export class Autocomplete {
|
||||||
// 挿入
|
// 挿入
|
||||||
this.text = trimmedBefore + value + after;
|
this.text = trimmedBefore + value + after;
|
||||||
|
|
||||||
|
// キャレットを戻す
|
||||||
|
nextTick(() => {
|
||||||
|
this.textarea.focus();
|
||||||
|
const pos = trimmedBefore.length + value.length;
|
||||||
|
this.textarea.setSelectionRange(pos, pos);
|
||||||
|
});
|
||||||
|
} else if (type === 'emojiComplete') {
|
||||||
|
const source = this.text;
|
||||||
|
|
||||||
|
const before = source.substring(0, caret);
|
||||||
|
const trimmedBefore = before.substring(0, before.lastIndexOf(':', before.length - 2));
|
||||||
|
const after = source.substring(caret);
|
||||||
|
|
||||||
|
// 挿入
|
||||||
|
this.text = trimmedBefore + value + after;
|
||||||
|
|
||||||
// キャレットを戻す
|
// キャレットを戻す
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
this.textarea.focus();
|
this.textarea.focus();
|
||||||
|
|
Loading…
Reference in New Issue