Improve emoji picker
This commit is contained in:
parent
abb3d2a8d9
commit
9c9cd168ee
4 changed files with 75 additions and 293 deletions
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
|
<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
|
||||||
<div class="omfetrab _popup">
|
<div class="omfetrab _popup" :class="{ compact }">
|
||||||
<input ref="search" class="search" v-model.trim="q" :placeholder="$t('search')" @paste.stop="paste" @keyup.enter="done()" autofocus>
|
<input ref="search" class="search" :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$t('search')" @paste.stop="paste" @keyup.enter="done()">
|
||||||
<div class="emojis">
|
<div class="emojis">
|
||||||
<section class="result">
|
<section class="result">
|
||||||
<div v-if="searchResultCustom.length > 0">
|
<div v-if="searchResultCustom.length > 0">
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<header class="_acrylic"><Fa :icon="faHistory" fixed-width/> {{ $t('recentUsed') }}</header>
|
<header class="_acrylic"><Fa :icon="faClock" fixed-width/> {{ $t('recentUsed') }}</header>
|
||||||
<div>
|
<div>
|
||||||
<button v-for="emoji in $store.state.device.recentlyUsedEmojis"
|
<button v-for="emoji in $store.state.device.recentlyUsedEmojis"
|
||||||
class="_button"
|
class="_button"
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
import { defineComponent, markRaw } from 'vue';
|
import { defineComponent, markRaw } from 'vue';
|
||||||
import { emojilist } from '../../misc/emojilist';
|
import { emojilist } from '../../misc/emojilist';
|
||||||
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
|
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
|
||||||
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faClock, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faHeart, faFlag, faLaugh } from '@fortawesome/free-regular-svg-icons';
|
import { faHeart, faFlag, faLaugh } from '@fortawesome/free-regular-svg-icons';
|
||||||
import MkModal from '@/components/ui/modal.vue';
|
import MkModal from '@/components/ui/modal.vue';
|
||||||
import Particle from '@/components/particle.vue';
|
import Particle from '@/components/particle.vue';
|
||||||
|
@ -112,6 +112,9 @@ export default defineComponent({
|
||||||
overridePinned: {
|
overridePinned: {
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
|
compact: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ['done', 'closed'],
|
emits: ['done', 'closed'],
|
||||||
|
@ -127,7 +130,7 @@ export default defineComponent({
|
||||||
q: null,
|
q: null,
|
||||||
searchResultCustom: [],
|
searchResultCustom: [],
|
||||||
searchResultUnicode: [],
|
searchResultUnicode: [],
|
||||||
faGlobe, faHistory, faChevronDown,
|
faGlobe, faClock, faChevronDown,
|
||||||
categories: [{
|
categories: [{
|
||||||
name: 'face',
|
name: 'face',
|
||||||
icon: faLaugh,
|
icon: faLaugh,
|
||||||
|
@ -311,9 +314,12 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$refs.search.focus({
|
const isIos = navigator.userAgent.includes('WebKit') && !navigator.userAgent.includes('Chrome');
|
||||||
preventScroll: true
|
if (!isIos) {
|
||||||
});
|
this.$refs.search.focus({
|
||||||
|
preventScroll: true
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -379,8 +385,19 @@ export default defineComponent({
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.omfetrab {
|
.omfetrab {
|
||||||
width: 350px;
|
$eachSize: 40px;
|
||||||
|
$pad: 8px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: ($eachSize * 7) + ($pad * 2);
|
||||||
contain: content;
|
contain: content;
|
||||||
|
--height: 300px;
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
width: ($eachSize * 5) + ($pad * 2);
|
||||||
|
--height: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
> .search {
|
> .search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -391,17 +408,27 @@ export default defineComponent({
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
|
|
||||||
|
&:not(.filled) {
|
||||||
|
order: 1;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0px -1px 0 0px var(--divider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .emojis {
|
> .emojis {
|
||||||
$height: 300px;
|
height: var(--height);
|
||||||
|
|
||||||
height: $height;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
> .index {
|
> .index {
|
||||||
min-height: $height;
|
min-height: var(--height);
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom: solid 1px var(--divider);
|
border-bottom: solid 1px var(--divider);
|
||||||
|
|
||||||
|
@ -428,45 +455,33 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
display: grid;
|
padding: $pad;
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
> button {
|
> button {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: $eachSize;
|
||||||
|
height: $eachSize;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: solid 2px var(--focus);
|
outline: solid 2px var(--focus);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:before {
|
&:hover {
|
||||||
content: '';
|
background: rgba(0, 0, 0, 0.05);
|
||||||
display: block;
|
|
||||||
width: 1px;
|
|
||||||
height: 0;
|
|
||||||
padding-bottom: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:active {
|
||||||
> * {
|
background: var(--accent);
|
||||||
transform: scale(1.2);
|
box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
|
||||||
transition: transform 0s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
position: absolute;
|
font-size: 24px;
|
||||||
top: 0;
|
height: 1.25em;
|
||||||
left: 0;
|
vertical-align: -.25em;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
font-size: 28px;
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -474,6 +489,10 @@ export default defineComponent({
|
||||||
|
|
||||||
&.result {
|
&.result {
|
||||||
border-bottom: solid 1px var(--divider);
|
border-bottom: solid 1px var(--divider);
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unicode {
|
&.unicode {
|
||||||
|
|
|
@ -498,36 +498,20 @@ export default defineComponent({
|
||||||
react(viaKeyboard = false) {
|
react(viaKeyboard = false) {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
this.blur();
|
this.blur();
|
||||||
if (this.$store.state.device.useFullReactionPicker) {
|
os.popup(import('@/components/emoji-picker.vue'), {
|
||||||
os.popup(import('@/components/emoji-picker.vue'), {
|
src: this.$refs.reactButton,
|
||||||
src: this.$refs.reactButton,
|
compact: !this.$store.state.device.useFullReactionPicker
|
||||||
}, {
|
}, {
|
||||||
done: reaction => {
|
done: reaction => {
|
||||||
if (reaction) {
|
if (reaction) {
|
||||||
os.api('notes/reactions/create', {
|
os.api('notes/reactions/create', {
|
||||||
noteId: this.appearNote.id,
|
noteId: this.appearNote.id,
|
||||||
reaction: reaction
|
reaction: reaction
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.focus();
|
this.focus();
|
||||||
},
|
},
|
||||||
}, 'closed');
|
}, 'closed');
|
||||||
} else {
|
|
||||||
os.popup(import('@/components/reaction-picker.vue'), {
|
|
||||||
showFocus: viaKeyboard,
|
|
||||||
src: this.$refs.reactButton,
|
|
||||||
}, {
|
|
||||||
done: reaction => {
|
|
||||||
if (reaction) {
|
|
||||||
os.api('notes/reactions/create', {
|
|
||||||
noteId: this.appearNote.id,
|
|
||||||
reaction: reaction
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.focus();
|
|
||||||
},
|
|
||||||
}, 'closed');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reactDirectly(reaction) {
|
reactDirectly(reaction) {
|
||||||
|
|
|
@ -1,214 +0,0 @@
|
||||||
<template>
|
|
||||||
<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
|
|
||||||
<div class="rdfaahpb _popup" v-hotkey="keymap">
|
|
||||||
<div class="buttons" ref="buttons" :class="{ showFocus }">
|
|
||||||
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="reaction" v-particle><XReactionIcon :reaction="reaction"/></button>
|
|
||||||
</div>
|
|
||||||
<input class="text" ref="text" v-model.trim="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText">
|
|
||||||
</div>
|
|
||||||
</MkModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { emojiRegex } from '../../misc/emoji-regex';
|
|
||||||
import XReactionIcon from '@/components/reaction-icon.vue';
|
|
||||||
import MkModal from '@/components/ui/modal.vue';
|
|
||||||
import { Autocomplete } from '@/scripts/autocomplete';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
XReactionIcon,
|
|
||||||
MkModal,
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
reactions: {
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
|
|
||||||
showFocus: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
|
|
||||||
src: {
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ['done', 'closed'],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
rs: this.reactions || this.$store.state.settings.reactions,
|
|
||||||
text: null,
|
|
||||||
focus: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
keymap(): any {
|
|
||||||
return {
|
|
||||||
'esc': this.close,
|
|
||||||
'enter|space|plus': this.choose,
|
|
||||||
'up|k': this.focusUp,
|
|
||||||
'left|h|shift+tab': this.focusLeft,
|
|
||||||
'right|l|tab': this.focusRight,
|
|
||||||
'down|j': this.focusDown,
|
|
||||||
'1': () => this.react(this.rs[0]),
|
|
||||||
'2': () => this.react(this.rs[1]),
|
|
||||||
'3': () => this.react(this.rs[2]),
|
|
||||||
'4': () => this.react(this.rs[3]),
|
|
||||||
'5': () => this.react(this.rs[4]),
|
|
||||||
'6': () => this.react(this.rs[5]),
|
|
||||||
'7': () => this.react(this.rs[6]),
|
|
||||||
'8': () => this.react(this.rs[7]),
|
|
||||||
'9': () => this.react(this.rs[8]),
|
|
||||||
'0': () => this.react(this.rs[9]),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
focus(i) {
|
|
||||||
this.$refs.buttons.children[i].focus({
|
|
||||||
preventScroll: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.focus = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: detach when unmount
|
|
||||||
new Autocomplete(this.$refs.text, this, { model: 'text' });
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
close() {
|
|
||||||
this.$emit('done');
|
|
||||||
this.$refs.modal.close();
|
|
||||||
},
|
|
||||||
|
|
||||||
react(reaction) {
|
|
||||||
this.$emit('done', reaction);
|
|
||||||
this.$refs.modal.close();
|
|
||||||
},
|
|
||||||
|
|
||||||
reactText() {
|
|
||||||
if (!this.text) return;
|
|
||||||
this.react(this.text);
|
|
||||||
},
|
|
||||||
|
|
||||||
tryReactText() {
|
|
||||||
if (!this.text) return;
|
|
||||||
if (!this.text.match(emojiRegex)) return;
|
|
||||||
this.reactText();
|
|
||||||
},
|
|
||||||
|
|
||||||
focusUp() {
|
|
||||||
this.focus = this.focus == 0 ? 9 : this.focus < 5 ? (this.focus + 4) : (this.focus - 5);
|
|
||||||
},
|
|
||||||
|
|
||||||
focusDown() {
|
|
||||||
this.focus = this.focus == 9 ? 0 : this.focus >= 5 ? (this.focus - 4) : (this.focus + 5);
|
|
||||||
},
|
|
||||||
|
|
||||||
focusRight() {
|
|
||||||
this.focus = this.focus == 9 ? 0 : (this.focus + 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
focusLeft() {
|
|
||||||
this.focus = this.focus == 0 ? 9 : (this.focus - 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
choose() {
|
|
||||||
this.$refs.buttons.children[this.focus].click();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.rdfaahpb {
|
|
||||||
> .buttons {
|
|
||||||
padding: 6px 6px 0 6px;
|
|
||||||
width: 212px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
@media (max-width: 1025px) {
|
|
||||||
padding: 8px 8px 0 8px;
|
|
||||||
width: 256px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.showFocus {
|
|
||||||
> button:focus {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
border: 2px solid var(--focus);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> button {
|
|
||||||
padding: 0;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
font-size: 24px;
|
|
||||||
border-radius: 2px;
|
|
||||||
|
|
||||||
@media (max-width: 1025px) {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
font-size: 26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&: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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .text {
|
|
||||||
width: 208px;
|
|
||||||
padding: 8px;
|
|
||||||
margin: 0 0 6px 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 16px;
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--fg);
|
|
||||||
|
|
||||||
@media (max-width: 1025px) {
|
|
||||||
width: 256px;
|
|
||||||
margin: 4px 0 8px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -80,18 +80,11 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
preview(ev) {
|
preview(ev) {
|
||||||
if (this.$store.state.device.useFullReactionPicker) {
|
os.popup(import('@/components/emoji-picker.vue'), {
|
||||||
os.popup(import('@/components/emoji-picker.vue'), {
|
overridePinned: this.splited,
|
||||||
overridePinned: this.splited,
|
compact: !this.$store.state.device.useFullReactionPicker,
|
||||||
src: ev.currentTarget || ev.target,
|
src: ev.currentTarget || ev.target,
|
||||||
}, {}, 'closed');
|
}, {}, 'closed');
|
||||||
} else {
|
|
||||||
os.popup(import('@/components/reaction-picker.vue'), {
|
|
||||||
reactions: this.splited,
|
|
||||||
showFocus: false,
|
|
||||||
src: ev.currentTarget || ev.target,
|
|
||||||
}, {}, 'closed');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setDefault() {
|
setDefault() {
|
||||||
|
|
Loading…
Reference in a new issue