Skip to content

Commit

Permalink
Feat: emoji-picker - emoji search
Browse files Browse the repository at this point in the history
  • Loading branch information
sousuke0422 authored and fs5m8 committed Jan 1, 2023
1 parent 0e0dec7 commit 283925a
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 4 deletions.
10 changes: 7 additions & 3 deletions src/client/app/common/views/components/emoji-picker-dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,13 @@ export default Vue.extend({
methods: {
onMousedown(e) {
e.preventDefault();
if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close();
return false;
if (!contains(this.$el, e.target) && (this.$el != e.target)) {
e.preventDefault();
this.close();
return false;
} else {
return true;
}
},
chosen(emoji) {
Expand Down
168 changes: 167 additions & 1 deletion src/client/app/common/views/components/emoji-picker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@
</button>
</header>
<div class="emojis">
<div class="search">
<ui-input v-model="q" :autofocus="false" style="margin: 0.4em;">
<span>{{ $t('search') }}</span>
</ui-input>
<div class="list" v-if="searchResults.length > 0">
<button v-for="(emoji, i) in (searchResults || [])"
:title="emoji.sources ? emoji.sources.map(x => `${x.name}@${x.host}`).join(',\n') : emoji.name"
@click="chosen(emoji)"
:key="i"
>
<mk-emoji v-if="emoji.char != null" :emoji="emoji.char" :local="emoji.local"/>
<img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
</button>
</div>
</div>
<template v-if="categories[0].isActive">
<header class="category"><fa :icon="faHistory" fixed-width/> {{ $t('recent-emoji') }}</header>
<div class="list">
Expand Down Expand Up @@ -60,7 +75,7 @@ import Vue from 'vue';
import i18n from '../../../i18n';
import { emojilist } from '../../../../../misc/emojilist';
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory, faUser } from '@fortawesome/free-solid-svg-icons';
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons';
import { faHeart, faFlag, faLaugh } from '@fortawesome/free-regular-svg-icons';
import { groupByX } from '../../../../../prelude/array';
Expand All @@ -73,6 +88,8 @@ export default Vue.extend({
getStaticImageUrl,
customEmojis: {},
faGlobe, faHistory,
q: null,
searchResults: [],
categories: [{
text: this.$t('custom-emoji'),
icon: faAsterisk,
Expand Down Expand Up @@ -126,6 +143,147 @@ export default Vue.extend({
}
},
watch: {
q() {
if (this.q == null || this.q === '') {
this.searchResults = [];
return;
}
const q = this.q.replace(/:/g, '');
const searchCustom = () => {
const max = 8;
const emojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
const matches = new Set();
const exactMatch = emojis.find(e => e.name === q);
if (exactMatch) matches.add(exactMatch);
if (q.includes(' ')) { // AND検索
const keywords = q.split(' ');
// 名前にキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
// 名前またはエイリアスにキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
} else {
for (const emoji of emojis) {
if (emoji.name.startsWith(q)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.startsWith(q))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.name.includes(q)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.includes(q))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
}
return matches;
};
const searchUnicode = () => {
const max = 8;
const emojis = this.emojilist;
const matches = new Set();
const exactMatch = emojis.find(e => e.name === q);
if (exactMatch) matches.add(exactMatch);
if (q.includes(' ')) { // AND検索
const keywords = q.split(' ');
// 名前にキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
// 名前またはエイリアスにキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
} else {
for (const emoji of emojis) {
if (emoji.name.startsWith(q)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.keywords.some(keyword => keyword.startsWith(q))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.name.includes(q)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.keywords.some(keyword => keyword.includes(q))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
}
return matches;
};
const searchResultCustom = Array.from(searchCustom());
const searchResultUnicode = Array.from(searchUnicode());
this.searchResults = searchResultCustom.concat(searchResultUnicode);
}
},
created() {
let local = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
local = groupByX(local, (x: any) => x.category || '');
Expand Down Expand Up @@ -197,6 +355,14 @@ export default Vue.extend({
overflow-y auto
overflow-x hidden
> .search
top 0
left 0
z-index 1
padding 8px
background var(--faceHeader)
color var(--text)
> header.category
position sticky
top 0
Expand Down

0 comments on commit 283925a

Please sign in to comment.