remove search from private-messages (#12044)

* remove search from private-messages + paged conversations + fixes

* remove autoSize call

* add conversation border at the top

* border-bottom under `Disable Private Messages` - revert border-bottom on conversation items
This commit is contained in:
negue
2020-04-20 16:32:54 +02:00
committed by GitHub
parent 0e36c1aa0f
commit cbcc7cd479
6 changed files with 135 additions and 160 deletions

View File

@@ -58,7 +58,6 @@
"vue-mugen-scroll": "^0.2.6",
"vue-router": "^3.1.6",
"vue-template-compiler": "^2.6.11",
"vue2-perfect-scrollbar": "^1.4.0",
"vuedraggable": "^2.23.1",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^4.42.1"

View File

@@ -1,21 +1,23 @@
<template>
<perfect-scrollbar
<div
ref="container"
class="container-fluid"
:class="{'disable-perfect-scroll': disablePerfectScroll}"
:options="psOptions"
>
<div class="row loadmore">
<div v-if="canLoadMore && !isLoading">
<div class="loadmore-divider-holder">
<div class="loadmore-divider"></div>
</div>
<button
class="btn btn-secondary"
@click="triggerLoad()"
>
{{ $t('loadEarlierMessages') }}
</button>
<div class="loadmore-divider-holder">
<div class="loadmore-divider"></div>
</div>
</div>
<h2
v-show="isLoading"
class="col-12 loading"
@@ -30,11 +32,10 @@
:class="{ 'margin-right': user._id !== msg.uuid}"
>
<div
v-if="user._id !== msg.uuid"
class="d-flex flex-grow-1"
>
<avatar
v-if="conversationOpponentUser"
v-if="user._id !== msg.uuid && conversationOpponentUser"
class="avatar-left"
:member="conversationOpponentUser"
:avatar-only="true"
@@ -42,20 +43,10 @@
:hide-class-badge="true"
@click.native="showMemberModal(msg.uuid)"
/>
<div class="card card-right">
<message-card
:msg="msg"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@message-card-mounted="itemWasMounted"
/>
</div>
</div>
<div
v-if="user._id === msg.uuid"
class="d-flex flex-grow-1"
class="card"
:class="{'card-right': user._id !== msg.uuid, 'card-left': user._id === msg.uuid}"
>
<div class="card card-left">
<message-card
:msg="msg"
@message-removed="messageRemoved"
@@ -64,7 +55,7 @@
/>
</div>
<avatar
v-if="user"
v-if="user && user._id === msg.uuid"
class="avatar-right"
:member="user"
:avatar-only="true"
@@ -74,16 +65,11 @@
/>
</div>
</div>
</perfect-scrollbar>
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css';
.disable-perfect-scroll {
overflow-y: inherit !important;
}
.avatar {
width: 170px;
@@ -162,6 +148,8 @@
.loadmore {
justify-content: center;
margin-right: 12px;
margin-top: 12px;
margin-bottom: 24px;
> div {
display: flex;
@@ -171,15 +159,11 @@
button {
text-align: center;
color: $gray-50;
margin-top: 12px;
margin-bottom: 24px;
}
}
}
.loadmore-divider {
height: 1px;
background-color: $gray-500;
.loadmore-divider-holder {
flex: 1;
margin-left: 24px;
margin-right: 24px;
@@ -189,6 +173,13 @@
}
}
.loadmore-divider {
height: 1px;
border-top: 1px $gray-500 solid;
width: 100%;
}
.loading {
padding-left: 1.5rem;
margin-bottom: 1rem;
@@ -200,7 +191,6 @@
<script>
import moment from 'moment';
import debounce from 'lodash/debounce';
import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
import { mapState } from '@/libs/store';
import Avatar from '../avatar';
@@ -210,7 +200,6 @@ export default {
components: {
Avatar,
messageCard,
PerfectScrollbar,
},
props: {
chat: {},
@@ -248,15 +237,10 @@ export default {
messages () {
return this.chat;
},
psOptions () {
return {
suppressScrollX: true,
};
},
},
methods: {
async triggerLoad () {
const container = this.$refs.container.$el;
const { container } = this.$refs;
// get current offset
this.lastOffset = container.scrollTop - (container.scrollHeight - container.clientHeight);
@@ -274,8 +258,9 @@ export default {
}
},
displayDivider (message) {
if (this.currentDayDividerDisplay !== moment(message.timestamp).day()) {
this.currentDayDividerDisplay = moment(message.timestamp).day();
const day = moment(message.timestamp).day();
if (this.currentDayDividerDisplay !== day) {
this.currentDayDividerDisplay = day;
return true;
}
@@ -288,7 +273,7 @@ export default {
if (this.handleScrollBack) {
this.handleScrollBack = false;
const container = this.$refs.container.$el;
const { container } = this.$refs;
const offset = container.scrollHeight - container.clientHeight;
const newOffset = offset + this.lastOffset;

View File

@@ -19,7 +19,10 @@
<!-- placeholder -->
</div>
</div>
<div class="d-flex selected-conversion">
<div
v-if="selectedConversation && selectedConversation.key"
class="d-flex selected-conversion"
>
<router-link
:to="{'name': 'userProfile', 'params': {'userId': selectedConversation.key}}"
>
@@ -49,13 +52,6 @@
@change="toggleOpt()"
/>
</div>
<div class="search-section">
<b-form-input
v-model="search"
class="input-search"
:placeholder="$t('search')"
/>
</div>
<div
v-if="filtersConversations.length > 0"
class="conversations"
@@ -75,6 +71,13 @@
@click="selectConversation(conversation.key)"
/>
</div>
<button
class="btn btn-secondary"
v-if="canLoadMoreConversations"
@click="loadConversations()"
>
{{ $t('loadMore') }}
</button>
</div>
<div class="messages-column d-flex flex-column align-items-center">
<div
@@ -142,7 +145,7 @@
<h4>{{ disabledTexts.title }}</h4>
<p>{{ disabledTexts.description }}</p>
</div>
<div>
<div class="full-width">
<div
class="new-message-row d-flex align-items-center"
>
@@ -152,10 +155,8 @@
class="flex-fill"
:placeholder="$t('needsTextPlaceholder')"
:maxlength="MAX_MESSAGE_LENGTH"
:class="{'has-content': newMessage !== '', 'disabled': newMessageDisabled}"
:style="{'--textarea-auto-height': textareaAutoHeight}"
:class="{'has-content': newMessage.trim() !== '', 'disabled': newMessageDisabled}"
@keyup.ctrl.enter="sendPrivateMessage()"
@input="autoSize()"
>
</textarea>
</div>
@@ -293,20 +294,6 @@
margin-left: 12px;
}
.input-search {
background-repeat: no-repeat;
background-position: center left 16px;
background-size: 16px 16px;
background-image: url(~@/assets/svg/for-css/search_gray.svg) !important;
padding-left: 40px;
height: 40px;
}
.input-search::placeholder {
color: $gray-200 !important;
}
.selected-conversion {
justify-content: center;
align-items: center;
@@ -321,6 +308,8 @@
height: 44px;
background-color: $gray-600;
padding: 0.75rem 1.5rem;
border-bottom: 1px solid $gray-500;
}
@@ -417,6 +406,10 @@
}
}
.full-width {
width: 100%;
}
.new-message-row {
width: 100%;
padding-left: 1.5rem;
@@ -435,8 +428,12 @@
background-color: $gray-500;
}
min-height: var(--textarea-auto-height, 40px);
&.has-content {
--textarea-auto-height: 80px
}
max-height: var(--textarea-auto-height, 40px);
min-height: var(--textarea-auto-height, 40px);
}
}
@@ -502,11 +499,6 @@
padding: 0;
border-bottom-left-radius: 8px;
.search-section {
padding: 1rem 1.5rem;
border-bottom: 1px solid $gray-500;
}
@media only screen and (max-width: 768px) {
width: 280px;
}
@@ -555,7 +547,6 @@
<script>
import Vue from 'vue';
import moment from 'moment';
import filter from 'lodash/filter';
import groupBy from 'lodash/groupBy';
import orderBy from 'lodash/orderBy';
import habiticaMarkdown from 'habitica-markdown';
@@ -574,7 +565,9 @@ import faceAvatar from '@/components/faceAvatar';
import Avatar from '@/components/avatar';
import { EVENTS } from '@/libs/events';
const MAX_TEXTAREA_HEIGHT = 80;
// extract to a shared path
const CONVERSATIONS_PER_PAGE = 10;
const PM_PER_PAGE = 10;
export default {
components: {
@@ -597,19 +590,21 @@ export default {
messageIcon,
mail,
}),
displayCreate: true,
selectedConversation: {},
search: '',
newMessage: '',
showPopover: false,
messages: [],
messagesByConversation: {}, // cache {uuid: []}
loadedConversations: [],
loaded: false,
messagesLoading: false,
showPopover: false,
/* Conversation-specific data */
initiatedConversation: null,
updateConversationsCounter: 0,
textareaAutoHeight: undefined,
selectedConversation: {},
conversationPage: 0,
canLoadMoreConversations: false,
loadedConversations: [],
messagesByConversation: {}, // cache {uuid: []}
newMessage: '',
messages: [],
messagesLoading: false,
MAX_MESSAGE_LENGTH: MAX_MESSAGE_LENGTH.toString(),
};
},
@@ -724,13 +719,9 @@ export default {
// Vue-subscribe to changes
const subscribeToUpdate = this.updateConversationsCounter > -1;
const filtered = subscribeToUpdate && !this.search
? this.conversations
const filtered = subscribeToUpdate && this.conversations;
/* eslint-disable max-len */
: filter(this.conversations, conversation => conversation.name.toLowerCase().indexOf(this.search.toLowerCase()) !== -1);
const ordered = orderBy(filtered, [o => moment(o.date).toDate()], ['desc']);
const ordered = orderBy(filtered, [o => o.date], ['desc']);
return ordered;
},
@@ -810,14 +801,25 @@ export default {
async reload () {
this.loaded = false;
const conversationRes = await axios.get('/api/v4/inbox/conversations');
this.loadedConversations = conversationRes.data.data;
this.loadedConversations = [];
this.selectedConversation = {};
await this.loadConversations();
await this.$store.dispatch('user:markPrivMessagesRead');
this.loaded = true;
},
async loadConversations () {
const query = ['/api/v4/inbox/conversations'];
query.push(`?page=${this.conversationPage}`);
this.conversationPage += 1;
const conversationRes = await axios.get(query.join(''));
const loadedConversations = conversationRes.data.data;
this.canLoadMoreConversations = loadedConversations.length === CONVERSATIONS_PER_PAGE;
this.loadedConversations.push(...loadedConversations);
},
messageRemoved (message) {
const messages = this.messagesByConversation[this.selectedConversation.key];
@@ -833,9 +835,6 @@ export default {
};
}
},
toggleClick () {
this.displayCreate = !this.displayCreate;
},
toggleOpt () {
this.$store.dispatch('user:togglePrivateMessagesOpt');
},
@@ -848,11 +847,7 @@ export default {
await this.loadMessages();
}
Vue.nextTick(() => {
if (!this.$refs.chatscroll) return;
const chatscroll = this.$refs.chatscroll.$el;
chatscroll.scrollTop = chatscroll.scrollHeight;
});
this.scrollToBottom();
},
sendPrivateMessage () {
if (!this.newMessage) return;
@@ -890,11 +885,7 @@ export default {
this.selectedConversation.lastMessageText = this.newMessage;
this.selectedConversation.date = new Date();
Vue.nextTick(() => {
if (!this.$refs.chatscroll) return;
const chatscroll = this.$refs.chatscroll.$el;
chatscroll.scrollTop = chatscroll.scrollHeight;
});
this.scrollToBottom();
this.$store.dispatch('members:sendPrivateMessage', {
toUserId: this.selectedConversation.key,
@@ -908,7 +899,13 @@ export default {
});
this.newMessage = '';
this.autoSize();
},
scrollToBottom () {
Vue.nextTick(() => {
if (!this.$refs.chatscroll) return;
const chatscroll = this.$refs.chatscroll.$el;
chatscroll.scrollTop = chatscroll.scrollHeight;
});
},
removeTags (html) {
const tmp = document.createElement('DIV');
@@ -943,31 +940,17 @@ export default {
const res = await axios.get(requestUrl);
const loadedMessages = res.data.data;
/* eslint-disable max-len */
this.messagesByConversation[conversationKey] = this.messagesByConversation[conversationKey] || [];
/* eslint-disable max-len */
const loadedMessagesToAdd = loadedMessages
.filter(m => this.messagesByConversation[conversationKey].findIndex(mI => mI.id === m.id) === -1);
this.messagesByConversation[conversationKey].push(...loadedMessagesToAdd);
// only show the load more Button if the max count was returned
this.selectedConversation.canLoadMore = loadedMessages.length === 10;
this.selectedConversation.canLoadMore = loadedMessages.length === PM_PER_PAGE;
this.messagesLoading = false;
},
autoSize () {
const { textarea } = this.$refs;
// weird issue: browser only removing the scrollHeight / clientHeight per key event - 56-54-52
let { scrollHeight } = textarea;
if (this.newMessage === '') {
// reset height when the message was removed again
scrollHeight = 40;
}
if (scrollHeight > MAX_TEXTAREA_HEIGHT) {
scrollHeight = MAX_TEXTAREA_HEIGHT;
}
this.textareaAutoHeight = `${scrollHeight}px`;
},
selectFirstConversation () {
if (this.loadedConversations.length > 0) {
this.selectConversation(this.loadedConversations[0].uuid, true);

View File

@@ -81,6 +81,9 @@ api.clearMessages = {
* This is for API v4 which must not be used in third-party tools.
* For API v3, use "Get inbox messages for a user".
*
* @apiParam (Query) {Number} page (optional) Load the conversations of the selected Page
* - 10 conversations per Page
*
* @apiSuccess {Array} data An array of inbox conversations
*
* @apiSuccessExample {json} Success-Response:
@@ -104,8 +107,9 @@ api.conversations = {
url: '/inbox/conversations',
async handler (req, res) {
const { user } = res.locals;
const { page } = req.query;
const result = await listConversations(user);
const result = await listConversations(user, page);
res.respond(200, result);
},
@@ -129,8 +133,7 @@ api.getInboxMessages = {
middlewares: [authWithHeaders()],
async handler (req, res) {
const { user } = res.locals;
const { page } = req.query;
const { conversation } = req.query;
const { page, conversation } = req.query;
const userInbox = await getUserInbox(user, {
page, conversation, mapProps: true,

View File

@@ -41,10 +41,10 @@ async function usersMapByConversations (users) {
return usersMap;
}
export async function listConversations (owner) {
// group messages by user owned by logged-in user
const query = Inbox
.aggregate([
const CONVERSATION_PER_PAGE = 10;
export async function listConversations (owner, page) {
const aggregateQuery = [
{
$match: {
ownerId: owner._id,
@@ -61,7 +61,16 @@ export async function listConversations (owner) {
},
},
{ $sort: { timestamp: -1 } }, // sort by latest message
]);
];
if (page >= 0) {
aggregateQuery.push({ $skip: page * CONVERSATION_PER_PAGE });
aggregateQuery.push({ $limit: CONVERSATION_PER_PAGE });
}
// group messages by user owned by logged-in user
const query = Inbox
.aggregate(aggregateQuery);
const conversationsList = await query.exec();

View File

@@ -2,8 +2,6 @@ import { mapInboxMessage, inboxModel as Inbox } from '../../models/message';
import { getUserInfo, sendTxn as sendTxnEmail } from '../email'; // eslint-disable-line import/no-cycle
import { sendNotification as sendPushNotification } from '../pushNotifications'; // eslint-disable-line import/no-cycle
const PM_PER_PAGE = 10;
export async function sentMessage (sender, receiver, message, translate) {
const messageSent = await sender.sendMessage(receiver, { receiverMsg: message });
const senderName = getUserInfo(sender, ['name']).name;
@@ -33,17 +31,15 @@ export async function sentMessage (sender, receiver, message, translate) {
return messageSent;
}
const PM_PER_PAGE = 10;
export async function getUserInbox (user, options = {
const getUserInboxDefaultOptions = {
asArray: true, page: 0, conversation: null, mapProps: false,
}) {
if (typeof options.asArray === 'undefined') {
options.asArray = true;
}
};
if (typeof options.mapProps === 'undefined') {
options.mapProps = false;
}
export async function getUserInbox (user, optionParams = getUserInboxDefaultOptions) {
// if not all properties are passed, fill the default values
const options = { ...getUserInboxDefaultOptions, ...optionParams };
const findObj = { ownerId: user._id };
@@ -57,8 +53,8 @@ export async function getUserInbox (user, options = {
if (typeof options.page !== 'undefined') {
query = query
.limit(PM_PER_PAGE)
.skip(PM_PER_PAGE * Number(options.page));
.skip(PM_PER_PAGE * Number(options.page))
.limit(PM_PER_PAGE);
}
const messages = (await query.exec()).map(msg => {