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

View File

@ -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

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/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

View File

@ -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">

View File

@ -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 {

View File

@ -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">

View File

@ -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

View File

@ -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');
} }
} }
} }

View File

@ -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');
} }
} }
} }

View File

@ -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">

View File

@ -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),

View File

@ -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">

View File

@ -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')

View File

@ -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
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 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)

View File

@ -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
View 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')
})

View File

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

View File

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

View File

@ -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",