mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
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:
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user