yay emoji!!!

This commit is contained in:
Craig
2020-02-01 20:35:48 +00:00
parent 1f1e67b829
commit 98980cc565
36 changed files with 2211 additions and 11779 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -12,7 +12,7 @@
@import "vendor/swal";
@import "vendor/tooltip";
@import "vendor/github";
@import "vendor/emoji_20";
@import "vendor/emoji";
html, body {
-webkit-font-smoothing: subpixel-antialiased;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
<div class="window">
<div class="loading" v-if="loading">
<div class="logo">
<img src="@/assets/logo.svg" alt="n.eko" />
<img src="@/assets/images/logo.svg" alt="n.eko" />
<span><b>N</b>.EKO</span>
</div>
<div class="loader">

View File

@ -42,9 +42,8 @@
v-model="content"
@click.stop.prevent="emoji = false"
/>
<div class="emoji" @click.stop.prevent="emoji = !emoji">
<neko-emoji v-if="emoji" @picked="onEmojiPicked" />
</div>
<neko-emoji v-if="emoji" @picked="onEmojiPicked" />
<i class="emoji-menu fas fa-laugh" @click.stop.prevent="emoji = !emoji"></i>
</div>
</div>
</div>
@ -101,6 +100,8 @@
word-wrap: break-word;
&.message {
font-size: 16px;
.author {
flex-grow: 0;
flex-shrink: 0;
@ -134,7 +135,6 @@
display: inline-block;
color: $text-normal;
font-weight: 500;
font-size: 16px;
}
.timestamp {
@ -152,7 +152,7 @@
::v-deep .content-body {
color: $text-normal;
line-height: 20px;
line-height: 22px;
word-wrap: break-word;
overflow-wrap: break-word;
@ -273,14 +273,15 @@
height: 100%;
background-color: rgba($color: #fff, $alpha: 0.05);
border-radius: 5px;
position: relative;
display: flex;
.emoji {
.emoji-menu {
width: 20px;
height: 20px;
// background: #fff;
margin: 3px 3px 0 0;
position: relative;
font-size: 20px;
margin: 8px 5px 0 0;
cursor: pointer;
}
textarea {

View File

@ -2,7 +2,7 @@
<div class="connect">
<div class="window">
<div class="logo">
<img src="@/assets/logo.svg" alt="n.eko" />
<img src="@/assets/images/logo.svg" alt="n.eko" />
<span><b>n</b>.eko</span>
</div>
<form class="message" v-if="!connecting" @submit.stop.prevent="connect">

View File

@ -6,39 +6,39 @@
</div>
</div>
<div class="list" ref="scroll" @scroll="onScroll">
<ul :class="[search === '' ? 'group-list' : 'emoji-list']">
<template v-if="search === ''">
<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 group.list"
:key="`${group.id}-${emoji}`"
:class="['emoji', hovered === emoji ? 'active' : '']"
>
<span
:class="['emoji-20', `e-${emoji}`]"
@mouseenter.stop.prevent="onMouseEnter($event, emoji)"
@click.stop.prevent="onClick($event, emoji)"
></span>
</li>
</ul>
</li>
</template>
<template v-else>
<li v-for="emoji in filtered" :key="emoji" :class="['emoji', hovered === emoji ? 'active' : '']">
<span
:class="['emoji-20', `e-${emoji}`]"
@mouseenter.stop.prevent="onMouseEnter($event, emoji)"
@click.stop.prevent="onClick($event, emoji)"
></span>
</li>
</template>
<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>
</ul>
</div>
<div class="details">
<div class="icon-container" v-if="hovered !== ''">
<span :class="['icon', 'emoji-20', `e-${hovered}`]"></span><span class="emoji">:{{ hovered }}:</span>
<div class="details-container" v-if="hovered !== ''">
<span :class="['emoji']" :data-emoji="hovered" /><span class="emoji-id">:{{ hovered }}:</span>
</div>
</div>
<div class="groups">
@ -46,10 +46,10 @@
<li
v-for="(group, index) in groups"
:key="index"
:class="[group.id, active === group.id && search === '' ? 'active' : '']"
:class="[group.id, active.id === group.id && search === '' ? 'active' : '']"
@click.stop.prevent="scrollTo($event, index)"
>
<span :class="[`group-${group.id}`]" />
<span :class="[`group-${group.id} fas`]" />
</li>
</ul>
</div>
@ -65,8 +65,8 @@
width: $emoji-width;
height: 350px;
background: $background-secondary;
bottom: 30px;
right: 0;
bottom: 75px;
right: 5px;
display: flex;
flex-direction: column;
border-radius: 5px;
@ -169,7 +169,7 @@
flex-direction: row;
flex-wrap: wrap;
li {
&.emoji {
&.emoji-container {
padding: 2px;
border-radius: 3px;
cursor: pointer;
@ -191,7 +191,7 @@
height: 36px;
background: $background-tertiary;
.icon-container {
.details-container {
display: flex;
align-content: center;
flex-direction: row;
@ -200,11 +200,11 @@
span {
cursor: default;
&.icon {
&.emoji {
margin: 0 5px 0 10px;
}
&.emoji {
&.emoji-id {
line-height: 20px;
font-size: 16px;
font-weight: 500;
@ -242,36 +242,42 @@
margin: 0 auto;
height: 20px;
width: 20px;
font-size: 16px;
line-height: 20px;
text-align: center;
&.group-recent {
background-color: #fff;
&.group-recent::before {
content: '\f017';
}
&.group-neko {
background-color: #fff;
&.group-neko::before {
content: '\f6be';
}
&.group-people {
background-color: #fff;
&.group-emotion::before {
content: '\f118';
}
&.group-nature {
background-color: #fff;
&.group-people::before {
content: '\f0c0';
}
&.group-food {
background-color: #fff;
&.group-nature::before {
content: '\f1b0';
}
&.group-activity {
background-color: #fff;
&.group-food::before {
content: '\f5d1';
}
&.group-travel {
background-color: #fff;
&.group-activity::before {
content: '\f44e';
}
&.group-objects {
background-color: #fff;
&.group-travel::before {
content: '\f1b9';
}
&.group-symbols {
background-color: #fff;
&.group-objects::before {
content: '\f0eb';
}
&.group-flags {
background-color: #fff;
&.group-symbols::before {
content: '\f86d';
}
&.group-flags::before {
content: '\f024';
}
}
}
@ -283,9 +289,7 @@
<script lang="ts">
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { list } from './emoji/list'
import { keywords } from './emoji/keywords'
import { groups } from './emoji/groups'
import { get, set } from '../utils/localstorage'
@Component({
name: 'neko-emoji',
@ -297,19 +301,32 @@
waitingForPaint = false
search = ''
active = groups[0].id
index = 0
hovered = ''
recent: string[] = JSON.parse(get('emoji_recent', '[]'))
get active() {
return this.$accessor.emoji.groups[this.index]
}
get keywords() {
return this.$accessor.emoji.keywords
}
get groups() {
return groups
return this.$accessor.emoji.groups
}
get list() {
return this.$accessor.emoji.list
}
get filtered() {
const filtered = []
for (const emoji of list) {
for (const emoji of this.list) {
if (
emoji.includes(this.search) || typeof keywords[emoji] !== 'undefined'
? keywords[emoji].some(keyword => keyword.includes(this.search))
emoji.includes(this.search) || typeof this.keywords[emoji] !== 'undefined'
? this.keywords[emoji].some(keyword => keyword.includes(this.search))
: false
) {
filtered.push(emoji)
@ -319,16 +336,10 @@
}
scrollTo(event: MouseEvent, index: number) {
const ele = this._groups[index]
if (!ele) {
if (!this._groups[index]) {
return
}
let top = ele.offsetTop
if (index == 0) {
top = 0
}
this._scroll.scrollTop = top
this._scroll.scrollTop = index == 0 ? 0 : this._groups[index].offsetTop
}
onScroll() {
@ -341,16 +352,17 @@
onScrollPaint() {
this.waitingForPaint = false
let scrollTop = this._scroll.scrollTop
let active = this.groups[0]
for (let i = 0, l = this.groups.length; i < l; i++) {
let group = this.groups[i]
let active = 0
for (const [i, group] of this.groups.entries()) {
let component = this._groups[i]
if (component && component.offsetTop > scrollTop) {
break
}
active = group
active = i
}
if (this.index !== active) {
this.index = active
}
this.active = active.id
}
onMouseExit(event: MouseEvent, emoji: string) {
@ -363,6 +375,7 @@
}
onClick(event: MouseEvent, emoji: string) {
this.$accessor.emoji.setRecent(emoji)
this.$emit('picked', emoji)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -29,27 +29,27 @@
background-size: contain;
&.celebrate {
background-image: url('../assets/celebrate.png');
background-image: url('../assets/images/emote/celebrate.png');
}
&.clap {
background-image: url('../assets/clap.png');
background-image: url('../assets/images/emote/clap.png');
}
&.exclam {
background-image: url('../assets/exclam.png');
background-image: url('../assets/images/emote/exclam.png');
}
&.heart {
background-image: url('../assets/heart.png');
background-image: url('../assets/images/emote/heart.png');
}
&.laughing {
background-image: url('../assets/laughing.png');
background-image: url('../assets/images/emote/laughing.png');
}
&.sleep {
background-image: url('../assets/sleep.png');
background-image: url('../assets/images/emote/sleep.png');
}
}
}

View File

@ -30,27 +30,27 @@
background-size: contain;
&.celebrate {
background-image: url('../assets/celebrate.png');
background-image: url('../assets/images/emote/celebrate.png');
}
&.clap {
background-image: url('../assets/clap.png');
background-image: url('../assets/images/emote/clap.png');
}
&.exclam {
background-image: url('../assets/exclam.png');
background-image: url('../assets/images/emote/exclam.png');
}
&.heart {
background-image: url('../assets/heart.png');
background-image: url('../assets/images/emote/heart.png');
}
&.laughing {
background-image: url('../assets/laughing.png');
background-image: url('../assets/images/emote/laughing.png');
}
&.sleep {
background-image: url('../assets/sleep.png');
background-image: url('../assets/images/emote/sleep.png');
}
}
}

View File

@ -1,7 +1,7 @@
<template>
<div class="header">
<div class="neko">
<img src="@/assets/logo.svg" alt="n.eko" />
<img src="@/assets/images/logo.svg" alt="n.eko" />
<span><b>n</b>.eko</span>
</div>
<ul class="menu">

View File

@ -212,6 +212,18 @@ const rules: MarkdownRules = {
...br,
match: md.anyScopeRegex(/^\n/),
},
emoji: {
order: md.defaultRules.strong.order,
match: source => /^:([a-zA-z_-]*):/.exec(source),
parse(capture) {
return {
id: capture[1],
}
},
html(node, output, state) {
return htmlTag('span', '', { class: `emoji`, 'data-emoji': node.id }, state)
},
},
emoticon: {
order: md.defaultRules.text.order,
match: source => /^(¯\\_\(ツ\)_\/¯)/.exec(source),

View File

@ -2,7 +2,7 @@
<div class="unsupported">
<div class="window">
<div class="logo">
<img src="@/assets/logo.svg" alt="n.eko" />
<img src="@/assets/images/logo.svg" alt="n.eko" />
<span><b>n</b>.eko</span>
</div>
<div class="message">

View File

@ -26,5 +26,6 @@ new Vue({
render: h => h(app),
created() {
this.$client.init(this)
this.$accessor.initialise()
},
}).$mount('#neko')

View File

@ -1,6 +1,14 @@
import { PluginObject } from 'vue'
import axios, { AxiosStatic } from 'axios'
declare global {
const $http: AxiosStatic
interface Window {
$http: AxiosStatic
}
}
declare module 'vue/types/vue' {
interface Vue {
$http: AxiosStatic
@ -9,7 +17,8 @@ declare module 'vue/types/vue' {
const plugin: PluginObject<undefined> = {
install(Vue) {
Vue.prototype.$http = axios
window.$http = axios
Vue.prototype.$http = window.$http
},
}

74
client/src/store/emoji.ts Normal file
View File

@ -0,0 +1,74 @@
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
import { get, set } from '~/utils/localstorage'
import { accessor } from '~/store'
export const namespaced = true
interface Group {
name: string
id: string
list: string[]
}
interface Keywords {
[name: string]: string[]
}
interface Emojis {
groups: Group[]
keywords: Keywords
list: string[]
}
export const state = () => ({
groups: [
{
id: 'recent',
name: 'Recent',
list: JSON.parse(get('emoji_recent', '[]')) as string[],
},
] as Group[],
keywords: {} as Keywords,
list: [] as string[],
})
export const getters = getterTree(state, {})
export const mutations = mutationTree(state, {
setRecent(state, emoji: string) {
if (!state.groups[0].list.includes(emoji)) {
if (state.groups[0].list.length > 30) {
state.groups[0].list.shift()
}
state.groups[0].list.push(emoji)
set('emoji_recent', JSON.stringify(state.groups[0].list))
}
},
addGroup(state, group: Group) {
state.groups.push(group)
},
setKeywords(state, keywords: Keywords) {
state.keywords = keywords
},
setList(state, list: string[]) {
state.list = list
},
})
export const actions = actionTree(
{ state, getters, mutations },
{
initialise() {
$http
.get<Emojis>('/emoji.json')
.then(req => {
for (const group of req.data.groups) {
accessor.emoji.addGroup(group)
}
accessor.emoji.setList(req.data.list)
accessor.emoji.setKeywords(req.data.keywords)
})
.catch(console.error)
},
},
)

View File

@ -8,6 +8,7 @@ import * as remote from './remote'
import * as user from './user'
import * as settings from './settings'
import * as client from './client'
import * as emoji from './emoji'
export const state = () => ({
connecting: false,
@ -34,6 +35,11 @@ export const mutations = mutationTree(state, {
export const actions = actionTree(
{ state, mutations },
{
//
initialise(store) {
accessor.emoji.initialise()
},
//
connect(store, { username, password }: { username: string; password: string }) {
$client.connect(password, username)
@ -45,7 +51,7 @@ export const storePattern = {
state,
mutations,
actions,
modules: { video, chat, user, remote, settings, client },
modules: { video, chat, user, remote, settings, client, emoji },
}
Vue.use(Vuex)

View File

@ -44,10 +44,6 @@ export const mutations = mutationTree(state, {
export const actions = actionTree(
{ state, getters, mutations },
{
initialise({ commit }) {
//
},
sendClipboard({ getters }, clipboard: string) {
if (!accessor.connected || !getters.hosting) {
return