Skip to content
Snippets Groups Projects
Commit dd193b1e authored by Gabriel Engel's avatar Gabriel Engel
Browse files

Merge pull request #3102 from pitamar/develop

New message editing features
parents 68810b04 06fc2fd6
No related merge requests found
...@@ -111,28 +111,8 @@ Meteor.startup -> ...@@ -111,28 +111,8 @@ Meteor.startup ->
message = @_arguments[1] message = @_arguments[1]
msg = $(event.currentTarget).closest('.message')[0] msg = $(event.currentTarget).closest('.message')[0]
$("\##{msg.id} .message-dropdown").hide() $("\##{msg.id} .message-dropdown").hide()
return if msg.classList.contains("system")
swal { chatMessages[Session.get('openedRoom')].confirmDeleteMsg(message)
title: t('Are_you_sure')
text: t('You_will_not_be_able_to_recover')
type: 'warning'
showCancelButton: true
confirmButtonColor: '#DD6B55'
confirmButtonText: t('Yes_delete_it')
cancelButtonText: t('Cancel')
closeOnConfirm: false
html: false
}, ->
swal
title: t('Deleted')
text: t('Your_entry_has_been_deleted')
type: 'success'
timer: 1000
showConfirmButton: false
if chatMessages[Session.get('openedRoom')].editing.id is message._id
chatMessages[Session.get('openedRoom')].clearEditing(message)
chatMessages[Session.get('openedRoom')].deleteMsg(message)
validation: (message) -> validation: (message) ->
hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid) hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid)
isDeleteAllowed = RocketChat.settings.get 'Message_AllowDeleting' isDeleteAllowed = RocketChat.settings.get 'Message_AllowDeleting'
......
class @ChatMessages class @ChatMessages
init: (node) -> init: (node) ->
this.editing = {} this.editing = {}
this.records = {}
this.messageMaxSize = RocketChat.settings.get('Message_MaxAllowedSize') this.messageMaxSize = RocketChat.settings.get('Message_MaxAllowedSize')
this.wrapper = $(node).find(".wrapper") this.wrapper = $(node).find(".wrapper")
this.input = $(node).find(".input-message").get(0) this.input = $(node).find(".input-message").get(0)
this.$input = $(this.input)
this.hasValue = new ReactiveVar false this.hasValue = new ReactiveVar false
this.bindEvents() this.bindEvents()
return return
...@@ -13,23 +15,6 @@ class @ChatMessages ...@@ -13,23 +15,6 @@ class @ChatMessages
$(".messages-box").css $(".messages-box").css
height: "calc(100% - #{dif}px)" height: "calc(100% - #{dif}px)"
toPrevMessage: ->
msgs = this.wrapper.get(0).querySelectorAll(".own:not(.system)")
if msgs.length
if this.editing.element
if msgs[this.editing.index - 1]
this.edit msgs[this.editing.index - 1], this.editing.index - 1
else
this.edit msgs[msgs.length - 1], msgs.length - 1
toNextMessage: ->
if this.editing.element
msgs = this.wrapper.get(0).querySelectorAll(".own:not(.system)")
if msgs[this.editing.index + 1]
this.edit msgs[this.editing.index + 1], this.editing.index + 1
else
this.clearEditing()
getEditingIndex: (element) -> getEditingIndex: (element) ->
msgs = this.wrapper.get(0).querySelectorAll(".own:not(.system)") msgs = this.wrapper.get(0).querySelectorAll(".own:not(.system)")
index = 0 index = 0
...@@ -39,9 +24,63 @@ class @ChatMessages ...@@ -39,9 +24,63 @@ class @ChatMessages
index++ index++
return -1 return -1
recordInputAsDraft: () ->
id = this.editing.id
record = this.records[id] || {}
draft = this.input.value
if(draft is record.original)
this.clearCurrentDraft()
else
record.draft = draft
this.records[id] = record
recordOriginalMessage: (message) ->
record = this.records[message._id] || {}
record.original = message.msg
this.records[message._id] = record
getMessageDraft: (id) ->
return this.records[id]
clearMessageDraft: (id) ->
delete this.records[id]
clearCurrentDraft: () ->
this.clearMessageDraft this.editing.id
resetToDraft: (id) ->
this.input.value = this.records[id].original
getMessageOfElement: (element) -> return ChatMessage.findOne( { _id: element.getAttribute("id") } )
toPrevMessage: ->
index = this.editing.index
this.editByIndex if index? then index - 1 else undefined
toNextMessage: ->
index = this.editing.index
this.clearEditing() unless this.editByIndex index + 1
editByIndex: (index) ->
return false if not this.editing.element and index?
msgs = this.wrapper.get(0).querySelectorAll(".own:not(.system)")
index = msgs.length - 1 if not index?
return false unless msgs[index]
element = msgs[index]
this.edit element, index
return true
edit: (element, index) -> edit: (element, index) ->
id = element.getAttribute("id") index = this.getEditingIndex(element) if not index?
message = ChatMessage.findOne { _id: id }
message = this.getMessageOfElement(element)
hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid) hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid)
editAllowed = RocketChat.settings.get 'Message_AllowEditing' editAllowed = RocketChat.settings.get 'Message_AllowEditing'
editOwn = message?.u?._id is Meteor.userId() editOwn = message?.u?._id is Meteor.userId()
...@@ -56,31 +95,53 @@ class @ChatMessages ...@@ -56,31 +95,53 @@ class @ChatMessages
if currentTsDiff > blockEditInMinutes if currentTsDiff > blockEditInMinutes
return return
msg = this.getMessageDraft(message._id)?.draft
msg = message.msg unless msg?
this.recordOriginalMessage message
editingNext = this.editing.index < index
old_input = this.input.value
this.clearEditing() this.clearEditing()
this.input.value = message.msg
this.hasValue.set true this.hasValue.set true
this.editing.element = element this.editing.element = element
this.editing.index = index or this.getEditingIndex(element) this.editing.index = index
this.editing.id = id this.editing.id = message._id
element.classList.add("editing") element.classList.add("editing")
this.input.classList.add("editing") this.input.classList.add("editing")
$(this.input).closest('.message-form').addClass('editing'); this.$input.closest('.message-form').addClass('editing')
setTimeout => setTimeout =>
this.input.focus() this.input.focus()
this.input.value = msg
cursor_pos = if editingNext then 0 else -1
this.$input.setCursorPosition(cursor_pos)
, 5 , 5
clearEditing: -> clearEditing: ->
if this.editing.element if this.editing.element
this.recordInputAsDraft()
this.editing.element.classList.remove("editing") this.editing.element.classList.remove("editing")
this.input.classList.remove("editing") this.input.classList.remove("editing")
$(this.input).closest('.message-form').removeClass('editing'); this.$input.closest('.message-form').removeClass('editing')
this.editing.id = null delete this.editing.id
this.editing.element = null delete this.editing.element
this.editing.index = null delete this.editing.index
this.input.value = this.editing.saved or "" this.input.value = this.editing.saved or ""
cursor_pos = this.editing.savedCursor ? -1
this.$input.setCursorPosition(cursor_pos)
this.hasValue.set this.input.value isnt '' this.hasValue.set this.input.value isnt ''
else else
this.editing.saved = this.input.value this.editing.saved = this.input.value
this.editing.savedCursor = this.input.selectionEnd
send: (rid, input) -> send: (rid, input) ->
if _.trim(input.value) isnt '' if _.trim(input.value) isnt ''
...@@ -98,6 +159,7 @@ class @ChatMessages ...@@ -98,6 +159,7 @@ class @ChatMessages
if this.isMessageTooLong(msgObject.msg) if this.isMessageTooLong(msgObject.msg)
return toastr.error t('Message_too_long') return toastr.error t('Message_too_long')
this.clearCurrentDraft()
if this.editing.id if this.editing.id
this.update(this.editing.id, rid, msgObject.msg) this.update(this.editing.id, rid, msgObject.msg)
return return
...@@ -117,6 +179,41 @@ class @ChatMessages ...@@ -117,6 +179,41 @@ class @ChatMessages
return return
Meteor.call 'sendMessage', msgObject Meteor.call 'sendMessage', msgObject
else if this.editing.element
element = this.editing.element
message = this.getMessageOfElement(element)
this.resetToDraft this.editing.id
this.confirmDeleteMsg message
confirmDeleteMsg: (message) ->
return if RocketChat.MessageTypes.isSystemMessage(message)
swal {
title: t('Are_you_sure')
text: t('You_will_not_be_able_to_recover')
type: 'warning'
showCancelButton: true
confirmButtonColor: '#DD6B55'
confirmButtonText: t('Yes_delete_it')
cancelButtonText: t('Cancel')
closeOnConfirm: false
html: false
}, =>
swal
title: t('Deleted')
text: t('Your_entry_has_been_deleted')
type: 'success'
timer: 1000
showConfirmButton: false
if this.editing.id is message._id
this.clearEditing message
this.deleteMsg message
this.$input.focus()
# In order to avoid issue "[Callback not called when still animating](https://github.com/t4t5/sweetalert/issues/528)"
$('.sweet-alert').addClass 'visible'
deleteMsg: (message) -> deleteMsg: (message) ->
blockDeleteInMinutes = RocketChat.settings.get 'Message_AllowDeleting_BlockDeleteInMinutes' blockDeleteInMinutes = RocketChat.settings.get 'Message_AllowDeleting_BlockDeleteInMinutes'
...@@ -129,7 +226,7 @@ class @ChatMessages ...@@ -129,7 +226,7 @@ class @ChatMessages
Meteor.call 'deleteMessage', message, (error, result) -> Meteor.call 'deleteMessage', message, (error, result) ->
if error if error
return handleError(error); return handleError(error)
pinMsg: (message) -> pinMsg: (message) ->
message.pinned = true message.pinned = true
...@@ -205,39 +302,54 @@ class @ChatMessages ...@@ -205,39 +302,54 @@ class @ChatMessages
keydown: (rid, event) -> keydown: (rid, event) ->
input = event.currentTarget input = event.currentTarget
$input = $(input)
k = event.which k = event.which
this.resize(input) this.resize(input)
if k is 13 and not event.shiftKey if k is 13 and not event.shiftKey # Enter without shift
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
this.send(rid, input) this.send(rid, input)
return return
if k is 9 if k is 9 # Tab
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@tryCompletion input @tryCompletion input
if k is 27 if k is 27 # Escape
if this.editing.id if this.editing.index?
record = this.getMessageDraft(this.editing.id)
if this.input.value is record?.original
this.clearCurrentDraft()
this.clearEditing()
else
this.resetToDraft this.editing.id
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
this.clearEditing()
return return
else if k is 38 or k is 40 # Arrow Up or down else if k is 38 or k is 40 # Arrow Up or down
return true if event.shiftKey return true if event.shiftKey
return true if $(input).val().length and !this.editing?.id cursor_pos = input.selectionEnd
if k is 38 if k is 38 # Arrow Up
return if input.value.slice(0, input.selectionStart).match(/[\n]/) isnt null if cursor_pos is 0
this.toPrevMessage() this.toPrevMessage()
else else if event.altKey
return if input.value.slice(input.selectionEnd, input.value.length).match(/[\n]/) isnt null this.$input.setCursorPosition(0)
this.toNextMessage() else
return true
event.preventDefault()
event.stopPropagation() else # Arrow Down
if cursor_pos is input.value.length
this.toNextMessage()
else if event.altKey
this.$input.setCursorPosition(-1)
else
return true
return false
# ctrl (command) + shift + k -> clear room messages # ctrl (command) + shift + k -> clear room messages
else if k is 75 and ((navigator?.platform?.indexOf('Mac') isnt -1 and event.metaKey and event.shiftKey) or (navigator?.platform?.indexOf('Mac') is -1 and event.ctrlKey and event.shiftKey)) else if k is 75 and ((navigator?.platform?.indexOf('Mac') isnt -1 and event.metaKey and event.shiftKey) or (navigator?.platform?.indexOf('Mac') is -1 and event.ctrlKey and event.shiftKey))
......
// Adapted from http://stackoverflow.com/a/499158
$.fn.setCursorPosition = function(pos) {
this.each(function(index, elem) {
var p = pos < 0 ? elem.value.length - pos : pos;
if (elem.setSelectionRange) {
elem.setSelectionRange(p, p);
} else if (elem.createTextRange) {
var range = elem.createTextRange();
range.collapse(true);
range.moveEnd('character', p);
range.moveStart('character', p);
range.select();
}
});
return this;
};
...@@ -72,6 +72,9 @@ Package.onUse(function(api) { ...@@ -72,6 +72,9 @@ Package.onUse(function(api) {
// LIB CLIPBOARDJS // LIB CLIPBOARDJS
api.addFiles('lib/clipboardjs/clipboard.js', 'client'); api.addFiles('lib/clipboardjs/clipboard.js', 'client');
// TEXTAREA CURSOR MANAGEMENT
api.addFiles('lib/textarea-cursor/set-cursor-position.js', 'client');
// TEMPLATE FILES // TEMPLATE FILES
api.addFiles('views/cmsPage.html', 'client'); api.addFiles('views/cmsPage.html', 'client');
api.addFiles('views/fxos.html', 'client'); api.addFiles('views/fxos.html', 'client');
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment