107952ff00
When it was on an author row the cursor was shown as a pointer, even if clicking on the author row itself does nothing. On the other hand, avatars used the default cursor, even if clicking on them either shows the contacts menu (in the case of the author row, only when the avatar is for a different user than the current one) or inserts a mention (for avatars shown in the list of suggested mentions), depending on the case. Now, the author row uses the default cursor, and avatars (and their associated user name) use a pointer cursor if clicking on them will trigger an action (either showing the contacts menu or inserting a mention). Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
720 lines
21 KiB
JavaScript
720 lines
21 KiB
JavaScript
/*
|
||
* Copyright (c) 2016
|
||
*
|
||
* This file is licensed under the Affero General Public License version 3
|
||
* or later.
|
||
*
|
||
* See the COPYING-README file.
|
||
*
|
||
*/
|
||
|
||
/* global Handlebars, escapeHTML */
|
||
|
||
(function(OC, OCA) {
|
||
var TEMPLATE =
|
||
'<ul class="comments">' +
|
||
'</ul>' +
|
||
'<div class="emptycontent hidden"><div class="icon-comment"></div>' +
|
||
'<p>{{emptyResultLabel}}</p></div>' +
|
||
'<input type="button" class="showMore hidden" value="{{moreLabel}}"' +
|
||
' name="show-more" id="show-more" />' +
|
||
'<div class="loading hidden" style="height: 50px"></div>';
|
||
|
||
var EDIT_COMMENT_TEMPLATE =
|
||
'<div class="newCommentRow comment" data-id="{{id}}">' +
|
||
' <div class="authorRow">' +
|
||
' <div class="avatar currentUser" data-username="{{actorId}}"></div>' +
|
||
' <div class="author currentUser">{{actorDisplayName}}</div>' +
|
||
'{{#if isEditMode}}' +
|
||
' <a href="#" class="action delete icon icon-delete has-tooltip" title="{{deleteTooltip}}"></a>' +
|
||
'{{/if}}' +
|
||
' </div>' +
|
||
' <form class="newCommentForm">' +
|
||
' <div contentEditable="true" class="message" data-placeholder="{{newMessagePlaceholder}}">{{message}}</div>' +
|
||
' <input class="submit icon-confirm" type="submit" value="" />' +
|
||
'{{#if isEditMode}}' +
|
||
' <input class="cancel pull-right" type="button" value="{{cancelText}}" />' +
|
||
'{{/if}}' +
|
||
' <div class="submitLoading icon-loading-small hidden"></div>'+
|
||
' </form>' +
|
||
'</div>';
|
||
|
||
var COMMENT_TEMPLATE =
|
||
'<li class="comment{{#if isUnread}} unread{{/if}}{{#if isLong}} collapsed{{/if}}" data-id="{{id}}">' +
|
||
' <div class="authorRow">' +
|
||
' <div class="avatar{{#if isUserAuthor}} currentUser{{/if}}" {{#if actorId}}data-username="{{actorId}}"{{/if}}> </div>' +
|
||
' <div class="author{{#if isUserAuthor}} currentUser{{/if}}">{{actorDisplayName}}</div>' +
|
||
'{{#if isUserAuthor}}' +
|
||
' <a href="#" class="action edit icon icon-rename has-tooltip" title="{{editTooltip}}"></a>' +
|
||
'{{/if}}' +
|
||
' <div class="date has-tooltip live-relative-timestamp" data-timestamp="{{timestamp}}" title="{{altDate}}">{{date}}</div>' +
|
||
' </div>' +
|
||
' <div class="message">{{{formattedMessage}}}</div>' +
|
||
'{{#if isLong}}' +
|
||
' <div class="message-overlay"></div>' +
|
||
'{{/if}}' +
|
||
'</li>';
|
||
|
||
/**
|
||
* @memberof OCA.Comments
|
||
*/
|
||
var CommentsTabView = OCA.Files.DetailTabView.extend(
|
||
/** @lends OCA.Comments.CommentsTabView.prototype */ {
|
||
id: 'commentsTabView',
|
||
className: 'tab commentsTabView',
|
||
_autoCompleteData: undefined,
|
||
|
||
events: {
|
||
'submit .newCommentForm': '_onSubmitComment',
|
||
'click .showMore': '_onClickShowMore',
|
||
'click .action.edit': '_onClickEditComment',
|
||
'click .action.delete': '_onClickDeleteComment',
|
||
'click .cancel': '_onClickCloseComment',
|
||
'click .comment': '_onClickComment',
|
||
'keyup div.message': '_onTextChange',
|
||
'change div.message': '_onTextChange',
|
||
'input div.message': '_onTextChange',
|
||
'paste div.message': '_onPaste'
|
||
},
|
||
|
||
_commentMaxLength: 1000,
|
||
|
||
initialize: function() {
|
||
OCA.Files.DetailTabView.prototype.initialize.apply(this, arguments);
|
||
this.collection = new OCA.Comments.CommentCollection();
|
||
this.collection.on('request', this._onRequest, this);
|
||
this.collection.on('sync', this._onEndRequest, this);
|
||
this.collection.on('add', this._onAddModel, this);
|
||
this.collection.on('change:message', this._onChangeModel, this);
|
||
|
||
this._commentMaxThreshold = this._commentMaxLength * 0.9;
|
||
|
||
// TODO: error handling
|
||
_.bindAll(this, '_onTypeComment', '_initAutoComplete', '_onAutoComplete');
|
||
},
|
||
|
||
template: function(params) {
|
||
if (!this._template) {
|
||
this._template = Handlebars.compile(TEMPLATE);
|
||
}
|
||
var currentUser = OC.getCurrentUser();
|
||
return this._template(_.extend({
|
||
actorId: currentUser.uid,
|
||
actorDisplayName: currentUser.displayName
|
||
}, params));
|
||
},
|
||
|
||
editCommentTemplate: function(params) {
|
||
if (!this._editCommentTemplate) {
|
||
this._editCommentTemplate = Handlebars.compile(EDIT_COMMENT_TEMPLATE);
|
||
}
|
||
var currentUser = OC.getCurrentUser();
|
||
return this._editCommentTemplate(_.extend({
|
||
actorId: currentUser.uid,
|
||
actorDisplayName: currentUser.displayName,
|
||
newMessagePlaceholder: t('comments', 'New comment …'),
|
||
deleteTooltip: t('comments', 'Delete comment'),
|
||
submitText: t('comments', 'Post'),
|
||
cancelText: t('comments', 'Cancel')
|
||
}, params));
|
||
},
|
||
|
||
commentTemplate: function(params) {
|
||
if (!this._commentTemplate) {
|
||
this._commentTemplate = Handlebars.compile(COMMENT_TEMPLATE);
|
||
}
|
||
|
||
params = _.extend({
|
||
editTooltip: t('comments', 'Edit comment'),
|
||
isUserAuthor: OC.getCurrentUser().uid === params.actorId,
|
||
isLong: this._isLong(params.message)
|
||
}, params);
|
||
|
||
if (params.actorType === 'deleted_users') {
|
||
// makes the avatar a X
|
||
params.actorId = null;
|
||
params.actorDisplayName = t('comments', '[Deleted user]');
|
||
}
|
||
|
||
return this._commentTemplate(params);
|
||
},
|
||
|
||
getLabel: function() {
|
||
return t('comments', 'Comments');
|
||
},
|
||
|
||
setFileInfo: function(fileInfo) {
|
||
if (fileInfo) {
|
||
this.model = fileInfo;
|
||
|
||
this.render();
|
||
this._initAutoComplete($('#commentsTabView').find('.newCommentForm .message'));
|
||
this.collection.setObjectId(this.model.id);
|
||
// reset to first page
|
||
this.collection.reset([], {silent: true});
|
||
this.nextPage();
|
||
} else {
|
||
this.model = null;
|
||
this.render();
|
||
this.collection.reset();
|
||
}
|
||
},
|
||
|
||
render: function() {
|
||
this.$el.html(this.template({
|
||
emptyResultLabel: t('comments', 'No comments yet, start the conversation!'),
|
||
moreLabel: t('comments', 'More comments …')
|
||
}));
|
||
this.$el.find('.comments').before(this.editCommentTemplate({}));
|
||
this.$el.find('.has-tooltip').tooltip();
|
||
this.$container = this.$el.find('ul.comments');
|
||
this.$el.find('.avatar').avatar(OC.getCurrentUser().uid, 32);
|
||
this.delegateEvents();
|
||
this.$el.find('.message').on('keydown input change', this._onTypeComment);
|
||
|
||
autosize(this.$el.find('.newCommentRow .message'))
|
||
},
|
||
|
||
_initAutoComplete: function($target) {
|
||
var s = this;
|
||
var limit = 10;
|
||
if(!_.isUndefined(OC.appConfig.comments)) {
|
||
limit = OC.appConfig.comments.maxAutoCompleteResults;
|
||
}
|
||
$target.atwho({
|
||
at: '@',
|
||
limit: limit,
|
||
callbacks: {
|
||
remoteFilter: s._onAutoComplete,
|
||
highlighter: function (li) {
|
||
// misuse the highlighter callback to instead of
|
||
// highlighting loads the avatars.
|
||
var $li = $(li);
|
||
$li.find('.avatar').avatar(undefined, 32);
|
||
return $li;
|
||
},
|
||
sorter: function (q, items) { return items; }
|
||
},
|
||
displayTpl: '<li>'
|
||
+ '<span class="avatar-name-wrapper">'
|
||
+ '<div class="avatar" '
|
||
+ 'data-username="${id}"' // for avatars
|
||
+ ' data-user="${id}"' // for contactsmenu
|
||
+ ' data-user-display-name="${label}"></div>'
|
||
+ ' <strong>${label}</strong>'
|
||
+ '</span></li>',
|
||
insertTpl: ''
|
||
+ '<span class="avatar-name-wrapper">'
|
||
+ '<div class="avatar" '
|
||
+ 'data-username="${id}"' // for avatars
|
||
+ ' data-user="${id}"' // for contactsmenu
|
||
+ ' data-user-display-name="${label}"></div>'
|
||
+ ' <strong>${label}</strong>'
|
||
+ '</span>',
|
||
searchKey: "label"
|
||
});
|
||
$target.on('inserted.atwho', function (je, $el) {
|
||
s._postRenderItem(
|
||
// we need to pass the parent of the inserted element
|
||
// passing the whole comments form would re-apply and request
|
||
// avatars from the server
|
||
$(je.target).find(
|
||
'div[data-username="' + $el.find('[data-username]').data('username') + '"]'
|
||
).parent()
|
||
);
|
||
});
|
||
},
|
||
|
||
_onAutoComplete: function(query, callback) {
|
||
if(_.isEmpty(query)) {
|
||
return;
|
||
}
|
||
var s = this;
|
||
if(!_.isUndefined(this._autoCompleteRequestTimer)) {
|
||
clearTimeout(this._autoCompleteRequestTimer);
|
||
}
|
||
this._autoCompleteRequestTimer = _.delay(function() {
|
||
if(!_.isUndefined(this._autoCompleteRequestCall)) {
|
||
this._autoCompleteRequestCall.abort();
|
||
}
|
||
this._autoCompleteRequestCall = $.get(
|
||
OC.generateUrl('/autocomplete/get'),
|
||
{
|
||
search: query,
|
||
itemType: 'files',
|
||
itemId: s.model.get('id'),
|
||
sorter: 'commenters|share-recipients',
|
||
limit: OC.appConfig.comments.maxAutoCompleteResults
|
||
},
|
||
function (data) {
|
||
callback(data);
|
||
}
|
||
);
|
||
}, 400);
|
||
},
|
||
|
||
_formatItem: function(commentModel) {
|
||
var timestamp = new Date(commentModel.get('creationDateTime')).getTime();
|
||
var data = _.extend({
|
||
timestamp: timestamp,
|
||
date: OC.Util.relativeModifiedDate(timestamp),
|
||
altDate: OC.Util.formatDate(timestamp),
|
||
formattedMessage: this._formatMessage(commentModel.get('message'), commentModel.get('mentions'))
|
||
}, commentModel.attributes);
|
||
return data;
|
||
},
|
||
|
||
_toggleLoading: function(state) {
|
||
this._loading = state;
|
||
this.$el.find('.loading').toggleClass('hidden', !state);
|
||
},
|
||
|
||
_onRequest: function(type) {
|
||
if (type === 'REPORT') {
|
||
this._toggleLoading(true);
|
||
this.$el.find('.showMore').addClass('hidden');
|
||
}
|
||
},
|
||
|
||
_onEndRequest: function(type) {
|
||
var fileInfoModel = this.model;
|
||
this._toggleLoading(false);
|
||
this.$el.find('.emptycontent').toggleClass('hidden', !!this.collection.length);
|
||
this.$el.find('.showMore').toggleClass('hidden', !this.collection.hasMoreResults());
|
||
|
||
if (type !== 'REPORT') {
|
||
return;
|
||
}
|
||
|
||
// find first unread comment
|
||
var firstUnreadComment = this.collection.findWhere({isUnread: true});
|
||
if (firstUnreadComment) {
|
||
// update read marker
|
||
this.collection.updateReadMarker(
|
||
null,
|
||
{
|
||
success: function() {
|
||
fileInfoModel.set('commentsUnread', 0);
|
||
}
|
||
}
|
||
);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* takes care of post-rendering after a new comment was added to the
|
||
* collection
|
||
*
|
||
* @param model
|
||
* @param collection
|
||
* @param options
|
||
* @private
|
||
*/
|
||
_onAddModel: function(model, collection, options) {
|
||
// we need to render it immediately, to ensure that the right
|
||
// order of comments is kept on opening comments tab
|
||
var $comment = $(this.commentTemplate(this._formatItem(model)));
|
||
if (!_.isUndefined(options.at) && collection.length > 1) {
|
||
this.$container.find('li').eq(options.at).before($comment);
|
||
} else {
|
||
this.$container.append($comment);
|
||
}
|
||
this._postRenderItem($comment);
|
||
$('#commentsTabView').find('.newCommentForm div.message').text('').prop('disabled', false);
|
||
|
||
// we need to update the model, because it consists of client data
|
||
// only, but the server might add meta data, e.g. about mentions
|
||
var oldMentions = model.get('mentions');
|
||
var self = this;
|
||
model.fetch({
|
||
success: function (model) {
|
||
if(_.isEqual(oldMentions, model.get('mentions'))) {
|
||
// don't attempt to render if unnecessary, avoids flickering
|
||
return;
|
||
}
|
||
var $updated = $(self.commentTemplate(self._formatItem(model)));
|
||
$comment.html($updated.html());
|
||
self._postRenderItem($comment);
|
||
}
|
||
})
|
||
|
||
},
|
||
|
||
/**
|
||
* takes care of post-rendering after a new comment was edited
|
||
*
|
||
* @param model
|
||
* @private
|
||
*/
|
||
_onChangeModel: function (model) {
|
||
if(model.get('message').trim() === model.previous('message').trim()) {
|
||
return;
|
||
}
|
||
|
||
var $form = this.$container.find('.comment[data-id="' + model.id + '"] form');
|
||
var $row = $form.closest('.comment');
|
||
var $target = $row.data('commentEl');
|
||
if(_.isUndefined($target)) {
|
||
// ignore noise – this is only set after editing a comment and hitting post
|
||
return;
|
||
}
|
||
var self = this;
|
||
|
||
// we need to update the model, because it consists of client data
|
||
// only, but the server might add meta data, e.g. about mentions
|
||
model.fetch({
|
||
success: function (model) {
|
||
$target.removeClass('hidden');
|
||
$row.remove();
|
||
|
||
var $message = $target.find('.message');
|
||
$message
|
||
.html(self._formatMessage(model.get('message'), model.get('mentions')))
|
||
.find('.avatar')
|
||
.each(function () { $(this).avatar(); });
|
||
self._postRenderItem($message);
|
||
}
|
||
});
|
||
},
|
||
|
||
_postRenderItem: function($el) {
|
||
$el.find('.has-tooltip').tooltip();
|
||
$el.find('.avatar').each(function() {
|
||
var $this = $(this);
|
||
$this.avatar($this.attr('data-username'), 32);
|
||
});
|
||
|
||
var username = $el.find('.avatar').data('username');
|
||
if (username !== oc_current_user) {
|
||
$el.find('.authorRow .avatar, .authorRow .author').contactsMenu(
|
||
username, 0, $el.find('.authorRow'));
|
||
}
|
||
|
||
var $message = $el.find('.message');
|
||
if($message.length === 0) {
|
||
// it is the case when writing a comment and mentioning a person
|
||
$message = $el;
|
||
}
|
||
this._postRenderMessage($message);
|
||
},
|
||
|
||
_postRenderMessage: function($el) {
|
||
$el.find('.avatar').each(function() {
|
||
var avatar = $(this);
|
||
var strong = $(this).next();
|
||
var appendTo = $(this).parent();
|
||
|
||
$.merge(avatar, strong).contactsMenu(avatar.data('user'), 0, appendTo);
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Convert a message to be displayed in HTML,
|
||
* converts newlines to <br> tags.
|
||
*/
|
||
_formatMessage: function(message, mentions) {
|
||
message = escapeHTML(message).replace(/\n/g, '<br/>');
|
||
|
||
for(var i in mentions) {
|
||
if(!mentions.hasOwnProperty(i)) {
|
||
return;
|
||
}
|
||
var mention = '@' + mentions[i].mentionId;
|
||
|
||
// escape possible regex characters in the name
|
||
mention = mention.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||
|
||
var displayName = this._composeHTMLMention(mentions[i].mentionId, mentions[i].mentionDisplayName);
|
||
|
||
// replace every mention either at the start of the input or after a whitespace
|
||
// followed by a non-word character.
|
||
message = message.replace(new RegExp("(^|\\s)(" + mention + ")\\b", 'g'),
|
||
function(match, p1) {
|
||
// to get number of whitespaces (0 vs 1) right
|
||
return p1+displayName;
|
||
}
|
||
);
|
||
}
|
||
return message;
|
||
},
|
||
|
||
_composeHTMLMention: function(uid, displayName) {
|
||
var avatar = '<div class="avatar" '
|
||
+ 'data-username="' + _.escape(uid) + '"'
|
||
+ ' data-user="' + _.escape(uid) + '"'
|
||
+ ' data-user-display-name="'
|
||
+ _.escape(displayName) + '"></div>';
|
||
|
||
return ''
|
||
+ '<span class="atwho-inserted" contenteditable="false">'
|
||
+ '<span class="avatar-name-wrapper">'
|
||
+ avatar + ' <strong>'+ _.escape(displayName)+'</strong>'
|
||
+ '</span>'
|
||
+ '</span>';
|
||
},
|
||
|
||
nextPage: function() {
|
||
if (this._loading || !this.collection.hasMoreResults()) {
|
||
return;
|
||
}
|
||
|
||
this.collection.fetchNext();
|
||
},
|
||
|
||
_onClickEditComment: function(ev) {
|
||
ev.preventDefault();
|
||
var $comment = $(ev.target).closest('.comment');
|
||
var commentId = $comment.data('id');
|
||
var commentToEdit = this.collection.get(commentId);
|
||
var $formRow = $(this.editCommentTemplate(_.extend({
|
||
isEditMode: true,
|
||
submitText: t('comments', 'Save')
|
||
}, commentToEdit.attributes)));
|
||
|
||
$comment.addClass('hidden').removeClass('collapsed');
|
||
// spawn form
|
||
$comment.after($formRow);
|
||
$formRow.data('commentEl', $comment);
|
||
$formRow.find('.message').on('keydown input change', this._onTypeComment);
|
||
|
||
// copy avatar element from original to avoid flickering
|
||
$formRow.find('.avatar:first').replaceWith($comment.find('.avatar:first').clone());
|
||
$formRow.find('.has-tooltip').tooltip();
|
||
|
||
var $message = $formRow.find('.message');
|
||
$message
|
||
.html(this._formatMessage(commentToEdit.get('message'), commentToEdit.get('mentions')))
|
||
.find('.avatar')
|
||
.each(function () { $(this).avatar(); });
|
||
this._postRenderItem($message);
|
||
|
||
// Enable autosize
|
||
autosize($formRow.find('.message'));
|
||
|
||
// enable autocomplete
|
||
this._initAutoComplete($formRow.find('.message'));
|
||
|
||
return false;
|
||
},
|
||
|
||
_onTypeComment: function(ev) {
|
||
var $field = $(ev.target);
|
||
var len = $field.val().length;
|
||
var $submitButton = $field.data('submitButtonEl');
|
||
if (!$submitButton) {
|
||
$submitButton = $field.closest('form').find('.submit');
|
||
$field.data('submitButtonEl', $submitButton);
|
||
}
|
||
$field.tooltip('hide');
|
||
if (len > this._commentMaxThreshold) {
|
||
$field.attr('data-original-title', t('comments', 'Allowed characters {count} of {max}', {count: len, max: this._commentMaxLength}));
|
||
$field.tooltip({trigger: 'manual'});
|
||
$field.tooltip('show');
|
||
$field.addClass('error');
|
||
}
|
||
|
||
var limitExceeded = (len > this._commentMaxLength);
|
||
$field.toggleClass('error', limitExceeded);
|
||
$submitButton.prop('disabled', limitExceeded);
|
||
|
||
//submits form on ctrl+Enter or cmd+Enter
|
||
if (ev.keyCode === 13 && (ev.ctrlKey || ev.metaKey)) {
|
||
$submitButton.click();
|
||
}
|
||
},
|
||
|
||
_onClickComment: function(ev) {
|
||
var $row = $(ev.target);
|
||
if (!$row.is('.comment')) {
|
||
$row = $row.closest('.comment');
|
||
}
|
||
$row.removeClass('collapsed');
|
||
},
|
||
|
||
_onClickCloseComment: function(ev) {
|
||
ev.preventDefault();
|
||
var $row = $(ev.target).closest('.comment');
|
||
$row.data('commentEl').removeClass('hidden');
|
||
$row.remove();
|
||
return false;
|
||
},
|
||
|
||
_onClickDeleteComment: function(ev) {
|
||
ev.preventDefault();
|
||
var $comment = $(ev.target).closest('.comment');
|
||
var commentId = $comment.data('id');
|
||
var $loading = $comment.find('.submitLoading');
|
||
|
||
$comment.addClass('disabled');
|
||
$loading.removeClass('hidden');
|
||
this.collection.get(commentId).destroy({
|
||
success: function() {
|
||
$comment.data('commentEl').remove();
|
||
$comment.remove();
|
||
},
|
||
error: function() {
|
||
$loading.addClass('hidden');
|
||
$comment.removeClass('disabled');
|
||
OC.Notification.showTemporary(t('comments', 'Error occurred while retrieving comment with id {id}', {id: commentId}));
|
||
}
|
||
});
|
||
|
||
return false;
|
||
},
|
||
|
||
_onClickShowMore: function(ev) {
|
||
ev.preventDefault();
|
||
this.nextPage();
|
||
},
|
||
|
||
/**
|
||
* takes care of updating comment element states after submit (either new
|
||
* comment or edit).
|
||
*
|
||
* @param {OC.Backbone.Model} model
|
||
* @param {jQuery} $form
|
||
* @private
|
||
*/
|
||
_onSubmitSuccess: function(model, $form) {
|
||
var $submit = $form.find('.submit');
|
||
var $loading = $form.find('.submitLoading');
|
||
|
||
$submit.removeClass('hidden');
|
||
$loading.addClass('hidden');
|
||
},
|
||
|
||
_commentBodyHTML2Plain: function($el) {
|
||
var $comment = $el.clone();
|
||
|
||
$comment.find('.avatar-name-wrapper').each(function () {
|
||
var $this = $(this);
|
||
var $inserted = $this.parent();
|
||
$inserted.html('@' + $this.find('.avatar').data('username'));
|
||
});
|
||
|
||
var oldHtml;
|
||
var html = $comment.html();
|
||
do {
|
||
// replace works one by one
|
||
oldHtml = html;
|
||
html = oldHtml.replace("<br>", "\n"); // preserve line breaks
|
||
console.warn(html)
|
||
} while(oldHtml !== html);
|
||
$comment.html(html);
|
||
|
||
return $comment.text();
|
||
},
|
||
|
||
_onSubmitComment: function(e) {
|
||
var self = this;
|
||
var $form = $(e.target);
|
||
var commentId = $form.closest('.comment').data('id');
|
||
var currentUser = OC.getCurrentUser();
|
||
var $submit = $form.find('.submit');
|
||
var $loading = $form.find('.submitLoading');
|
||
var $commentField = $form.find('.message');
|
||
var message = $commentField.text().trim();
|
||
e.preventDefault();
|
||
|
||
if (!message.length || message.length > this._commentMaxLength) {
|
||
return;
|
||
}
|
||
|
||
$commentField.prop('disabled', true);
|
||
$submit.addClass('hidden');
|
||
$loading.removeClass('hidden');
|
||
|
||
message = this._commentBodyHTML2Plain($commentField);
|
||
if (commentId) {
|
||
// edit mode
|
||
var comment = this.collection.get(commentId);
|
||
comment.save({
|
||
message: message
|
||
}, {
|
||
success: function(model) {
|
||
self._onSubmitSuccess(model, $form);
|
||
},
|
||
error: function() {
|
||
self._onSubmitError($form, commentId);
|
||
}
|
||
});
|
||
} else {
|
||
this.collection.create({
|
||
actorId: currentUser.uid,
|
||
actorDisplayName: currentUser.displayName,
|
||
actorType: 'users',
|
||
verb: 'comment',
|
||
message: message,
|
||
creationDateTime: (new Date()).toUTCString()
|
||
}, {
|
||
at: 0,
|
||
// wait for real creation before adding
|
||
wait: true,
|
||
success: function(model) {
|
||
self._onSubmitSuccess(model, $form);
|
||
},
|
||
error: function() {
|
||
self._onSubmitError($form, undefined);
|
||
}
|
||
});
|
||
}
|
||
|
||
return false;
|
||
},
|
||
|
||
/**
|
||
* takes care of updating the UI after an error on submit (either new
|
||
* comment or edit).
|
||
*
|
||
* @param {jQuery} $form
|
||
* @param {string|undefined} commentId
|
||
* @private
|
||
*/
|
||
_onSubmitError: function($form, commentId) {
|
||
$form.find('.submit').removeClass('hidden');
|
||
$form.find('.submitLoading').addClass('hidden');
|
||
$form.find('.message').prop('disabled', false);
|
||
|
||
if(!_.isUndefined(commentId)) {
|
||
OC.Notification.show(t('comments', 'Error occurred while updating comment with id {id}', {id: commentId}), {type: 'error'});
|
||
} else {
|
||
OC.Notification.show(t('comments', 'Error occurred while posting comment'), {type: 'error'});
|
||
}
|
||
},
|
||
|
||
/**
|
||
* ensures the contenteditable div is really empty, when user removed
|
||
* all input, so that the placeholder will be shown again
|
||
*
|
||
* @private
|
||
*/
|
||
_onTextChange: function() {
|
||
var $message = $('#commentsTabView').find('.newCommentForm div.message');
|
||
if(!$message.text().trim().length) {
|
||
$message.empty();
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Limit pasting to plain text
|
||
*
|
||
* @param e
|
||
* @private
|
||
*/
|
||
_onPaste: function (e) {
|
||
e.preventDefault();
|
||
var text = e.originalEvent.clipboardData.getData("text/plain");
|
||
document.execCommand('insertText', false, text);
|
||
},
|
||
|
||
/**
|
||
* Returns whether the given message is long and needs
|
||
* collapsing
|
||
*/
|
||
_isLong: function(message) {
|
||
return message.length > 250 || (message.match(/\n/g) || []).length > 1;
|
||
}
|
||
});
|
||
|
||
OCA.Comments.CommentsTabView = CommentsTabView;
|
||
})(OC, OCA);
|