custom emojis

This commit is contained in:
supertiger 2019-03-29 20:41:03 +00:00
parent f40cfd489e
commit 103f1777f2
29 changed files with 738 additions and 183 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

9
public/browserconfig.xml Normal file
View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,30 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<title>Nertivia</title>
<!-- Google recaptcha -->
<script src="https://www.google.com/recaptcha/api.js?onload=vueRecaptchaApiLoaded&render=explicit" async defer>
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-131765299-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-131765299-1');
</script>
</head>
<body>
<noscript>
<strong>We're sorry but Nertivia doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="apple-touch-icon" sizes="180x180" href="<%= BASE_URL %>apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="<%= BASE_URL %>favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="<%= BASE_URL %>favicon-16x16.png">
<link rel="manifest" href="<%= BASE_URL %>site.webmanifest">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<!-- Preview meta tags -->
<!-- Search Tags -->
<title>Nertivia - Chat Client</title>
<meta name="description" content="The best chat client that wont sell your data. ">
<meta name="robots" content="follow, index">
<!-- FB Open Graph data -->
<meta property="og:title" content="Nertivia - Chat Client">
<meta property="og:type" content="website">
<meta property="og:image" content="https://nertivia.supertiger.tk/img/logo.3287e62d.png">
<meta property="og:description" content="The best chat client that wont sell your data. ">
<meta property="og:url" content="https://nertivia.tk/">
<!-- Twitter Card data -->
<meta name="twitter:card" content="app">
<meta name="twitter:title" content="Nertivia - Chat Client">
<meta name="twitter:description" content="The best chat client that wont sell your data. ">
<meta name="twitter:image" content="https://nertivia.supertiger.tk/img/logo.3287e62d.png">
<meta name="twitter:url" content="https://nertivia.tk/">
<!-- Preview meta tags -->
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Google recaptcha -->
<script src="https://www.google.com/recaptcha/api.js?onload=vueRecaptchaApiLoaded&render=explicit" async defer>
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-131765299-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-131765299-1');
</script>
</head>
<body>
<noscript>
<strong>We're sorry but Nertivia doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

BIN
public/mstile-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

19
public/site.webmanifest Normal file
View file

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-384x384.png",
"sizes": "384x384",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View file

