Merge pull request #5087 from negue/copyMessageTodo

copy chat messages as todo
This commit is contained in:
negue
2015-05-07 22:21:42 +02:00
9 changed files with 217 additions and 4 deletions

View File

@@ -96,5 +96,9 @@
"abuseReported": "Thank you for reporting this violation. The moderators have been notified.", "abuseReported": "Thank you for reporting this violation. The moderators have been notified.",
"abuseAlreadyReported": "You have already reported this message.", "abuseAlreadyReported": "You have already reported this message.",
"needsText": "Please type a message.", "needsText": "Please type a message.",
"needsTextPlaceholder": "Type your message here." "needsTextPlaceholder": "Type your message here.",
"copyMessageAsToDo": "Copy message as To-Do",
"messageAddedAsToDo": "Message copied as To-Do.",
"messageWroteIn": "<%= user %> wrote in <%= group %>",
"msgPreviewHeading": "Message Preview"
} }

View File

@@ -68,6 +68,76 @@ describe('Groups Controller', function() {
}); });
}); });
describe("Chat Controller", function() {
var scope, ctrl, user, $rootScope, $controller;
beforeEach(function() {
module(function($provide) {
$provide.value('User', {});
});
inject(function(_$rootScope_, _$controller_){
user = specHelper.newUser();
user._id = "unique-user-id";
$rootScope = _$rootScope_;
scope = _$rootScope_.$new();
$controller = _$controller_;
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: {user: user}});
ctrl = $controller('ChatCtrl', {$scope: scope});
});
});
describe('copyToDo', function() {
it('when copying a user message it opens modal with information from message', function() {
scope.group = {
name: "Princess Bride"
};
var modalSpy = sinon.spy($rootScope, "openModal");
var message = {
uuid: 'the-dread-pirate-roberts',
user: 'Wesley',
text: 'As you wish'
};
scope.copyToDo(message);
modalSpy.should.have.been.calledOnce;
modalSpy.should.have.been.calledWith('copyChatToDo', sinon.match(function(callArgToMatch){
return callArgToMatch.controller == 'CopyMessageModalCtrl'
&& callArgToMatch.scope.text == message.text
}));
});
it('when copying a system message it opens modal with information from message', function() {
scope.group = {
name: "Princess Bride"
};
var modalSpy = sinon.spy($rootScope, "openModal");
var message = {
uuid: 'system',
text: 'Wesley attacked the ROUS in the Fire Swamp'
};
scope.copyToDo(message);
modalSpy.should.have.been.calledOnce;
modalSpy.should.have.been.calledWith('copyChatToDo', sinon.match(function(callArgToMatch){
return callArgToMatch.controller == 'CopyMessageModalCtrl'
&& callArgToMatch.scope.text == message.text
}));
});
});
});
describe("Autocomplete controller", function() { describe("Autocomplete controller", function() {
var scope, ctrl, user, $rootScope, $controller; var scope, ctrl, user, $rootScope, $controller;
@@ -172,3 +242,57 @@ describe("Autocomplete controller", function() {
}); });
}); });
}); });
describe("CopyMessageModal controller", function() {
var scope, ctrl, user, Notification, $rootScope, $controller;
beforeEach(function() {
module(function($provide) {
$provide.value('User', {});
});
inject(function($rootScope, _$controller_, _Notification_){
user = specHelper.newUser();
user._id = "unique-user-id";
user.ops = {
addTask: sinon.spy()
};
scope = $rootScope.$new();
scope.$close = sinon.spy();
$controller = _$controller_;
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: {user: user}});
ctrl = $controller('CopyMessageModalCtrl', {$scope: scope, User: {user: user}});
Notification = _Notification_;
Notification.text = sinon.spy();
});
});
describe("saveTodo", function() {
it('saves todo', function() {
scope.text = "A Tavern msg";
scope.notes = "Some notes";
var payload = {
body: {
text: scope.text,
type: 'todo',
notes: scope.notes
}
};
scope.saveTodo();
user.ops.addTask.should.have.been.calledOnce;
user.ops.addTask.should.have.been.calledWith(payload);
Notification.text.should.have.been.calledOnce;
Notification.text.should.have.been.calledWith(window.env.t('messageAddedAsToDo'));
scope.$close.should.have.been.calledOnce;
});
});
});

View File

@@ -187,3 +187,6 @@ a.label
color: #fff !important color: #fff !important
.line-through .line-through
text-decoration line-through text-decoration line-through
.markdown-preview markdown code
white-space inherit

View File

@@ -142,6 +142,14 @@ for $stage in $stages
padding: 0 padding: 0
font-weight: 300 font-weight: 300
.task-column.preview
padding: 0
background: transparent
border: 0;
.task:hover
cursor: auto
// 50% width columns with scrollbars for tablets // 50% width columns with scrollbars for tablets
@media (min-width: 768px) and (max-width: 970px) @media (min-width: 768px) and (max-width: 970px)
.task-column .task-column

View File

