Archived
2
0

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

@ -15,6 +15,7 @@
"scripts": { "scripts": {
"serve": "vue-cli-service serve --mode development", "serve": "vue-cli-service serve --mode development",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"build:emoji": "ts-node --files --project tools/tsconfig.json tools/emoji.ts",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
@ -22,6 +23,8 @@
"animejs": "^3.1.0", "animejs": "^3.1.0",
"axios": "^0.19.1", "axios": "^0.19.1",
"date-fns": "^2.9.0", "date-fns": "^2.9.0",
"emoji-datasource": "^5.0.1",
"emojilib": "^2.4.0",
"eventemitter3": "^4.0.0", "eventemitter3": "^4.0.0",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"simple-markdown": "^0.7.2", "simple-markdown": "^0.7.2",
@ -37,6 +40,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/animejs": "^3.1.0", "@types/animejs": "^3.1.0",
"@types/node": "^13.7.0",
"@types/vue": "^2.0.0", "@types/vue": "^2.0.0",
"@vue/cli-plugin-babel": "^4.1.0", "@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0", "@vue/cli-plugin-eslint": "^4.1.0",
@ -51,6 +55,7 @@
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
"ts-node": "^8.6.2",
"typescript": "~3.5.3", "typescript": "~3.5.3",
"vue-template-compiler": "^2.6.10" "vue-template-compiler": "^2.6.10"
} }

1
client/public/emoji.json Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

(image error) Size: 1.7 KiB

Binary file not shown.

Before

(image error) Size: 1.0 MiB

Before

(image error) Size: 2.6 KiB

After

(image error) Size: 2.6 KiB

Before

(image error) Size: 3.7 KiB

After

(image error) Size: 3.7 KiB

Before

(image error) Size: 2.7 KiB

After

(image error) Size: 2.7 KiB

Before

(image error) Size: 1.8 KiB

After

(image error) Size: 1.8 KiB

Before

(image error) Size: 3.9 KiB

After

(image error) Size: 3.9 KiB

Before

(image error) Size: 4.5 KiB

After

(image error) Size: 4.5 KiB

Before

(image error) Size: 4.1 KiB

After

(image error) Size: 4.1 KiB

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

@ -3,7 +3,7 @@
<div class="window"> <div class="window">
<div class="loading" v-if="loading"> <div class="loading" v-if="loading">
<div class="logo"> <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> <span><b>N</b>.EKO</span>
</div> </div>
<div class="loader"> <div class="loader">

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

@ -2,7 +2,7 @@
<div class="connect"> <div class="connect">
<div class="window"> <div class="window">
<div class="logo"> <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> <span><b>n</b>.eko</span>
</div> </div>
<form class="message" v-if="!connecting" @submit.stop.prevent="connect"> <form class="message" v-if="!connecting" @submit.stop.prevent="connect">

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

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

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

@ -1,7 +1,7 @@
<template> <template>
<div class="header"> <div class="header">
<div class="neko"> <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> <span><b>n</b>.eko</span>
</div> </div>
<ul class="menu"> <ul class="menu">

@ -212,6 +212,18 @@ const rules: MarkdownRules = {
...br, ...br,
match: md.anyScopeRegex(/^\n/), 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: { emoticon: {
order: md.defaultRules.text.order, order: md.defaultRules.text.order,
match: source => /^(¯\\_\(ツ\)_\/¯)/.exec(source), match: source => /^(¯\\_\(ツ\)_\/¯)/.exec(source),

@ -2,7 +2,7 @@
<div class="unsupported"> <div class="unsupported">
<div class="window"> <div class="window">
<div class="logo"> <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> <span><b>n</b>.eko</span>
</div> </div>
<div class="message"> <div class="message">

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

@ -1,6 +1,14 @@
import { PluginObject } from 'vue' import { PluginObject } from 'vue'
import axios, { AxiosStatic } from 'axios' import axios, { AxiosStatic } from 'axios'
declare global {
const $http: AxiosStatic
interface Window {
$http: AxiosStatic
}
}
declare module 'vue/types/vue' { declare module 'vue/types/vue' {
interface Vue { interface Vue {
$http: AxiosStatic $http: AxiosStatic
@ -9,7 +17,8 @@ declare module 'vue/types/vue' {
const plugin: PluginObject<undefined> = { const plugin: PluginObject<undefined> = {
install(Vue) { 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 user from './user'
import * as settings from './settings' import * as settings from './settings'
import * as client from './client' import * as client from './client'
import * as emoji from './emoji'
export const state = () => ({ export const state = () => ({
connecting: false, connecting: false,
@ -34,6 +35,11 @@ export const mutations = mutationTree(state, {
export const actions = actionTree( export const actions = actionTree(
{ state, mutations }, { state, mutations },
{ {
//
initialise(store) {
accessor.emoji.initialise()
},
// //
connect(store, { username, password }: { username: string; password: string }) { connect(store, { username, password }: { username: string; password: string }) {
$client.connect(password, username) $client.connect(password, username)
@ -45,7 +51,7 @@ export const storePattern = {
state, state,
mutations, mutations,
actions, actions,
modules: { video, chat, user, remote, settings, client }, modules: { video, chat, user, remote, settings, client, emoji },
} }
Vue.use(Vuex) Vue.use(Vuex)

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

@ -0,0 +1,7 @@
export const custom = [
{
name: 'neko',
file: 'neko.png',
keywords: ['neko', 'cat', 'cat butt', 'butt'],
},
]

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"types": [
"node"
],
},
"include": [
"**/*.ts"
]
}

@ -13,6 +13,7 @@
"skipLibCheck": true, "skipLibCheck": true,
"baseUrl": ".", "baseUrl": ".",
"types": [ "types": [
"node",
"webpack-env" "webpack-env"
], ],
"paths": { "paths": {
@ -31,6 +32,7 @@
], ],
}, },
"include": [ "include": [
"tools/**/*.ts",
"src/**/*.ts", "src/**/*.ts",
"src/**/*.tsx", "src/**/*.tsx",
"src/**/*.vue", "src/**/*.vue",