mirror of
https://github.com/danbulant/Nertivia-Client
synced 2026-06-13 19:42:01 +00:00
improved emoji suggestions open method
This commit is contained in:
parent
7ba62da699
commit
41ce5ae36a
5 changed files with 206 additions and 101 deletions
|
|
@ -3,74 +3,72 @@
|
|||
<div class="change-log">
|
||||
<span class="news-title">Changes in this release</span>
|
||||
|
||||
<div class="change">
|
||||
<div class="date">{{changelog.date}}</div>
|
||||
<div class="changes-title">{{changelog.title}}</div>
|
||||
<div class="information">
|
||||
|
||||
<div v-if="changelog.new">
|
||||
<strong>What's new?</strong><br>
|
||||
<ul>
|
||||
<li v-for="(wnew, index) in changelog.new" :key="index">{{wnew}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="changelog.fix">
|
||||
<strong>Issues fixed</strong><br>
|
||||
<ul>
|
||||
<li v-for="(wfix, index) in changelog.fix" :key="index">{{wfix}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="changelog.next">
|
||||
<strong>Up next</strong><br>
|
||||
<ul>
|
||||
<li v-for="(wnext, index) in changelog.next" :key="index">{{wnext}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="changelog.msg">
|
||||
{{changelog.msg}}
|
||||
</div>
|
||||
|
||||
<div class="change" v-for="(change, index) in changelog" :key="change.title">
|
||||
<div :class="`heading ${index === 0 ? 'latest': ''}`">
|
||||
<div class="date">{{change.date}}</div>
|
||||
<div class="changes-title">{{change.title}}</div>
|
||||
</div>
|
||||
<div class="information">
|
||||
<div v-if="change.new">
|
||||
<strong>What's new?</strong>
|
||||
<br>
|
||||
<ul>
|
||||
<li v-for="(wnew, index) in change.new" :key="index">{{wnew}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="change.fix">
|
||||
<strong>Issues fixed</strong>
|
||||
<br>
|
||||
<ul>
|
||||
<li v-for="(wfix, index) in change.fix" :key="index">{{wfix}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="change.next">
|
||||
<strong>Up next</strong>
|
||||
<br>
|
||||
<ul>
|
||||
<li v-for="(wnext, index) in change.next" :key="index">{{wnext}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="change.msg">{{change.msg}}</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</li>
|
||||
<li>Typing indicator</li>
|
||||
<li>Sending files</li>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from '@/components/Spinner.vue'
|
||||
import ChangeLog from '@/components/ChangeLog.vue'
|
||||
import changelog from '@/utils/changelog.js'
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import changelog from "@/utils/changelog.js";
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
changelog: changelog[0]
|
||||
}
|
||||
changelog: changelog
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
.news {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin:20px;
|
||||
margin: 20px;
|
||||
color: white;
|
||||
overflow: auto;
|
||||
}
|
||||
|
|
@ -83,21 +81,35 @@ export default {
|
|||
padding-bottom: 10px;
|
||||
border-bottom: solid 1px white;
|
||||
}
|
||||
.todo-list{
|
||||
.todo-list {
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
background: rgba(0, 0, 0, 0.137);
|
||||
padding: 20px;
|
||||
}
|
||||
.change-log{
|
||||
.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 {
|
||||
background: rgba(0, 0, 0, 0.137);
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.plan-list{
|
||||
.plan-list {
|
||||
color: white;
|
||||
}
|
||||
.date{
|
||||
.date {
|
||||
text-align: left;
|
||||
margin-right: 50px;
|
||||
color: rgba(255, 255, 255, 0.692);
|
||||
|
|
@ -114,12 +126,11 @@ export default {
|
|||
.news {
|
||||
flex-direction: column;
|
||||
}
|
||||
.todo-list{
|
||||
.todo-list {
|
||||
margin-left: 0;
|
||||
}
|
||||
.change-log {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@
|
|||
<span v-else>{{channelName}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading" v-if="selectedChannelID && !messages[selectedChannelID]">
|
||||
<div class="loading" v-if="selectedChannelID && !selectedChannelMessages">
|
||||
<spinner/>
|
||||
</div>
|
||||
<div v-else-if="selectedChannelID" class="message-logs" @wheel="invertScroll">
|
||||
<div class="scroll">
|
||||
<message
|
||||
v-for="(msg, index) in messages[selectedChannelID]"
|
||||
v-for="(msg, index) in selectedChannelMessages"
|
||||
:key="index"
|
||||
:date="msg.created"
|
||||
:admin="msg.creator.admin"
|
||||
|
|
@ -29,11 +29,12 @@
|
|||
<uploadsQueue v-if="uploadQueue !== undefined" :queue="uploadQueue"/>
|
||||
</div>
|
||||
</div>
|
||||
<news v-if="!selectedChannelID && !messages[selectedChannelID]"/>
|
||||
<news v-if="!selectedChannelID "/>
|
||||
<div class="chat-input-area" v-if="selectedChannelID">
|
||||
<div class="emoji-suggestion-outer" v-if="showEmojiSuggestions">
|
||||
<emoji-suggestions :emojiArray="emojiSuggestionsArray"/>
|
||||
</div>
|
||||
|
||||
<div class="message-area">
|
||||
<input type="file" ref="sendFileBrowse" @change="attachmentChange" class="hidden">
|
||||
<div class="attachment-button" @click="attachmentButton">
|
||||
|
|
@ -44,8 +45,8 @@
|
|||
rows="1"
|
||||
ref="input-box"
|
||||
placeholder="Message"
|
||||
@keydown="chatInput"
|
||||
@keyup="delayedResize"
|
||||
@keydown="keyDown"
|
||||
@keyup="keyUp"
|
||||
@change="resize"
|
||||
@input="onInput"
|
||||
v-model="message"
|
||||
|
|
@ -131,6 +132,7 @@ export default {
|
|||
|
||||
if (this.message == "") return;
|
||||
if (this.message.length > 5000) return;
|
||||
this.showEmojiSuggestions = false;
|
||||
clearInterval(this.postTimerID);
|
||||
this.postTimerID = null;
|
||||
this.messageLength = 0;
|
||||
|
|
@ -150,6 +152,10 @@ export default {
|
|||
});
|
||||
|
||||
this.message = "";
|
||||
|
||||
let input = this.$refs["input-box"];
|
||||
input.style.height = "1em";
|
||||
|
||||
this.$store.dispatch("updateChannelLastMessage", this.selectedChannelID);
|
||||
const { ok, error, result } = await messagesService.post(
|
||||
this.selectedChannelID,
|
||||
|
|
@ -191,21 +197,52 @@ export default {
|
|||
input.style.height = `calc(${input.scrollHeight}px - 1em)`;
|
||||
}
|
||||
},
|
||||
delayedResize(event) {
|
||||
this.resize(event);
|
||||
emojiSwitchKey(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() {
|
||||
const shortcode = this.message.split(" ").pop();
|
||||
if (!shortcode || !shortcode.startsWith(":") || shortcode.endsWith(":") || shortcode.length < 3)
|
||||
return (this.showEmojiSuggestions = false);
|
||||
const searchArr = emojiParser.searchEmoji(shortcode.slice(1, -1));
|
||||
if (searchArr.length <= 0) return (this.showEmojiSuggestions = false);
|
||||
this.showEmojiSuggestions = true;
|
||||
this.emojiSuggestionsArray = searchArr;
|
||||
GetWordByPos(str, pos) {
|
||||
let left = str.substr(0, pos);
|
||||
let right = str.substr(pos);
|
||||
|
||||
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);
|
||||
this.emojiSuggestionsArray = searchArr;
|
||||
this.showEmojiSuggestions = true;
|
||||
}
|
||||
},
|
||||
async onInput(event) {
|
||||
this.showEmojiPopout();
|
||||
this.delayedResize(event);
|
||||
this.resize(event);
|
||||
this.messageLength = this.message.length;
|
||||
const value = event.target.value.trim();
|
||||
if (value && this.postTimerID == null) {
|
||||
|
|
@ -213,9 +250,13 @@ export default {
|
|||
await typingService.post(this.selectedChannelID);
|
||||
}
|
||||
},
|
||||
chatInput(event) {
|
||||
this.delayedResize(event);
|
||||
|
||||
keyUp(event) {
|
||||
this.resize(event);
|
||||
this.showEmojiPopout(event);
|
||||
},
|
||||
keyDown(event) {
|
||||
this.resize(event);
|
||||
this.emojiSwitchKey(event);
|
||||
// when enter is press
|
||||
if (event.keyCode == 13) {
|
||||
// and the shift key is not held
|
||||
|
|
@ -331,8 +372,9 @@ export default {
|
|||
channel() {
|
||||
return this.$store.getters.channels[this.selectedChannelID];
|
||||
},
|
||||
messages() {
|
||||
return this.$store.getters.messages;
|
||||
selectedChannelMessages() {
|
||||
const selectedChannel = this.$store.getters.selectedChannelID;
|
||||
return this.$store.getters.messages[selectedChannel];
|
||||
},
|
||||
selectedChannelID() {
|
||||
return this.$store.getters.selectedChannelID;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,75 @@
|
|||
<template>
|
||||
<div class="emoji-suggetions-list">
|
||||
<div class="emoji" v-for="emoji in $props.emojiArray" :key="emoji.hexcode">
|
||||
<div class="preview">{{emoji.unicode}}</div>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
import { bus } from "@/main";
|
||||
import emojiParser from "@/utils/emojiParser.js";
|
||||
export default {
|
||||
props: ['emojiArray'],
|
||||
methods: {},
|
||||
computed: {}
|
||||
props: ["emojiArray"],
|
||||
data() {
|
||||
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>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.emoji-suggetions-list{
|
||||
.selected {
|
||||
background: rgba(66, 66, 66, 0.89);
|
||||
}
|
||||
.emoji-suggetions-list {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 70px;
|
||||
|
|
@ -36,24 +84,27 @@ export default {
|
|||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
.emoji-suggetions-list:hover{
|
||||
.emoji-suggetions-list:hover {
|
||||
background: rgba(32, 32, 32, 0.966);
|
||||
}
|
||||
.preview{
|
||||
|
||||
.preview {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.name{
|
||||
.name {
|
||||
flex: 1;
|
||||
}
|
||||
.short-code {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.emoji{
|
||||
.emoji {
|
||||
display: flex;
|
||||
padding: 5px;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.emoji.selected {
|
||||
|
||||
@media (max-height: 441px) {
|
||||
.emoji-suggetions-list {
|
||||
max-height: 150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import twemoji from "twemoji";
|
||||
import emojis from "emojibase-data/en/compact.json";
|
||||
import matchSorter from "match-sorter";
|
||||
import {
|
||||
|
|
@ -15,17 +16,15 @@ export default {
|
|||
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) => {
|
||||
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']});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
import futoji from 'futoji'
|
||||
import twemoji from 'twemoji'
|
||||
import emojiParser from '@/utils/emojiParser';
|
||||
|
||||
|
||||
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({
|
||||
name: 'bold-and-italic',
|
||||
|
|
@ -58,7 +54,13 @@ export default (message) => {
|
|||
recursive: false,
|
||||
transformer: text => `<code>${text}</code>`,
|
||||
})
|
||||
return futoji.format(message);
|
||||
|
||||
|
||||
message = futoji.format(escapeHtml(message));
|
||||
|
||||
message = emojiParser.replaceEmojis(message);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue