feat(messages): load message replies data

This commit is contained in:
OldHawk
2017-06-18 18:02:15 +08:00
parent ba46261f6a
commit 23b229af11
9 changed files with 239 additions and 38 deletions

View File

@@ -261,6 +261,7 @@
EDIT_COMMENT: 'Edit Comment',
REPLY_COMMENT: 'Reply Comment',
SUBMIT_COMMENT: 'Submit Comment',
SUBMIT_REPLY: 'Submit Reply',
SUBMIT_CANCEL: 'Cancel',
MARKDOWN_LINK: 'Styling with Markdown is supported',
COMMENT_REPLY_BUTTON: '@ & reply',
@@ -465,6 +466,7 @@
BUTTON_SEARCH: 'Search',
BUTTON_DELETE: ' Delete ',
BUTTON_CLOSE: ' Close ',
BUTTON_REPLY: ' Reply ',
INPUT_EMAIL: 'email',
SEND_INVITE_SUCCESSFULLY: 'Send invitation successfully',
SEND_INVITE_ERROR: 'Send invitation failed',
@@ -484,6 +486,7 @@
TT_REQUIRED: 'Please enter message title',
CT_REQUIRED: 'Please enter message content',
LIST_TITLE: 'Title',
LIST_REPLIES: 'Replies',
LIST_TYPE: 'Type',
LIST_SENDAT: 'SendedAt',
LIST_SELECT: 'Select',

View File

@@ -261,6 +261,7 @@
EDIT_COMMENT: '编辑评论',
REPLY_COMMENT: '回复评论',
SUBMIT_COMMENT: '提交评论',
SUBMIT_REPLY: '提交回复',
SUBMIT_CANCEL: '取消',
MARKDOWN_LINK: '排版支持 Markdown 全部格式',
COMMENT_REPLY_BUTTON: '@ 楼主并回复',
@@ -465,6 +466,7 @@
BUTTON_SEARCH: '搜索',
BUTTON_DELETE: ' 删除 ',
BUTTON_CLOSE: ' 关闭 ',
BUTTON_REPLY: ' 回复 ',
INPUT_EMAIL: '邮箱地址',
SEND_INVITE_SUCCESSFULLY: '发送邀请成功',
SEND_INVITE_ERROR: '发送邀请失败',
@@ -484,6 +486,7 @@
TT_REQUIRED: '请输入消息标题',
CT_REQUIRED: '请输入消息内容',
LIST_TITLE: '标题',
LIST_REPLIES: '回复',
LIST_TYPE: '类型',
LIST_SENDAT: '发送时间',
LIST_SELECT: '选择',

View File

@@ -6,10 +6,10 @@
.controller('MessageController', MessageController);
MessageController.$inject = ['$scope', '$state', '$translate', '$timeout', 'Authentication', '$filter', 'NotifycationService', '$stateParams', 'MessagesService',
'MeanTorrentConfig', 'ModalConfirmService'];
'MeanTorrentConfig', 'ModalConfirmService', 'marked'];
function MessageController($scope, $state, $translate, $timeout, Authentication, $filter, NotifycationService, $stateParams, MessagesService,
MeanTorrentConfig, ModalConfirmService) {
MeanTorrentConfig, ModalConfirmService, marked) {
var vm = this;
vm.messageConfig = MeanTorrentConfig.meanTorrentConfig.messages;
vm.user = Authentication.user;
@@ -23,7 +23,9 @@
$state.go('authentication.signin');
}
document.getElementById('popupSlide').addEventListener('transitionend', onTransitionEnd, false);
if (document.getElementById('popupSlide')) {
document.getElementById('popupSlide').addEventListener('transitionend', onTransitionEnd, false);
}
/**
* checkSendTo
@@ -108,6 +110,8 @@
* pageChanged
*/
vm.pageChanged = function () {
vm.selectedMessage = undefined;
vm.hideMessage();
vm.figureOutItemsToDisplay();
};
@@ -161,8 +165,6 @@
* @param event
*/
function onTransitionEnd(event) {
console.log('end');
var e = $('.popup-overlay');
if (vm.selectedMessage) {
if (!e.hasClass('popup-visible')) {
@@ -176,8 +178,8 @@
* @param msg
*/
vm.showMessage = function (msg) {
console.log(msg);
vm.selectedMessage = msg;
var e = $('.popup-overlay');
if (e.hasClass('popup-visible')) {
e.removeClass('popup-visible');
@@ -197,5 +199,92 @@
e.removeClass('popup-visible');
}
};
/**
* getMessageClass
* @param m
* @returns {*}
*/
vm.getMessageClass = function (m) {
if (m) {
if (m.from_user._id === vm.user._id) {
if (m.from_status === 0) {
return 'unread';
}
}
if (m.to_user._id === vm.user._id) {
if (m.to_status === 0) {
return 'unread';
}
}
}
return 'read';
};
/**
* getContentMarked
* @param m
* @returns {*}
*/
vm.getContentMarked = function (m) {
if (m) {
return marked(m.content, {sanitize: true});
}
};
/**
* replyMessage
*/
vm.replyMessage = function (m) {
var rmsg = new MessagesService({
_messageId: m._id,
title: '',
content: vm.replyContent,
type: 'user',
from_user: vm.user._id,
to_user: fromIsMe(m) ? m.to_user._id : m.from_user._id
});
rmsg.$save(function (response) {
successCallback(response);
}, function (errorResponse) {
errorCallback(errorResponse);
});
function successCallback(res) {
console.log(res);
vm.selectedMessage = res;
vm.messages.splice(vm.messages.indexOf(m), 0, res);
vm.messages.splice(vm.messages.indexOf(m), 1);
vm.figureOutItemsToDisplay();
vm.replyContent = undefined;
NotifycationService.showSuccessNotify('MESSAGE_SEND_SUCCESSFULLY');
}
function errorCallback(res) {
NotifycationService.showErrorNotify(res.data.message, 'MESSAGE_SEND_FAILED');
}
};
/**
* fromIsMe
* @param m
* @returns {boolean}
*/
function fromIsMe(m) {
return (m.from_user._id === vm.user._id) ? true : false;
}
/**
* toIsMe
* @param m
* @returns {boolean}
*/
function toIsMe(m) {
return (m.to_user._id === vm.user._id) ? true : false;
}
}
}());

View File

@@ -36,6 +36,13 @@
height: 38px;
width: 38px;
}
.reply-avatar {
float: left;
margin-right: 10px;
border-radius: 3px;
height: 30px;
width: 30px;
}
.message-title {
font-weight: bold;
font-size: 14px;
@@ -45,6 +52,25 @@
color: #999;
margin: 0 0;
}
.read {
color: #999;
}
.unread {
color: #333;
}
.title-info {
margin-left: 48px;
}
.reply-info {
margin-left: 40px;
.message-info {
margin-top: -10px;
}
}
.message-reply {
margin-left: 50px;
margin-top: 10px;
}
}
.popup-overlay {
@@ -55,16 +81,16 @@
bottom: 0;
right: 0;
overflow: auto;
z-index: 10000;
z-index: 999;
transition: all 0.3s ease-out;
/* csslint ignore:start */
transform: translateX(101%) translateY(0);
/* csslint ignore:end */
@media (min-width: @screen-sm-min) {
min-width: 450px;
width: 450px;
}
@media (max-width: @screen-xs-max) {
min-width: ~"calc(100% - 50px)";
min-width: ~"calc(100% - 150px)";
}
}
@@ -75,20 +101,28 @@
}
.bottom-control {
background-color: #eee;
background-color: #fdfdfd;
border-top: solid 1px #e6e6e6;
width: 100%;
height: 50px;
height: 150px;
position: absolute;
left: 0;
bottom: 0;
padding: 7px 10px;
padding: 8px 10px;
}
.message-popup {
background-color: #fafafa;
background-color: #fdfdfd;
width: 100%;
height: ~"calc(100% - 50px)";
overflow: auto;
padding: 10px;
padding: 15px;
}
.reply-textarea {
resize: none;
width: 100%;
min-height: 90px;
margin-bottom: 8px;
}