@ -283,8 +283,8 @@ export default {
</style>
<style>
img.emoji {
height: 1.5em;
width: 1.5em;
height: 1.7em;
width: 1.7em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}

View file

@ -148,6 +148,7 @@ export default {
this.messageLength = 0;
const msg = emojiParser.replaceShortcode(this.message);
const tempID = this.generateNum(25);
this.$store.dispatch("addMessage", {
@ -270,9 +271,10 @@ export default {
this.showEmojiPopout(event);
},
enterEmojiSuggestion(){
this.$store.dispatch('setLastEmoji', this.emojiArray[this.emojiIndex].shortcodes[0])
const emoji = this.emojiArray[this.emojiIndex];
this.$store.dispatch('settingsModule/addRecentEmoji', emoji.name || emoji.shortcodes[0])
this.$refs["input-box"].focus();
const emojiShortCode = `:${this.emojiArray[this.emojiIndex].shortcodes[0]}: `
const emojiShortCode = `:${emoji.name || emoji.shortcodes[0]}: `
const cursorPosition = this.$refs['input-box'].selectionStart;
const cursorWord = this.ReturnWord(this.message, cursorPosition);
@ -286,7 +288,7 @@ export default {
const target = this.$refs["input-box"];
target.focus();
document.execCommand('insertText', false, `:${shortcode}: `);
this.$store.dispatch('setLastEmoji', shortcode)
this.$store.dispatch('settingsModule/addRecentEmoji', shortcode)
},
keyDown(event) {
this.resize(event);

View file

@ -3,72 +3,79 @@
<div class="settings-box">
<div class="tabs">
<div
:class="{tab: true, selected: currentTab == 'my-profile'}"
@click="tabClicked('my-profile','account_circle', 'My Profile')">
<div class="material-icons">account_circle</div>
<div>My Profile</div>
</div>
<div
:class="{tab: true, selected: currentTab == 'ddddd'}"
@click="tabClicked('ddddd','palette', 'Coming soon!')">
<div class="material-icons">palette</div>
<div>Message Themes</div>
</div>
<div
:class="{tab: true, selected: currentTab == 'eee'}"
@click="tabClicked('eee','error', 'Spoopi')">
<div class="material-icons">error</div>
<div>Another Tab</div>
v-for="(tab, index) in tabs"
:key="index"
:class="{tab: true, selected: currentTab === index}"
@click="currentTab = index"
>
<div class="material-icons">{{tab.icon}}</div>
<div class="tab-name">{{tab.name}}</div>
</div>
</div>
<div class="panel">
<div class="title">
<div class="material-icons">{{icon}}</div>
<div class="in-title">{{title}}</div>
<div class="material-icons">{{tabs[currentTab].icon}}</div>
<div class="in-title">{{tabs[currentTab].tabName}}</div>
<div class="close-button" @click="close">
<div class="material-icons">close</div>
</div>
</div>
<component :is="currentTab"></component>
<component :is="tabs[currentTab].component"></component>
</div>
</div>
</div>
</template>
<script>
import {bus} from '../../main'
import MyProfile from './SettingsPanels/MyProfile.vue'
import { bus } from "../../main";
import MyProfile from "./SettingsPanels/MyProfile.vue";
import ManageEmojis from "./SettingsPanels/ManageEmojis.vue";
export default {
components: {
MyProfile
MyProfile,
ManageEmojis
},
data() {
return {
currentTab: "my-profile",
icon: "account_circle",
title: "My Profile"
}
currentTab: 0,
tabs: [
{
name: "My Profile",
tabName: "My Profile",
icon: "account_circle",
component: "my-profile"
},
{
name: "Message Themes",
tabName: "Coming soon!",
icon: "palette",
component: "ddddsd"
},
{
name: "Manage Emojis",
tabName: "Manage Emojis",
icon: "face",
component: "manage-emojis"
}
]
};
},
methods: {
tabClicked(currentTab, icon, title) {
this.currentTab = currentTab;
this.icon = icon;
this.title = title;
},
close() {
this.$store.dispatch('setPopoutVisibility', {name: 'settings', visibility: false})
this.$store.dispatch("setPopoutVisibility", {
name: "settings",
visibility: false
});
}
}
}
};
</script>
<style scoped>
.settings-darken-background{
.settings-darken-background {
position: absolute;
background: rgba(0, 0, 0, 0.541);
top: 0;
@ -79,17 +86,19 @@ export default {
display: flex;
color: white;
}
.settings-box{
.settings-box {
display: flex;
margin: auto;
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.507);
}
.tabs{
.tabs {
background: rgba(24, 24, 24, 0.938);
height: 600px;
width: 200px;
}
.panel {
display: flex;
flex-direction: column;
background: rgba(31, 31, 31, 0.924);
height: 600px;
width: 600px;
@ -103,24 +112,25 @@ export default {
cursor: default;
user-select: none;
transition: 0.3s;
align-items: center;
}
.tab-name {
margin-left: 10px;
}
.tab.selected {
background: rgb(61, 61, 61) !important;
}
.tab div {
margin: 5px;
}
.tab:hover {
background: rgba(61, 61, 61, 0.616);
}
.title{
.title {
display: flex;
padding: 10px;
font-size: 25px;
background: rgb(20, 20, 20);
margin-bottom: 20px;
}
.title .material-icons{
.title .material-icons {
font-size: 40px;
}
.title div {
@ -129,9 +139,9 @@ export default {
margin-right: 5px;
}
.in-title {
flex:1;
flex: 1;
}
.close-button{
.close-button {
display: flex;
border-radius: 50%;
padding: 5px;
@ -139,7 +149,7 @@ export default {
user-select: none;
transition: 0.3s;
}
.close-button:hover{
.close-button:hover {
background: rgba(37, 37, 37, 0.692);
}
.close-button .material-icons {
@ -148,9 +158,9 @@ export default {
}
@media (max-width: 815px) {
.settings-box{
width:100%
.settings-box {
width: 100%;
}
}
}
</style>

View file

@ -0,0 +1,316 @@
<template>
<div class="manage-emoji-panel">
<div class="info">
<div
class="title"
>Upload your own pretty emojis for free! Emojis must be 1MB or less. (png, jpg, gif)</div>
<div class="button" @click="addEmojiBtn">
<i class="material-icons">add_box</i>Add Emoji
</div>
</div>
<div class="emojis-list">
<div class="emoji" v-for="emoji in customEmojis" :key="emoji.emojiID">
<img class="preview" :src="`${domain}${emoji.emojiID}`">
<div class="emoji-name">
<input
type="text"
:value="emoji.name"
@keydown="keyDownEvent"
@blur="blurEvent(emoji.emojiID, $event)"
>
</div>
<div class="delete-button" @click="removeEmoji(emoji.emojiID)">
<div class="material-icons">close</div>
<div class="inner"></div>
</div>
</div>
</div>
<input type="file" accept="image/*" ref="emojiBrowser" @change="emojiBrowse" class="hidden">
<!-- <div class="option" @click="changePassword">Change Password</div> -->
<div class="alert-outer" v-if="alert.show">
<div class="alert">
<div class="alert-title">Error</div>
<div class="alert-content">{{alert.content}}</div>
<div class="alert-buttons">
<div class="alert-button" @click="alert.show = false">Okay</div>
</div>
</div>
</div>
</div>
</template>
<script>
import ProfilePicture from "@/components/ProfilePictureTemplate.vue";
import customEmoji from "@/services/customEmoji.js";
import config from "@/config.js";
import { bus } from "@/main";
import path from "path";
import { mapState } from "vuex";
import emojiParser from "@/utils/emojiParser.js";
export default {
components: {},
data() {
return {
alert: {
content: "",
show: false
},
domain: config.domain + "/files/"
};
},
methods: {
keyDownEvent(event) {
const keyCode = event.keyCode;
if (keyCode == 13) {
event.target.blur();
}
},
async blurEvent(emojiID, event) {
// send put request
const { ok, error, result } = await customEmoji.put({emojiID, name: event.target.value});
if (!ok) {
this.alert.content =
"Upload failed - " + error.response.data.message ||
"Something weng wrong. Try again later.";
return (this.alert.show = true);
}
},
onProgress(percent) {
//update vue
console.log("emoji upload progress: ", percent);
},
async emojiBrowse(event) {
const file = event.target.files[0];
event.target.value = "";
const allowedFormats = [".png", ".jpeg", ".gif", ".jpg"];
if (!allowedFormats.includes(path.extname(file.name).toLowerCase())) {
this.alert.content = "Upload failed - Unsupported image file.";
return (this.alert.show = true);
} else if (file.size >= 1048576) {
// 1048576 = 1mb
this.alert.content =
"Upload failed - Image size must be less than 1 megabytes.";
return (this.alert.show = true);
}
const formData = new FormData();
//check if emoji name is already used by twemoji
const fileName = path.basename(file.name, path.extname(file.name));
const emojiExists = emojiParser.allEmojis.find(e =>
e.shortcodes.find(ee => ee === fileName.toLowerCase())
);
if (emojiExists) {
formData.append(
"emoji",
file,
`${fileName}-1${path.extname(file.name)}`
);
} else {
formData.append("emoji", file);
}
const { ok, error, result } = await customEmoji.post(
formData,
this.onProgress
);
if (!ok) {
this.alert.content =
"Upload failed - " + error.response.data.message ||
"Something weng wrong. Try again later.";
return (this.alert.show = true);
}
},
addEmojiBtn() {
if (!this.GDriveLinked) {
return this.$store.dispatch("setPopoutVisibility", {
name: "GDLinkMenu",
visibility: true
});
}
this.$refs.emojiBrowser.click();
},
async removeEmoji(emojiID) {
const { ok, error, result } = await customEmoji.delete(emojiID);
if (!ok) {
this.alert.content =
"Upload failed - " + error.response.data.message ||
"Something weng wrong. Try again later.";
return (this.alert.show = true);
}
}
},
computed: {
...mapState("settingsModule", ["GDriveLinked", "customEmojis"])
}
};
</script>
<style scoped>
input {
margin-top: 0;
margin-bottom: 0;
background: none;
}
input:hover {
background: rgba(26, 26, 26, 0.24);
}
input:focus {
background: rgba(26, 26, 26, 0.527);
}
.delete-button {
display: flex;
align-items: right;
align-content: right;
padding-right: 0px;
padding-left: 0px;
height: 100%;
width: 50px;
position: relative;
overflow: hidden;
}
.delete-button .material-icons {
margin: auto;
margin-right: 10px;
z-index: 999;
}
.delete-button:hover > .inner {
width: 100%;
}
.delete-button .inner {
background: rgba(255, 0, 0, 0.205);
position: absolute;
height: 100%;
width: 0%;
right: 0;
transition: 0.3s;
border-top-left-radius: 60px;
border-bottom-left-radius: 60px;
}
.preview {
height: 30px;
width: auto;
margin-left: 10px;
margin-right: 10px;
}
.title {
margin: 10px;
}
.manage-emoji-panel {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.emojis-list {
display: flex;
flex-direction: column;
background: rgba(47, 47, 47, 0.767);
overflow-y: auto;
overflow-x: hidden;
height: calc(100% - 120px);
width: calc(100% - 30px);
margin: auto;
margin-top: 5px;
}
.emoji {
background: rgba(63, 63, 63, 0.411);
height: 50px;
width: calc(100% - 10px);
display: flex;
margin: 5px;
align-items: center;
transition: 0.3s;
user-select: none;
cursor: default;
flex-shrink: 0;
}
.emoji:hover {
background: rgba(75, 75, 75, 0.712);
}
.emoji-name {
margin: auto;
margin-left: 5px;
flex: 1;
}
.button {
display: inline-block;
width: inherit;
padding: 10px;
border-radius: 5px;
background: rgba(54, 54, 54, 0.603);
margin-bottom: 10px;
margin-left: 20px;
user-select: none;
transition: 0.3s;
}
.button:hover {
background: rgb(54, 54, 54);
}
.button .material-icons {
vertical-align: -6px;
margin-right: 5px;
}
.hidden {
display: none;
}
.alert-title {
background: rgb(34, 34, 34);
font-size: 20px;
color: white;
padding: 10px;
}
.alert-outer {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
background: rgba(0, 0, 0, 0.267);
}
.alert {
margin: auto;
background: rgb(49, 49, 49);
width: 500px;
box-shadow: 0px 0px 30px #000000;
display: flex;
flex-direction: column;
user-select: none;
cursor: default;
}
.alert-content {
margin: auto;
font-size: 16px;
color: white;
padding: 10px;
padding-top: 30px;
padding-bottom: 40px;
}
.alert-buttons {
margin-left: auto;
margin-right: auto;
margin-bottom: 20px;
}
.alert-button {
color: white;
margin: auto;
background: rgba(73, 53, 53, 0.712);
padding: 10px;
transition: 0.3s;
}
.alert-button:hover {
background: rgb(83, 53, 53);
}
</style>

View file

@ -35,6 +35,7 @@ import AvatarUpload from "@/services/AvatarUpload.js";
import config from "@/config.js";
import { bus } from "@/main";
import path from "path";
import {mapState} from 'vuex'
export default {
components: {
@ -88,7 +89,7 @@ export default {
return (this.alert.show = true);
},
editAvatarBtn() {
if (!this.$store.getters.settings.GDriveLinked) {
if (!this.GDriveLinked) {
return this.$store.dispatch("setPopoutVisibility", {
name: "GDLinkMenu",
visibility: true
@ -98,6 +99,7 @@ export default {
}
},
computed: {
...mapState('settingsModule', ['GDriveLinked']),
user() {
return this.$store.getters.user;
},

View file

@ -2,19 +2,40 @@
<div class="emoji-panel" v-click-outside="closePanel">
<div class="emoji-panel-inner">
<div class="emojis-list">
<!-- Recent Emojis Category -->
<div class="category">
<div class="category-name">Recent</div>
<div class="list">
<div class="emoji-item" v-for="(recentEmoji, index) in recentEmojiList" :key="index" @click="clickEvent(recentEmoji)">
<div
class="emoji-item"
v-for="(recentEmoji, index) in this.recentEmojisList"
:key="index"
@click="emojiClickEvent(recentEmoji)"
>
<img
class="panel emoji"
v-lazyload
:data-url="emojiShortcodeToPath(':' + recentEmoji + ':')"
:data-url=" getCustomEmoji(recentEmoji) || emojiShortcodeToPath(':' + recentEmoji + ':')"
>
</div>
</div>
</div>
<!-- Custom Emojis Category -->
<div class="category">
<div class="category-name">Custom Emojis</div>
<div class="list">
<div
class="emoji-item"
v-for="(customEmoji, index) in this.customEmojisList"
:key="index"
@click="customEmojiClickEvent(customEmoji)"
>
<img class="panel emoji" v-lazyload :data-url="customEmojiPath + customEmoji.emojiID">
</div>
</div>
</div>
<div class="category" v-for="(group, index) in groups" :key="group">
<div class="category-name">{{group}}</div>
<div class="list">
@ -22,7 +43,7 @@
class="emoji-item"
v-for="emojiSorted in emojiByGroup(index)"
:key="emojiSorted.shortcodes[0]"
@click="clickEvent(emojiSorted.shortcodes[0])"
@click="emojiClickEvent(emojiSorted.shortcodes[0])"
>
<img class="panel emoji" v-lazyload :data-url="parseEmojiPath(emojiSorted.unicode)">
</div>
@ -39,7 +60,7 @@
v-for="(emoji, index) in groupUnicodes"
:key="index"
@mouseenter="mouseHover(emoji, $event)"
@click="scrollToCategory(index + 1)"
@click="scrollToCategory(index + 2)"
>
<img class="panel-emoji" :src="selectRandom(emoji)">
<div class="tooltip">{{ groups[index]}}</div>
@ -54,7 +75,8 @@
import { bus } from "@/main";
import emojiParser from "@/utils/emojiParser.js";
import lazyLoad from "@/directives/LazyLoad.js";
import {mapState} from 'vuex'
import config from "@/config.js";
export default {
@ -312,12 +334,19 @@ export default {
"🇨🇭"
]
],
emojis: emojiParser.getAllEmojis(),
groups: emojiParser.getGroups(),
recentEmojiList: this.$store.getters.recentEmojis
emojis: emojiParser.allEmojis,
groups: emojiParser.allGroups,
recentEmojisList: null,
customEmojisList : null,
customEmojiPath: config.domain + "/files/"
};
},
methods: {
getCustomEmoji(shortCode){
const customEmoji = emojiParser.getCustomEmojisByShortCode(shortCode)
return (customEmoji ? this.customEmojiPath + customEmoji.emojiID : undefined)
},
closePanel() {
this.$store.dispatch("setPopoutVisibility", {
name: "emojiPanel",
@ -338,7 +367,10 @@ export default {
const randomNum = Math.floor(Math.random() * array.length);
return this.parseEmojiPath(array[randomNum]);
},
clickEvent(shortcode) {
customEmojiClickEvent(emoji) {
bus.$emit("emojiPanel:Selected", emoji.name);
},
emojiClickEvent(shortcode) {
bus.$emit("emojiPanel:Selected", shortcode);
},
mouseHover(emoji, event) {
@ -349,8 +381,12 @@ export default {
elements[index].scrollIntoView();
}
},
mounted() {
this.recentEmojiList = this.$store.getters.recentEmojis
beforeMount() {
this.recentEmojisList = this.recentEmojis
this.customEmojisList = this.customEmojis
},
computed: {
...mapState('settingsModule', ['recentEmojis', 'customEmojis'])
}
};
</script>
@ -408,7 +444,7 @@ export default {
padding: 2px;
border-radius: 5px;
height: 30px;
width: 30px;
min-width: 30px;
}
.emoji-item:hover {
background: rgb(59, 59, 59);
@ -429,7 +465,7 @@ export default {
}
.tabs img {
height: 20px;
width: 20px;
width: auto;
margin: auto;
filter: grayscale(100%);
transition: 0.1s;
@ -482,7 +518,7 @@ export default {
align-self: flex-end;
margin-right: 70px;
}
.tooltip{
.tooltip {
display: none;
position: absolute;
margin: auto;
@ -499,5 +535,6 @@ img.panel.emoji {
margin-left: 3px;
margin-top: 3px;
margin: auto;
width: auto;
}
</style>

View file

@ -5,10 +5,15 @@
:class="{emojiItem: true, selected: index === emojiIndex}"
@mouseenter="hoverEvent"
@click="clickEvent"
:key="emoji.hexcode"
:key="emoji.hexcode || emoji.emojiID"
>
<div class="preview" v-html="emojiParser(emoji.unicode)"></div>
<div class="short-code">:{{emoji.shortcodes[0]}}:</div>
<div class="preview">
<span v-if="emoji.unicode" v-html="emojiParser(emoji.unicode)"></span>
<span v-else >
<img class="custom-emoji" :src="customEmojiPath + emoji.emojiID" >
</span>
</div>
<div class="short-code">:{{emoji.name || emoji.shortcodes[0]}}:</div>
</div>
</div>
</template>
@ -16,8 +21,14 @@
<script>
import { bus } from "@/main";
import emojiParser from "@/utils/emojiParser.js";
import config from "@/config.js";
export default {
props: ["emojiArray"],
data(){
return {
customEmojiPath: config.domain + "/files/"
}
},
methods: {
emojiParser(emoji) {
return emojiParser.replaceEmojis(emoji);
@ -70,6 +81,10 @@ export default {
<style scoped>
.custom-emoji {
height: 1.5em;
width: auto;
}
.selected {
background: rgba(66, 66, 66, 0.89);
}

View file

@ -40,6 +40,7 @@ import filesize from "filesize";
import emojiParser from "@/utils/emojiParser.js";
import messagesService from "@/services/messagesService";
import { bus } from "../../main";
import {mapState} from 'vuex';
export default {
props: ["file"],
data() {
@ -140,7 +141,7 @@ export default {
visibility: false
});
}
if (!this.$store.getters.settings.GDriveLinked) {
if (!this.GDriveLinked) {
this.$store.dispatch("setPopoutVisibility", {
name: "uploadDialog",
visibility: false
@ -161,6 +162,7 @@ export default {
document.removeEventListener("keydown", this.keyDownEvent);
},
computed: {
...mapState('settingsModule', ['GDriveLinked']),
selectedChannelID() {
return this.$store.getters.selectedChannelID;
},

View file

@ -130,7 +130,7 @@ export default {
height: 150px;
width: 150px;
background: url(./../../assets/logo.png);
background-size: 129%;
background-size: 105%;
background-position: center;
border-radius: 50%;
box-shadow: 0px 0px 96px -4px rgba(69,212,255,1);

View file

@ -0,0 +1,29 @@
import {
instance,
wrapper
} from './Api';
export default {
post (data, onProgress) {
const url = `/settings/emoji`;
let config = {
onUploadProgress(progressEvent) {
var percentCompleted = Math.round((progressEvent.loaded * 100) /
progressEvent.total);
// execute the callback
if (onProgress) onProgress(percentCompleted)
return percentCompleted;
},
};
return wrapper(instance().post(url, data, config));
},
delete(emojiID) {
return wrapper(instance().delete(`/settings/emoji`, {data: {emojiID}}));
},
put(data) {
return wrapper(instance().put(`/settings/emoji`, {emojiID: data.emojiID, name: data.name}));
}
}

View file

@ -28,7 +28,7 @@ export const store = new Vuex.Store({
emojiSuggestionModule
},
state: {
},
getters: {

View file

@ -6,17 +6,17 @@ import {
const state = {
settings: {
recentEmojis: []
}
GDriveLinked: false,
customEmojis: [],
recentEmojis: JSON.parse(localStorage.getItem('recentEmojis')) || []
}
const getters = {
settings(state) {
return state.settings;
return state;
},
recentEmojis() {
return state.settings.recentEmojis || JSON.parse(localStorage.getItem('recentEmojis'))
return state.recentEmojis || JSON.parse(localStorage.getItem('recentEmojis'))
}
}
@ -27,7 +27,7 @@ const actions = {
setGDriveLinked(context, status) {
context.commit('GoogleDriveLinked', status)
},
setLastEmoji(context, shortcode) {
addRecentEmoji(context, shortcode) {
const recentEmojis = JSON.parse(localStorage.getItem('recentEmojis')) || [];
let filter = recentEmojis.filter(function (item) {
@ -38,25 +38,66 @@ const actions = {
filter = filter.slice(0, 16)
localStorage.setItem("recentEmojis", JSON.stringify(filter));
context.commit('setLastEmoji', filter)
}
context.commit('setRecentEmojis', filter)
},
addCustomEmoji(context, customEmoji){
context.commit('addCustomEmoji', customEmoji)
},
removeCustomEmoji(context, customEmoji) {
const emojiID = customEmoji.emoji.emojiID;
const customEmojiList = context.state.customEmojis;
for (let index = 0; index < customEmojiList.length; index++) {
const element = customEmojiList[index];
if (element.emojiID === emojiID){
context.commit('removeCustomEmoji', index);
break;
}
}
},
renameCustomEmoji(context, renamedEmoji){
const customEmojiList = context.state.customEmojis;
for (let index = 0; index < customEmojiList.length; index++) {
const element = customEmojiList[index];
if (element.emojiID === renamedEmoji.emoji.emojiID){
context.commit('renameCustomEmoji', {emoji: renamedEmoji.emoji, index});
break;
}
}
},
setCustomEmojis({commit}, customEmojis) {
commit('setCustomEmojis', customEmojis)
},
}
const mutations = {
setSettings(state, settings) {
state.settings = settings;
state = Object.assign(state, settings)
},
GoogleDriveLinked(state, status) {
Vue.set(state.settings, 'GDriveLinked', status)
Vue.set(state, 'GDriveLinked', status)
},
setLastEmoji(state, newEmojiList) {
Vue.set(state.settings, 'recentEmojis', newEmojiList)
addCustomEmoji(state, customEmoji) {
const customEmojisList = state.customEmojis || [];
customEmojisList.push(customEmoji.emoji)
Vue.set(state, "customEmojis", customEmojisList)
},
removeCustomEmoji(state, index) {
Vue.delete(state.customEmojis, index)
},
renameCustomEmoji(state, {emoji, index}) {
Vue.set(state.customEmojis, index, emoji)
},
setRecentEmojis(state, newEmojiList) {
Vue.set(state, 'recentEmojis', newEmojiList)
},
}
export default {
namespace: true,
namespaced: true,
state,
getters,
actions,

View file

@ -45,7 +45,7 @@ const actions = {
}
context.commit('addAllChannels', channelsObject)
context.dispatch('addAllNotifications', notifications)
context.dispatch('setSettings', settings)
context.dispatch('settingsModule/setSettings', settings)
},
@ -105,7 +105,16 @@ const actions = {
},
['socket_googleDrive:linked'](context) {
context.dispatch('setPopoutVisibility', {name: 'GDLinkMenu', visibility: false})
context.dispatch('setGDriveLinked', true)
context.dispatch('settingsModule/setGDriveLinked', true)
},
['socket_customEmoji:uploaded'](context, emoji) {
context.dispatch('settingsModule/addCustomEmoji', emoji)
},
['socket_customEmoji:remove'](context, emoji) {
context.dispatch('settingsModule/removeCustomEmoji', emoji)
},
['socket_customEmoji:rename'](context, emoji) {
context.dispatch('settingsModule/renameCustomEmoji', emoji)
}
}

View file

@ -2,11 +2,12 @@ const config = [
{
title: 'Custom emojis!',
shortTitle: 'Custom emojis!',
date: '23/03/2019',
headColor: "rgba(255, 48, 48, 0.87)",
date: '29/03/2019',
headColor: "rgba(255, 48, 48, 0.77)",
new: [
'You can now add your own emojis for free.',
"User status in the top bar to easily view if someone is still online or, if you're talking to a wall on the phone."
"User status in the top bar to easily view if someone is still online or, if you're talking to a wall on the phone.",
'Switching dms should be faster now.'
],
next: ['Servers']
},
@ -14,7 +15,7 @@ const config = [
title: 'Emoji tabs and recent emojis',
shortTitle: 'Emoji tabs and recent emojis',
date: '22/03/2019',
headColor: "rgba(244, 169, 65, 0.87)",
headColor: "rgba(244, 169, 65, 0.77)",
new: [
'Tabs in emoji panel',
'Recent Emojis now show in the emoji panel'
@ -28,7 +29,7 @@ const config = [
title: 'Emojis :D',
shortTitle: 'Emojis',
date: '20/03/2019',
headColor: "rgba(17, 153, 69, 0.87)",
headColor: "rgba(17, 153, 69, 0.77)",
new: [
'Emoji suggestions in chat when typing in any emoji :ok_hand:',
'Emoji picker',
@ -44,7 +45,7 @@ const config = [
title: 'Upload anything!',
shortTitle: 'Upload anything!',
date: '08/03/2019',
headColor: "rgba(38, 139, 255, 0.87)",
headColor: "rgba(38, 139, 255, 0.77)",
new: [
'You can now upload any kind of files to friends. (Google drive required)',
'Shift + enter should expand the text area.',

View file

@ -2,19 +2,32 @@ import twemoji from "twemoji";
import matchSorter from "match-sorter";
import emojis from "@/utils/emojiData/emojis.json";
import groups from "@/utils/emojiData/groups.json";
import config from "@/config.js";
import {
store
} from '@/store/index';
export default {
getCustomEmojisByShortCode(shortcode) {
const customEmojis = store.state['settingsModule'].customEmojis;
return customEmojis.find(emoji => emoji.name === shortcode)
},
replaceShortcode: (message) => {
const customEmojis = store.state['settingsModule'].customEmojis;
const regex = /:([\w]+):/g;
return message.replace(regex, (x) => {
const emoji = emojiExists(x.replace(/[::]+/g, ''))
if (emoji) return emoji.unicode
const customEmoji = customEmojis.find(e => e.name === x.substr(1).slice(0, -1))
if (customEmoji) return `:${customEmoji.name}&${customEmoji.emojiID}:`
return x
});
},
replaceEmojis: (string) => {
return twemoji.parse(string,
function (icon, options, variant) {
if (!icon) return string;
@ -26,15 +39,20 @@ export default {
twemoji.parse(string,
function (icon, options, variant) {
if (!icon) return string;
emojiPath = require("twemoji/2/svg/" + icon + ".svg")
emojiPath = require("twemoji/2/svg/" + icon + ".svg")
})
return emojiPath;
},
searchEmoji: (shortCode) => {
return matchSorter(emojis, shortCode, {keys: ['shortcodes']});
const customEmojis = store.state['settingsModule'].customEmojis;
return [...matchSorter(customEmojis, shortCode, {
keys: ['name']
}), ...matchSorter(emojis, shortCode, {
keys: ['shortcodes']
})];
},
getAllEmojis: _ => emojis,
getGroups: _ => groups
allEmojis: emojis,
allGroups: groups
}
function emojiExists(shortCode) {

View file

@ -1,63 +1,73 @@
import futoji from 'futoji'
import twemoji from 'twemoji'
import emojiParser from '@/utils/emojiParser';
import config from "@/config.js";
futoji.addTransformer({
name: 'custom emoji',
symbol: ':',
recursive: false,
transformer: text => {
const split = text.split('&');
if (!split || split.length <= 1) return `:${text}:`;
const url = split[split.length - 1].slice(4);
return `<img class="emoji" draggable="false" alt=":${split[0]}:" src="${config.domain + "/files/" + url}">`
}
})
futoji.addTransformer({
name: 'bold-and-italic',
symbol: '***',
transformer: text => `<strong><em>${text}</em></strong>`
})
futoji.addTransformer({
name: 'bold',
symbol: '**',
transformer: text => `<strong>${text}</strong>`
})
futoji.addTransformer({
name: 'italic',
symbol: '*',
transformer: text => `<em>${text}</em>`
})
futoji.addTransformer({
name: 'underline',
symbol: '__',
transformer: text => `<u>${text}</u>`
})
futoji.addTransformer({
name: 'italic',
symbol: '_',
transformer: text => `<em>${text}</em>`
})
futoji.addTransformer({
name: 'srike',
symbol: '~~',
transformer: text => `<s>${text.trim()}</s>`
})
futoji.addTransformer({
name: 'code-block',
symbol: '```',
recursive: false,
transformer: text => `<div class="codeblock"><code>${formatCode(text).trim()}</code></div>`,
})
futoji.addTransformer({
name: 'code',
symbol: '`',
recursive: false,
transformer: text => `<code>${text}</code>`,
})
export default (message) => {
futoji.addTransformer({
name: 'bold-and-italic',
symbol: '***',
transformer: text => `<strong><em>${text}</em></strong>`
})
futoji.addTransformer({
name: 'bold',
symbol: '**',
transformer: text => `<strong>${text}</strong>`
})
futoji.addTransformer({
name: 'italic',
symbol: '*',
transformer: text => `<em>${text}</em>`
})
futoji.addTransformer({
name: 'underline',
symbol: '__',
transformer: text => `<u>${text}</u>`
})
futoji.addTransformer({
name: 'italic',
symbol: '_',
transformer: text => `<em>${text}</em>`
})
futoji.addTransformer({
name: 'srike',
symbol: '~~',
transformer: text => `<s>${text.trim()}</s>`
})
futoji.addTransformer({
name: 'code-block',
symbol: '```',
recursive: false,
transformer: text => `<div class="codeblock"><code>${formatCode(text).trim()}</code></div>`,
})
futoji.addTransformer({
name: 'code',
symbol: '`',
recursive: false,
transformer: text => `<code>${text}</code>`,
})
message = futoji.format(escapeHtml(message));
message = emojiParser.replaceEmojis(message);
return message;

View file

@ -222,7 +222,7 @@ button {
height: 30px;
width: 30px;
background: url(./../assets/logo.png);
background-size: 125%;
background-size: 105%;
background-position: center;
border-radius: 50%;
box-shadow: 0px 0px 96px -4px rgba(69,212,255,1);