From a98182437db7dc937cb7fb46b30026b87cf029f5 Mon Sep 17 00:00:00 2001 From: mei23 Date: Fri, 18 Oct 2019 01:53:25 +0900 Subject: [PATCH 1/4] Improve emoji-picker --- locales/ja-JP.yml | 4 + .../1571220798684-CustomEmojiCategory.ts | 13 ++ src/client/app/admin/views/emoji.vue | 12 +- .../common/views/components/emoji-picker.vue | 112 +++++++++++++----- src/client/app/store.ts | 2 + src/models/entities/emoji.ts | 5 + src/prelude/array.ts | 13 ++ src/server/api/endpoints/admin/emoji/add.ts | 5 + src/server/api/endpoints/admin/emoji/list.ts | 9 +- .../api/endpoints/admin/emoji/update.ts | 5 + src/server/api/endpoints/meta.ts | 15 ++- 11 files changed, 163 insertions(+), 32 deletions(-) create mode 100644 migration/1571220798684-CustomEmojiCategory.ts diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 672c9710bed6..83b10c16427c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -673,7 +673,10 @@ common/views/components/reaction-picker.vue: input-reaction-placeholder: "または絵文字を入力" common/views/components/emoji-picker.vue: + recent-emoji: "最近使った絵文字" custom-emoji: "カスタム絵文字" + remote-emoji: "リモートカスタム絵文字" + no-category: "カテゴリなし" people: "人" animals-and-nature: "動物&自然" food-and-drink: "食べ物&飲み物" @@ -1591,6 +1594,7 @@ admin/views/emoji.vue: title: "絵文字の登録" name: "絵文字名" name-desc: "a~z 0~9 _ の文字が使えます。" + category: "カテゴリ" aliases: "エイリアス" aliases-desc: "スペースで区切って複数設定できます。" url: "絵文字画像URL" diff --git a/migration/1571220798684-CustomEmojiCategory.ts b/migration/1571220798684-CustomEmojiCategory.ts new file mode 100644 index 000000000000..37f63fa3d0c3 --- /dev/null +++ b/migration/1571220798684-CustomEmojiCategory.ts @@ -0,0 +1,13 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class CustomEmojiCategory1571220798684 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "emoji" ADD "category" character varying(128)`, undefined); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "category"`, undefined); + } + +} diff --git a/src/client/app/admin/views/emoji.vue b/src/client/app/admin/views/emoji.vue index 9f2f3a0c8808..296d5f924fa6 100644 --- a/src/client/app/admin/views/emoji.vue +++ b/src/client/app/admin/views/emoji.vue @@ -8,6 +8,9 @@ {{ $t('add-emoji.name') }} + + {{ $t('add-emoji.category') }} + {{ $t('add-emoji.aliases') }} @@ -24,7 +27,7 @@ -
+
@@ -33,6 +36,9 @@ {{ $t('add-emoji.name') }} + + {{ $t('add-emoji.category') }} + {{ $t('add-emoji.aliases') }} @@ -61,6 +67,7 @@ export default Vue.extend({ data() { return { name: '', + category: '', url: '', aliases: '', emojis: [], @@ -76,6 +83,7 @@ export default Vue.extend({ 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 +102,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 +113,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 88761ae93344..abae69e28ab2 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 @@
-
{{ categories.find(x => x.isActive).text }}
-
- -
-
- -
+ + +
{{ categories.find(x => x.isActive).text }}
+ +
@@ -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 f4ec9e501afd..fd3aceb72814 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 020636a7fb06..d6080ae09911 100644 --- a/src/models/entities/emoji.ts +++ b/src/models/entities/emoji.ts @@ -24,6 +24,11 @@ export class Emoji { }) public host: string | null; + @Column('varchar', { + length: 128, nullable: true + }) + public category: string | null; + @Column('varchar', { length: 512, }) diff --git a/src/prelude/array.ts b/src/prelude/array.ts index 839bbc920b9e..f4d684d57430 100644 --- a/src/prelude/array.ts +++ b/src/prelude/array.ts @@ -84,6 +84,19 @@ export function groupOn(f: (x: T) => S, xs: T[]): T[][] { return groupBy((a, b) => f(a) === f(b), xs); } +export function groupByX(collections: T[], keySelector: (x: T) => string) { + return collections.reduce((obj: Record, 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 6a91c31a9505..73339cdc0b77 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 54686a5c5a2a..d2a5e7df0d61 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 062a8d0fb853..f4a01a3976bc 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 0b56a9d4efad..153780e3fa38 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, From cff084b14cad65fe5e715f54fb82ad1a0520248c Mon Sep 17 00:00:00 2001 From: mei23 Date: Fri, 18 Oct 2019 02:03:40 +0900 Subject: [PATCH 2/4] remove unimplanted translation --- locales/ja-JP.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 83b10c16427c..1ef397a2e183 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -675,7 +675,6 @@ common/views/components/reaction-picker.vue: common/views/components/emoji-picker.vue: recent-emoji: "最近使った絵文字" custom-emoji: "カスタム絵文字" - remote-emoji: "リモートカスタム絵文字" no-category: "カテゴリなし" people: "人" animals-and-nature: "動物&自然" From ed273f9de27104ef8828d87a0934cd3b1e9db560 Mon Sep 17 00:00:00 2001 From: mei23 Date: Fri, 18 Oct 2019 20:02:55 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E3=82=AB=E3=83=86=E3=82=B4=E3=83=AA?= =?UTF-8?q?=E3=81=AE=E3=82=B5=E3=82=B8=E3=82=A7=E3=82=B9=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/app/admin/views/emoji.vue | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/client/app/admin/views/emoji.vue b/src/client/app/admin/views/emoji.vue index 296d5f924fa6..f4d2573a0660 100644 --- a/src/client/app/admin/views/emoji.vue +++ b/src/client/app/admin/views/emoji.vue @@ -8,8 +8,8 @@ {{ $t('add-emoji.name') }} - - {{ $t('add-emoji.category') }} + + {{ $t('add-emoji.category') }} {{ $t('add-emoji.aliases') }} @@ -36,7 +36,7 @@ {{ $t('add-emoji.name') }} - + {{ $t('add-emoji.category') }} @@ -61,6 +61,7 @@ import Vue from 'vue'; import i18n from '../../i18n'; import { faGrin } from '@fortawesome/free-regular-svg-icons'; +import { groupByX } from '../../../../prelude/array'; export default Vue.extend({ i18n: i18n('admin/views/emoji.vue'), @@ -79,6 +80,13 @@ export default Vue.extend({ this.fetchEmojis(); }, + computed: { + categoryList() { + const grouped = groupByX(this.emojis, (x: any) => x.category || ''); + return Object.keys(grouped).filter(x => x !== ''); + } + }, + methods: { add() { this.$root.api('admin/emoji/add', { From 8fefa57d8b5e7a24f1c6972ceea336151cf4b282 Mon Sep 17 00:00:00 2001 From: mei23 Date: Sat, 19 Oct 2019 02:13:30 +0900 Subject: [PATCH 4/4] use unique --- src/client/app/admin/views/emoji.vue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/app/admin/views/emoji.vue b/src/client/app/admin/views/emoji.vue index f4d2573a0660..2925fcab57b1 100644 --- a/src/client/app/admin/views/emoji.vue +++ b/src/client/app/admin/views/emoji.vue @@ -61,7 +61,7 @@ import Vue from 'vue'; import i18n from '../../i18n'; import { faGrin } from '@fortawesome/free-regular-svg-icons'; -import { groupByX } from '../../../../prelude/array'; +import { unique } from '../../../../prelude/array'; export default Vue.extend({ i18n: i18n('admin/views/emoji.vue'), @@ -82,8 +82,7 @@ export default Vue.extend({ computed: { categoryList() { - const grouped = groupByX(this.emojis, (x: any) => x.category || ''); - return Object.keys(grouped).filter(x => x !== ''); + return unique(this.emojis.map((x: any) => x.category || '').filter((x: string) => x !== '')); } },