@@ -362,7 +362,25 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
}); });
}); });
} }
} };
$scope.copyToDo = function(message) {
var taskNotes = env.t("messageWroteIn", {
user: message.uuid == 'system'
? 'system'
: '[' + message.user + '](' + env.BASE_URL + '/static/front/#?memberId=' + message.uuid + ')',
group: '[' + $scope.group.name + '](' + window.location.href + ')'
});
var newScope = $scope.$new();
newScope.text = message.text;
newScope.notes = taskNotes;
$rootScope.openModal('copyChatToDo',{
controller:'CopyMessageModalCtrl',
scope: newScope
});
};
$scope.sync = function(group){ $scope.sync = function(group){
group.$get(); group.$get();
@@ -567,3 +585,20 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
} }
} }
]) ])
.controller("CopyMessageModalCtrl", ['$scope', 'User', 'Notification',
function($scope, User, Notification){
$scope.saveTodo = function() {
var newTask = {
text: $scope.text,
type: 'todo',
notes: $scope.notes
};
User.user.ops.addTask({body:newTask});
Notification.text(window.env.t('messageAddedAsToDo'));
$scope.$close();
}
}
]);

View File

@@ -31,6 +31,9 @@ mixin chatMessages(inbox)
| &nbsp; &nbsp; | &nbsp; &nbsp;
a(ng-click="flagChatMessage(group._id, message)", ng-if=':: user.contributor.admin || (!message.sent && user.flags.communityGuidelinesAccepted && message.uuid != user.id && message.uuid != "system")') a(ng-click="flagChatMessage(group._id, message)", ng-if=':: user.contributor.admin || (!message.sent && user.flags.communityGuidelinesAccepted && message.uuid != user.id && message.uuid != "system")')
span.glyphicon.glyphicon-flag(tooltip="{{message.flags[user._id] ? env.t('abuseAlreadyReported') : env.t('abuseFlag')}}" ng-class='message.flags[user._id] ? "text-danger" : ""') span.glyphicon.glyphicon-flag(tooltip="{{message.flags[user._id] ? env.t('abuseAlreadyReported') : env.t('abuseFlag')}}" ng-class='message.flags[user._id] ? "text-danger" : ""')
| &nbsp; &nbsp;
a(ng-click="copyToDo(message)")
span.glyphicon.glyphicon-share(tooltip=env.t('copyMessageAsToDo'))
span.float-label(ng-class='::contribText(message.contributor, message.backer).length > 30 ? "long-title" : ""') span.float-label(ng-class='::contribText(message.contributor, message.backer).length > 30 ? "long-title" : ""')
a.label.label-default.chat-message(ng-if=':: message.user', ng-class='::userLevelStyleFromLevel(message.contributor.level, message.backer.npc, style)', ng-click='clickMember(message.uuid, true)') a.label.label-default.chat-message(ng-if=':: message.user', ng-class='::userLevelStyleFromLevel(message.contributor.level, message.backer.npc, style)', ng-click='clickMember(message.uuid, true)')
span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent') span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent')

View File

@@ -113,3 +113,33 @@ script(type='text/ng-template', id='partials/options.social.html')
.tab-content .tab-content
.tab-pane.active .tab-pane.active
div(ui-view) div(ui-view)
script(type='text/ng-template', id='modals/copyChatToDo.html')
.modal-header
h4=env.t('copyMessageAsToDo')
.modal-body
.form-group
input.form-control(type='text',ng-model='text', ng-model-options="{debounce: 1000}")
.form-group
textarea.form-control(rows='5',ng-model='notes', ng-model-options="{debounce: 1000}", focus-me)
hr
div.task-column.preview
div(ng-init='popoverOpen = false', class='task todo uncompleted color-neutral', popover-trigger='mouseenter', data-popover-html="{{popoverOpen ? '' : notes | markdown}}", popover-placement="top")
// right-hand side control buttons
.task-meta-controls
// Icons only available if you own the tasks (aka, hidden from challenge stats)
span(ng-if='!obj._locked')
// notes
span.task-notes(ng-show='notes', ng-click='popoverOpen = !popoverOpen', popover-trigger='click', data-popover-html="{{notes | markdown}}", popover-placement="top")
span.glyphicon.glyphicon-comment
| &nbsp;
// main content
div.task-text
markdown(text='text',target='_blank')
.modal-footer
button.btn.btn-default(ng-click='$close()')=env.t('close')
button.btn.btn-primary(ng-click='saveTodo()')=env.t('submit')

View File

@@ -11,3 +11,9 @@ mixin aLink(url, label)
a(href="", ng-click="externalLink('#{url}')")= label a(href="", ng-click="externalLink('#{url}')")= label
else else
a(href='#{url}', target='_blank')= label a(href='#{url}', target='_blank')= label
mixin previewMarkdown(text)
.panel.panel-warning
.panel-heading=env.t('msgPreviewHeading')
.panel-body.markdown-preview
markdown(text='#{text}')

View File

@@ -1,4 +1,4 @@
li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', popover-trigger='mouseenter', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}', ng-show='shouldShow(task, list, user.preferences)') li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', popover-trigger='mouseenter', data-popover-html="{{task.popoverOpen ? '' : task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}', ng-show='shouldShow(task, list, user.preferences)')
// right-hand side control buttons // right-hand side control buttons
.task-meta-controls .task-meta-controls
@@ -53,7 +53,7 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
span.glyphicon.glyphicon-signal span.glyphicon.glyphicon-signal
| &nbsp; | &nbsp;
// notes // notes
span.task-notes(ng-show='task.notes && !task._editing') span.task-notes(ng-show='task.notes && !task._editing', ng-click='task.popoverOpen = !task.popoverOpen', popover-trigger='click', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}')
span.glyphicon.glyphicon-comment span.glyphicon.glyphicon-comment
| &nbsp; | &nbsp;