mirror of
https://github.com/danbulant/Nertivia-Client
synced 2026-06-24 17:11:43 +00:00
emoji panel added
This commit is contained in:
parent
ec37b3ac92
commit
a9276faf4b
6 changed files with 249 additions and 25 deletions
|
|
@ -4,7 +4,7 @@
|
||||||
<span class="news-title">Changes in this release</span>
|
<span class="news-title">Changes in this release</span>
|
||||||
|
|
||||||
<div class="change" v-for="(change, index) in changelog" :key="change.title">
|
<div class="change" v-for="(change, index) in changelog" :key="change.title">
|
||||||
<div :class="`heading ${index === 0 ? 'latest': ''}`">
|
<div class="heading" :style="change.headColor ? `background-color: ${change.headColor}` : ``">
|
||||||
<div class="date">{{change.date}}</div>
|
<div class="date">{{change.date}}</div>
|
||||||
<div class="changes-title">{{change.title}}</div>
|
<div class="changes-title">{{change.title}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -34,19 +34,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="todo-list">
|
|
||||||
<span class="news-title">Planned Features</span>
|
|
||||||
<p>Features that are coming soon:</p>
|
|
||||||
<ul class="plan-list">
|
|
||||||
<li>Online, Offline status(Done)</li>
|
|
||||||
<li>Profile picture (done)</li>
|
|
||||||
<li>Typing indicator (done)</li>
|
|
||||||
<li>Sending files (done)</li>
|
|
||||||
<li>Custom emojis</li>
|
|
||||||
<li>Guilds</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -96,6 +83,7 @@ export default {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: rgba(0, 0, 0, 0.555);
|
background: rgba(0, 0, 0, 0.555);
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
.heading.latest {
|
.heading.latest {
|
||||||
background: rgba(38, 139, 255, 0.87);
|
background: rgba(38, 139, 255, 0.87);
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,10 @@
|
||||||
</div>
|
</div>
|
||||||
<news v-if="!selectedChannelID "/>
|
<news v-if="!selectedChannelID "/>
|
||||||
<div class="chat-input-area" v-if="selectedChannelID">
|
<div class="chat-input-area" v-if="selectedChannelID">
|
||||||
<div class="emoji-suggestion-outer" v-if="emojiArray">
|
|
||||||
<emoji-suggestions :emojiArray="emojiArray"/>
|
<div style="position: relative;" >
|
||||||
|
<emoji-suggestions v-if="emojiArray" :emojiArray="emojiArray"/>
|
||||||
|
<emoji-panel v-if="showEmojiPanel"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="message-area">
|
<div class="message-area">
|
||||||
|
|
@ -52,10 +54,14 @@
|
||||||
v-model="message"
|
v-model="message"
|
||||||
@paste="onPaste"
|
@paste="onPaste"
|
||||||
></textarea>
|
></textarea>
|
||||||
|
<button
|
||||||
|
class="emojis-button"
|
||||||
|
@click="showEmojiPanel = !showEmojiPanel">
|
||||||
|
<i class="material-icons">face</i>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
:class="{'send-button': true, 'error-send-button': messageLength > 5000}"
|
:class="{'send-button': true, 'error-send-button': messageLength > 5000}"
|
||||||
@click="sendMessage"
|
@click="sendMessage">
|
||||||
>
|
|
||||||
<i class="material-icons">send</i>
|
<i class="material-icons">send</i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -85,6 +91,7 @@ import Spinner from "@/components/Spinner.vue";
|
||||||
import TypingStatus from "@/components/app/TypingStatus.vue";
|
import TypingStatus from "@/components/app/TypingStatus.vue";
|
||||||
import uploadsQueue from "@/components/app/uploadsQueue.vue";
|
import uploadsQueue from "@/components/app/uploadsQueue.vue";
|
||||||
import emojiSuggestions from "@/components/app/emojiSuggestions.vue";
|
import emojiSuggestions from "@/components/app/emojiSuggestions.vue";
|
||||||
|
import emojiPanel from "@/components/app/emojiPanel.vue";
|
||||||
import emojiParser from "@/utils/emojiParser.js";
|
import emojiParser from "@/utils/emojiParser.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -94,7 +101,8 @@ export default {
|
||||||
News,
|
News,
|
||||||
TypingStatus,
|
TypingStatus,
|
||||||
uploadsQueue,
|
uploadsQueue,
|
||||||
emojiSuggestions
|
emojiSuggestions,
|
||||||
|
emojiPanel
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -103,7 +111,8 @@ export default {
|
||||||
postTimerID: null,
|
postTimerID: null,
|
||||||
getTimerID: null,
|
getTimerID: null,
|
||||||
typing: false,
|
typing: false,
|
||||||
whosTyping: ""
|
whosTyping: "",
|
||||||
|
showEmojiPanel: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -267,7 +276,12 @@ export default {
|
||||||
const end = cursorPosition;
|
const end = cursorPosition;
|
||||||
|
|
||||||
this.message = this.message.substring(0, start) + emojiShortCode + this.message.substring(end);
|
this.message = this.message.substring(0, start) + emojiShortCode + this.message.substring(end);
|
||||||
return (this.$store.dispatch('setEmojiArray', null));
|
this.$store.dispatch('setEmojiArray', null);
|
||||||
|
},
|
||||||
|
enterEmojiPanel(shortcode){
|
||||||
|
const target = this.$refs["input-box"];
|
||||||
|
target.focus();
|
||||||
|
document.execCommand('insertText', false, `:${shortcode}:`);
|
||||||
},
|
},
|
||||||
keyDown(event) {
|
keyDown(event) {
|
||||||
this.resize(event);
|
this.resize(event);
|
||||||
|
|
@ -353,6 +367,7 @@ export default {
|
||||||
};
|
};
|
||||||
bus.$on("newMessage", this.hideTypingStatus);
|
bus.$on("newMessage", this.hideTypingStatus);
|
||||||
bus.$on("emojiSuggestions:Selected", this.enterEmojiSuggestion)
|
bus.$on("emojiSuggestions:Selected", this.enterEmojiSuggestion)
|
||||||
|
bus.$on("emojiPanel:Selected", this.enterEmojiPanel)
|
||||||
//dismiss notification on focus
|
//dismiss notification on focus
|
||||||
window.onfocus = () => {
|
window.onfocus = () => {
|
||||||
bus.$emit("title:change", "Nertivia");
|
bus.$emit("title:change", "Nertivia");
|
||||||
|
|
@ -370,6 +385,7 @@ export default {
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
bus.$off("newMessage", this.hideTypingStatus);
|
bus.$off("newMessage", this.hideTypingStatus);
|
||||||
bus.$off("emojiSuggestions:Selected", this.enterEmojiSuggestion)
|
bus.$off("emojiSuggestions:Selected", this.enterEmojiSuggestion)
|
||||||
|
bus.$on("emojiPanel:Selected", this.enterEmojiPanel)
|
||||||
delete this.$options.sockets.typingStatus;
|
delete this.$options.sockets.typingStatus;
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -442,9 +458,6 @@ export default {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.emoji-suggestion-outer {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.show-menu-button {
|
.show-menu-button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
@ -506,6 +519,7 @@ export default {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
.attachment-button:hover {
|
.attachment-button:hover {
|
||||||
background: rgba(0, 0, 0, 0.322);
|
background: rgba(0, 0, 0, 0.322);
|
||||||
|
|
@ -553,6 +567,7 @@ export default {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 30vh;
|
max-height: 30vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input:hover {
|
.chat-input:hover {
|
||||||
|
|
@ -576,6 +591,8 @@ export default {
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
.send-button .material-icons {
|
.send-button .material-icons {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
@ -590,6 +607,29 @@ export default {
|
||||||
.error-send-button:hover {
|
.error-send-button:hover {
|
||||||
background-color: rgba(255, 0, 0, 0.294);
|
background-color: rgba(255, 0, 0, 0.294);
|
||||||
}
|
}
|
||||||
|
.emojis-button{
|
||||||
|
font-size: 20px;
|
||||||
|
color: white;
|
||||||
|
background: rgba(0, 0, 0, 0.274);
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
margin-left: 2px;
|
||||||
|
|
||||||
|
min-height: 40px;
|
||||||
|
width: 50px;
|
||||||
|
transition: 0.3s;
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emojis-button .material-icons {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.emojis-button:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.514);
|
||||||
|
}
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.show-menu-button {
|
.show-menu-button {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
||||||
123
src/components/app/emojiPanel.vue
Normal file
123
src/components/app/emojiPanel.vue
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
<template>
|
||||||
|
<div class="emoji-panel">
|
||||||
|
<div class="emoji-panel-inner">
|
||||||
|
<div class="emojis-list">
|
||||||
|
<div class="category" v-for="(group, index) in groups" :key="group">
|
||||||
|
<div class="category-name">{{group}}</div>
|
||||||
|
<div class="list">
|
||||||
|
<div
|
||||||
|
class="emoji-item"
|
||||||
|
v-for="emojiSorted in emojiByGroup(index)"
|
||||||
|
:key="emojiSorted.shortcodes[0]"
|
||||||
|
@click="clickEvent(emojiSorted.shortcodes[0])">
|
||||||
|
<img class="panel emoji" v-lazyload :data-url="parseEmojiPath(emojiSorted.unicode)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="triangle"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { bus } from "@/main";
|
||||||
|
import emojiParser from "@/utils/emojiParser.js";
|
||||||
|
import lazyLoad from "@/directives/LazyLoad.js"
|
||||||
|
|
||||||
|
import emojis from "emojibase-data/en/compact.json";
|
||||||
|
import { groups } from "emojibase-data/meta/groups.json";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
directives: {
|
||||||
|
lazyload: lazyLoad
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
emojiByGroup(index) {
|
||||||
|
index = parseInt(index);
|
||||||
|
return emojis.filter(_emoji => _emoji.group === index);
|
||||||
|
},
|
||||||
|
parseEmojiPath(emoji) {
|
||||||
|
return emojiParser.GetEmojiPath(emoji);
|
||||||
|
},
|
||||||
|
clickEvent(shortcode) {
|
||||||
|
bus.$emit('emojiPanel:Selected', shortcode)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
groups() {
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.emoji-panel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 20px;
|
||||||
|
width: 370px;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-panel-inner{
|
||||||
|
background: rgba(32, 32, 32, 0.87);
|
||||||
|
transition: 0.3s;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
.emoji-panel-inner:hover {
|
||||||
|
background: rgb(32, 32, 32);
|
||||||
|
}
|
||||||
|
.emojis-list {
|
||||||
|
color: white;
|
||||||
|
padding: 5px;
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
transition: 0.32s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
}
|
||||||
|
.category-name {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
}
|
||||||
|
.emoji-item {
|
||||||
|
background: rgba(59, 59, 59, 0.521);
|
||||||
|
transition: 0.3s;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 5px;
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.emoji-item:hover {
|
||||||
|
background: rgb(59, 59, 59);
|
||||||
|
}
|
||||||
|
.triangle {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 10px solid transparent;
|
||||||
|
border-right: 10px solid transparent;
|
||||||
|
border-top: 15px solid rgba(32, 32, 32, 0.87);
|
||||||
|
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-right: 70px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
img.panel.emoji {
|
||||||
|
margin-left: 3px;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
49
src/directives/LazyLoad.js
Normal file
49
src/directives/LazyLoad.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
export default {
|
||||||
|
inserted: el => {
|
||||||
|
function loadImage() {
|
||||||
|
const imageElement = el
|
||||||
|
if (imageElement) {
|
||||||
|
imageElement.addEventListener("load", () => {
|
||||||
|
setTimeout(() => el.classList.add("loaded"), 100);
|
||||||
|
});
|
||||||
|
imageElement.addEventListener("error", () => console.log("error"));
|
||||||
|
imageElement.src = imageElement.dataset.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleIntersect(entries, observer) {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (!entry.isIntersecting) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
loadImage();
|
||||||
|
observer.unobserve(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createObserver() {
|
||||||
|
const options = {
|
||||||
|
// circumstances under which the observer's callback is invoked
|
||||||
|
root: null, // defaults to the browser viewport if not specified or if null
|
||||||
|
threshold: "0" // the degree of intersection between the target element and its root (0 - 1)
|
||||||
|
// threshold of 1.0 means that when 100% of the target is visible within
|
||||||
|
//the element specified by the root option, the callback is invoked
|
||||||
|
};
|
||||||
|
|
||||||
|
// Whether you're using the viewport or some other element as the root,the API works the same way,
|
||||||
|
// executing a callback function you provide whenever the visibility of the target element changes
|
||||||
|
// so that it crosses desired amounts of intersection with the root
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(handleIntersect, options);
|
||||||
|
|
||||||
|
observer.observe(el); // target element to watch
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window["IntersectionObserver"]) {
|
||||||
|
loadImage();
|
||||||
|
} else {
|
||||||
|
createObserver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,8 +1,24 @@
|
||||||
const config = [
|
const config = [
|
||||||
|
{
|
||||||
|
title: 'Emojis :D',
|
||||||
|
shortTitle: 'Emojis',
|
||||||
|
date: '20/03/2019',
|
||||||
|
headColor: "rgba(17, 153, 69, 0.87)",
|
||||||
|
new: [
|
||||||
|
'Emoji suggestions in chat when typing in any emoji :ok_hand:',
|
||||||
|
'Emoji picker',
|
||||||
|
'Removed planned features from changelog'
|
||||||
|
],
|
||||||
|
next: [
|
||||||
|
'make tabs in emoji panel',
|
||||||
|
'Custom emojis for freeeee!',
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
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)",
|
||||||
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.',
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,15 @@ export default {
|
||||||
return require("twemoji/2/svg/" + icon + ".svg")
|
return require("twemoji/2/svg/" + icon + ".svg")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
GetEmojiPath: (string) => {
|
||||||
|
let emojiPath;
|
||||||
|
twemoji.parse(string,
|
||||||
|
function (icon, options, variant) {
|
||||||
|
if (!icon) return string;
|
||||||
|
emojiPath = require("twemoji/2/svg/" + icon + ".svg")
|
||||||
|
})
|
||||||
|
return emojiPath;
|
||||||
|
},
|
||||||
searchEmoji: (shortCode) => {
|
searchEmoji: (shortCode) => {
|
||||||
return matchSorter(emojis, shortCode, {keys: ['shortcodes']});
|
return matchSorter(emojis, shortCode, {keys: ['shortcodes']});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue