Commit 87406f5f authored by JC Brand's avatar JC Brand

Allow messages to be edited by clicking a pencil icon

updates #421
parent 8069e73f
......@@ -69620,6 +69620,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
// Leaky abstraction from MUC
events: {
'change input.fileupload': 'onFileSelection',
'click .chat-msg__action-edit': 'onMessageEditButtonClicked',
'click .chatbox-navback': 'showControlBox',
'click .close-chatbox-button': 'close',
'click .new-msgs-indicator': 'viewUnreadMessages',
......@@ -69632,8 +69633,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu',
'click .upload-file': 'toggleFileUpload',
'keydown .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged'
'input .chat-textarea': 'inputChanged',
'keydown .chat-textarea': 'keyPressed'
},
initialize() {
......@@ -70240,6 +70241,27 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
}));
},
onMessageEditButtonClicked(ev) {
const idx = this.model.messages.findLastIndex('correcting'),
currently_correcting = idx >= 0 ? this.model.messages.at(idx) : null,
message_el = u.ancestor(ev.target, '.chat-msg'),
message = this.model.messages.findWhere({
'msgid': message_el.getAttribute('data-msgid')
});
if (currently_correcting !== message) {
if (!_.isNil(currently_correcting)) {
currently_correcting.save('correcting', false);
}
message.save('correcting', true);
this.insertIntoTextArea(message.get('message'), true);
} else {
message.save('correcting', false);
this.insertIntoTextArea('', true);
}
},
editLaterMessage() {
let message;
let idx = this.model.messages.findLastIndex('correcting');
......@@ -74647,7 +74669,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
});
_converse.MessageView = _converse.ViewWithAvatar.extend({
events: {
'click .chat-msg-edited': 'showMessageVersionsModal'
'click .chat-msg__edit-modal': 'showMessageVersionsModal'
},
initialize() {
......@@ -85733,17 +85755,17 @@ __p += '\n </span>\n ';
if (!o.is_me_message) { ;
__p += '<div class="chat-msg__body">';
} ;
__p += ' \n ';
__p += '\n ';
if (o.edited) { ;
__p += ' <i title="' +
__e(o.__('This message has been edited')) +
'" class="fa fa-edit chat-msg-edited"></i> ';
'" class="fa fa-edit chat-msg__edit-modal"></i> ';
} ;
__p += '\n ';
if (!o.is_me_message) { ;
__p += '<div class="chat-msg__message">';
} ;
__p += ' \n ';
__p += '\n ';
if (o.is_spoiler) { ;
__p += '\n <div class="chat-msg__spoiler-hint">\n <span class="spoiler-hint">' +
__e(o.spoiler_hint) +
......@@ -85759,11 +85781,17 @@ __p += '"><!-- message gets added here via renderMessage --></div>\n
if (!o.is_me_message) { ;
__p += '</div>';
} ;
__p += '\n ';
__p += '\n ';
if (o.type !== 'headline' && !o.is_me_message && o.sender === 'me') { ;
__p += '\n <div class="chat-msg__actions">\n <button class="chat-msg__action chat-msg__action-edit fa fa-pencil" title="' +
__e(o.__('Edit this message')) +
'">&nbsp;</button>\n </div>\n ';
} ;
__p += '\n\n ';
if (!o.is_me_message) { ;
__p += '</div>';
} ;
__p += ' \n </div>\n</div>\n';
__p += '\n </div>\n</div>\n';
return __p
};
......@@ -19,7 +19,106 @@
describe("A Chat Message", function () {
it("can be sent as a correction",
it("can be sent as a correction by clicking the pencil icon",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current', 1);
test_utils.openControlBox();
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
const view = _converse.chatboxviews.get(contact_jid);
const textarea = view.el.querySelector('textarea.chat-textarea');
textarea.value = 'But soft, what light through yonder airlock breaks?';
view.keyPressed({
target: textarea,
preventDefault: _.noop,
keyCode: 13 // Enter
});
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg__text').textContent)
.toBe('But soft, what light through yonder airlock breaks?');
expect(textarea.value).toBe('');
const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
expect(view.el.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(1);
let action = view.el.querySelector('.chat-msg .chat-msg__action');
expect(action.getAttribute('title')).toBe('Edit this message');
action.style.opacity = 1;
action.click();
expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true);
spyOn(_converse.connection, 'send');
textarea.value = 'But soft, what light through yonder window breaks?';
view.keyPressed({
target: textarea,
preventDefault: _.noop,
keyCode: 13 // Enter
});
expect(_converse.connection.send).toHaveBeenCalled();
const msg = _converse.connection.send.calls.all()[0].args[0];
expect(msg.toLocaleString())
.toBe(`<message from='dummy@localhost/resource' `+
`to='max.frankfurter@localhost' type='chat' id='${msg.nodeTree.getAttribute('id')}' `+
`xmlns='jabber:client'>`+
`<body>But soft, what light through yonder window breaks?</body>`+
`<active xmlns='http://jabber.org/protocol/chatstates'/>`+
`<replace xmlns='urn:xmpp:message-correct:0' id='${first_msg.get('msgid')}'/>`+
`</message>`);
expect(view.model.messages.models.length).toBe(1);
const corrected_message = view.model.messages.at(0);
expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
expect(corrected_message.get('correcting')).toBe(false);
expect(corrected_message.get('older_versions').length).toBe(1);
expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?');
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
// Test that clicking the pencil icon a second time cancels editing.
action = view.el.querySelector('.chat-msg .chat-msg__action');
action.style.opacity = 1;
action.click();
expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
expect(view.model.messages.at(0).get('correcting')).toBe(true);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true);
action = view.el.querySelector('.chat-msg .chat-msg__action');
action.style.opacity = 1;
action.click();
expect(textarea.value).toBe('');
expect(view.model.messages.at(0).get('correcting')).toBe(false);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
// Test that messages from other users don't have the pencil icon
_converse.chatboxes.onMessage(
$msg({
'from': contact_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': (new Date()).getTime()
}).c('body').t('Hello').up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
);
expect(view.el.querySelectorAll('.chat-msg .chat-msg__action').length).toBe(1);
done();
}));
it("can be sent as a correction by using the up arrow",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
......@@ -180,19 +279,20 @@
spyOn(_converse, 'emit');
const message = 'This is a received message';
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
const msg = $msg({
// We don't already have an open chatbox for this user
expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
_converse.chatboxes.onMessage(
$msg({
'from': sender_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': (new Date()).getTime()
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
);
// We don't already have an open chatbox for this user
expect(_converse.chatboxes.get(sender_jid)).not.toBeDefined();
// onMessage is a handler for received XMPP messages
_converse.chatboxes.onMessage(msg);
expect(_converse.emit).toHaveBeenCalledWith('message', jasmine.any(Object));
// Check that the chatbox and its view now exist
......@@ -1804,7 +1904,7 @@
}).catch(_.partial(console.error, _));
}));
it("can be sent as a correction",
it("can be sent as a correction by using the up arrow",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
......
......@@ -237,7 +237,7 @@
_converse.UserDetailsModal = _converse.BootstrapModal.extend({
events: {
events: {
'click button.remove-contact': 'removeContact',
'click button.refresh-contact': 'refreshContact'
},
......@@ -321,6 +321,7 @@
events: {
'change input.fileupload': 'onFileSelection',
'click .chat-msg__action-edit': 'onMessageEditButtonClicked',
'click .chatbox-navback': 'showControlBox',
'click .close-chatbox-button': 'close',
'click .new-msgs-indicator': 'viewUnreadMessages',
......@@ -333,8 +334,8 @@
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu',
'click .upload-file': 'toggleFileUpload',
'keydown .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged'
'input .chat-textarea': 'inputChanged',
'keydown .chat-textarea': 'keyPressed'
},
initialize () {
......@@ -931,6 +932,24 @@
return f(this.model.messages.filter({'sender': 'me'}));
},
onMessageEditButtonClicked (ev) {
const idx = this.model.messages.findLastIndex('correcting'),
currently_correcting = idx >=0 ? this.model.messages.at(idx) : null,
message_el = u.ancestor(ev.target, '.chat-msg'),
message = this.model.messages.findWhere({'msgid': message_el.getAttribute('data-msgid')});
if (currently_correcting !== message) {
if (!_.isNil(currently_correcting)) {
currently_correcting.save('correcting', false);
}
message.save('correcting', true);
this.insertIntoTextArea(message.get('message'), true);
} else {
message.save('correcting', false);
this.insertIntoTextArea('', true);
}
},
editLaterMessage () {
let message;
let idx = this.model.messages.findLastIndex('correcting');
......
......@@ -80,9 +80,8 @@
_converse.MessageView = _converse.ViewWithAvatar.extend({
events: {
'click .chat-msg-edited': 'showMessageVersionsModal'
'click .chat-msg__edit-modal': 'showMessageVersionsModal'
},
initialize () {
......
......@@ -10,9 +10,9 @@
</span>
{[ if (!o.is_me_message) { ]}<time timestamp="{{{o.isodate}}}" class="chat-msg__time">{{{o.pretty_time}}}</time>{[ } ]}
</span>
{[ if (!o.is_me_message) { ]}<div class="chat-msg__body">{[ } ]}
{[ if (o.edited) { ]} <i title="{{{o.__('This message has been edited')}}}" class="fa fa-edit chat-msg-edited"></i> {[ } ]}
{[ if (!o.is_me_message) { ]}<div class="chat-msg__message">{[ } ]}
{[ if (!o.is_me_message) { ]}<div class="chat-msg__body">{[ } ]}
{[ if (o.edited) { ]} <i title="{{{o.__('This message has been edited')}}}" class="fa fa-edit chat-msg__edit-modal"></i> {[ } ]}
{[ if (!o.is_me_message) { ]}<div class="chat-msg__message">{[ } ]}
{[ if (o.is_spoiler) { ]}
<div class="chat-msg__spoiler-hint">
<span class="spoiler-hint">{{{o.spoiler_hint}}}</span>
......@@ -22,6 +22,12 @@
<div class="chat-msg__text{[ if (o.is_spoiler) { ]} spoiler collapsed{[ } ]}"><!-- message gets added here via renderMessage --></div>
<div class="chat-msg__media"></div>
{[ if (!o.is_me_message) { ]}</div>{[ } ]}
{[ if (!o.is_me_message) { ]}</div>{[ } ]}
{[ if (o.type !== 'headline' && !o.is_me_message && o.sender === 'me') { ]}
<div class="chat-msg__actions">
<button class="chat-msg__action chat-msg__action-edit fa fa-pencil" title="{{{o.__('Edit this message')}}}">&nbsp;</button>
</div>
{[ } ]}
{[ if (!o.is_me_message) { ]}</div>{[ } ]}
</div>
</div>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment