yay emoji!!!
@ -15,6 +15,7 @@
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --mode development",
|
||||
"build": "vue-cli-service build",
|
||||
"build:emoji": "ts-node --files --project tools/tsconfig.json tools/emoji.ts",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -22,6 +23,8 @@
|
||||
"animejs": "^3.1.0",
|
||||
"axios": "^0.19.1",
|
||||
"date-fns": "^2.9.0",
|
||||
"emoji-datasource": "^5.0.1",
|
||||
"emojilib": "^2.4.0",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"simple-markdown": "^0.7.2",
|
||||
@ -37,6 +40,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/animejs": "^3.1.0",
|
||||
"@types/node": "^13.7.0",
|
||||
"@types/vue": "^2.0.0",
|
||||
"@vue/cli-plugin-babel": "^4.1.0",
|
||||
"@vue/cli-plugin-eslint": "^4.1.0",
|
||||
@ -51,6 +55,7 @@
|
||||
"node-sass": "^4.12.0",
|
||||
"prettier": "^1.19.1",
|
||||
"sass-loader": "^8.0.0",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "~3.5.3",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
}
|
||||
|
1
client/public/emoji.json
Normal file
BIN
client/src/assets/images/emoji/neko.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@ -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;
|
||||
|
1726
client/src/assets/styles/vendor/_emoji.scss
vendored
Normal file
5185
client/src/assets/styles/vendor/_emoji_20.scss
vendored
@ -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">
|
||||
|
@ -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>
|
||||
<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 {
|
||||
|
@ -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">
|
||||
|
@ -6,39 +6,39 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="list" ref="scroll" @scroll="onScroll">
|
||||
<ul :class="[search === '' ? 'group-list' : 'emoji-list']">
|
||||
<template v-if="search === ''">
|
||||
<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 group.list"
|
||||
v-for="emoji in index === 0 ? recent : group.list"
|
||||
:key="`${group.id}-${emoji}`"
|
||||
:class="['emoji', hovered === emoji ? 'active' : '']"
|
||||
:class="['emoji-container', hovered === emoji ? 'active' : '']"
|
||||
>
|
||||
<span
|
||||
:class="['emoji-20', `e-${emoji}`]"
|
||||
:class="['emoji']"
|
||||
@mouseenter.stop.prevent="onMouseEnter($event, emoji)"
|
||||
@click.stop.prevent="onClick($event, emoji)"
|
||||
:data-emoji="emoji"
|
||||
></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
<template v-else>
|
||||
<li v-for="emoji in filtered" :key="emoji" :class="['emoji', hovered === emoji ? 'active' : '']">
|
||||
</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-20', `e-${emoji}`]"
|
||||
:class="['emoji']"
|
||||
@mouseenter.stop.prevent="onMouseEnter($event, emoji)"
|
||||
@click.stop.prevent="onClick($event, emoji)"
|
||||
:data-emoji="emoji"
|
||||
></span>
|
||||
</li>
|
||||
</template>
|
||||
</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)
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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),
|
||||
|
@ -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">
|
||||
|
@ -26,5 +26,6 @@ new Vue({
|
||||
render: h => h(app),
|
||||
created() {
|
||||
this.$client.init(this)
|
||||
this.$accessor.initialise()
|
||||
},
|
||||
}).$mount('#neko')
|
||||
|
@ -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
@ -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)
|
||||
},
|
||||
},
|
||||
)
|
@ -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)
|
||||
|
@ -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
|
||||
|
234
client/tools/emoji.ts
Normal file
@ -0,0 +1,234 @@
|
||||
import * as fs from 'fs'
|
||||
import { custom } from './emoji_custom'
|
||||
|
||||
const datasource = require('emoji-datasource/emoji.json') as EmojiDatasource[]
|
||||
const emojis = require('emojilib/emojis.json') as { [id: string]: Emoji }
|
||||
|
||||
interface EmojiDatasource {
|
||||
name: string
|
||||
unified: string
|
||||
non_qualified: string | null
|
||||
docomo: string | null
|
||||
au: string | null
|
||||
softbank: string | null
|
||||
google: string | null
|
||||
image: string
|
||||
sheet_x: number
|
||||
sheet_y: number
|
||||
short_name: string
|
||||
short_names: string[]
|
||||
text: string | null
|
||||
texts: string | null
|
||||
category: string
|
||||
sort_order: number
|
||||
added_in: string
|
||||
has_img_apple: boolean
|
||||
has_img_google: boolean
|
||||
has_img_twitter: boolean
|
||||
has_img_facebook: boolean
|
||||
skin_variations: {
|
||||
[id: string]: {
|
||||
unified: string
|
||||
image: string
|
||||
sheet_x: number
|
||||
sheet_y: number
|
||||
added_in: string
|
||||
has_img_apple: boolean
|
||||
has_img_google: boolean
|
||||
has_img_twitter: boolean
|
||||
has_img_facebook: boolean
|
||||
}
|
||||
}
|
||||
obsoletes: string
|
||||
obsoleted_by: string
|
||||
}
|
||||
|
||||
interface Emoji {
|
||||
keywords: string[]
|
||||
char: string
|
||||
fitzpatrick_scale: boolean
|
||||
category: string
|
||||
}
|
||||
|
||||
const SHEET_COLUMNS = 57
|
||||
const MULTIPLY = 100 / (SHEET_COLUMNS - 1)
|
||||
|
||||
const css: string[] = []
|
||||
const keywords: { [name: string]: string[] } = {}
|
||||
const list: string[] = []
|
||||
const groups: { [name: string]: string[] } = { neko: [] }
|
||||
|
||||
for (const emoji of custom) {
|
||||
groups['neko'].push(emoji.name)
|
||||
list.push(emoji.name)
|
||||
keywords[emoji.name] = emoji.keywords
|
||||
|
||||
// prettier-ignore
|
||||
css.push(`&[data-emoji='${emoji.name}'] { background-size: contain; background-image: url('../images/emoji/${emoji.file}'); }`)
|
||||
}
|
||||
|
||||
for (const source of datasource) {
|
||||
const unified = source.unified.split('-').map(v => v.toLowerCase())
|
||||
|
||||
let emoji: Emoji | null = null
|
||||
let emoji_id: string = ''
|
||||
for (const id of Object.keys(emojis)) {
|
||||
if (unified.includes(emojis[id].char.codePointAt(0)!.toString(16))) {
|
||||
emoji_id = id
|
||||
emoji = emojis[id]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!source.has_img_twitter) {
|
||||
console.log(source.short_name, 'not avalible for set twitter')
|
||||
continue
|
||||
}
|
||||
|
||||
// keywords
|
||||
let words: string[] = []
|
||||
if (!emoji) {
|
||||
console.log(source.short_name, 'no keywords')
|
||||
} else {
|
||||
words = [emoji_id, ...emoji.keywords]
|
||||
}
|
||||
|
||||
for (const name of source.short_names) {
|
||||
if (!words.includes(name)) {
|
||||
words.push(name)
|
||||
}
|
||||
}
|
||||
|
||||
keywords[source.short_name] = words
|
||||
|
||||
// keywords
|
||||
let group = ''
|
||||
switch (source.category) {
|
||||
case 'Symbols':
|
||||
group = 'symbols'
|
||||
break
|
||||
case 'Activities':
|
||||
group = 'activity'
|
||||
break
|
||||
case 'Flags':
|
||||
group = 'flags'
|
||||
break
|
||||
case 'Travel & Places':
|
||||
group = 'travel'
|
||||
break
|
||||
case 'Food & Drink':
|
||||
group = 'food'
|
||||
break
|
||||
case 'Animals & Nature':
|
||||
group = 'nature'
|
||||
break
|
||||
case 'People & Body':
|
||||
group = 'people'
|
||||
break
|
||||
case 'Smileys & Emotion':
|
||||
group = 'emotion'
|
||||
break
|
||||
case 'Objects':
|
||||
group = 'objects'
|
||||
break
|
||||
case 'Skin Tones':
|
||||
continue
|
||||
default:
|
||||
console.log(`unknown category ${source.category}`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!groups[group]) {
|
||||
groups[group] = [source.short_name]
|
||||
} else {
|
||||
groups[group].push(source.short_name)
|
||||
}
|
||||
|
||||
// list
|
||||
list.push(source.short_name)
|
||||
|
||||
// css
|
||||
// prettier-ignore
|
||||
css.push(`&[data-emoji='${source.short_name}'] { background-position: ${MULTIPLY * source.sheet_x}% ${MULTIPLY * source.sheet_y}% }`)
|
||||
}
|
||||
|
||||
fs.writeFile(
|
||||
'src/assets/styles/vendor/_emoji.scss',
|
||||
`
|
||||
.emoji {
|
||||
display: inline-block;
|
||||
background-size: ${SHEET_COLUMNS * 100}%;
|
||||
background-image: url('~emoji-datasource/img/twitter/sheets/32.png');
|
||||
background-repeat: no-repeat;
|
||||
vertical-align: bottom;
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
|
||||
${css.map(v => ` ${v}`).join('\n')}
|
||||
}
|
||||
`,
|
||||
() => {
|
||||
console.log('_emoji.scss done')
|
||||
},
|
||||
)
|
||||
|
||||
const data = {
|
||||
groups: [
|
||||
{
|
||||
id: 'neko',
|
||||
name: 'Neko',
|
||||
list: groups['neko'] ? groups['neko'] : [],
|
||||
},
|
||||
{
|
||||
id: 'emotion',
|
||||
name: 'Emotion',
|
||||
list: groups['emotion'] ? groups['emotion'] : [],
|
||||
},
|
||||
{
|
||||
id: 'people',
|
||||
name: 'People',
|
||||
list: groups['people'] ? groups['people'] : [],
|
||||
},
|
||||
{
|
||||
id: 'nature',
|
||||
name: 'Nature',
|
||||
list: groups['nature'] ? groups['nature'] : [],
|
||||
},
|
||||
{
|
||||
id: 'food',
|
||||
name: 'Food',
|
||||
list: groups['food'] ? groups['food'] : [],
|
||||
},
|
||||
{
|
||||
id: 'activity',
|
||||
name: 'Activity',
|
||||
list: groups['activity'] ? groups['activity'] : [],
|
||||
},
|
||||
{
|
||||
id: 'travel',
|
||||
name: 'Travel',
|
||||
list: groups['travel'] ? groups['travel'] : [],
|
||||
},
|
||||
{
|
||||
id: 'objects',
|
||||
name: 'Objects',
|
||||
list: groups['objects'] ? groups['objects'] : [],
|
||||
},
|
||||
{
|
||||
id: 'symbols',
|
||||
name: 'Symbols',
|
||||
list: groups['symbols'] ? groups['symbols'] : [],
|
||||
},
|
||||
{
|
||||
id: 'flags',
|
||||
name: 'Flags',
|
||||
list: groups['flags'] ? groups['flags'] : [],
|
||||
},
|
||||
],
|
||||
list,
|
||||
keywords,
|
||||
}
|
||||
|
||||
fs.writeFile('public/emoji.json', JSON.stringify(data), () => {
|
||||
console.log('emoji.json done')
|
||||
})
|
7
client/tools/emoji_custom.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const custom = [
|
||||
{
|
||||
name: 'neko',
|
||||
file: 'neko.png',
|
||||
keywords: ['neko', 'cat', 'cat butt', 'butt'],
|
||||
},
|
||||
]
|
13
client/tools/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"node",
|
||||
"webpack-env"
|
||||
],
|
||||
"paths": {
|
||||
@ -31,6 +32,7 @@
|
||||
],
|
||||
},
|
||||
"include": [
|
||||
"tools/**/*.ts",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
|