added load more messages and scroll down button.

This commit is contained in:
supertiger1234 2019-08-16 21:37:53 +01:00
parent 6f65d1291d
commit 53e8fff392
12 changed files with 532 additions and 305 deletions

View file

@ -8,6 +8,7 @@
<script> <script>
import messageFormatter from "@/utils/messageFormatter.js"; import messageFormatter from "@/utils/messageFormatter.js";
import { bus } from "../../main";
export default { export default {
props: ['data'], props: ['data'],
@ -20,6 +21,9 @@ export default {
this.close(); this.close();
} }
}, },
mounted() {
bus.$emit('scrollDown')
},
created() { created() {
document.addEventListener('keydown', this.keyDownEvent) document.addEventListener('keydown', this.keyDownEvent)
}, },
@ -46,7 +50,7 @@ export default {
background: rgba(23, 112, 255, 0.877); background: rgba(23, 112, 255, 0.877);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
z-index: 99999; z-index:1;
border-radius: 10px; border-radius: 10px;
margin: 10px; margin: 10px;
margin-bottom: 0; margin-bottom: 0;

View file

@ -1,15 +1,10 @@
<template> <template>
<div <div v-click-outside="closePanel" class="emoji-panel">
v-click-outside="closePanel"
class="emoji-panel"
>
<div class="emoji-panel-inner"> <div class="emoji-panel-inner">
<div class="emojis-list"> <div class="emojis-list">
<!-- Recent Emojis Category --> <!-- Recent Emojis Category -->
<div class="category"> <div class="category">
<div class="category-name"> <div class="category-name">Recent</div>
Recent
</div>
<div class="list"> <div class="list">
<div <div
v-for="(recentEmoji, index) in this.recentEmojisList" v-for="(recentEmoji, index) in this.recentEmojisList"
@ -21,16 +16,14 @@
v-lazyload v-lazyload
class="panel emoji" class="panel emoji"
:data-url=" getCustomEmoji(recentEmoji) || emojiShortcodeToPath(':' + recentEmoji + ':')" :data-url=" getCustomEmoji(recentEmoji) || emojiShortcodeToPath(':' + recentEmoji + ':')"
> />
</div> </div>
</div> </div>
</div> </div>
<!-- Custom Emojis Category --> <!-- Custom Emojis Category -->
<div class="category"> <div class="category">
<div class="category-name"> <div class="category-name">Custom Emojis</div>
Custom Emojis
</div>
<div class="list"> <div class="list">
<div <div
v-for="(customEmoji, index) in this.customEmojisList" v-for="(customEmoji, index) in this.customEmojisList"
@ -38,23 +31,13 @@
class="emoji-item" class="emoji-item"
@click="customEmojiClickEvent(customEmoji)" @click="customEmojiClickEvent(customEmoji)"
> >
<img <img v-lazyload class="panel emoji" :data-url="customEmojiPath + customEmoji.emojiID" />
v-lazyload
class="panel emoji"
:data-url="customEmojiPath + customEmoji.emojiID"
>
</div> </div>
</div> </div>
</div> </div>
<div <div v-for="(group, index) in groups" :key="group" class="category">
v-for="(group, index) in groups" <div class="category-name">{{ group }}</div>
:key="group"
class="category"
>
<div class="category-name">
{{ group }}
</div>
<div class="list"> <div class="list">
<div <div
v-for="emojiSorted in emojiByGroup(index)" v-for="emojiSorted in emojiByGroup(index)"
@ -62,33 +45,19 @@
class="emoji-item" class="emoji-item"
@click="emojiClickEvent(emojiSorted.shortcodes[0])" @click="emojiClickEvent(emojiSorted.shortcodes[0])"
> >
<img <img v-lazyload class="panel emoji" :data-url="parseEmojiPath(emojiSorted.unicode)" />
v-lazyload
class="panel emoji"
:data-url="parseEmojiPath(emojiSorted.unicode)"
>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="tabs"> <div class="tabs">
<div <div class="tab" @click="scrollToCategory(0)">
class="tab"
@click="scrollToCategory(0)"
>
<i class="material-icons">history</i> <i class="material-icons">history</i>
<div class="tooltip"> <div class="tooltip">Recent</div>
Recent
</div>
</div> </div>
<div <div class="tab" @click="scrollToCategory(1)">
class="tab"
@click="scrollToCategory(1)"
>
<i class="material-icons">face</i> <i class="material-icons">face</i>
<div class="tooltip"> <div class="tooltip">Custom Emojis</div>
Custom Emojis
</div>
</div> </div>
<div <div
v-for="(emoji, index) in groupUnicodes" v-for="(emoji, index) in groupUnicodes"
@ -97,13 +66,8 @@
@mouseenter="mouseHover(emoji, $event)" @mouseenter="mouseHover(emoji, $event)"
@click="scrollToCategory(index + 2)" @click="scrollToCategory(index + 2)"
> >
<img <img class="panel-emoji" :src="selectRandom(emoji)" />
class="panel-emoji" <div class="tooltip">{{ groups[index] }}</div>
:src="selectRandom(emoji)"
>
<div class="tooltip">
{{ groups[index] }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -115,10 +79,9 @@
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 { mapState } from "vuex";
import config from "@/config.js"; import config from "@/config.js";
export default { export default {
directives: { directives: {
lazyload: lazyLoad lazyload: lazyLoad
@ -377,23 +340,23 @@ export default {
emojis: emojiParser.allEmojis, emojis: emojiParser.allEmojis,
groups: emojiParser.allGroups, groups: emojiParser.allGroups,
recentEmojisList: null, recentEmojisList: null,
customEmojisList : null, customEmojisList: null,
customEmojiPath: config.domain + "/files/" customEmojiPath: config.domain + "/media/"
}; };
}, },
beforeMount() { beforeMount() {
this.recentEmojisList = this.recentEmojis this.recentEmojisList = this.recentEmojis;
this.customEmojisList = this.customEmojis this.customEmojisList = this.customEmojis;
}, },
methods: { methods: {
getCustomEmoji(shortCode){ getCustomEmoji(shortCode) {
const customEmoji = emojiParser.getCustomEmojisByShortCode(shortCode) const customEmoji = emojiParser.getCustomEmojisByShortCode(shortCode);
return (customEmoji ? this.customEmojiPath + customEmoji.emojiID : undefined) return customEmoji
? this.customEmojiPath + customEmoji.emojiID
: undefined;
}, },
closePanel(event) { closePanel(event) {
if (!event.target.closest('.emojis-button')) if (!event.target.closest(".emojis-button")) this.$emit("close");
this.$emit('close')
}, },
emojiByGroup(index) { emojiByGroup(index) {
index = parseInt(index); index = parseInt(index);
@ -422,9 +385,9 @@ export default {
const elements = document.querySelectorAll(".category-name"); const elements = document.querySelectorAll(".category-name");
elements[index].scrollIntoView(); elements[index].scrollIntoView();
} }
}, },
computed: { computed: {
...mapState('settingsModule', ['recentEmojis', 'customEmojis']) ...mapState("settingsModule", ["recentEmojis", "customEmojis"])
} }
}; };
</script> </script>
@ -483,6 +446,7 @@ export default {
padding: 2px; padding: 2px;
border-radius: 5px; border-radius: 5px;
min-width: 30px; min-width: 30px;
cursor: pointer;
} }
.emoji-item:hover { .emoji-item:hover {
background: rgb(59, 59, 59); background: rgb(59, 59, 59);
@ -529,6 +493,7 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
cursor: pointer;
} }
.tab:hover { .tab:hover {
@ -571,12 +536,10 @@ export default {
</style> </style>
<style> <style>
img.panel.emoji { img.panel.emoji {
margin: auto; margin: auto;
padding: 2px; padding: 2px;
object-fit: contain; object-fit: contain;
height: 2em; height: 2em;
width: 2em; width: 2em;
} }
</style> </style>

View file

@ -34,7 +34,7 @@ export default {
props: ["emojiArray"], props: ["emojiArray"],
data(){ data(){
return { return {
customEmojiPath: config.domain + "/files/" customEmojiPath: config.domain + "/media/"
} }
}, },
computed: { computed: {

View file

@ -0,0 +1,259 @@
<template>
<div ref="msg-logs" class="message-logs" @scroll="scrollEvent" @resize="onResize">
<div class="load-more-button" v-if="!noMoreLoadMore && selectedChannelMessages.length >= 50">
<spinner :size="30" v-if="loadingMore" />
<div class="text" v-if="!loadingMore" @click="loadMoreMessages">Load more</div>
</div>
<message
class="message-container"
v-for="(msg, index) in selectedChannelMessages"
:key="index + selectedChannelID"
:date="msg.created"
:admin="msg.creator.admin"
:username="msg.creator.username"
:uniqueID="msg.creator.uniqueID"
:avatar="msg.creator.avatar"
:message="msg.message"
:embed="msg.embed"
:files="msg.files"
:status="msg.status"
:messageID="msg.messageID"
:channelID="msg.channelID"
:type="msg.type"
:timeEdited="msg.timeEdited"
/>
<uploadsQueue v-if="uploadQueue !== undefined" :queue="uploadQueue"/>
</div>
</template>
<script>
import messagesService from "@/services/messagesService";
import { bus } from "../../main";
import Message from "../../components/app/MessageTemplate.vue";
import Spinner from "@/components/Spinner.vue";
import uploadsQueue from "@/components/app/uploadsQueue.vue";
import windowProperties from '@/utils/windowProperties';
export default {
components: {
Message,
Spinner,
uploadsQueue,
},
data() {
return {
scrolledDown: true,
scrolledTop: false,
//load more messages
loadingMore: false,
noMoreLoadMore: false,
selectedChannelID: null,
currentScrollTopPos: null
};
},
methods: {
scrollEvent(event) {
const { currentTarget: { scrollTop, clientHeight, scrollHeight} } = event;
this.scrolledDown = Math.abs(scrollHeight - scrollTop - clientHeight) <= 3.0;
this.scrolledTop = scrollTop === 0;
this.currentScrollTopPos = scrollTop;
},
scrollDown(data) {
const element = this.$refs['msg-logs']
const force = data && data.force ? data.force : false;
const pos = data && data.pos ? data.pos : undefined;
if (!force && !this.scrolledDown) return;
if (!element) return;
element.scrollTop = pos || element.scrollHeight;
},
onResize(dimentions) {
this.scrollDown();
},
async loadMoreMessages() {
const msgLogs = this.$refs['msg-logs'];
const scrollTop = msgLogs.scrollTop;
const scrollHeight = msgLogs.scrollHeight;
const continueMessageID = this.selectedChannelMessages[0].messageID;
this.loadingMore = true
const {ok, result, error} = await messagesService.get(this.selectedChannelID, continueMessageID)
if (ok) {
if (!result.data.messages.length) {
this.loadingMore = false;
this.noMoreLoadMore = true;
return;
}
this.$store.dispatch('addMessages', result.data.messages)
this.$nextTick(_ => {
this.loadingMore = false;
msgLogs.scrollTop = msgLogs.scrollHeight - scrollHeight;
})
}
},
scrolledUpEvent() {
this.loadMoreMessages();
},
scrolledDownEvent(){
this.noMoreLoadMore = false;
this.unloadTopMessages()
},
unloadTopMessages(){
if (this.selectedChannelMessages && this.selectedChannelMessages.length)
this.$store.dispatch('unloadTopMessages', {channelID: this.selectedChannelID});
},
backToBottomEvent() {
this.scrollDown({force: true});
this.unloadTopMessages();
},
},
mounted() {
this.selectedChannelID = this.$store.getters.selectedChannelID;
const pos = this.$store.getters.scrollPosition[this.selectedChannelID];
bus.$on('backToBottom', this.backToBottomEvent)
bus.$on('scrollDown', this.scrollDown)
bus.$emit('scrolledDown',this.scrolledDown);
this.$nextTick( _ => {
this.scrollDown({force: pos, pos: pos});
})
},
beforeDestroy() {
this.$store.dispatch("setEditMessage", null);
this.$store.dispatch('changeScrollPosition',{ channelID: this.selectedChannelID, pos: this.currentScrollTopPos });
bus.$off('backToBottom', this.backToBottomEvent);
bus.$off('scrollDown', this.scrollDown)
},
watch: {
selectedChannelMessages(newMessages, oldMessages){
this.noMoreLoadMore = false;
const msgLogs = this.$refs['msg-logs'];
this.$nextTick(function () {
this.scrollDown();
})
},
uploadQueue() {
this.$nextTick(function () {
this.scrollDown({force: true});
})
},
getWindowWidth(dimentions) {
this.onResize();
},
scrolledTop(scrolledTop) {
if (scrolledTop)
this.scrolledUpEvent();
},
scrolledDown(scrolledDown) {
bus.$emit('scrolledDown',scrolledDown);
if (scrolledDown)
this.scrolledDownEvent();
}
},
computed: {
uploadQueue() {
const allUploads = this.$store.getters.getAllUploads;
const selectedChannelID = this.$store.getters.selectedChannelID;
const filteredArray = [];
for (let upload in allUploads) {
if (allUploads[upload].channelID === selectedChannelID) {
filteredArray.push(allUploads[upload]);
}
}
if (!filteredArray.length) return undefined;
return filteredArray;
},
user() {
return this.$store.getters.user;
},
channel() {
return this.$store.getters.channels[this.selectedChannelID];
},
selectedChannelMessages() {
const selectedChannel = this.$store.getters.selectedChannelID;
return this.$store.getters.messages[selectedChannel];
},
editMessage() {
let editMessage = this.$store.getters.popouts.editMessage;
if (!editMessage) return null;
return editMessage;
},
getWindowWidth() {
return {width: windowProperties.resizeWidth, height: windowProperties.resizeHeight};
},
scrollPosition() {
return this.$store.getters.scrollPosition;
}
}
};
</script>
<style lang="scss" scoped>
.message-logs {
overflow: auto;
flex: 1;
margin-right: 2px;
position: relative;
}
.load-more-button {
background: rgba(0, 0, 0, 0.46);
padding: 5px;
user-select: none;
border-radius: 5px;
margin: 5px;
height: 30px;
display: flex;
flex-direction: column;
justify-content: center;
.text {
text-align: center;
color: white;
cursor: pointer;
align-self: center;
}
}
.back-to-bottom-button {
&:hover {
background: rgb(23, 124, 255);
box-shadow: 0px 0px 15px 0px #0000008a;
}
transition: 0.2s;
background: rgba(23, 124, 255, 0.818);
color: white;
position: absolute;
bottom: 15px;
right: 25px;
border-radius: 10px;
height: 50px;
z-index: 2;
display: flex;
justify-content: center;
flex-shrink: 0;
box-shadow: 0px 0px 7px 0px #0000008a;
align-content: center;
align-items: center;
padding-left: 10px;
user-select: none;
cursor: pointer;
.material-icons {
align-self: center;
flex-shrink: 0;
font-size: 35px;
}
}
</style>

View file

@ -3,48 +3,31 @@
<heading <heading
:uniqueID="recipients && recipients.length ? recipients[0].uniqueID : undefined" :uniqueID="recipients && recipients.length ? recipients[0].uniqueID : undefined"
:type="selectedChannelID && channel && !channel.server_id ? 1 : channel && channel.server_id ? 2 : 0" :type="selectedChannelID && channel && !channel.server_id ? 1 : channel && channel.server_id ? 2 : 0"
:status-color="userStatusColor"
:name="selectedChannelID ? channelName : `Welcome back, ${user.username}!` " :name="selectedChannelID ? channelName : `Welcome back, ${user.username}!` "
/> />
<div class="loading" v-if="selectedChannelID && !selectedChannelMessages"> <div class="loading" v-if="selectedChannelID && !selectedChannelMessages">
<spinner/> <spinner />
</div>
<div v-else-if="selectedChannelID" ref="msg-logs" class="message-logs" @scroll="scrollEvent">
<div class="scroll">
<message
class="message-container"
v-for="(msg, index) in selectedChannelMessages"
:key="index + selectedChannelID"
:date="msg.created"
:admin="msg.creator.admin"
:username="msg.creator.username"
:uniqueID="msg.creator.uniqueID"
:avatar="msg.creator.avatar"
:message="msg.message"
:embed="msg.embed"
:files="msg.files"
:status="msg.status"
:messageID="msg.messageID"
:channelID="msg.channelID"
:type="msg.type"
:timeEdited="msg.timeEdited"
/>
<uploadsQueue v-if="uploadQueue !== undefined" :queue="uploadQueue"/>
</div>
</div> </div>
<message-logs v-else-if="selectedChannelID && selectedChannelMessages" :key="selectedChannelID" />
<div class="no-channel-selected" v-if="!selectedChannelID "> <div class="no-channel-selected" v-if="!selectedChannelID ">
<div class="material-icons">chat</div> <div class="material-icons">chat</div>
<div class="message">Select a person to message!</div> <div class="message">Select a person to message!</div>
</div> </div>
<div class="chat-input-area" v-if="selectedChannelID"> <div class="chat-input-area" v-if="selectedChannelID">
<div style="position: relative;"> <div style="position: relative;">
<emoji-suggestions v-if="emojiArray" :emojiArray="emojiArray"/> <transition name="show-up">
<emoji-panel v-if="showEmojiPanel" @close="showEmojiPanel = false"/> <div class="back-to-bottom-button" @click="backToTopButton" v-if="!scrolledDown && selectedChannelMessages">
Back to bottom
<i class="material-icons">keyboard_arrow_down</i>
</div>
</transition>
<emoji-suggestions v-if="emojiArray" :emojiArray="emojiArray" />
<emoji-panel v-if="showEmojiPanel" @close="showEmojiPanel = false" />
</div> </div>
<edit-panel v-if="editMessage" :data='editMessage' /> <edit-panel v-if="editMessage" :data="editMessage" />
<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">
<i class="material-icons">attach_file</i> <i class="material-icons">attach_file</i>
</div> </div>
@ -64,7 +47,8 @@
<i class="material-icons">face</i> <i class="material-icons">face</i>
</button> </button>
<button <button
:class="{'send-button': true, 'error-send-button': messageLength > 5000}" class="send-button"
:class="{'error-send-button': messageLength > 5000}"
@click="editMessage ? updateMessage() : sendMessage()" @click="editMessage ? updateMessage() : sendMessage()"
> >
<i class="material-icons">{{editMessage ? 'edit' : 'send'}}</i> <i class="material-icons">{{editMessage ? 'edit' : 'send'}}</i>
@ -90,30 +74,26 @@ import messagesService from "@/services/messagesService";
import typingService from "@/services/TypingService"; import typingService from "@/services/TypingService";
import { bus } from "../../main"; import { bus } from "../../main";
import JQuery from "jquery"; import JQuery from "jquery";
let $ = JQuery;
import Message from "../../components/app/MessageTemplate.vue";
import Spinner from "@/components/Spinner.vue"; 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 heading from "@/components/app/MessagePanel/Heading.vue"; import heading from "@/components/app/MessagePanel/Heading.vue";
import emojiSuggestions from "@/components/app/EmojiPanels/emojiSuggestions.vue"; import emojiSuggestions from "@/components/app/EmojiPanels/emojiSuggestions.vue";
import emojiParser from "@/utils/emojiParser.js"; import MessageLogs from "@/components/app/MessageLogs.vue";
import statuses from "@/utils/statuses"; import emojiParser from "@/utils/emojiParser.js";
import windowProperties from '@/utils/windowProperties'; import windowProperties from "@/utils/windowProperties";
const emojiPanel = () => import("@/components/app/EmojiPanels/emojiPanel.vue"); const emojiPanel = () => import("@/components/app/EmojiPanels/emojiPanel.vue");
const EditPanel = () => import("@/components/app/EditPanel.vue"); const EditPanel = () => import("@/components/app/EditPanel.vue");
export default { export default {
components: { components: {
Message,
Spinner, Spinner,
TypingStatus, TypingStatus,
uploadsQueue,
emojiSuggestions, emojiSuggestions,
emojiPanel, emojiPanel,
heading, heading,
EditPanel EditPanel,
MessageLogs
}, },
data() { data() {
return { return {
@ -124,8 +104,8 @@ export default {
typing: false, typing: false,
typingRecipients: {}, typingRecipients: {},
showEmojiPanel: false, showEmojiPanel: false,
scrolledDown: true,
scrolledTop: false, scrolledDown: true
}; };
}, },
methods: { methods: {
@ -199,7 +179,7 @@ export default {
const editMessage = this.editMessage; const editMessage = this.editMessage;
this.$refs["input-box"].focus(); this.$refs["input-box"].focus();
this.message = this.message.trim(); this.message = this.message.trim();
if (this.message === this.editMessage.message){ if (this.message === this.editMessage.message) {
this.$store.dispatch("setEditMessage", null); this.$store.dispatch("setEditMessage", null);
this.message = ""; this.message = "";
return; return;
@ -212,29 +192,33 @@ export default {
this.messageLength = 0; this.messageLength = 0;
const msg = emojiParser.replaceShortcode(this.message); const msg = emojiParser.replaceShortcode(this.message);
this.$store.dispatch('updateMessage', { this.$store.dispatch("updateMessage", {
channelID: editMessage.channelID, channelID: editMessage.channelID,
messageID: editMessage.messageID, messageID: editMessage.messageID,
message: {message: msg, status: 0} message: { message: msg, status: 0 }
}) });
this.$store.dispatch("setEditMessage", null); this.$store.dispatch("setEditMessage", null);
this.message = ""; this.message = "";
const {ok, error, result} = await messagesService.update(editMessage.messageID, editMessage.channelID, { const { ok, error, result } = await messagesService.update(
message: msg editMessage.messageID,
}) editMessage.channelID,
{
message: msg
}
);
if (ok) { if (ok) {
this.$store.dispatch('updateMessage', { this.$store.dispatch("updateMessage", {
channelID: editMessage.channelID, channelID: editMessage.channelID,
messageID: editMessage.messageID, messageID: editMessage.messageID,
message: {status: 1} message: { status: 1 }
}) });
} else { } else {
this.$store.dispatch('updateMessage', { this.$store.dispatch("updateMessage", {
channelID: editMessage.channelID, channelID: editMessage.channelID,
messageID: editMessage.messageID, messageID: editMessage.messageID,
message: {message: msg, status: 2} message: { message: msg, status: 2 }
}) });
} }
}, },
async postTimer() { async postTimer() {
@ -242,14 +226,13 @@ export default {
if (this.message.trim() == "") { if (this.message.trim() == "") {
clearInterval(this.postTimerID); clearInterval(this.postTimerID);
this.postTimerID = null; this.postTimerID = null;
}else { } else {
if (this.selectedChannelID) if (this.selectedChannelID)
await typingService.post(this.selectedChannelID); await typingService.post(this.selectedChannelID);
if (this.postTimerID) if (this.postTimerID) this.postTimer();
this.postTimer()
} }
}, 2000) }, 2000);
}, },
resize() { resize() {
let input = this.$refs["input-box"]; let input = this.$refs["input-box"];
@ -259,9 +242,8 @@ export default {
} else { } else {
input.style.height = "auto"; input.style.height = "auto";
input.style.height = `calc(${input.scrollHeight}px - 1em)`; input.style.height = `calc(${input.scrollHeight}px - 1em)`;
this.scrollDown();
} }
bus.$emit('scrollDown');
}, },
emojiSwitchKey(event) { emojiSwitchKey(event) {
if (!this.emojiArray) return; if (!this.emojiArray) return;
@ -347,11 +329,16 @@ export default {
}, },
enterEmojiPanel(shortcode) { enterEmojiPanel(shortcode) {
const target = this.$refs["input-box"]; const target = this.$refs["input-box"];
if (!target) return;
target.focus(); target.focus();
const isSuccessful = document.execCommand("insertText", false, `:${shortcode}: `); const isSuccessful = document.execCommand(
"insertText",
false,
`:${shortcode}: `
);
if (!isSuccessful) { if (!isSuccessful) {
this.message = this.message + `:${shortcode}: `; this.message = this.message + `:${shortcode}: `;
} }
target.blur(); target.blur();
this.$store.dispatch("settingsModule/addRecentEmoji", shortcode); this.$store.dispatch("settingsModule/addRecentEmoji", shortcode);
@ -369,17 +356,20 @@ export default {
return; return;
} }
if (this.editMessage) { if (this.editMessage) {
return this.updateMessage() return this.updateMessage();
} else { } else {
this.sendMessage(); this.sendMessage();
} }
} }
} }
if (event.keyCode === 38){ //38 = up arrow if (event.keyCode === 38) {
//38 = up arrow
if (this.message !== "") return; if (this.message !== "") return;
if (this.editMessage) return; if (this.editMessage) return;
const messagesFiltered = this.selectedChannelMessages.filter(m => m.creator.uniqueID === this.user.uniqueID); const messagesFiltered = this.selectedChannelMessages.filter(
m => m.creator.uniqueID === this.user.uniqueID
);
if (!messagesFiltered.length) return; if (!messagesFiltered.length) return;
event.preventDefault(); event.preventDefault();
@ -389,14 +379,19 @@ export default {
channelID: lastMessage.channelID, channelID: lastMessage.channelID,
message: lastMessage.message message: lastMessage.message
}); });
} }
}, },
hideTypingStatus(data) { hideTypingStatus(data) {
if (this.user.uniqueID === data.message.creator.uniqueID) return; if (this.user.uniqueID === data.message.creator.uniqueID) return;
if (!this.typingRecipients[data.channelID] || !this.typingRecipients[data.channelID][data.message.creator.uniqueID]) return; if (
clearTimeout( this.typingRecipients[data.channelID][data.message.creator.uniqueID].timer ); !this.typingRecipients[data.channelID] ||
!this.typingRecipients[data.channelID][data.message.creator.uniqueID]
)
return;
clearTimeout(
this.typingRecipients[data.channelID][data.message.creator.uniqueID]
.timer
);
this.$delete( this.$delete(
this.typingRecipients[data.channelID], this.typingRecipients[data.channelID],
data.message.creator.uniqueID data.message.creator.uniqueID
@ -456,38 +451,16 @@ export default {
}); });
} }
}, },
scrollEvent(event) { backToTopButton() {
const { currentTarget: { scrollTop, clientHeight, scrollHeight} } = event; bus.$emit('backToBottom');
this.scrolledDown = Math.abs(scrollHeight - scrollTop - clientHeight) <= 3.0;
this.scrolledTop = scrollTop === 0;
}, },
scrollDown() { editMessageEvent(editMessage) {
if (!this.scrolledDown) return; this.message = editMessage ? emojiParser.emojiToShortcode(editMessage.message) : '';
const element = this.$refs['msg-logs']
if (!element) return;
element.scrollTop = element.scrollHeight;
},
onResize(dimentions) {
this.scrollDown();
},
async loadMoreMessages() {
const msgLogs = this.$refs['msg-logs'];
const scrollTop = msgLogs.scrollTop;
const scrollHeight = msgLogs.scrollHeight;
const continueMessageID = this.selectedChannelMessages[0].messageID;
const {ok, result, error} = await messagesService.get(this.selectedChannelID, continueMessageID)
if (ok) {
if (!result.data.messages.length) return
this.$store.dispatch('addMessages', result.data.messages)
this.$nextTick(_ => {
msgLogs.scrollTop = msgLogs.scrollHeight - scrollHeight;
})
}
}, },
scrolledUpEvent() { onBlur() {
this.loadMoreMessages(); clearTimeout(this.postTimerID);
this.postTimerID = null;
} }
}, },
mounted() { mounted() {
@ -520,59 +493,34 @@ export default {
2500 2500
); );
}; };
this.scrollDown();
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); bus.$on("emojiPanel:Selected", this.enterEmojiPanel);
window.onblur = () => {
clearTimeout(this.postTimerID);
this.postTimerID = null;
}
window.addEventListener('focus', this.onFocus)
bus.$on('scrolledDown', (scrolledDown) => {
this.scrolledDown = scrolledDown;
})
window.addEventListener('blur', this.onBlur)
window.addEventListener("focus", this.onFocus);
}, },
beforeDestroy() { beforeDestroy() {
clearTimeout(this.postTimerID); clearTimeout(this.postTimerID);
this.postTimerID = null; this.postTimerID = null;
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); bus.$on("emojiPanel:Selected", this.enterEmojiPanel);
window.removeEventListener('focus', this.onFocus) window.removeEventListener("focus", this.onFocus);
window.removeEventListener("blur", this.onBlur);
delete this.$options.sockets.typingStatus; delete this.$options.sockets.typingStatus;
}, },
watch: { watch: {
selectedChannelMessages(newMessages, oldMessages){
this.$nextTick(function () {
this.scrollDown();
})
},
selectedChannelID(){
this.scrolledDown = true;
},
uploadQueue() {
this.$nextTick(function () {
this.scrollDown(true);
})
},
editMessage(editMessage) { editMessage(editMessage) {
if (!editMessage) { this.editMessageEvent(editMessage);
this.message = ""
} else {
this.message = emojiParser.emojiToShortcode(editMessage.message)
}
this.$nextTick(() => {
this.resize();
this.scrollDown();
})
},
getWindowWidth(dimentions) {
this.onResize();
},
scrolledTop(scrolledTop) {
if (scrolledTop)
this.scrolledUpEvent();
} }
}, },
computed: { computed: {
@ -617,37 +565,30 @@ export default {
const channel = this.$store.getters.channels[selectedChannel]; const channel = this.$store.getters.channels[selectedChannel];
return channel ? channel.recipients : undefined; return channel ? channel.recipients : undefined;
}, },
userStatusColor() {
const selectedChannel = this.$store.getters.selectedChannelID;
const channel = this.$store.getters.channels[selectedChannel];
const presences = this.$store.getters['members/presences'];
let status = 0;
if (!channel || !channel.recipients || !channel.recipients.length) {
status = 0;
} else if (
this.$store.getters.user.friends[channel.recipients[0].uniqueID]
) {
status = presences[channel.recipients[0].uniqueID] || 0;
}
return statuses[status].color;
},
editMessage() { editMessage() {
let editMessage = this.$store.getters.popouts.editMessage; let editMessage = this.$store.getters.popouts.editMessage;
if (!editMessage) return null; if (!editMessage) return null;
return editMessage; return editMessage;
}, },
getWindowWidth() { getWindowWidth() {
return {width: windowProperties.resizeWidth, height: windowProperties.resizeHeight}; return {
width: windowProperties.resizeWidth,
height: windowProperties.resizeHeight
};
}, },
} }
}; };
</script> </script>
<style lang="scss" scoped>
.show-up-enter-active,
<style scoped> .show-up-leave-active {
transition: 0.3s;
}
.show-up-enter, .show-up-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(10px);
}
.no-channel-selected { .no-channel-selected {
display: flex; display: flex;
@ -659,19 +600,13 @@ export default {
justify-content: center; justify-content: center;
color: white; color: white;
font-size: 20px; font-size: 20px;
} .message {
.no-channel-selected .message { margin-top: 20px;
margin-top: 20px; text-align: center;
text-align: center; }
} .material-icons {
.no-channel-selected .material-icons { font-size: 50px;
font-size: 50px; }
}
.channel-selected {
display: flex;
width: 100%;
align-items: center;
height: 100%;
} }
.hidden { .hidden {
@ -695,11 +630,8 @@ export default {
.message-logs { .message-logs {
overflow: auto; overflow: auto;
flex: 1; flex: 1;
} margin-right: 2px;
.message-logs, position: relative;
.message-logs .scroll {
/* transform: scale(1, -1) translate3d(0,0,0); */
margin-right: 5px;
} }
.loading { .loading {
@ -725,14 +657,16 @@ export default {
user-select: none; user-select: none;
transition: 0.3s; transition: 0.3s;
border-radius: 5px; border-radius: 5px;
&:hover {
background: rgba(0, 0, 0, 0.322);
}
.material-icons {
color: white;
margin: auto;
}
} }
.attachment-button:hover {
background: rgba(0, 0, 0, 0.322);
}
.attachment-button .material-icons {
color: white;
margin: auto;
}
.chat-input-area .info { .chat-input-area .info {
color: rgba(255, 255, 255, 0.466); color: rgba(255, 255, 255, 0.466);
font-size: 12px; font-size: 12px;
@ -740,15 +674,18 @@ export default {
margin-top: 5px; margin-top: 5px;
display: flex; display: flex;
} }
.typing-outer { .typing-outer {
flex: 1; flex: 1;
height: 20px; height: 20px;
} }
.message-count { .message-count {
float: right; float: right;
margin-right: 20px; margin-right: 20px;
margin-top: 3px; margin-top: 3px;
} }
.message-area { .message-area {
display: flex; display: flex;
width: 100%; width: 100%;
@ -772,14 +709,14 @@ export default {
max-height: 30vh; max-height: 30vh;
overflow-y: auto; overflow-y: auto;
border-radius: 5px; border-radius: 5px;
}
.chat-input:hover { &:hover {
background: rgba(0, 0, 0, 0.288); background: rgba(0, 0, 0, 0.288);
} }
.chat-input:focus { &:focus {
background: rgba(0, 0, 0, 0.466); background: rgba(0, 0, 0, 0.466);
}
} }
.send-button { .send-button {
@ -797,20 +734,22 @@ export default {
flex-shrink: 0; flex-shrink: 0;
border-radius: 5px; border-radius: 5px;
user-select: none; user-select: none;
} cursor: pointer;
.send-button .material-icons { .material-icons {
margin: auto; margin: auto;
} }
.send-button:hover { &:hover {
background: rgba(0, 0, 0, 0.514); background: rgba(0, 0, 0, 0.514);
} }
.error-send-button {
background-color: rgba(255, 0, 0, 0.294);
} }
.error-send-button:hover { .error-send-button {
background-color: rgba(255, 0, 0, 0.294); background-color: rgba(255, 0, 0, 0.294);
&:hover {
background-color: rgba(255, 0, 0, 0.294);
}
} }
.emojis-button { .emojis-button {
font-size: 20px; font-size: 20px;
color: white; color: white;
@ -818,7 +757,7 @@ export default {
border: none; border: none;
outline: none; outline: none;
margin-left: 2px; margin-left: 2px;
cursor: pointer;
min-height: 40px; min-height: 40px;
width: 50px; width: 50px;
transition: 0.3s; transition: 0.3s;
@ -826,12 +765,41 @@ export default {
flex-shrink: 0; flex-shrink: 0;
border-radius: 5px; border-radius: 5px;
user-select: none; user-select: none;
.material-icons {
margin: auto;
}
&:hover {
background: rgba(0, 0, 0, 0.514);
}
} }
.emojis-button .material-icons { .back-to-bottom-button {
margin: auto; &:hover {
} background: rgb(23, 124, 255);
.emojis-button:hover { box-shadow: 0px 0px 15px 0px #0000008a;
background: rgba(0, 0, 0, 0.514); }
transition: 0.2s;
background: rgba(23, 124, 255, 0.818);
color: white;
position: absolute;
bottom: 15px;
right: 25px;
border-radius: 10px;
height: 50px;
z-index: 2;
display: flex;
justify-content: center;
flex-shrink: 0;
box-shadow: 0px 0px 7px 0px #0000008a;
align-content: center;
align-items: center;
padding-left: 10px;
user-select: none;
cursor: pointer;
.material-icons {
align-self: center;
flex-shrink: 0;
font-size: 35px;
}
} }
</style> </style>

View file

@ -1,23 +1,15 @@
<template> <template>
<div class="heading"> <div class="heading">
<div <div class="show-menu-button" @click="toggleLeftMenu">
class="show-menu-button"
@click="toggleLeftMenu"
>
<i class="material-icons">menu</i> <i class="material-icons">menu</i>
</div> </div>
<div <div
v-if="type === 1" v-if="type === 1"
class="user-status" class="user-status"
:style="`box-shadow: 0px 0px 14px 3px ${statusColor}; background-color: ${statusColor};`" :style="`box-shadow: 0px 0px 14px 3px ${userStatusColor}; background-color: ${userStatusColor};`"
/> />
<div class="information"> <div class="information">
<div <div :class="{name: true, clickable: !!uniqueID }" @click="openUserInfoPanel">{{ name }}</div>
:class="{name: true, clickable: !!uniqueID }"
@click="openUserInfoPanel"
>
{{ name }}
</div>
</div> </div>
<div <div
v-if="type === 2 && selectedServerID" v-if="type === 2 && selectedServerID"
@ -31,18 +23,14 @@
<script> <script>
import { bus } from "@/main"; import { bus } from "@/main";
import statuses from "@/utils/statuses";
export default { export default {
props: [ props: [
"type", // 0: without online status; 1: with online status; 2: server. "type", // 0: without online status; 1: with online status; 2: server.
"statusColor", // only if type is set to 1;
"name", "name",
"uniqueID" "uniqueID"
], ],
computed: {
selectedServerID() {
return this.$store.getters['servers/selectedServerID'];
}
},
methods: { methods: {
openUserInfoPanel() { openUserInfoPanel() {
if (this.uniqueID) if (this.uniqueID)
@ -54,6 +42,26 @@ export default {
toggleMembersPanel() { toggleMembersPanel() {
bus.$emit("toggleMembersPanel"); bus.$emit("toggleMembersPanel");
} }
},
computed: {
selectedServerID() {
return this.$store.getters["servers/selectedServerID"];
},
userStatusColor() {
const selectedChannel = this.$store.getters.selectedChannelID;
const channel = this.$store.getters.channels[selectedChannel];
const presences = this.$store.getters["members/presences"];
let status = 0;
if (!channel || !channel.recipients || !channel.recipients.length) {
status = 0;
} else if (
this.$store.getters.user.friends[channel.recipients[0].uniqueID]
) {
status = presences[channel.recipients[0].uniqueID] || 0;
}
return statuses[status].color;
}
} }
}; };
</script> </script>
@ -75,7 +83,7 @@ export default {
user-select: none; user-select: none;
display: none; display: none;
} }
.show-members-button{ .show-members-button {
display: inline-block; display: inline-block;
margin-right: 5px; margin-right: 5px;
margin-top: 3px; margin-top: 3px;
@ -127,7 +135,6 @@ export default {
text-decoration: underline; text-decoration: underline;
} }
@media (max-width: 949px) { @media (max-width: 949px) {
.show-members-button { .show-members-button {
display: block; display: block;

View file

@ -167,7 +167,7 @@ export default {
if (!files || files.length === 0 || !files[0].dimensions) if (!files || files.length === 0 || !files[0].dimensions)
return undefined; return undefined;
const messageLog = document.querySelector('.scroll'); const messageLog = document.querySelector('.message-logs');
const w = messageLog.offsetWidth; const w = messageLog.offsetWidth;
const h = messageLog.offsetHeight; const h = messageLog.offsetHeight;
@ -187,6 +187,9 @@ export default {
const imageTag = this.$refs['image']; const imageTag = this.$refs['image'];
imageTag.firstChild.style.width = "100%"
imageTag.firstChild.style.height = "100%"
imageTag.style.width = this.clamp(newDimentions.width, 0, srcWidth) + "px" imageTag.style.width = this.clamp(newDimentions.width, 0, srcWidth) + "px"
imageTag.style.height = this.clamp(newDimentions.height, 0, srcHeight) + "px" imageTag.style.height = this.clamp(newDimentions.height, 0, srcHeight) + "px"
}, },
@ -425,8 +428,10 @@ export default {
flex-direction: column; flex-direction: column;
} }
.image-content img { .image-content img {
width: 100%; width: 100px;
height: 100%; object-fit: contain;
height: 100px;
} }
.image-content:hover img { .image-content:hover img {
filter: brightness(70%); filter: brightness(70%);

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="content"> <div class="content">
<errors-list-template :errors="errors" v-if="errors" /> <errors-list-template :errors="errors" v-if="errors" />
<div class="content-inner"> <div class="content-inner" :key="key">
<div class="top"> <div class="top">
<profile-picture <profile-picture
class="server-avatar" class="server-avatar"
@ -65,7 +65,8 @@ export default {
changed: false, changed: false,
errors: null, errors: null,
avatarDomain: config.domain + "/avatars/", avatarDomain: config.domain + "/avatars/",
update: {} update: {},
key: 1,
}; };
}, },
methods: { methods: {
@ -93,6 +94,7 @@ export default {
this.requestSent = false; this.requestSent = false;
return; return;
} }
this.key = Math.random();
this.update = {}; this.update = {};
this.requestSent = false; this.requestSent = false;
}, },

View file

@ -3,7 +3,7 @@
<errors-list-template :errors="errors" v-if="errors" /> <errors-list-template :errors="errors" v-if="errors" />
<div class="inner-content"> <div class="inner-content">
<div class="left"> <div class="left">
<form> <form :key="key">
<div class="outer-input"> <div class="outer-input">
<div class="title">Username</div> <div class="title">Username</div>
<div class="user-tag"> <div class="user-tag">
@ -18,7 +18,7 @@
</div> </div>
<div class="outer-input"> <div class="outer-input">
<div class="title">Current Password</div> <div class="title">Current Password</div>
<input type="password" autocomplete="new-password" @input="inputEvent('password', $event)" /> <input type="password" autocomplete="new-password" ref="passwordInput" @input="inputEvent('password', $event)" />
</div> </div>
<div class="link" v-if="!resetPassword" @click="resetPassword = true">Reset Password</div> <div class="link" v-if="!resetPassword" @click="resetPassword = true">Reset Password</div>
<div class="outer-input" v-if="resetPassword"> <div class="outer-input" v-if="resetPassword">
@ -68,7 +68,8 @@ export default {
requestSent: false, requestSent: false,
changed: false, changed: false,
resetPassword: false, resetPassword: false,
update: {} update: {},
key: 0,
}; };
}, },
methods: { methods: {
@ -121,8 +122,10 @@ export default {
} }
this.errors = data.errors; this.errors = data.errors;
} else { } else {
this.$refs['passwordInput'].value = "";
this.resetPassword = false this.resetPassword = false
this.update = {}; this.$set(this, 'update', {})
this.key = Math.random();
} }
this.requestSent = false; this.requestSent = false;
} }

View file

@ -36,7 +36,7 @@ export default {
return undefined; return undefined;
const embed = this.$refs['embed']; const embed = this.$refs['embed'];
const messageLog = document.querySelector('.scroll'); const messageLog = document.querySelector('.message-logs');
const w = messageLog.offsetWidth; const w = messageLog.offsetWidth;
const h = messageLog.offsetHeight; const h = messageLog.offsetHeight;
@ -77,7 +77,7 @@ export default {
imageURL() { imageURL() {
const image = this.embed.image; const image = this.embed.image;
if (!image) return undefined; if (!image) return undefined;
if (typeof image === 'string') return image; if (typeof image === 'string') return undefined;
if (image.url) return image.url; if (image.url) return image.url;
return undefined; return undefined;
} }

View file

@ -5,12 +5,16 @@ import channelService from "@/services/channelService";
import messagesService from "@/services/messagesService"; import messagesService from "@/services/messagesService";
const state = { const state = {
messages: {} messages: {},
scrollPosition: {}
}; };
const getters = { const getters = {
messages(state) { messages(state) {
return state.messages; return state.messages;
},
scrollPosition(state) {
return state.scrollPosition
} }
}; };
@ -103,6 +107,14 @@ const actions = {
return true; return true;
} }
}) })
},
changeScrollPosition(context, {channelID, pos}) {
context.commit('changeScrollPosition', {channelID, pos})
},
unloadTopMessages(context, {channelID}) {
const messages = context.state.messages[channelID];
const unloaded = messages.slice(Math.max(messages.length - 50, 0))
context.commit('messages', {channelID, messages: unloaded})
} }
}; };
@ -127,6 +139,9 @@ async function getMessages(context, channelID, isServerChannel) {
const mutations = { const mutations = {
changeScrollPosition(state, {channelID, pos}) {
Vue.set(state.scrollPosition, channelID, pos);
},
deleteMessage(state, {channelID, index}) { deleteMessage(state, {channelID, index}) {
Vue.delete(state.messages[channelID], index) Vue.delete(state.messages[channelID], index)
}, },

View file

@ -18,11 +18,12 @@ const config = [
version: 6.1, version: 6.1,
title: "Download button, bug fixes", title: "Download button, bug fixes",
shortTitle: "", shortTitle: "",
date: "13/08/2019", date: "16/08/2019",
headColor: "rgba(205, 80, 87, 0.77)", headColor: "rgba(25, 300, 87, 0.77)",
new: [ new: [
"Added download button.", "Added download button.",
"Scroll up to load more messages.",
"Scroll to bottom button has been added."
], ],
fix: [ fix: [
"Fixed emoji size (2emx2em).", "Fixed emoji size (2emx2em).",