improved emoji suggestions open method

This commit is contained in:
supertiger 2019-03-16 17:57:44 +00:00
parent 7ba62da699
commit 41ce5ae36a
5 changed files with 206 additions and 101 deletions

View file

@ -3,33 +3,34 @@
<div class="change-log"> <div class="change-log">
<span class="news-title">Changes in this release</span> <span class="news-title">Changes in this release</span>
<div class="change"> <div class="change" v-for="(change, index) in changelog" :key="change.title">
<div class="date">{{changelog.date}}</div> <div :class="`heading ${index === 0 ? 'latest': ''}`">
<div class="changes-title">{{changelog.title}}</div> <div class="date">{{change.date}}</div>
<div class="changes-title">{{change.title}}</div>
</div>
<div class="information"> <div class="information">
<div v-if="change.new">
<div v-if="changelog.new"> <strong>What's new?</strong>
<strong>What's new?</strong><br> <br>
<ul> <ul>
<li v-for="(wnew, index) in changelog.new" :key="index">{{wnew}}</li> <li v-for="(wnew, index) in change.new" :key="index">{{wnew}}</li>
</ul> </ul>
</div> </div>
<div v-if="changelog.fix"> <div v-if="change.fix">
<strong>Issues fixed</strong><br> <strong>Issues fixed</strong>
<br>
<ul> <ul>
<li v-for="(wfix, index) in changelog.fix" :key="index">{{wfix}}</li> <li v-for="(wfix, index) in change.fix" :key="index">{{wfix}}</li>
</ul> </ul>
</div> </div>
<div v-if="changelog.next"> <div v-if="change.next">
<strong>Up next</strong><br> <strong>Up next</strong>
<br>
<ul> <ul>
<li v-for="(wnext, index) in changelog.next" :key="index">{{wnext}}</li> <li v-for="(wnext, index) in change.next" :key="index">{{wnext}}</li>
</ul> </ul>
</div> </div>
<div v-if="changelog.msg"> <div v-if="change.msg">{{change.msg}}</div>
{{changelog.msg}}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -39,34 +40,31 @@
<p>Features that are coming soon:</p> <p>Features that are coming soon:</p>
<ul class="plan-list"> <ul class="plan-list">
<li>Online, Offline status(Done)</li> <li>Online, Offline status(Done)</li>
<li>Profile picture</li> <li>Profile picture (done)</li>
<li>Typing indicator</li> <li>Typing indicator (done)</li>
<li>Sending files</li> <li>Sending files (done)</li>
<li>Custom emojis</li> <li>Custom emojis</li>
<li>Guilds</li> <li>Guilds</li>
</ul> </ul>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import Spinner from '@/components/Spinner.vue' import Spinner from "@/components/Spinner.vue";
import ChangeLog from '@/components/ChangeLog.vue' import changelog from "@/utils/changelog.js";
import changelog from '@/utils/changelog.js'
export default { export default {
components: {}, components: {},
data() { data() {
return { return {
changelog: changelog[0] changelog: changelog
} };
}
} }
};
</script> </script>
<style scoped> <style scoped>
.news { .news {
display: flex; display: flex;
flex: 1; flex: 1;
@ -89,10 +87,24 @@ export default {
background: rgba(0, 0, 0, 0.137); background: rgba(0, 0, 0, 0.137);
padding: 20px; padding: 20px;
} }
.change {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: solid 1px white;
}
.heading{
padding: 10px;
background: rgba(0, 0, 0, 0.555);
margin-bottom: 10px;
}
.heading.latest {
background: rgba(38, 139, 255, 0.87);
}
.change-log { .change-log {
background: rgba(0, 0, 0, 0.137); background: rgba(0, 0, 0, 0.137);
padding: 20px; padding: 20px;
flex: 1; flex: 1;
overflow-y: auto;
} }
.plan-list { .plan-list {
color: white; color: white;
@ -121,5 +133,4 @@ export default {
margin-bottom: 20px; margin-bottom: 20px;
} }
} }
</style> </style>

View file

@ -9,13 +9,13 @@
<span v-else>{{channelName}}</span> <span v-else>{{channelName}}</span>
</div> </div>
</div> </div>
<div class="loading" v-if="selectedChannelID && !messages[selectedChannelID]"> <div class="loading" v-if="selectedChannelID && !selectedChannelMessages">
<spinner/> <spinner/>
</div> </div>
<div v-else-if="selectedChannelID" class="message-logs" @wheel="invertScroll"> <div v-else-if="selectedChannelID" class="message-logs" @wheel="invertScroll">
<div class="scroll"> <div class="scroll">
<message <message
v-for="(msg, index) in messages[selectedChannelID]" v-for="(msg, index) in selectedChannelMessages"
:key="index" :key="index"
:date="msg.created" :date="msg.created"
:admin="msg.creator.admin" :admin="msg.creator.admin"
@ -29,11 +29,12 @@
<uploadsQueue v-if="uploadQueue !== undefined" :queue="uploadQueue"/> <uploadsQueue v-if="uploadQueue !== undefined" :queue="uploadQueue"/>
</div> </div>
</div> </div>
<news v-if="!selectedChannelID && !messages[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="showEmojiSuggestions"> <div class="emoji-suggestion-outer" v-if="showEmojiSuggestions">
<emoji-suggestions :emojiArray="emojiSuggestionsArray"/> <emoji-suggestions :emojiArray="emojiSuggestionsArray"/>
</div> </div>
<div class="message-area"> <div class="message-area">
<input type="file" ref="sendFileBrowse" @change="attachmentChange" class="hidden"> <input type="file" ref="sendFileBrowse" @change="attachmentChange" class="hidden">
<div class="attachment-button" @click="attachmentButton"> <div class="attachment-button" @click="attachmentButton">
@ -44,8 +45,8 @@
rows="1" rows="1"
ref="input-box" ref="input-box"
placeholder="Message" placeholder="Message"
@keydown="chatInput" @keydown="keyDown"
@keyup="delayedResize" @keyup="keyUp"
@change="resize" @change="resize"
@input="onInput" @input="onInput"
v-model="message" v-model="message"
@ -131,6 +132,7 @@ export default {
if (this.message == "") return; if (this.message == "") return;
if (this.message.length > 5000) return; if (this.message.length > 5000) return;
this.showEmojiSuggestions = false;
clearInterval(this.postTimerID); clearInterval(this.postTimerID);
this.postTimerID = null; this.postTimerID = null;
this.messageLength = 0; this.messageLength = 0;
@ -150,6 +152,10 @@ export default {
}); });
this.message = ""; this.message = "";
let input = this.$refs["input-box"];
input.style.height = "1em";
this.$store.dispatch("updateChannelLastMessage", this.selectedChannelID); this.$store.dispatch("updateChannelLastMessage", this.selectedChannelID);
const { ok, error, result } = await messagesService.post( const { ok, error, result } = await messagesService.post(
this.selectedChannelID, this.selectedChannelID,
@ -191,21 +197,52 @@ export default {
input.style.height = `calc(${input.scrollHeight}px - 1em)`; input.style.height = `calc(${input.scrollHeight}px - 1em)`;
} }
}, },
delayedResize(event) { emojiSwitchKey(event) {
this.resize(event); if (!this.showEmojiSuggestions) return;
const keyCode = event.keyCode;
if (keyCode == 38) {
//up
bus.$emit("emojiSuggestions:up");
event.preventDefault();
return;
}
if (keyCode == 40) {
//down
bus.$emit("emojiSuggestions:down");
event.preventDefault();
return;
}
}, },
showEmojiPopout() { GetWordByPos(str, pos) {
const shortcode = this.message.split(" ").pop(); let left = str.substr(0, pos);
if (!shortcode || !shortcode.startsWith(":") || shortcode.endsWith(":") || shortcode.length < 3) let right = str.substr(pos);
return (this.showEmojiSuggestions = false);
const searchArr = emojiParser.searchEmoji(shortcode.slice(1, -1)); left = left.replace(/^.+ /g, "");
right = right.replace(/ .+$/g, "");
return left + right;
},
showEmojiPopout(event) {
if (event.keyCode == 38 || event.keyCode == 40) return; // up/down
const cursorPosition = event.target.selectionStart;
const cursorWord = this.GetWordByPos(this.message, cursorPosition)
const cursorLetter = this.message.substring(cursorPosition - 1, cursorPosition)
if (cursorLetter.trim() == "" || cursorWord.endsWith(":"))
return this.showEmojiSuggestions = false;
if (cursorWord.startsWith(":") && cursorWord.length >= 3) {
const searchArr = emojiParser.searchEmoji(cursorWord.slice(1, -1));
if (searchArr.length <= 0) return (this.showEmojiSuggestions = false); if (searchArr.length <= 0) return (this.showEmojiSuggestions = false);
this.showEmojiSuggestions = true;
this.emojiSuggestionsArray = searchArr; this.emojiSuggestionsArray = searchArr;
this.showEmojiSuggestions = true;
}
}, },
async onInput(event) { async onInput(event) {
this.showEmojiPopout(); this.resize(event);
this.delayedResize(event);
this.messageLength = this.message.length; this.messageLength = this.message.length;
const value = event.target.value.trim(); const value = event.target.value.trim();
if (value && this.postTimerID == null) { if (value && this.postTimerID == null) {
@ -213,9 +250,13 @@ export default {
await typingService.post(this.selectedChannelID); await typingService.post(this.selectedChannelID);
} }
}, },
chatInput(event) { keyUp(event) {
this.delayedResize(event); this.resize(event);
this.showEmojiPopout(event);
},
keyDown(event) {
this.resize(event);
this.emojiSwitchKey(event);
// when enter is press // when enter is press
if (event.keyCode == 13) { if (event.keyCode == 13) {
// and the shift key is not held // and the shift key is not held
@ -331,8 +372,9 @@ export default {
channel() { channel() {
return this.$store.getters.channels[this.selectedChannelID]; return this.$store.getters.channels[this.selectedChannelID];
}, },
messages() { selectedChannelMessages() {
return this.$store.getters.messages; const selectedChannel = this.$store.getters.selectedChannelID;
return this.$store.getters.messages[selectedChannel];
}, },
selectedChannelID() { selectedChannelID() {
return this.$store.getters.selectedChannelID; return this.$store.getters.selectedChannelID;

View file

@ -1,26 +1,74 @@
<template> <template>
<div class="emoji-suggetions-list"> <div class="emoji-suggetions-list">
<div class="emoji" v-for="emoji in $props.emojiArray" :key="emoji.hexcode"> <div
<div class="preview">{{emoji.unicode}}</div> v-for="(emoji, index) in $props.emojiArray.slice(0,10)"
:class="{emoji: true, selected: index === selectedIndex}"
@mouseover="hoverEvent"
:key="emoji.hexcode"
>
<div class="preview" v-html="emojiParser(emoji.unicode)"></div>
<div class="short-code">:{{emoji.shortcodes[0]}}:</div> <div class="short-code">:{{emoji.shortcodes[0]}}:</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { bus } from "@/main";
import emojiParser from "@/utils/emojiParser.js";
export default { export default {
props: ['emojiArray'], props: ["emojiArray"],
methods: {}, data() {
computed: {} return {
selectedIndex: 0
};
},
methods: {
emojiParser(emoji) {
return emojiParser.replaceEmojis(emoji);
},
hoverEvent(event) {
const emoji = event.target.closest(".emoji");
const parent = event.target.parentElement.children;
if (!emoji || !emoji) return;
const index = [...parent].findIndex(el => el === emoji);
if (index >= 0) this.selectedIndex = index;
},
KeySwitch(key) {
if (key == "up") {
if (this.selectedIndex == 0)
return (this.selectedIndex =
this.$props.emojiArray.slice(0, 10).length - 1);
this.selectedIndex--;
}
if (key == "down") {
if (
this.selectedIndex ==
this.$props.emojiArray.slice(0, 10).length - 1
)
return (this.selectedIndex = 0);
this.selectedIndex++;
}
},
},
mounted() {
bus.$on("emojiSuggestions:up", () => this.KeySwitch("up"));
bus.$on("emojiSuggestions:down", () => this.KeySwitch("down"));
},
watch: {
emojiArray() {
this.selectedIndex = 0;
}
}
}; };
</script> </script>
<style scoped> <style scoped>
.selected {
background: rgba(66, 66, 66, 0.89);
}
.emoji-suggetions-list { .emoji-suggetions-list {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@ -40,7 +88,6 @@ export default {
background: rgba(32, 32, 32, 0.966); background: rgba(32, 32, 32, 0.966);
} }
.preview { .preview {
margin-right: 5px; margin-right: 5px;
} }
.name { .name {
@ -52,8 +99,12 @@ export default {
.emoji { .emoji {
display: flex; display: flex;
padding: 5px; padding: 5px;
align-content: center;
align-items: center;
}
@media (max-height: 441px) {
.emoji-suggetions-list {
max-height: 150px;
} }
.emoji.selected {
} }
</style> </style>

View file

@ -1,3 +1,4 @@
import twemoji from "twemoji";
import emojis from "emojibase-data/en/compact.json"; import emojis from "emojibase-data/en/compact.json";
import matchSorter from "match-sorter"; import matchSorter from "match-sorter";
import { import {
@ -15,17 +16,15 @@ export default {
return x return x
}); });
}, },
replaceEmojis: (string) => {
return twemoji.parse(string,
function (icon, options, variant) {
if (!icon) return string;
return require("twemoji/2/svg/" + icon + ".svg")
})
},
searchEmoji: (shortCode) => { searchEmoji: (shortCode) => {
let array = []
for (let index = 0; index < emojis.length; index++) {
const element = emojis[index];
for (let i = 0; i < element.shortcodes.length; i++) {
const el2 = element.shortcodes[i];
if (el2.includes(shortCode)) array.push(element);
}
}
return matchSorter(emojis, shortCode, {keys: ['shortcodes']}); return matchSorter(emojis, shortCode, {keys: ['shortcodes']});
} }
} }

View file

@ -1,15 +1,11 @@
import futoji from 'futoji' import futoji from 'futoji'
import twemoji from 'twemoji' import twemoji from 'twemoji'
import emojiParser from '@/utils/emojiParser';
export default (message) => { export default (message) => {
message = twemoji.parse(escapeHtml(message),
function (icon, options, variant) {
if (!icon) return message;
return require("twemoji/2/svg/" + icon + ".svg")
})
futoji.addTransformer({ futoji.addTransformer({
name: 'bold-and-italic', name: 'bold-and-italic',
@ -58,7 +54,13 @@ export default (message) => {
recursive: false, recursive: false,
transformer: text => `<code>${text}</code>`, transformer: text => `<code>${text}</code>`,
}) })
return futoji.format(message);
message = futoji.format(escapeHtml(message));
message = emojiParser.replaceEmojis(message);
return message;
} }
/** /**