View File

@@ -9,7 +9,7 @@
function MessagesService($resource) {
return $resource('/api/messages/:messageId', {
messageId: '@_id'
messageId: '@_messageId'
}, {
update: {
method: 'PUT'

View File

@@ -33,28 +33,32 @@
<thead>
<tr>
<th>{{ 'MESSAGES_FIELD.LIST_TITLE' | translate}}</th>
<th class="text-center">{{ 'MESSAGES_FIELD.LIST_REPLIES' | translate}}</th>
<th class="text-center">{{ 'MESSAGES_FIELD.LIST_TYPE' | translate}}</th>
<th class="text-center">{{ 'MESSAGES_FIELD.LIST_SENDAT' | translate}}</th>
<th class="text-center">{{ 'MESSAGES_FIELD.LIST_SELECT' | translate}}</th>
</tr>
</thead>
<tbody>
<tr class="message-item" ng-repeat="m in vm.pagedItems">
<td ng-click="vm.showMessage(m);">
<tr class="message-item" ng-repeat="m in vm.pagedItems" ng-click="vm.showMessage(m);">
<td>
<img class="message-avatar" ng-src="/{{m.from_user.profileImageURL}}">
<span class="message-title" ng-bind="m.title"></span>
<span class="message-title" ng-class="vm.getMessageClass(m);" ng-bind="m.title"></span>
<p class="message-info">
{{m.from_user.displayName}} {{'MESSAGES_FIELD.INFO_SEND_TO' | translate}} {{m.to_user.displayName}} {{'MESSAGES_FIELD.INFO_SEND_AT' | translate}} {{m.createdat | date: 'yyyy-MM-dd HH:mm:ss' }}
</p>
</td>
<td class="td-v-middle text-center">
{{'MESSAGE_TYPE_' + m.type.toUpperCase() | translate}}
<span ng-class="vm.getMessageClass(m);">{{m._replies.length}}</span>
</td>
<td class="td-v-middle text-center">
{{m.createdat | life}}
<span ng-class="vm.getMessageClass(m);">{{'MESSAGE_TYPE_' + m.type.toUpperCase() | translate}}</span>
</td>
<td class="td-v-middle text-center">
<span ng-class="vm.getMessageClass(m);">{{m.createdat | life}}</span>
</td>
<td class="td-v-middle text-center" ng-click="$event.stopPropagation();">
<input type="checkbox" class="tcheckbox" ng-model="vm.selected[m._id]"
id="checkbox_{{m._id}}">
</label>
@@ -79,12 +83,42 @@
</div>
<div id="popupSlide" class="popup-overlay">
<div class="message-popup">
<div class="message-popup message-item">
<div>
<img class="message-avatar" ng-src="/{{vm.selectedMessage.from_user.profileImageURL}}">
<div class="title-info">
<span class="message-title" ng-class="vm.getMessageClass(vm.selectedMessage);" ng-bind="vm.selectedMessage.title"></span>
<p class="message-info">
{{vm.selectedMessage.from_user.displayName}} {{'MESSAGES_FIELD.INFO_SEND_TO' | translate}} {{vm.selectedMessage.to_user.displayName}} {{'MESSAGES_FIELD.INFO_SEND_AT' | translate}} {{vm.selectedMessage.createdat | date: 'yyyy-MM-dd HH:mm:ss' }}
</p>
</div>
</div>
<li class="status-divider"></li>
<div class="message-content" ng-bind-html="vm.getContentMarked(vm.selectedMessage);"></div>
<li class="status-divider" ng-show="vm.selectedMessage._replies.length>0"></li>
<div class="message-reply" ng-repeat="r in vm.selectedMessage._replies">
<img class="reply-avatar" ng-src="/{{r.from_user.profileImageURL}}">
<div class="reply-info">
<div class="message-content" ng-bind-html="vm.getContentMarked(r);"></div>
<p class="message-info">
{{r.from_user.displayName}} | {{r.createdat | date: 'yyyy-MM-dd HH:mm:ss' }}
</p>
</div>
</div>
</div>
<div class="bottom-control">
<textarea class="form-control reply-textarea" id="reply-content" name="reply-content"
ng-model="vm.replyContent" autofocus></textarea>
<button class="btn btn-default" ng-click="vm.hideMessage();">{{ 'BUTTON_CLOSE' | translate }}</button>
<button class="btn btn-success pull-right">{{ 'BUTTON_DELETE' | translate }}</button>
<button class="btn btn-default">{{ 'BUTTON_DELETE' | translate }}</button>
<button class="btn btn-success pull-right" ng-disabled="!vm.replyContent" ng-click="vm.replyMessage(vm.selectedMessage);">{{ 'BUTTON_REPLY' | translate }}</button>
</div>
</div>
</section>

View File

@@ -52,8 +52,18 @@ exports.list = function (req, res) {
]
})
.sort('-updatedat, -createdat')
.populate('from_user')
.populate('to_user')
.populate('from_user', 'displayName profileImageURL uploaded downloaded')
.populate('to_user', 'displayName profileImageURL uploaded downloaded')
.populate({
path: '_replies.from_user',
select: 'displayName profileImageURL uploaded downloaded',
model: 'User'
})
.populate({
path: '_replies.to_user',
select: 'displayName profileImageURL uploaded downloaded',
model: 'User'
})
.exec(function (err, messages) {
if (err) {
return res.status(422).send({
@@ -107,16 +117,38 @@ exports.delete = function (req, res) {
* @param res
*/
exports.createReply = function (req, res) {
var reply = new Message(req.body);
};
var message = req.message;
message._replies.push(reply);
message.updatedat = Date.now();
/**
* deleteReply
* @param req
* @param res
*/
exports.deleteReply = function (req, res) {
if (message.from_user._id.equals(req.user._id)) {
message.to_status = 0;
} else {
message.from_status = 0;
}
message.save(function (err) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
Message.populate(message._replies, {
path: 'from_user to_user',
select: 'displayName profileImageURL uploaded downloaded'
}, function (err, t) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(message);
}
});
}
});
};
/**
@@ -131,8 +163,18 @@ exports.messageByID = function (req, res, next, id) {
}
Message.findById(id)
.populate('from_user')
.populate('to_user')
.populate('from_user', 'displayName profileImageURL uploaded downloaded')
.populate('to_user', 'displayName profileImageURL uploaded downloaded')
.populate({
path: '_replies.from_user',
select: 'displayName profileImageURL uploaded downloaded',
model: 'User'
})
.populate({
path: '_replies.to_user',
select: 'displayName profileImageURL uploaded downloaded',
model: 'User'
})
.exec(function (err, message) {
if (err) {
return next(err);

View File

@@ -18,8 +18,7 @@ exports.invokeRolesPolicies = function () {
roles: ['admin', 'oper', 'user'],
allows: [
{resources: '/api/messages', permissions: '*'},
{resources: '/api/messages/:messageId', permissions: '*'},
{resources: '/api/messages/:messageId/:replyId', permissions: '*'}
{resources: '/api/messages/:messageId', permissions: '*'}
]
}
]

View File

@@ -16,8 +16,5 @@ module.exports = function (app) {
.delete(messages.delete)
.post(messages.createReply);
app.route('/api/messages/:messageId/:replyId').all(messagesPolicy.isAllowed)
.delete(messages.deleteReply);
app.param('messageId', messages.messageByID);
};