summaryrefslogtreecommitdiff
path: root/packages/client/src/components/MkEmojiPicker.vue
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-12-27 14:36:33 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2022-12-27 14:36:33 +0900
commit9384f5399da39e53855beb8e7f8ded1aa56bf72e (patch)
treece5959571a981b9c4047da3c7b3fd080aa44222c /packages/client/src/components/MkEmojiPicker.vue
parentwip: retention for dashboard (diff)
downloadmisskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.gz
misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.bz2
misskey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.zip
rename: client -> frontend
Diffstat (limited to 'packages/client/src/components/MkEmojiPicker.vue')
-rw-r--r--packages/client/src/components/MkEmojiPicker.vue569
1 files changed, 0 insertions, 569 deletions
diff --git a/packages/client/src/components/MkEmojiPicker.vue b/packages/client/src/components/MkEmojiPicker.vue
deleted file mode 100644
index 814f71168a..0000000000
--- a/packages/client/src/components/MkEmojiPicker.vue
+++ /dev/null
@@ -1,569 +0,0 @@
-<template>
-<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
- <input ref="search" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()">
- <div ref="emojis" class="emojis">
- <section class="result">
- <div v-if="searchResultCustom.length > 0" class="body">
- <button
- v-for="emoji in searchResultCustom"
- :key="emoji.id"
- class="_button item"
- :title="emoji.name"
- tabindex="0"
- @click="chosen(emoji, $event)"
- >
- <!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>-->
- <img class="emoji" :src="disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
- </button>
- </div>
- <div v-if="searchResultUnicode.length > 0" class="body">
- <button
- v-for="emoji in searchResultUnicode"
- :key="emoji.name"
- class="_button item"
- :title="emoji.name"
- tabindex="0"
- @click="chosen(emoji, $event)"
- >
- <MkEmoji class="emoji" :emoji="emoji.char"/>
- </button>
- </div>
- </section>
-
- <div v-if="tab === 'index'" class="group index">
- <section v-if="showPinned">
- <div class="body">
- <button
- v-for="emoji in pinned"
- :key="emoji"
- class="_button item"
- tabindex="0"
- @click="chosen(emoji, $event)"
- >
- <MkEmoji class="emoji" :emoji="emoji" :normal="true"/>
- </button>
- </div>
- </section>
-
- <section>
- <header class="_acrylic"><i class="ti ti-clock ti-fw"></i> {{ i18n.ts.recentUsed }}</header>
- <div class="body">
- <button
- v-for="emoji in recentlyUsedEmojis"
- :key="emoji"
- class="_button item"
- @click="chosen(emoji, $event)"
- >
- <MkEmoji class="emoji" :emoji="emoji" :normal="true"/>
- </button>
- </div>
- </section>
- </div>
- <div v-once class="group">
- <header class="_acrylic">{{ i18n.ts.customEmojis }}</header>
- <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.ts.other }}</XSection>
- </div>
- <div v-once class="group">
- <header class="_acrylic">{{ i18n.ts.emoji }}</header>
- <XSection v-for="category in categories" :key="category" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection>
- </div>
- </div>
- <div class="tabs">
- <button class="_button tab" :class="{ active: tab === 'index' }" @click="tab = 'index'"><i class="ti ti-asterisk ti-fw"></i></button>
- <button class="_button tab" :class="{ active: tab === 'custom' }" @click="tab = 'custom'"><i class="ti ti-mood-happy ti-fw"></i></button>
- <button class="_button tab" :class="{ active: tab === 'unicode' }" @click="tab = 'unicode'"><i class="ti ti-leaf ti-fw"></i></button>
- <button class="_button tab" :class="{ active: tab === 'tags' }" @click="tab = 'tags'"><i class="ti ti-hash ti-fw"></i></button>
- </div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { ref, computed, watch, onMounted } from 'vue';
-import * as Misskey from 'misskey-js';
-import XSection from '@/components/MkEmojiPicker.section.vue';
-import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist';
-import { getStaticImageUrl } from '@/scripts/get-static-image-url';
-import Ripple from '@/components/MkRipple.vue';
-import * as os from '@/os';
-import { isTouchUsing } from '@/scripts/touch';
-import { deviceKind } from '@/scripts/device-kind';
-import { emojiCategories, instance } from '@/instance';
-import { i18n } from '@/i18n';
-import { defaultStore } from '@/store';
-
-const props = withDefaults(defineProps<{
- showPinned?: boolean;
- asReactionPicker?: boolean;
- maxHeight?: number;
- asDrawer?: boolean;
-}>(), {
- showPinned: true,
-});
-
-const emit = defineEmits<{
- (ev: 'chosen', v: string): void;
-}>();
-
-const search = ref<HTMLInputElement>();
-const emojis = ref<HTMLDivElement>();
-
-const {
- reactions: pinned,
- reactionPickerSize,
- reactionPickerWidth,
- reactionPickerHeight,
- disableShowingAnimatedImages,
- recentlyUsedEmojis,
-} = defaultStore.reactiveState;
-
-const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1);
-const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3);
-const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2);
-const customEmojiCategories = emojiCategories;
-const customEmojis = instance.emojis;
-const q = ref<string>('');
-const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]);
-const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
-const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index');
-
-watch(q, () => {
- if (emojis.value) emojis.value.scrollTop = 0;
-
- if (q.value === '') {
- searchResultCustom.value = [];
- searchResultUnicode.value = [];
- return;
- }
-
- const newQ = q.value.replace(/:/g, '').toLowerCase();
-
- const searchCustom = () => {
- const max = 8;
- const emojis = customEmojis;
- const matches = new Set<Misskey.entities.CustomEmoji>();
-
- const exactMatch = emojis.find(emoji => emoji.name === newQ);
- if (exactMatch) matches.add(exactMatch);
-
- if (newQ.includes(' ')) { // AND検索
- const keywords = newQ.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(newQ)) {
- 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(newQ))) {
- matches.add(emoji);
- if (matches.size >= max) break;
- }
- }
- if (matches.size >= max) return matches;
-
- for (const emoji of emojis) {
- if (emoji.name.includes(newQ)) {
- 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(newQ))) {
- matches.add(emoji);
- if (matches.size >= max) break;
- }
- }
- }
-
- return matches;
- };
-
- const searchUnicode = () => {
- const max = 8;
- const emojis = emojilist;
- const matches = new Set<UnicodeEmojiDef>();
-
- const exactMatch = emojis.find(emoji => emoji.name === newQ);
- if (exactMatch) matches.add(exactMatch);
-
- if (newQ.includes(' ')) { // AND検索
- const keywords = newQ.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(newQ)) {
- 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(newQ))) {
- matches.add(emoji);
- if (matches.size >= max) break;
- }
- }
- if (matches.size >= max) return matches;
-
- for (const emoji of emojis) {
- if (emoji.name.includes(newQ)) {
- 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(newQ))) {
- matches.add(emoji);
- if (matches.size >= max) break;
- }
- }
- }
-
- return matches;
- };
-
- searchResultCustom.value = Array.from(searchCustom());
- searchResultUnicode.value = Array.from(searchUnicode());
-});
-
-function focus() {
- if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
- search.value?.focus({
- preventScroll: true,
- });
- }
-}
-
-function reset() {
- if (emojis.value) emojis.value.scrollTop = 0;
- q.value = '';
-}
-
-function getKey(emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef): string {
- return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`;
-}
-
-function chosen(emoji: any, ev?: MouseEvent) {
- const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
- if (el) {
- const rect = el.getBoundingClientRect();
- const x = rect.left + (el.offsetWidth / 2);
- const y = rect.top + (el.offsetHeight / 2);
- os.popup(Ripple, { x, y }, {}, 'end');
- }
-
- const key = getKey(emoji);
- emit('chosen', key);
-
- // 最近使った絵文字更新
- if (!pinned.value.includes(key)) {
- let recents = defaultStore.state.recentlyUsedEmojis;
- recents = recents.filter((emoji: any) => emoji !== key);
- recents.unshift(key);
- defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
- }
-}
-
-function input(): void {
- // Using custom input event instead of v-model to respond immediately on
- // Android, where composition happens on all languages
- // (v-model does not update during composition)
- q.value = search.value?.value.trim() ?? '';
-}
-
-function paste(event: ClipboardEvent): void {
- const pasted = event.clipboardData?.getData('text') ?? '';
- if (done(pasted)) {
- event.preventDefault();
- }
-}
-
-function done(query?: string): boolean | void {
- if (query == null) query = q.value;
- if (query == null || typeof query !== 'string') return;
-
- const q2 = query.replace(/:/g, '');
- const exactMatchCustom = customEmojis.find(emoji => emoji.name === q2);
- if (exactMatchCustom) {
- chosen(exactMatchCustom);
- return true;
- }
- const exactMatchUnicode = emojilist.find(emoji => emoji.char === q2 || emoji.name === q2);
- if (exactMatchUnicode) {
- chosen(exactMatchUnicode);
- return true;
- }
- if (searchResultCustom.value.length > 0) {
- chosen(searchResultCustom.value[0]);
- return true;
- }
- if (searchResultUnicode.value.length > 0) {
- chosen(searchResultUnicode.value[0]);
- return true;
- }
-}
-
-onMounted(() => {
- focus();
-});
-
-defineExpose({
- focus,
- reset,
-});
-</script>
-
-<style lang="scss" scoped>
-.omfetrab {
- $pad: 8px;
-
- display: flex;
- flex-direction: column;
-
- &.s1 {
- --eachSize: 40px;
- }
-
- &.s2 {
- --eachSize: 45px;
- }
-
- &.s3 {
- --eachSize: 50px;
- }
-
- &.w1 {
- width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
- --columns: 1fr 1fr 1fr 1fr 1fr;
- }
-
- &.w2 {
- width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
- --columns: 1fr 1fr 1fr 1fr 1fr 1fr;
- }
-
- &.w3 {
- width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
- --columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
- }
-
- &.w4 {
- width: calc((var(--eachSize) * 8) + (#{$pad} * 2));
- --columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
- }
-
- &.w5 {
- width: calc((var(--eachSize) * 9) + (#{$pad} * 2));
- --columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
- }
-
- &.h1 {
- height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
- }
-
- &.h2 {
- height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
- }
-
- &.h3 {
- height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
- }
-
- &.h4 {
- height: calc((var(--eachSize) * 10) + (#{$pad} * 2));
- }
-
- &.asDrawer {
- width: 100% !important;
-
- > .emojis {
- ::v-deep(section) {
- > header {
- height: 32px;
- line-height: 32px;
- padding: 0 12px;
- font-size: 15px;
- }
-
- > .body {
- display: grid;
- grid-template-columns: var(--columns);
- font-size: 30px;
-
- > .item {
- aspect-ratio: 1 / 1;
- width: auto;
- height: auto;
- min-width: 0;
- }
- }
- }
- }
- }
-
- > .search {
- width: 100%;
- padding: 12px;
- box-sizing: border-box;
- font-size: 1em;
- outline: none;
- border: none;
- background: transparent;
- color: var(--fg);
-
- &:not(.filled) {
- order: 1;
- z-index: 2;
- box-shadow: 0px -1px 0 0px var(--divider);
- }
- }
-
- > .tabs {
- display: flex;
- display: none;
-
- > .tab {
- flex: 1;
- height: 38px;
- border-top: solid 0.5px var(--divider);
-
- &.active {
- border-top: solid 1px var(--accent);
- color: var(--accent);
- }
- }
- }
-
- > .emojis {
- height: 100%;
- overflow-y: auto;
- overflow-x: hidden;
-
- scrollbar-width: none;
-
- &::-webkit-scrollbar {
- display: none;
- }
-
- > .group {
- &:not(.index) {
- padding: 4px 0 8px 0;
- border-top: solid 0.5px var(--divider);
- }
-
- > header {
- /*position: sticky;
- top: 0;
- left: 0;*/
- height: 32px;
- line-height: 32px;
- z-index: 2;
- padding: 0 8px;
- font-size: 12px;
- }
- }
-
- ::v-deep(section) {
- > header {
- position: sticky;
- top: 0;
- left: 0;
- height: 32px;
- line-height: 32px;
- z-index: 1;
- padding: 0 8px;
- font-size: 12px;
- cursor: pointer;
-
- &:hover {
- color: var(--accent);
- }
- }
-
- > .body {
- position: relative;
- padding: $pad;
-
- > .item {
- position: relative;
- padding: 0;
- width: var(--eachSize);
- height: var(--eachSize);
- contain: strict;
- border-radius: 4px;
- font-size: 24px;
-
- &:focus-visible {
- outline: solid 2px var(--focus);
- z-index: 1;
- }
-
- &:hover {
- background: rgba(0, 0, 0, 0.05);
- }
-
- &:active {
- background: var(--accent);
- box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
- }
-
- > .emoji {
- height: 1.25em;
- vertical-align: -.25em;
- pointer-events: none;
- }
- }
- }
-
- &.result {
- border-bottom: solid 0.5px var(--divider);
-
- &:empty {
- display: none;
- }
- }
- }
- }
-}
-</style>