summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMeiMei <30769358+mei23@users.noreply.github.com>2019-10-21 00:43:39 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2019-10-21 00:43:39 +0900
commit4c6c06c80afd3e2309b305f40c2e67e48863bf31 (patch)
treed9d24139b126867a3ff1f4e893175aea05ebd01e /src
parentAdd ssl to elasticsearch config settings (#5527) (diff)
downloadsharkey-4c6c06c80afd3e2309b305f40c2e67e48863bf31.tar.gz
sharkey-4c6c06c80afd3e2309b305f40c2e67e48863bf31.tar.bz2
sharkey-4c6c06c80afd3e2309b305f40c2e67e48863bf31.zip
Improve emoji-picker (#5515)
* Improve emoji-picker * remove unimplanted translation * カテゴリのサジェスト * use unique
Diffstat (limited to 'src')
-rw-r--r--src/client/app/admin/views/emoji.vue19
-rw-r--r--src/client/app/common/views/components/emoji-picker.vue112
-rw-r--r--src/client/app/store.ts2
-rw-r--r--src/models/entities/emoji.ts5
-rw-r--r--src/prelude/array.ts13
-rw-r--r--src/server/api/endpoints/admin/emoji/add.ts5
-rw-r--r--src/server/api/endpoints/admin/emoji/list.ts9
-rw-r--r--src/server/api/endpoints/admin/emoji/update.ts5
-rw-r--r--src/server/api/endpoints/meta.ts15
9 files changed, 153 insertions, 32 deletions
diff --git a/src/client/app/admin/views/emoji.vue b/src/client/app/admin/views/emoji.vue
index 9f2f3a0c88..2925fcab57 100644
--- a/src/client/app/admin/views/emoji.vue
+++ b/src/client/app/admin/views/emoji.vue
@@ -8,6 +8,9 @@
<span>{{ $t('add-emoji.name') }}</span>
<template #desc>{{ $t('add-emoji.name-desc') }}</template>
</ui-input>
+ <ui-input v-model="category" :datalist="categoryList">
+ <span>{{ $t('add-emoji.category') }}</span>
+ </ui-input>
<ui-input v-model="aliases">
<span>{{ $t('add-emoji.aliases') }}</span>
<template #desc>{{ $t('add-emoji.aliases-desc') }}</template>
@@ -24,7 +27,7 @@
<ui-card>
<template #title><fa :icon="faGrin"/> {{ $t('emojis.title') }}</template>
- <section v-for="emoji in emojis" class="oryfrbft">
+ <section v-for="emoji in emojis" :key="emoji.name" class="oryfrbft">
<div>
<img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/>
</div>
@@ -33,6 +36,9 @@
<ui-input v-model="emoji.name">
<span>{{ $t('add-emoji.name') }}</span>
</ui-input>
+ <ui-input v-model="emoji.category" :datalist="categoryList">
+ <span>{{ $t('add-emoji.category') }}</span>
+ </ui-input>
<ui-input v-model="emoji.aliases">
<span>{{ $t('add-emoji.aliases') }}</span>
</ui-input>
@@ -55,12 +61,14 @@
import Vue from 'vue';
import i18n from '../../i18n';
import { faGrin } from '@fortawesome/free-regular-svg-icons';
+import { unique } from '../../../../prelude/array';
export default Vue.extend({
i18n: i18n('admin/views/emoji.vue'),
data() {
return {
name: '',
+ category: '',
url: '',
aliases: '',
emojis: [],
@@ -72,10 +80,17 @@ export default Vue.extend({
this.fetchEmojis();
},
+ computed: {
+ categoryList() {
+ return unique(this.emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
+ }
+ },
+
methods: {
add() {
this.$root.api('admin/emoji/add', {
name: this.name,
+ category: this.category,
url: this.url,
aliases: this.aliases.split(' ').filter(x => x.length > 0)
}).then(() => {
@@ -94,7 +109,6 @@ export default Vue.extend({
fetchEmojis() {
this.$root.api('admin/emoji/list').then(emojis => {
- emojis.reverse();
for (const e of emojis) {
e.aliases = (e.aliases || []).join(' ');
}
@@ -106,6 +120,7 @@ export default Vue.extend({
this.$root.api('admin/emoji/update', {
id: emoji.id,
name: emoji.name,
+ category: emoji.category,
url: emoji.url,
aliases: emoji.aliases.split(' ').filter(x => x.length > 0)
}).then(() => {
diff --git a/src/client/app/common/views/components/emoji-picker.vue b/src/client/app/common/views/components/emoji-picker.vue
index 88761ae933..abae69e28a 100644
--- a/src/client/app/common/views/components/emoji-picker.vue
+++ b/src/client/app/common/views/components/emoji-picker.vue
@@ -11,25 +11,46 @@
</button>
</header>
<div class="emojis">
- <header><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
- <div v-if="categories.find(x => x.isActive).name">
- <button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
- :title="emoji.name"
- @click="chosen(emoji.char)"
- :key="emoji.name"
- >
- <mk-emoji :emoji="emoji.char"/>
- </button>
- </div>
- <div v-else>
- <button v-for="emoji in customEmojis"
- :title="emoji.name"
- @click="chosen(`:${emoji.name}:`)"
- :key="emoji.name"
- >
- <img :src="emoji.url" :alt="emoji.name"/>
- </button>
- </div>
+ <template v-if="categories[0].isActive">
+ <header class="category"><fa :icon="faHistory" fixed-width/> {{ $t('recent-emoji') }}</header>
+ <div class="list">
+ <button v-for="(emoji, i) in ($store.state.device.recentEmojis || [])"
+ :title="emoji.name"
+ @click="chosen(emoji)"
+ :key="i"
+ >
+ <mk-emoji v-if="emoji.char != null" :emoji="emoji.char"/>
+ <img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
+ </button>
+ </div>
+ </template>
+
+ <header class="category"><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
+ <template v-if="categories.find(x => x.isActive).name">
+ <div class="list">
+ <button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
+ :title="emoji.name"
+ @click="chosen(emoji)"
+ :key="emoji.name"
+ >
+ <mk-emoji :emoji="emoji.char"/>
+ </button>
+ </div>
+ </template>
+ <template v-else>
+ <div v-for="(key, i) in Object.keys(customEmojis)" :key="i">
+ <header class="sub">{{ key || $t('no-category') }}</header>
+ <div class="list">
+ <button v-for="emoji in customEmojis[key]"
+ :title="emoji.name"
+ @click="chosen(emoji)"
+ :key="emoji.name"
+ >
+ <img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
+ </button>
+ </div>
+ </div>
+ </template>
</div>
</div>
</template>
@@ -38,8 +59,10 @@
import Vue from 'vue';
import i18n from '../../../i18n';
import { emojilist } from '../../../../../misc/emojilist';
-import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice } from '@fortawesome/free-solid-svg-icons';
+import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
+import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory } from '@fortawesome/free-solid-svg-icons';
import { faHeart, faFlag } from '@fortawesome/free-regular-svg-icons';
+import { groupByX } from '../../../../../prelude/array';
export default Vue.extend({
i18n: i18n('common/views/components/emoji-picker.vue'),
@@ -47,7 +70,9 @@ export default Vue.extend({
data() {
return {
emojilist,
- customEmojis: [],
+ getStaticImageUrl,
+ customEmojis: {},
+ faGlobe, faHistory,
categories: [{
text: this.$t('custom-emoji'),
icon: faAsterisk,
@@ -97,18 +122,43 @@ export default Vue.extend({
},
created() {
- this.customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
+ let local = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
+ local = groupByX(local, (x: any) => x.category || '');
+ this.customEmojis = local;
+
+ if (this.$store.state.device.activeEmojiCategoryName) {
+ this.goCategory(this.$store.state.device.activeEmojiCategoryName);
+ }
},
methods: {
- go(category) {
+ go(category: any) {
+ this.goCategory(category.name);
+ },
+
+ goCategory(name: string) {
+ let matched = false;
for (const c of this.categories) {
- c.isActive = c.name === category.name;
+ c.isActive = c.name === name;
+ if (c.isActive) {
+ matched = true;
+ this.$store.commit('device/set', { key: 'activeEmojiCategoryName', value: c.name });
+ }
+ }
+ if (!matched) {
+ this.categories[0].isActive = true;
}
},
- chosen(emoji) {
- this.$emit('chosen', emoji);
+ chosen(emoji: any) {
+ const getKey = (emoji: any) => emoji.char || `:${emoji.name}:`;
+
+ let recents = this.$store.state.device.recentEmojis || [];
+ recents = recents.filter((e: any) => getKey(e) !== getKey(emoji));
+ recents.unshift(emoji)
+ this.$store.commit('device/set', { key: 'recentEmojis', value: recents.splice(0, 16) });
+
+ this.$emit('chosen', getKey(emoji));
}
}
});
@@ -142,7 +192,7 @@ export default Vue.extend({
overflow-y auto
overflow-x hidden
- > header
+ > header.category
position sticky
top 0
left 0
@@ -152,7 +202,12 @@ export default Vue.extend({
color var(--text)
font-size 12px
- > div
+ >>> header.sub
+ padding 4px 8px
+ color var(--text)
+ font-size 12px
+
+ >>> div.list
display grid
grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr
gap 4px
@@ -180,6 +235,7 @@ export default Vue.extend({
left 0
width 100%
height 100%
+ object-fit contain
font-size 28px
transition transform 0.2s ease
pointer-events none
diff --git a/src/client/app/store.ts b/src/client/app/store.ts
index f4ec9e501a..fd3aceb728 100644
--- a/src/client/app/store.ts
+++ b/src/client/app/store.ts
@@ -79,6 +79,8 @@ const defaultDeviceSettings = {
enableMobileQuickNotificationView: false,
roomGraphicsQuality: 'medium',
roomUseOrthographicCamera: true,
+ activeEmojiCategoryName: undefined,
+ recentEmojis: [],
};
export default (os: MiOS) => new Vuex.Store({
diff --git a/src/models/entities/emoji.ts b/src/models/entities/emoji.ts
index 020636a7fb..d6080ae099 100644
--- a/src/models/entities/emoji.ts
+++ b/src/models/entities/emoji.ts
@@ -25,6 +25,11 @@ export class Emoji {
public host: string | null;
@Column('varchar', {
+ length: 128, nullable: true
+ })
+ public category: string | null;
+
+ @Column('varchar', {
length: 512,
})
public url: string;
diff --git a/src/prelude/array.ts b/src/prelude/array.ts
index 839bbc920b..f4d684d574 100644
--- a/src/prelude/array.ts
+++ b/src/prelude/array.ts
@@ -84,6 +84,19 @@ export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
return groupBy((a, b) => f(a) === f(b), xs);
}
+export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
+ return collections.reduce((obj: Record<string, T[]>, item: T) => {
+ const key = keySelector(item);
+ if (!obj.hasOwnProperty(key)) {
+ obj[key] = [];
+ }
+
+ obj[key].push(item);
+
+ return obj;
+ }, {});
+}
+
/**
* Compare two arrays by lexicographical order
*/
diff --git a/src/server/api/endpoints/admin/emoji/add.ts b/src/server/api/endpoints/admin/emoji/add.ts
index 6a91c31a95..73339cdc0b 100644
--- a/src/server/api/endpoints/admin/emoji/add.ts
+++ b/src/server/api/endpoints/admin/emoji/add.ts
@@ -26,6 +26,10 @@ export const meta = {
validator: $.str.min(1)
},
+ category: {
+ validator: $.optional.str
+ },
+
aliases: {
validator: $.optional.arr($.str.min(1)),
default: [] as string[]
@@ -52,6 +56,7 @@ export default define(meta, async (ps, me) => {
id: genId(),
updatedAt: new Date(),
name: ps.name,
+ category: ps.category,
host: null,
aliases: ps.aliases,
url: ps.url,
diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts
index 54686a5c5a..d2a5e7df0d 100644
--- a/src/server/api/endpoints/admin/emoji/list.ts
+++ b/src/server/api/endpoints/admin/emoji/list.ts
@@ -23,12 +23,19 @@ export const meta = {
export default define(meta, async (ps) => {
const emojis = await Emojis.find({
- host: toPunyNullable(ps.host)
+ where: {
+ host: toPunyNullable(ps.host)
+ },
+ order: {
+ category: 'ASC',
+ name: 'ASC'
+ }
});
return emojis.map(e => ({
id: e.id,
name: e.name,
+ category: e.category,
aliases: e.aliases,
host: e.host,
url: e.url
diff --git a/src/server/api/endpoints/admin/emoji/update.ts b/src/server/api/endpoints/admin/emoji/update.ts
index 062a8d0fb8..f4a01a3976 100644
--- a/src/server/api/endpoints/admin/emoji/update.ts
+++ b/src/server/api/endpoints/admin/emoji/update.ts
@@ -25,6 +25,10 @@ export const meta = {
validator: $.str
},
+ category: {
+ validator: $.optional.str
+ },
+
url: {
validator: $.str
},
@@ -53,6 +57,7 @@ export default define(meta, async (ps) => {
await Emojis.update(emoji.id, {
updatedAt: new Date(),
name: ps.name,
+ category: ps.category,
aliases: ps.aliases,
url: ps.url,
type,
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index 0b56a9d4ef..153780e3fa 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -96,7 +96,19 @@ export const meta = {
export default define(meta, async (ps, me) => {
const instance = await fetchMeta(true);
- const emojis = await Emojis.find({ where: { host: null }, cache: { id: 'meta_emojis', milliseconds: 3600000 } }); // 1 hour
+ const emojis = await Emojis.find({
+ where: {
+ host: null
+ },
+ order: {
+ category: 'ASC',
+ name: 'ASC'
+ },
+ cache: {
+ id: 'meta_emojis',
+ milliseconds: 3600000 // 1 hour
+ }
+ });
const response: any = {
maintainerName: instance.maintainerName,
@@ -144,6 +156,7 @@ export default define(meta, async (ps, me) => {
id: e.id,
aliases: e.aliases,
name: e.name,
+ category: e.category,
url: e.url,
})),
enableEmail: instance.enableEmail,