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

View file

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

View file

@ -4,71 +4,78 @@
<div class="tabs"> <div class="tabs">
<div <div
:class="{tab: true, selected: currentTab == 'my-profile'}" v-for="(tab, index) in tabs"
@click="tabClicked('my-profile','account_circle', 'My Profile')"> :key="index"
<div class="material-icons">account_circle</div> :class="{tab: true, selected: currentTab === index}"
<div>My Profile</div> @click="currentTab = index"
</div> >
<div class="material-icons">{{tab.icon}}</div>
<div <div class="tab-name">{{tab.name}}</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>
</div> </div>
</div> </div>
<div class="panel"> <div class="panel">
<div class="title"> <div class="title">
<div class="material-icons">{{icon}}</div> <div class="material-icons">{{tabs[currentTab].icon}}</div>
<div class="in-title">{{title}}</div> <div class="in-title">{{tabs[currentTab].tabName}}</div>
<div class="close-button" @click="close"> <div class="close-button" @click="close">
<div class="material-icons">close</div> <div class="material-icons">close</div>
</div> </div>
</div> </div>
<component :is="currentTab"></component> <component :is="tabs[currentTab].component"></component>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import {bus} from '../../main' import { bus } from "../../main";
import MyProfile from './SettingsPanels/MyProfile.vue' import MyProfile from "./SettingsPanels/MyProfile.vue";
import ManageEmojis from "./SettingsPanels/ManageEmojis.vue";
export default { export default {
components: { components: {
MyProfile MyProfile,
ManageEmojis
}, },
data() { data() {
return { return {
currentTab: "my-profile", currentTab: 0,
icon: "account_circle", tabs: [
title: "My Profile" {
} 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: { methods: {
tabClicked(currentTab, icon, title) {
this.currentTab = currentTab;
this.icon = icon;
this.title = title;
},
close() { close() {
this.$store.dispatch('setPopoutVisibility', {name: 'settings', visibility: false}) this.$store.dispatch("setPopoutVisibility", {
name: "settings",
visibility: false
});
} }
} }
} };
</script> </script>
<style scoped> <style scoped>
.settings-darken-background{ .settings-darken-background {
position: absolute; position: absolute;
background: rgba(0, 0, 0, 0.541); background: rgba(0, 0, 0, 0.541);
top: 0; top: 0;
@ -79,17 +86,19 @@ export default {
display: flex; display: flex;
color: white; color: white;
} }
.settings-box{ .settings-box {
display: flex; display: flex;
margin: auto; margin: auto;
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.507); box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.507);
} }
.tabs{ .tabs {
background: rgba(24, 24, 24, 0.938); background: rgba(24, 24, 24, 0.938);
height: 600px; height: 600px;
width: 200px; width: 200px;
} }
.panel { .panel {
display: flex;
flex-direction: column;
background: rgba(31, 31, 31, 0.924); background: rgba(31, 31, 31, 0.924);
height: 600px; height: 600px;
width: 600px; width: 600px;
@ -103,24 +112,25 @@ export default {
cursor: default; cursor: default;
user-select: none; user-select: none;
transition: 0.3s; transition: 0.3s;
align-items: center;
}
.tab-name {
margin-left: 10px;
} }
.tab.selected { .tab.selected {
background: rgb(61, 61, 61) !important; background: rgb(61, 61, 61) !important;
} }
.tab div {
margin: 5px;
}
.tab:hover { .tab:hover {
background: rgba(61, 61, 61, 0.616); background: rgba(61, 61, 61, 0.616);
} }
.title{ .title {
display: flex; display: flex;
padding: 10px; padding: 10px;
font-size: 25px; font-size: 25px;
background: rgb(20, 20, 20); background: rgb(20, 20, 20);
margin-bottom: 20px;
} }
.title .material-icons{ .title .material-icons {
font-size: 40px; font-size: 40px;
} }
.title div { .title div {
@ -129,9 +139,9 @@ export default {
margin-right: 5px; margin-right: 5px;
} }
.in-title { .in-title {
flex:1; flex: 1;
} }
.close-button{ .close-button {
display: flex; display: flex;
border-radius: 50%; border-radius: 50%;
padding: 5px; padding: 5px;
@ -139,7 +149,7 @@ export default {
user-select: none; user-select: none;
transition: 0.3s; transition: 0.3s;
} }
.close-button:hover{ .close-button:hover {
background: rgba(37, 37, 37, 0.692); background: rgba(37, 37, 37, 0.692);
} }
.close-button .material-icons { .close-button .material-icons {
@ -148,8 +158,8 @@ export default {
} }
@media (max-width: 815px) { @media (max-width: 815px) {
.settings-box{ .settings-box {
width:100% width: 100%;
} }
} }
</style> </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 config from "@/config.js";
import { bus } from "@/main"; import { bus } from "@/main";
import path from "path"; import path from "path";
import {mapState} from 'vuex'
export default { export default {
components: { components: {
@ -88,7 +89,7 @@ export default {
return (this.alert.show = true); return (this.alert.show = true);
}, },
editAvatarBtn() { editAvatarBtn() {
if (!this.$store.getters.settings.GDriveLinked) { if (!this.GDriveLinked) {
return this.$store.dispatch("setPopoutVisibility", { return this.$store.dispatch("setPopoutVisibility", {
name: "GDLinkMenu", name: "GDLinkMenu",
visibility: true visibility: true
@ -98,6 +99,7 @@ export default {
} }
}, },
computed: { computed: {
...mapState('settingsModule', ['GDriveLinked']),
user() { user() {
return this.$store.getters.user; return this.$store.getters.user;
}, },

View file

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

View file

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

View file

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

View file

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

@ -6,17 +6,17 @@ import {
const state = { const state = {
settings: { GDriveLinked: false,
recentEmojis: [] customEmojis: [],
} recentEmojis: JSON.parse(localStorage.getItem('recentEmojis')) || []
} }
const getters = { const getters = {
settings(state) { settings(state) {
return state.settings; return state;
}, },
recentEmojis() { 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) { setGDriveLinked(context, status) {
context.commit('GoogleDriveLinked', status) context.commit('GoogleDriveLinked', status)
}, },
setLastEmoji(context, shortcode) { addRecentEmoji(context, shortcode) {
const recentEmojis = JSON.parse(localStorage.getItem('recentEmojis')) || []; const recentEmojis = JSON.parse(localStorage.getItem('recentEmojis')) || [];
let filter = recentEmojis.filter(function (item) { let filter = recentEmojis.filter(function (item) {
@ -38,25 +38,66 @@ const actions = {
filter = filter.slice(0, 16) filter = filter.slice(0, 16)
localStorage.setItem("recentEmojis", JSON.stringify(filter)); 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 = { const mutations = {
setSettings(state, settings) { setSettings(state, settings) {
state.settings = settings; state = Object.assign(state, settings)
}, },
GoogleDriveLinked(state, status) { GoogleDriveLinked(state, status) {
Vue.set(state.settings, 'GDriveLinked', status) Vue.set(state, 'GDriveLinked', status)
}, },
setLastEmoji(state, newEmojiList) { addCustomEmoji(state, customEmoji) {
Vue.set(state.settings, 'recentEmojis', newEmojiList) 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 { export default {
namespace: true, namespaced: true,
state, state,
getters, getters,
actions, actions,

View file

@ -45,7 +45,7 @@ const actions = {
} }
context.commit('addAllChannels', channelsObject) context.commit('addAllChannels', channelsObject)
context.dispatch('addAllNotifications', notifications) context.dispatch('addAllNotifications', notifications)
context.dispatch('setSettings', settings) context.dispatch('settingsModule/setSettings', settings)
}, },
@ -105,7 +105,16 @@ const actions = {
}, },
['socket_googleDrive:linked'](context) { ['socket_googleDrive:linked'](context) {
context.dispatch('setPopoutVisibility', {name: 'GDLinkMenu', visibility: false}) 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!', title: 'Custom emojis!',
shortTitle: 'Custom emojis!', shortTitle: 'Custom emojis!',
date: '23/03/2019', date: '29/03/2019',
headColor: "rgba(255, 48, 48, 0.87)", headColor: "rgba(255, 48, 48, 0.77)",
new: [ new: [
'You can now add your own emojis for free.', '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'] next: ['Servers']
}, },
@ -14,7 +15,7 @@ const config = [
title: 'Emoji tabs and recent emojis', title: 'Emoji tabs and recent emojis',
shortTitle: 'Emoji tabs and recent emojis', shortTitle: 'Emoji tabs and recent emojis',
date: '22/03/2019', date: '22/03/2019',
headColor: "rgba(244, 169, 65, 0.87)", headColor: "rgba(244, 169, 65, 0.77)",
new: [ new: [
'Tabs in emoji panel', 'Tabs in emoji panel',
'Recent Emojis now show in the emoji panel' 'Recent Emojis now show in the emoji panel'
@ -28,7 +29,7 @@ const config = [
title: 'Emojis :D', title: 'Emojis :D',
shortTitle: 'Emojis', shortTitle: 'Emojis',
date: '20/03/2019', date: '20/03/2019',
headColor: "rgba(17, 153, 69, 0.87)", headColor: "rgba(17, 153, 69, 0.77)",
new: [ new: [
'Emoji suggestions in chat when typing in any emoji :ok_hand:', 'Emoji suggestions in chat when typing in any emoji :ok_hand:',
'Emoji picker', 'Emoji picker',
@ -44,7 +45,7 @@ const config = [
title: 'Upload anything!', title: 'Upload anything!',
shortTitle: 'Upload anything!', shortTitle: 'Upload anything!',
date: '08/03/2019', date: '08/03/2019',
headColor: "rgba(38, 139, 255, 0.87)", headColor: "rgba(38, 139, 255, 0.77)",
new: [ new: [
'You can now upload any kind of files to friends. (Google drive required)', 'You can now upload any kind of files to friends. (Google drive required)',
'Shift + enter should expand the text area.', 'Shift + enter should expand the text area.',

View file

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

View file

@ -1,63 +1,73 @@
import futoji from 'futoji' import futoji from 'futoji'
import twemoji from 'twemoji' import twemoji from 'twemoji'
import emojiParser from '@/utils/emojiParser'; 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) => { 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 = futoji.format(escapeHtml(message));
message = emojiParser.replaceEmojis(message); message = emojiParser.replaceEmojis(message);
return message; return message;

View file

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