Archived
2
0
This repository has been archived on 2024-06-24. You can view files and clone it, but cannot push or open issues or pull requests.
neko-custom/client/src/components/emoji.vue

383 lines
9.3 KiB
Vue
Raw Normal View History

2020-02-01 10:45:41 +13:00
<template>
<div class="neko-emoji">
<div class="search">
2020-02-01 23:43:02 +13:00
<div class="search-contianer">
<input type="text" ref="search" v-model="search" />
</div>
2020-02-01 10:45:41 +13:00
</div>
<div class="list" ref="scroll" @scroll="onScroll">
2020-02-02 09:35:48 +13:00
<ul :class="['group-list']" :style="{ display: search === '' ? 'flex' : 'none' }">
<li v-for="(group, index) in groups" :key="index" class="group" ref="groups">
<span class="label">{{ group.name }}</span>
<ul class="emoji-list">
<li
v-for="emoji in index === 0 ? recent : group.list"
:key="`${group.id}-${emoji}`"
:class="['emoji-container', hovered === emoji ? 'active' : '']"
>
<span
:class="['emoji']"
@mouseenter.stop.prevent="onMouseEnter($event, emoji)"
@click.stop.prevent="onClick($event, emoji)"
:data-emoji="emoji"
></span>
</li>
</ul>
</li>
</ul>
<ul :class="['emoji-container']" :style="{ display: search === '' ? 'none' : 'flex' }">
<li v-for="emoji in filtered" :key="emoji" :class="['emoji-item', hovered === emoji ? 'active' : '']">
<span
:class="['emoji']"
@mouseenter.stop.prevent="onMouseEnter($event, emoji)"
@click.stop.prevent="onClick($event, emoji)"
:data-emoji="emoji"
></span>
</li>
2020-02-01 10:45:41 +13:00
</ul>
</div>
<div class="details">
2020-02-02 09:35:48 +13:00
<div class="details-container" v-if="hovered !== ''">
<span :class="['emoji']" :data-emoji="hovered" /><span class="emoji-id">:{{ hovered }}:</span>
2020-02-01 23:43:02 +13:00
</div>
2020-02-01 10:45:41 +13:00
</div>
<div class="groups">
<ul>
<li
v-for="(group, index) in groups"
:key="index"
2020-02-02 09:35:48 +13:00
:class="[group.id, active.id === group.id && search === '' ? 'active' : '']"
2020-02-01 10:45:41 +13:00
@click.stop.prevent="scrollTo($event, index)"
>
2020-02-02 09:35:48 +13:00
<span :class="[`group-${group.id} fas`]" />
2020-02-01 10:45:41 +13:00
</li>
</ul>
</div>
</div>
</template>
<style lang="scss" scoped>
2020-02-01 23:43:02 +13:00
$emoji-width: 300px;
2020-02-01 10:45:41 +13:00
.neko-emoji {
position: absolute;
2020-02-01 23:43:02 +13:00
z-index: 10000;
2020-02-01 10:45:41 +13:00
width: $emoji-width;
2020-02-01 23:43:02 +13:00
height: 350px;
2020-02-01 10:45:41 +13:00
background: $background-secondary;
2020-02-02 09:35:48 +13:00
bottom: 75px;
right: 5px;
2020-02-01 10:45:41 +13:00
display: flex;
flex-direction: column;
border-radius: 5px;
overflow: hidden;
2020-02-01 23:43:02 +13:00
box-shadow: $elevation-high;
2020-02-01 10:45:41 +13:00
.search {
flex-shrink: 0;
border-bottom: 1px solid $background-tertiary;
2020-02-01 23:43:02 +13:00
padding: 10px;
2020-02-01 10:45:41 +13:00
2020-02-01 23:43:02 +13:00
.search-contianer {
border-radius: 5px;
color: $interactive-normal;
position: relative;
display: flex;
flex-direction: column;
align-content: center;
overflow: hidden;
&::before {
content: '\f002';
font-weight: 900;
font-family: 'Font Awesome 5 Free';
position: absolute;
width: 15px;
height: 15px;
top: 6px;
right: 6px;
opacity: 0.5;
}
input {
border: none;
background-color: $background-floating;
color: $interactive-normal;
padding: 5px;
font-weight: 500;
&::placeholder {
color: $text-muted;
font-weight: 500;
}
}
2020-02-01 10:45:41 +13:00
}
}
.list {
position: relative;
flex-grow: 1;
overflow-y: scroll;
overflow-x: hidden;
scrollbar-width: thin;
scrollbar-color: $background-tertiary transparent;
scroll-behavior: smooth;
padding: 5px;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: $background-tertiary;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb:hover {
background-color: $background-floating;
}
.group-list {
width: $emoji-width;
display: flex;
flex-direction: column;
li {
&.group {
.label {
z-index: 2;
text-transform: uppercase;
font-weight: 500;
font-size: 12px;
position: sticky;
2020-02-01 23:43:02 +13:00
top: -5px;
background-color: rgba($color: $background-secondary, $alpha: 0.9);
width: 100%;
display: block;
padding: 8px 0;
2020-02-01 10:45:41 +13:00
}
}
}
}
.emoji-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
li {
2020-02-02 09:35:48 +13:00
&.emoji-container {
2020-02-01 10:45:41 +13:00
padding: 2px;
border-radius: 3px;
2020-02-01 23:43:02 +13:00
cursor: pointer;
2020-02-01 10:45:41 +13:00
&.active {
2020-02-01 23:43:02 +13:00
background-color: $background-floating;
2020-02-01 10:45:41 +13:00
}
}
}
}
}
.details {
flex-shrink: 0;
2020-02-01 23:43:02 +13:00
display: flex;
align-content: center;
justify-content: center;
flex-direction: column;
2020-02-01 10:45:41 +13:00
height: 36px;
background: $background-tertiary;
2020-02-02 09:35:48 +13:00
.details-container {
2020-02-01 23:43:02 +13:00
display: flex;
align-content: center;
flex-direction: row;
height: 20px;
span {
cursor: default;
2020-02-02 09:35:48 +13:00
&.emoji {
2020-02-01 23:43:02 +13:00
margin: 0 5px 0 10px;
}
2020-02-01 10:45:41 +13:00
2020-02-02 09:35:48 +13:00
&.emoji-id {
2020-02-01 23:43:02 +13:00
line-height: 20px;
font-size: 16px;
font-weight: 500;
}
2020-02-01 10:45:41 +13:00
}
}
}
.groups {
flex-shrink: 0;
2020-02-01 23:43:02 +13:00
height: 30px;
2020-02-01 10:45:41 +13:00
background: $background-floating;
2020-02-01 23:43:02 +13:00
padding: 0 5px;
2020-02-01 10:45:41 +13:00
ul {
display: flex;
flex-direction: row;
flex-wrap: wrap;
li {
flex-grow: 1;
2020-02-01 23:43:02 +13:00
display: flex;
flex-direction: row;
justify-content: center;
align-content: center;
flex-direction: column;
height: 27px;
cursor: pointer;
2020-02-01 10:45:41 +13:00
&.active {
2020-02-01 23:43:02 +13:00
border-bottom: 3px solid $style-primary;
}
span {
margin: 0 auto;
height: 20px;
width: 20px;
2020-02-02 09:35:48 +13:00
font-size: 16px;
line-height: 20px;
text-align: center;
2020-02-01 23:43:02 +13:00
2020-02-02 09:35:48 +13:00
&.group-recent::before {
content: '\f017';
}
&.group-neko::before {
content: '\f6be';
2020-02-01 23:43:02 +13:00
}
2020-02-02 09:35:48 +13:00
&.group-emotion::before {
content: '\f118';
2020-02-01 23:43:02 +13:00
}
2020-02-02 09:35:48 +13:00
&.group-people::before {
content: '\f0c0';
2020-02-01 23:43:02 +13:00
}
2020-02-02 09:35:48 +13:00
&.group-nature::before {
content: '\f1b0';
2020-02-01 23:43:02 +13:00
}
2020-02-02 09:35:48 +13:00
&.group-food::before {
content: '\f5d1';
2020-02-01 23:43:02 +13:00
}
2020-02-02 09:35:48 +13:00
&.group-activity::before {
content: '\f44e';
2020-02-01 23:43:02 +13:00
}
2020-02-02 09:35:48 +13:00
&.group-travel::before {
content: '\f1b9';
2020-02-01 23:43:02 +13:00
}
2020-02-02 09:35:48 +13:00
&.group-objects::before {
content: '\f0eb';
2020-02-01 23:43:02 +13:00
}
2020-02-02 09:35:48 +13:00
&.group-symbols::before {
content: '\f86d';
2020-02-01 23:43:02 +13:00
}
2020-02-02 09:35:48 +13:00
&.group-flags::before {
content: '\f024';
2020-02-01 23:43:02 +13:00
}
2020-02-01 10:45:41 +13:00
}
}
}
}
}
</style>
<script lang="ts">
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
2020-02-02 09:35:48 +13:00
import { get, set } from '../utils/localstorage'
2020-02-01 10:45:41 +13:00
@Component({
name: 'neko-emoji',
})
export default class extends Vue {
@Ref('scroll') readonly _scroll!: HTMLElement
@Ref('search') readonly _search!: HTMLInputElement
@Ref('groups') readonly _groups!: HTMLElement[]
waitingForPaint = false
search = ''
2020-02-02 09:35:48 +13:00
index = 0
2020-02-01 10:45:41 +13:00
hovered = ''
2020-02-02 09:35:48 +13:00
recent: string[] = JSON.parse(get('emoji_recent', '[]'))
get active() {
return this.$accessor.emoji.groups[this.index]
}
get keywords() {
return this.$accessor.emoji.keywords
}
2020-02-01 10:45:41 +13:00
get groups() {
2020-02-02 09:35:48 +13:00
return this.$accessor.emoji.groups
}
get list() {
return this.$accessor.emoji.list
2020-02-01 10:45:41 +13:00
}
get filtered() {
const filtered = []
2020-02-02 09:35:48 +13:00
for (const emoji of this.list) {
2020-02-01 10:45:41 +13:00
if (
2020-02-02 09:35:48 +13:00
emoji.includes(this.search) || typeof this.keywords[emoji] !== 'undefined'
? this.keywords[emoji].some(keyword => keyword.includes(this.search))
2020-02-01 10:45:41 +13:00
: false
) {
filtered.push(emoji)
}
}
return filtered
}
scrollTo(event: MouseEvent, index: number) {
2020-02-02 09:35:48 +13:00
if (!this._groups[index]) {
2020-02-01 10:45:41 +13:00
return
}
2020-02-02 09:35:48 +13:00
this._scroll.scrollTop = index == 0 ? 0 : this._groups[index].offsetTop
2020-02-01 10:45:41 +13:00
}
onScroll() {
if (!this.waitingForPaint) {
this.waitingForPaint = true
window.requestAnimationFrame(this.onScrollPaint.bind(this))
}
}
onScrollPaint() {
this.waitingForPaint = false
let scrollTop = this._scroll.scrollTop
2020-02-02 09:35:48 +13:00
let active = 0
for (const [i, group] of this.groups.entries()) {
2020-02-01 10:45:41 +13:00
let component = this._groups[i]
if (component && component.offsetTop > scrollTop) {
break
}
2020-02-02 09:35:48 +13:00
active = i
}
if (this.index !== active) {
this.index = active
2020-02-01 10:45:41 +13:00
}
}
onMouseExit(event: MouseEvent, emoji: string) {
this.hovered = ''
}
onMouseEnter(event: MouseEvent, emoji: string) {
this.hovered = emoji
this._search.placeholder = `:${emoji}:`
}
onClick(event: MouseEvent, emoji: string) {
2020-02-02 09:35:48 +13:00
this.$accessor.emoji.setRecent(emoji)
2020-02-01 23:43:02 +13:00
this.$emit('picked', emoji)
2020-02-01 10:45:41 +13:00
}
}
</script>