Cleanup old comments tab
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
e7f5516b4d
commit
afa7376522
|
@ -1,167 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/*
|
||||
* Copyright (c) 2016
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
(function(OC, OCA) {
|
||||
|
||||
/**
|
||||
* @class OCA.Comments.CommentCollection
|
||||
* @classdesc
|
||||
*
|
||||
* Collection of comments assigned to a file
|
||||
*
|
||||
*/
|
||||
var CommentCollection = OC.Backbone.Collection.extend(
|
||||
/** @lends OCA.Comments.CommentCollection.prototype */ {
|
||||
|
||||
sync: OC.Backbone.davSync,
|
||||
|
||||
model: OCA.Comments.CommentModel,
|
||||
|
||||
/**
|
||||
* Object type
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
_objectType: 'files',
|
||||
|
||||
/**
|
||||
* Object id
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
_objectId: null,
|
||||
|
||||
/**
|
||||
* True if there are no more page results left to fetch
|
||||
*
|
||||
* @type bool
|
||||
*/
|
||||
_endReached: false,
|
||||
|
||||
/**
|
||||
* Number of comments to fetch per page
|
||||
*
|
||||
* @type int
|
||||
*/
|
||||
_limit: 20,
|
||||
|
||||
/**
|
||||
* Initializes the collection
|
||||
*
|
||||
* @param {string} [options.objectType] object type
|
||||
* @param {string} [options.objectId] object id
|
||||
*/
|
||||
initialize: function(models, options) {
|
||||
options = options || {}
|
||||
if (options.objectType) {
|
||||
this._objectType = options.objectType
|
||||
}
|
||||
if (options.objectId) {
|
||||
this._objectId = options.objectId
|
||||
}
|
||||
},
|
||||
|
||||
url: function() {
|
||||
return OC.linkToRemote('dav') + '/comments/'
|
||||
+ encodeURIComponent(this._objectType) + '/'
|
||||
+ encodeURIComponent(this._objectId) + '/'
|
||||
},
|
||||
|
||||
setObjectId: function(objectId) {
|
||||
this._objectId = objectId
|
||||
},
|
||||
|
||||
hasMoreResults: function() {
|
||||
return !this._endReached
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this._endReached = false
|
||||
this._summaryModel = null
|
||||
return OC.Backbone.Collection.prototype.reset.apply(this, arguments)
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the next set of results
|
||||
*/
|
||||
fetchNext: function(options) {
|
||||
var self = this
|
||||
if (!this.hasMoreResults()) {
|
||||
return null
|
||||
}
|
||||
|
||||
var body = '<?xml version="1.0" encoding="utf-8" ?>\n'
|
||||
+ '<oc:filter-comments xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">\n'
|
||||
// load one more so we know there is more
|
||||
+ ' <oc:limit>' + (this._limit + 1) + '</oc:limit>\n'
|
||||
+ ' <oc:offset>' + this.length + '</oc:offset>\n'
|
||||
+ '</oc:filter-comments>\n'
|
||||
|
||||
options = options || {}
|
||||
var success = options.success
|
||||
options = _.extend({
|
||||
remove: false,
|
||||
parse: true,
|
||||
data: body,
|
||||
davProperties: CommentCollection.prototype.model.prototype.davProperties,
|
||||
success: function(resp) {
|
||||
if (resp.length <= self._limit) {
|
||||
// no new entries, end reached
|
||||
self._endReached = true
|
||||
} else {
|
||||
// remove last entry, for next page load
|
||||
resp = _.initial(resp)
|
||||
}
|
||||
if (!self.set(resp, options)) {
|
||||
return false
|
||||
}
|
||||
if (success) {
|
||||
success.apply(null, arguments)
|
||||
}
|
||||
self.trigger('sync', 'REPORT', self, options)
|
||||
}
|
||||
}, options)
|
||||
|
||||
return this.sync('REPORT', this, options)
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the matching summary model
|
||||
*
|
||||
* @returns {OCA.Comments.CommentSummaryModel} summary model
|
||||
*/
|
||||
getSummaryModel: function() {
|
||||
if (!this._summaryModel) {
|
||||
this._summaryModel = new OCA.Comments.CommentSummaryModel({
|
||||
id: this._objectId,
|
||||
objectType: this._objectType
|
||||
})
|
||||
}
|
||||
return this._summaryModel
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the read marker for this comment thread
|
||||
*
|
||||
* @param {Date} [date] optional date, defaults to now
|
||||
* @param {Object} [options] backbone options
|
||||
*/
|
||||
updateReadMarker: function(date, options) {
|
||||
options = options || {}
|
||||
|
||||
return this.getSummaryModel().save({
|
||||
readMarker: (date || new Date()).toUTCString()
|
||||
}, options)
|
||||
}
|
||||
})
|
||||
|
||||
OCA.Comments.CommentCollection = CommentCollection
|
||||
})(OC, OCA)
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
(function(OC, OCA) {
|
||||
|
||||
_.extend(OC.Files.Client, {
|
||||
PROPERTY_FILEID: '{' + OC.Files.Client.NS_OWNCLOUD + '}id',
|
||||
PROPERTY_MESSAGE: '{' + OC.Files.Client.NS_OWNCLOUD + '}message',
|
||||
PROPERTY_ACTORTYPE: '{' + OC.Files.Client.NS_OWNCLOUD + '}actorType',
|
||||
PROPERTY_ACTORID: '{' + OC.Files.Client.NS_OWNCLOUD + '}actorId',
|
||||
PROPERTY_ISUNREAD: '{' + OC.Files.Client.NS_OWNCLOUD + '}isUnread',
|
||||
PROPERTY_OBJECTID: '{' + OC.Files.Client.NS_OWNCLOUD + '}objectId',
|
||||
PROPERTY_OBJECTTYPE: '{' + OC.Files.Client.NS_OWNCLOUD + '}objectType',
|
||||
PROPERTY_ACTORDISPLAYNAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}actorDisplayName',
|
||||
PROPERTY_CREATIONDATETIME: '{' + OC.Files.Client.NS_OWNCLOUD + '}creationDateTime',
|
||||
PROPERTY_MENTIONS: '{' + OC.Files.Client.NS_OWNCLOUD + '}mentions',
|
||||
})
|
||||
|
||||
/**
|
||||
* @class OCA.Comments.CommentModel
|
||||
* @classdesc
|
||||
*
|
||||
* Comment
|
||||
*
|
||||
*/
|
||||
const CommentModel = OC.Backbone.Model.extend(
|
||||
/** @lends OCA.Comments.CommentModel.prototype */ {
|
||||
sync: OC.Backbone.davSync,
|
||||
|
||||
defaults: {
|
||||
actorType: 'users',
|
||||
objectType: 'files',
|
||||
},
|
||||
|
||||
davProperties: {
|
||||
id: OC.Files.Client.PROPERTY_FILEID,
|
||||
message: OC.Files.Client.PROPERTY_MESSAGE,
|
||||
actorType: OC.Files.Client.PROPERTY_ACTORTYPE,
|
||||
actorId: OC.Files.Client.PROPERTY_ACTORID,
|
||||
actorDisplayName: OC.Files.Client.PROPERTY_ACTORDISPLAYNAME,
|
||||
creationDateTime: OC.Files.Client.PROPERTY_CREATIONDATETIME,
|
||||
objectType: OC.Files.Client.PROPERTY_OBJECTTYPE,
|
||||
objectId: OC.Files.Client.PROPERTY_OBJECTID,
|
||||
isUnread: OC.Files.Client.PROPERTY_ISUNREAD,
|
||||
mentions: OC.Files.Client.PROPERTY_MENTIONS,
|
||||
},
|
||||
|
||||
parse(data) {
|
||||
return {
|
||||
id: data.id,
|
||||
message: data.message,
|
||||
actorType: data.actorType,
|
||||
actorId: data.actorId,
|
||||
actorDisplayName: data.actorDisplayName,
|
||||
creationDateTime: data.creationDateTime,
|
||||
objectType: data.objectType,
|
||||
objectId: data.objectId,
|
||||
isUnread: (data.isUnread === 'true'),
|
||||
mentions: this._parseMentions(data.mentions),
|
||||
}
|
||||
},
|
||||
|
||||
_parseMentions(mentions) {
|
||||
if (_.isUndefined(mentions)) {
|
||||
return {}
|
||||
}
|
||||
const result = {}
|
||||
for (const i in mentions) {
|
||||
const mention = mentions[i]
|
||||
if (_.isUndefined(mention.localName) || mention.localName !== 'mention') {
|
||||
continue
|
||||
}
|
||||
result[i] = {}
|
||||
for (let child = mention.firstChild; child; child = child.nextSibling) {
|
||||
if (_.isUndefined(child.localName) || !child.localName.startsWith('mention')) {
|
||||
continue
|
||||
}
|
||||
result[i][child.localName] = child.textContent
|
||||
}
|
||||
}
|
||||
return result
|
||||
},
|
||||
})
|
||||
|
||||
OCA.Comments.CommentModel = CommentModel
|
||||
})(OC, OCA)
|
|
@ -1,15 +1,9 @@
|
|||
import './app'
|
||||
import './templates'
|
||||
import './commentmodel'
|
||||
import './commentcollection'
|
||||
import './commentsummarymodel'
|
||||
import './commentstabview'
|
||||
import './commentsmodifymenu'
|
||||
import './filesplugin'
|
||||
import './activitytabviewplugin'
|
||||
|
||||
import './vendor/Caret.js/dist/jquery.caret.min'
|
||||
import './vendor/At.js/dist/js/jquery.atwho.min'
|
||||
|
||||
import './style/autocomplete.scss'
|
||||
import './style/comments.scss'
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* Construct a new CommentsModifyMenuinstance
|
||||
* @constructs CommentsModifyMenu
|
||||
* @memberof OC.Comments
|
||||
* @private
|
||||
*/
|
||||
const CommentsModifyMenu = OC.Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
className: 'commentsModifyMenu popovermenu bubble menu',
|
||||
_scopes: [
|
||||
{
|
||||
name: 'edit',
|
||||
displayName: t('comments', 'Edit comment'),
|
||||
iconClass: 'icon-rename',
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
displayName: t('comments', 'Delete comment'),
|
||||
iconClass: 'icon-delete',
|
||||
},
|
||||
],
|
||||
initialize() {
|
||||
|
||||
},
|
||||
events: {
|
||||
'click a.action': '_onClickAction',
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler whenever an action has been clicked within the menu
|
||||
*
|
||||
* @param {Object} event event object
|
||||
*/
|
||||
_onClickAction(event) {
|
||||
let $target = $(event.currentTarget)
|
||||
if (!$target.hasClass('menuitem')) {
|
||||
$target = $target.closest('.menuitem')
|
||||
}
|
||||
|
||||
OC.hideMenus()
|
||||
|
||||
this.trigger('select:menu-item-clicked', event, $target.data('action'))
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the menu with the currently set items
|
||||
*/
|
||||
render() {
|
||||
this.$el.html(OCA.Comments.Templates.commentsmodifymenu({
|
||||
items: this._scopes,
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays the menu
|
||||
* @param {Event} context the click event
|
||||
*/
|
||||
show(context) {
|
||||
this._context = context
|
||||
|
||||
for (const i in this._scopes) {
|
||||
this._scopes[i].active = false
|
||||
}
|
||||
|
||||
const $el = $(context.target)
|
||||
const offsetIcon = $el.offset()
|
||||
const offsetContainer = $el.closest('.authorRow').offset()
|
||||
|
||||
// adding some extra top offset to push the menu below the button.
|
||||
const position = {
|
||||
top: offsetIcon.top - offsetContainer.top + 48,
|
||||
left: '',
|
||||
right: '',
|
||||
}
|
||||
|
||||
position.left = offsetIcon.left - offsetContainer.left
|
||||
|
||||
if (position.left > 200) {
|
||||
// we need to position the menu to the right.
|
||||
position.left = ''
|
||||
position.right = this.$el.closest('.comment').find('.date').width()
|
||||
this.$el.removeClass('menu-left').addClass('menu-right')
|
||||
} else {
|
||||
this.$el.removeClass('menu-right').addClass('menu-left')
|
||||
}
|
||||
this.$el.css(position)
|
||||
this.render()
|
||||
this.$el.removeClass('hidden')
|
||||
|
||||
OC.showMenu(null, this.$el)
|
||||
},
|
||||
})
|
||||
|
||||
OCA.Comments = OCA.Comments || {}
|
||||
OCA.Comments.CommentsModifyMenu = CommentsModifyMenu
|
||||
})(OC, OCA)
|
|
@ -1,756 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2016
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
/* global Handlebars */
|
||||
|
||||
import escapeHTML from 'escape-html'
|
||||
|
||||
(function(OC, OCA) {
|
||||
|
||||
/**
|
||||
* @memberof OCA.Comments
|
||||
*/
|
||||
var CommentsTabView = OCA.Files.DetailTabView.extend(
|
||||
/** @lends OCA.Comments.CommentsTabView.prototype */ {
|
||||
id: 'commentsTabView',
|
||||
className: 'tab commentsTabView',
|
||||
_autoCompleteData: undefined,
|
||||
_commentsModifyMenu: undefined,
|
||||
|
||||
events: {
|
||||
'submit .newCommentForm': '_onSubmitComment',
|
||||
'click .showMore': '_onClickShowMore',
|
||||
'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) {
|
||||
var currentUser = OC.getCurrentUser()
|
||||
return OCA.Comments.Templates['view'](_.extend({
|
||||
actorId: currentUser.uid,
|
||||
actorDisplayName: currentUser.displayName
|
||||
}, params))
|
||||
},
|
||||
|
||||
editCommentTemplate: function(params) {
|
||||
var currentUser = OC.getCurrentUser()
|
||||
return OCA.Comments.Templates['edit_comment'](_.extend({
|
||||
actorId: currentUser.uid,
|
||||
actorDisplayName: currentUser.displayName,
|
||||
newMessagePlaceholder: t('comments', 'New comment …'),
|
||||
submitText: t('comments', 'Post'),
|
||||
cancelText: t('comments', 'Cancel'),
|
||||
tag: 'li'
|
||||
}, params))
|
||||
},
|
||||
|
||||
commentTemplate: function(params) {
|
||||
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 OCA.Comments.Templates['comment'](params)
|
||||
},
|
||||
|
||||
getLabel: function() {
|
||||
return t('comments', 'Comments')
|
||||
},
|
||||
|
||||
getIcon: function() {
|
||||
return 'icon-comment'
|
||||
},
|
||||
|
||||
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({ tag: 'div' }))
|
||||
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'))
|
||||
this.$el.find('.newCommentForm .message').focus()
|
||||
},
|
||||
|
||||
_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: function(item) {
|
||||
return '<li>'
|
||||
+ '<span class="avatar-name-wrapper">'
|
||||
+ '<span class="avatar" '
|
||||
+ 'data-username="' + escapeHTML(item.id) + '" ' // for avatars
|
||||
+ 'data-user="' + escapeHTML(item.id) + '" ' // for contactsmenu
|
||||
+ 'data-user-display-name="' + escapeHTML(item.label) + '">'
|
||||
+ '</span>'
|
||||
+ '<strong>' + escapeHTML(item.label) + '</strong>'
|
||||
+ '</span></li>'
|
||||
},
|
||||
insertTpl: function(item) {
|
||||
return ''
|
||||
+ '<span class="avatar-name-wrapper">'
|
||||
+ '<span class="avatar" '
|
||||
+ 'data-username="' + escapeHTML(item.id) + '" ' // for avatars
|
||||
+ 'data-user="' + escapeHTML(item.id) + '" ' // for contactsmenu
|
||||
+ 'data-user-display-name="' + escapeHTML(item.label) + '">'
|
||||
+ '</span>'
|
||||
+ '<strong>' + escapeHTML(item.label) + '</strong>'
|
||||
+ '</span>'
|
||||
},
|
||||
searchKey: 'label'
|
||||
})
|
||||
$target.on('inserted.atwho', function(je, $el) {
|
||||
var editionMode = true
|
||||
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(
|
||||
'span[data-username="' + $el.find('[data-username]').data('username') + '"]'
|
||||
).parent(),
|
||||
editionMode
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
_onAutoComplete: function(query, callback) {
|
||||
var s = this
|
||||
if (!_.isUndefined(this._autoCompleteRequestTimer)) {
|
||||
clearTimeout(this._autoCompleteRequestTimer)
|
||||
}
|
||||
this._autoCompleteRequestTimer = _.delay(function() {
|
||||
if (!_.isUndefined(this._autoCompleteRequestCall)) {
|
||||
this._autoCompleteRequestCall.abort()
|
||||
}
|
||||
this._autoCompleteRequestCall = $.ajax({
|
||||
url: OC.linkToOCS('core', 2) + 'autocomplete/get',
|
||||
data: {
|
||||
search: query,
|
||||
itemType: 'files',
|
||||
itemId: s.model.get('id'),
|
||||
sorter: 'commenters|share-recipients',
|
||||
limit: OC.appConfig.comments.maxAutoCompleteResults
|
||||
},
|
||||
beforeSend: function(request) {
|
||||
request.setRequestHeader('Accept', 'application/json')
|
||||
},
|
||||
success: function(result) {
|
||||
callback(result.ocs.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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
this.$el.find('.newCommentForm .message').focus()
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 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('contenteditable', true)
|
||||
|
||||
// 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, editionMode) {
|
||||
$el.find('.has-tooltip').tooltip()
|
||||
var inlineAvatars = $el.find('.message .avatar')
|
||||
if ($($el.context).hasClass('message')) {
|
||||
inlineAvatars = $el.find('.avatar')
|
||||
}
|
||||
inlineAvatars.each(function() {
|
||||
var $this = $(this)
|
||||
$this.avatar($this.attr('data-username'), 16)
|
||||
})
|
||||
$el.find('.authorRow .avatar').each(function() {
|
||||
var $this = $(this)
|
||||
$this.avatar($this.attr('data-username'), 32)
|
||||
})
|
||||
|
||||
var username = $el.find('.avatar').data('username')
|
||||
if (username !== OC.getCurrentUser().uid) {
|
||||
$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
|
||||
}
|
||||
|
||||
if (!editionMode) {
|
||||
var self = this
|
||||
// add the dropdown menu to display the edit and delete option
|
||||
var modifyCommentMenu = new OCA.Comments.CommentsModifyMenu()
|
||||
$el.find('.authorRow').append(modifyCommentMenu.$el)
|
||||
$el.find('.more').on('click', _.bind(modifyCommentMenu.show, modifyCommentMenu))
|
||||
|
||||
self.listenTo(modifyCommentMenu, 'select:menu-item-clicked', function(ev, action) {
|
||||
if (action === 'edit') {
|
||||
self._onClickEditComment(ev)
|
||||
} else if (action === 'delete') {
|
||||
self._onClickDeleteComment(ev)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this._postRenderMessage($message, editionMode)
|
||||
},
|
||||
|
||||
_postRenderMessage: function($el, editionMode) {
|
||||
if (editionMode) {
|
||||
return
|
||||
}
|
||||
|
||||
$el.find('.avatar-name-wrapper').each(function() {
|
||||
var $this = $(this)
|
||||
var $avatar = $this.find('.avatar')
|
||||
|
||||
var user = $avatar.data('user')
|
||||
if (user !== OC.getCurrentUser().uid) {
|
||||
$this.contactsMenu(user, 0, $this)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a message to be displayed in HTML,
|
||||
* converts newlines to <br> tags.
|
||||
*/
|
||||
_formatMessage: function(message, mentions, editMode) {
|
||||
message = escapeHTML(message).replace(/\n/g, '<br/>')
|
||||
|
||||
for (var i in mentions) {
|
||||
if (!mentions.hasOwnProperty(i)) {
|
||||
return
|
||||
}
|
||||
var mention = '@' + mentions[i].mentionId
|
||||
if (mentions[i].mentionId.indexOf(' ') !== -1) {
|
||||
mention = _.escape('@"' + mentions[i].mentionId + '"')
|
||||
}
|
||||
|
||||
// escape possible regex characters in the name
|
||||
mention = mention.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
var regex = new RegExp('(^|\\s)(' + mention + ')\\b', 'g')
|
||||
if (mentions[i].mentionId.indexOf(' ') !== -1) {
|
||||
regex = new RegExp('(^|\\s)(' + mention + ')', '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(regex,
|
||||
function(match, p1) {
|
||||
// to get number of whitespaces (0 vs 1) right
|
||||
return p1 + displayName
|
||||
}
|
||||
)
|
||||
}
|
||||
if (editMode !== true) {
|
||||
message = OCP.Comments.plainToRich(message)
|
||||
}
|
||||
return message
|
||||
},
|
||||
|
||||
_composeHTMLMention: function(uid, displayName) {
|
||||
var avatar = ''
|
||||
+ '<span class="avatar" '
|
||||
+ 'data-username="' + _.escape(uid) + '" '
|
||||
+ 'data-user="' + _.escape(uid) + '" '
|
||||
+ 'data-user-display-name="' + _.escape(displayName) + '">'
|
||||
+ '</span>'
|
||||
|
||||
var isCurrentUser = (uid === OC.getCurrentUser().uid)
|
||||
|
||||
return ''
|
||||
+ '<span class="atwho-inserted" contenteditable="false">'
|
||||
+ '<span class="avatar-name-wrapper' + (isCurrentUser ? ' currentUser' : '') + '">'
|
||||
+ 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'), true))
|
||||
.find('.avatar')
|
||||
.each(function() { $(this).avatar() })
|
||||
var editionMode = true
|
||||
this._postRenderItem($message, editionMode)
|
||||
|
||||
// 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.text().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 with Enter, but Shift+Enter is a new line. If the
|
||||
// autocomplete popover is being shown Enter does not submit the
|
||||
// form either; it will be handled by At.js which will add the
|
||||
// currently selected item to the message.
|
||||
if (ev.keyCode === 13 && !ev.shiftKey && !$field.atwho('isSelecting')) {
|
||||
$submitButton.click()
|
||||
ev.preventDefault()
|
||||
}
|
||||
},
|
||||
|
||||
_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('.deleteLoading')
|
||||
var $moreIcon = $comment.find('.more')
|
||||
|
||||
$comment.addClass('disabled')
|
||||
$loading.removeClass('hidden')
|
||||
$moreIcon.addClass('hidden')
|
||||
|
||||
$comment.data('commentEl', $comment)
|
||||
|
||||
this.collection.get(commentId).destroy({
|
||||
success: function() {
|
||||
$comment.data('commentEl').remove()
|
||||
$comment.remove()
|
||||
},
|
||||
error: function() {
|
||||
$loading.addClass('hidden')
|
||||
$moreIcon.removeClass('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')
|
||||
var $message = $form.find('.message')
|
||||
|
||||
$submit.removeClass('hidden')
|
||||
$loading.addClass('hidden')
|
||||
$message.prop('contenteditable', true)
|
||||
$message.text('')
|
||||
},
|
||||
|
||||
_commentBodyHTML2Plain: function($el) {
|
||||
var $comment = $el.clone()
|
||||
|
||||
$comment.find('.avatar-name-wrapper').each(function() {
|
||||
var $this = $(this)
|
||||
var $inserted = $this.parent()
|
||||
var userId = $this.find('.avatar').data('username').toString()
|
||||
if (userId.indexOf(' ') !== -1) {
|
||||
$inserted.html('@"' + userId + '"')
|
||||
} else {
|
||||
$inserted.html('@' + userId)
|
||||
}
|
||||
})
|
||||
|
||||
$comment.html(OCP.Comments.richToPlain($comment.html()))
|
||||
|
||||
var oldHtml
|
||||
var html = $comment.html()
|
||||
do {
|
||||
// replace works one by one
|
||||
oldHtml = html
|
||||
html = oldHtml.replace('<br>', '\n') // preserve line breaks
|
||||
} 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('contenteditable', false)
|
||||
$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)
|
||||
if (model.get('message').trim() === model.previous('message').trim()) {
|
||||
// model change event doesn't trigger, manually remove the row.
|
||||
var $row = $form.closest('.comment')
|
||||
$row.data('commentEl').removeClass('hidden')
|
||||
$row.remove()
|
||||
}
|
||||
},
|
||||
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('contenteditable', true)
|
||||
|
||||
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)
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
(function(OC, OCA) {
|
||||
|
||||
_.extend(OC.Files.Client, {
|
||||
PROPERTY_READMARKER: '{' + OC.Files.Client.NS_OWNCLOUD + '}readMarker',
|
||||
})
|
||||
|
||||
/**
|
||||
* @class OCA.Comments.CommentSummaryModel
|
||||
* @classdesc
|
||||
*
|
||||
* Model containing summary information related to comments
|
||||
* like the read marker.
|
||||
*
|
||||
*/
|
||||
const CommentSummaryModel = OC.Backbone.Model.extend(
|
||||
/** @lends OCA.Comments.CommentSummaryModel.prototype */ {
|
||||
sync: OC.Backbone.davSync,
|
||||
|
||||
/**
|
||||
* Object type
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
_objectType: 'files',
|
||||
|
||||
/**
|
||||
* Object id
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
_objectId: null,
|
||||
|
||||
davProperties: {
|
||||
readMarker: OC.Files.Client.PROPERTY_READMARKER,
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the summary model
|
||||
*
|
||||
* @param {any} [attrs] ignored
|
||||
* @param {Object} [options] destructuring object
|
||||
* @param {string} [options.objectType] object type
|
||||
* @param {string} [options.objectId] object id
|
||||
*/
|
||||
initialize(attrs, options) {
|
||||
options = options || {}
|
||||
if (options.objectType) {
|
||||
this._objectType = options.objectType
|
||||
}
|
||||
},
|
||||
|
||||
url() {
|
||||
return OC.linkToRemote('dav') + '/comments/'
|
||||
+ encodeURIComponent(this._objectType) + '/'
|
||||
+ encodeURIComponent(this.id) + '/'
|
||||
},
|
||||
})
|
||||
|
||||
OCA.Comments.CommentSummaryModel = CommentSummaryModel
|
||||
})(OC, OCA)
|
|
@ -45,8 +45,6 @@
|
|||
return
|
||||
}
|
||||
|
||||
fileList.registerTabView(new OCA.Comments.CommentsTabView('commentsTabView'))
|
||||
|
||||
const oldGetWebdavProperties = fileList._getWebdavProperties
|
||||
fileList._getWebdavProperties = function() {
|
||||
const props = oldGetWebdavProperties.apply(this, arguments)
|
||||
|
@ -104,7 +102,8 @@
|
|||
actionHandler(fileName, context) {
|
||||
context.$file.find('.action-comment').tooltip('hide')
|
||||
// open sidebar in comments section
|
||||
context.fileList.showDetailsView(fileName, 'comments')
|
||||
OCA.Files.Sidebar.setActiveTab('comments')
|
||||
OCA.Files.Sidebar.open('/' + fileName)
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import { processResponsePayload } from 'webdav/dist/node/response'
|
|||
import client from './DavClient'
|
||||
import { genFileInfo } from '../utils/fileUtils'
|
||||
|
||||
export const DEFAULT_LIMIT = 5
|
||||
export const DEFAULT_LIMIT = 20
|
||||
/**
|
||||
* Retrieve the comments list
|
||||
*
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/**
|
||||
* based upon apps/comments/js/vendor/At.js/dist/css/jquery.atwho.css,
|
||||
* only changed colors and font-weight
|
||||
*/
|
||||
|
||||
.atwho-view {
|
||||
position:absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: none;
|
||||
margin-top: 18px;
|
||||
background: var(--color-main-background);
|
||||
color: var(--color-main-text);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 0 5px var(--color-box-shadow);
|
||||
min-width: 120px;
|
||||
z-index: 11110 !important;
|
||||
}
|
||||
|
||||
.atwho-view .atwho-header {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
border-bottom: solid 1px var(--color-border);
|
||||
color: var(--color-main-text);
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.atwho-view .atwho-header .small {
|
||||
color: var(--color-main-text);
|
||||
float: right;
|
||||
padding-top: 2px;
|
||||
margin-right: -5px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.atwho-view .atwho-header:hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.atwho-view .cur {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-primary-text);
|
||||
}
|
||||
.atwho-view .cur small {
|
||||
color: var(--color-primary-text);
|
||||
}
|
||||
.atwho-view strong {
|
||||
color: var(--color-main-text);
|
||||
font-weight: normal;
|
||||
}
|
||||
.atwho-view .cur strong {
|
||||
color: var(--color-primary-text);
|
||||
font-weight: normal;
|
||||
}
|
||||
.atwho-view ul {
|
||||
/* width: 100px; */
|
||||
list-style:none;
|
||||
padding:0;
|
||||
margin:auto;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.atwho-view ul li {
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
cursor: pointer;
|
||||
}
|
||||
.atwho-view small {
|
||||
font-size: smaller;
|
||||
color: var(--color-main-text);
|
||||
font-weight: normal;
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
#commentsTabView .emptycontent {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#commentsTabView .newCommentForm {
|
||||
margin-left: 36px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#commentsTabView .newCommentForm .message {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
min-height: 44px;
|
||||
margin: 0;
|
||||
|
||||
/* Prevent the text from overlapping with the submit button. */
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
#commentsTabView .newCommentForm {
|
||||
.submit,
|
||||
.submitLoading {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
margin: 0;
|
||||
padding: 13px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
opacity: .3;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#commentsTabView .deleteLoading {
|
||||
padding: 14px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#commentsTabView .newCommentForm .submit:not(:disabled):hover,
|
||||
#commentsTabView .newCommentForm .submit:not(:disabled):focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#commentsTabView .newCommentForm div.message {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
#commentsTabView .newCommentForm div.message:empty:before {
|
||||
content: attr(data-placeholder);
|
||||
color: grey;
|
||||
}
|
||||
|
||||
#commentsTabView .comment {
|
||||
position: relative;
|
||||
/** padding bottom is little more so that the top and bottom gap look uniform **/
|
||||
padding: 10px 0 15px;
|
||||
}
|
||||
|
||||
#commentsTabView .comments .comment {
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
#commentsTabView .comment .avatar,
|
||||
.atwho-view-ul * .avatar{
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#commentsTabView .comment .message .avatar,
|
||||
.atwho-view-ul * .avatar
|
||||
{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#activityTabView li.comment.collapsed .activitymessage,
|
||||
#commentsTabView .comment.collapsed .message {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#activityTabView li.comment.collapsed .activitymessage,
|
||||
#commentsTabView .comment.collapsed .message {
|
||||
max-height: 70px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#activityTabView li.comment .message-overlay,
|
||||
#commentsTabView .comment .message-overlay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#activityTabView li.comment.collapsed .message-overlay,
|
||||
#commentsTabView .comment.collapsed .message-overlay {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
height: 50px;
|
||||
pointer-events: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: -moz-linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
|
||||
background: -webkit-linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
|
||||
background: -o-linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
|
||||
background: -ms-linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
|
||||
background: linear-gradient(rgba(var(--color-main-background), 0), var(--color-main-background));
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#commentsTabView .hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/** set min-height as 44px to ensure that it fits the button sizes. **/
|
||||
#commentsTabView .comment .authorRow {
|
||||
min-height: 44px;
|
||||
}
|
||||
#commentsTabView .comment .authorRow .tooltip {
|
||||
/** because of the padding on the element, the tooltip appear too far up,
|
||||
adding this brings them closer to the element**/
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.atwho-view-ul * .avatar-name-wrapper,
|
||||
#commentsTabView .comment .authorRow {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#commentsTabView .comment:not(.newCommentRow) .message .avatar-name-wrapper:not(.currentUser),
|
||||
#commentsTabView .comment:not(.newCommentRow) .message .avatar-name-wrapper:not(.currentUser) .avatar,
|
||||
#commentsTabView .comment:not(.newCommentRow) .message .avatar-name-wrapper:not(.currentUser) .avatar img,
|
||||
#commentsTabView .comment .authorRow .avatar:not(.currentUser),
|
||||
#commentsTabView .comment .authorRow .author:not(.currentUser) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.atwho-view-ul .avatar-name-wrapper,
|
||||
.atwho-view-ul .avatar-name-wrapper .avatar,
|
||||
.atwho-view-ul .avatar-name-wrapper .avatar img {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#commentsTabView .comments li .message .atwho-inserted,
|
||||
#commentsTabView .newCommentForm .atwho-inserted {
|
||||
.avatar-name-wrapper {
|
||||
/* Make the wrapper the positioning context of its child contacts
|
||||
* menu. */
|
||||
position: relative;
|
||||
|
||||
display: inline;
|
||||
vertical-align: top;
|
||||
background-color: var(--color-background-dark);
|
||||
border-radius: 50vh;
|
||||
padding: 1px 7px 1px 1px;
|
||||
|
||||
/* Ensure that the avatar and the user name will be kept together. */
|
||||
white-space: nowrap;
|
||||
|
||||
.avatar {
|
||||
img {
|
||||
vertical-align: top;
|
||||
}
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
vertical-align: middle;
|
||||
padding: 1px;
|
||||
margin-top: -3px;
|
||||
margin-left: 0;
|
||||
margin-right: 2px;
|
||||
}
|
||||
strong {
|
||||
/* Ensure that the user name is shown in bold, as different browsers
|
||||
* use different font weights for strong elements. */
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.avatar-name-wrapper.currentUser {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-primary-text);
|
||||
}
|
||||
}
|
||||
|
||||
.atwho-view-ul * .avatar-name-wrapper {
|
||||
white-space: nowrap;
|
||||
}
|
||||
#commentsTabView .comment .author,
|
||||
#commentsTabView .comment .date {
|
||||
opacity: .5;
|
||||
}
|
||||
#commentsTabView .comment .author {
|
||||
max-width: 210px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#commentsTabView .comment .date {
|
||||
margin-left: auto;
|
||||
/** this is to fix the tooltip being too close due to the margin-top applied
|
||||
to bring the tooltip closer for the action icons **/
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
#commentsTabView .comments > li:not(.newCommentRow) .message {
|
||||
padding-left: 40px;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
#commentsTabView .comment .action {
|
||||
opacity: 0.3;
|
||||
padding: 14px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#commentsTabView .comment .action:hover,
|
||||
#commentsTabView .comment .action:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#commentsTabView .newCommentRow .action-container {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#commentsTabView .comment.disabled .message {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
#commentsTabView .comment.disabled .action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#commentsTabView .message.error {
|
||||
color: #e9322d;
|
||||
border-color: #e9322d;
|
||||
box-shadow: 0 0 6px #f8b9b7;
|
||||
}
|
||||
|
||||
.app-files .action-comment {
|
||||
padding: 16px 14px;
|
||||
}
|
||||
|
||||
#commentsTabView .comment .message .contactsmenu-popover {
|
||||
left: -6px;
|
||||
top: 24px;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<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 more icon icon-more has-tooltip"></a>
|
||||
<div class="deleteLoading icon-loading-small hidden"></div>
|
||||
{{/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>
|
|
@ -1,14 +0,0 @@
|
|||
<ul>
|
||||
{{#each items}}
|
||||
<li>
|
||||
<a href="#" class="menuitem action {{name}} permanent" data-action="{{name}}">
|
||||
{{#if iconClass}}
|
||||
<span class="icon {{iconClass}}"></span>
|
||||
{{else}}
|
||||
<span class="no-icon"></span>
|
||||
{{/if}}
|
||||
<span>{{displayName}}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
|
@ -1,16 +0,0 @@
|
|||
<{{tag}} 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}}
|
||||
<div class="action-container">
|
||||
<a href="#" class="action cancel icon icon-close has-tooltip" title="{{cancelText}}"></a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<form class="newCommentForm">
|
||||
<div contentEditable="true" class="message" data-placeholder="{{newMessagePlaceholder}}">{{message}}</div>
|
||||
<input class="submit icon-confirm has-tooltip" type="submit" value="" title="{{submitText}}"/>
|
||||
<div class="submitLoading icon-loading-small hidden"></div>
|
||||
</form>
|
||||
</{{tag}}>
|
|
@ -1,6 +0,0 @@
|
|||
<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>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016
|
||||
*
|
||||
* This file is licensed under the Affero General Public License comment 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
describe('OCA.Comments.CommentCollection', function() {
|
||||
var CommentCollection = OCA.Comments.CommentCollection;
|
||||
var collection, syncStub;
|
||||
var comment1, comment2, comment3;
|
||||
|
||||
beforeEach(function() {
|
||||
syncStub = sinon.stub(CommentCollection.prototype, 'sync');
|
||||
collection = new CommentCollection();
|
||||
collection.setObjectId(5);
|
||||
|
||||
comment1 = {
|
||||
id: 1,
|
||||
actorType: 'users',
|
||||
actorId: 'user1',
|
||||
actorDisplayName: 'User One',
|
||||
objectType: 'files',
|
||||
objectId: 5,
|
||||
message: 'First',
|
||||
creationDateTime: Date.UTC(2016, 1, 3, 10, 5, 0)
|
||||
};
|
||||
comment2 = {
|
||||
id: 2,
|
||||
actorType: 'users',
|
||||
actorId: 'user2',
|
||||
actorDisplayName: 'User Two',
|
||||
objectType: 'files',
|
||||
objectId: 5,
|
||||
message: 'Second\nNewline',
|
||||
creationDateTime: Date.UTC(2016, 1, 3, 10, 0, 0)
|
||||
};
|
||||
comment3 = {
|
||||
id: 3,
|
||||
actorType: 'users',
|
||||
actorId: 'user3',
|
||||
actorDisplayName: 'User Three',
|
||||
objectType: 'files',
|
||||
objectId: 5,
|
||||
message: 'Third',
|
||||
creationDateTime: Date.UTC(2016, 1, 3, 5, 0, 0)
|
||||
};
|
||||
});
|
||||
afterEach(function() {
|
||||
syncStub.restore();
|
||||
});
|
||||
|
||||
it('fetches the next page', function() {
|
||||
collection._limit = 2;
|
||||
collection.fetchNext();
|
||||
|
||||
expect(syncStub.calledOnce).toEqual(true);
|
||||
expect(syncStub.lastCall.args[0]).toEqual('REPORT');
|
||||
var options = syncStub.lastCall.args[2];
|
||||
expect(options.remove).toEqual(false);
|
||||
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(options.data, "application/xml");
|
||||
expect(doc.getElementsByTagNameNS('http://owncloud.org/ns', 'limit')[0].textContent).toEqual('3');
|
||||
expect(doc.getElementsByTagNameNS('http://owncloud.org/ns', 'offset')[0].textContent).toEqual('0');
|
||||
|
||||
syncStub.yieldTo('success', [comment1, comment2, comment3]);
|
||||
|
||||
expect(collection.length).toEqual(2);
|
||||
expect(collection.hasMoreResults()).toEqual(true);
|
||||
|
||||
collection.fetchNext();
|
||||
|
||||
expect(syncStub.calledTwice).toEqual(true);
|
||||
options = syncStub.lastCall.args[2];
|
||||
doc = parser.parseFromString(options.data, "application/xml");
|
||||
expect(doc.getElementsByTagNameNS('http://owncloud.org/ns', 'limit')[0].textContent).toEqual('3');
|
||||
expect(doc.getElementsByTagNameNS('http://owncloud.org/ns', 'offset')[0].textContent).toEqual('2');
|
||||
|
||||
syncStub.yieldTo('success', [comment3]);
|
||||
|
||||
expect(collection.length).toEqual(3);
|
||||
expect(collection.hasMoreResults()).toEqual(false);
|
||||
|
||||
collection.fetchNext();
|
||||
|
||||
// no further requests
|
||||
expect(syncStub.calledTwice).toEqual(true);
|
||||
});
|
||||
it('resets page counted when calling reset', function() {
|
||||
collection.fetchNext();
|
||||
|
||||
syncStub.yieldTo('success', [comment1]);
|
||||
|
||||
expect(collection.hasMoreResults()).toEqual(false);
|
||||
|
||||
collection.reset();
|
||||
|
||||
expect(collection.hasMoreResults()).toEqual(true);
|
||||
});
|
||||
describe('resetting read marker', function() {
|
||||
var updateStub;
|
||||
var clock;
|
||||
|
||||
beforeEach(function() {
|
||||
updateStub = sinon.stub(OCA.Comments.CommentSummaryModel.prototype, 'save');
|
||||
clock = sinon.useFakeTimers(Date.UTC(2016, 1, 3, 10, 5, 9));
|
||||
});
|
||||
afterEach(function() {
|
||||
updateStub.restore();
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('resets read marker to the default date', function() {
|
||||
var successStub = sinon.stub();
|
||||
collection.updateReadMarker(null, {
|
||||
success: successStub
|
||||
});
|
||||
|
||||
expect(updateStub.calledOnce).toEqual(true);
|
||||
expect(updateStub.lastCall.args[0]).toEqual({
|
||||
readMarker: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
|
||||
});
|
||||
|
||||
updateStub.yieldTo('success');
|
||||
|
||||
expect(successStub.calledOnce).toEqual(true);
|
||||
});
|
||||
it('resets read marker to the given date', function() {
|
||||
var successStub = sinon.stub();
|
||||
collection.updateReadMarker(new Date(Date.UTC(2016, 1, 2, 3, 4, 5)), {
|
||||
success: successStub
|
||||
});
|
||||
|
||||
expect(updateStub.calledOnce).toEqual(true);
|
||||
expect(updateStub.lastCall.args[0]).toEqual({
|
||||
readMarker: new Date(Date.UTC(2016, 1, 2, 3, 4, 5)).toUTCString()
|
||||
});
|
||||
|
||||
updateStub.yieldTo('success');
|
||||
|
||||
expect(successStub.calledOnce).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,688 +0,0 @@
|
|||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Vincent Petry
|
||||
* @copyright 2016 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* comment 3 of the License, or any later comment.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('OCA.Comments.CommentsTabView tests', function() {
|
||||
var view, fileInfoModel;
|
||||
var fetchStub;
|
||||
var avatarStub;
|
||||
var testComments;
|
||||
var clock;
|
||||
|
||||
/**
|
||||
* Creates a dummy message with the given length
|
||||
*
|
||||
* @param {int} len length
|
||||
* @return {string} message
|
||||
*/
|
||||
function createMessageWithLength(len) {
|
||||
var bigMessage = '';
|
||||
for (var i = 0; i < len; i++) {
|
||||
bigMessage += 'a';
|
||||
}
|
||||
return bigMessage;
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
clock = sinon.useFakeTimers(Date.UTC(2016, 1, 3, 10, 5, 9));
|
||||
fetchStub = sinon.stub(OCA.Comments.CommentCollection.prototype, 'fetchNext');
|
||||
avatarStub = sinon.stub($.fn, 'avatar');
|
||||
view = new OCA.Comments.CommentsTabView();
|
||||
fileInfoModel = new OCA.Files.FileInfoModel({
|
||||
id: 5,
|
||||
name: 'One.txt',
|
||||
mimetype: 'text/plain',
|
||||
permissions: 31,
|
||||
path: '/subdir',
|
||||
size: 123456789,
|
||||
etag: 'abcdefg',
|
||||
mtime: Date.UTC(2016, 1, 0, 0, 0, 0)
|
||||
});
|
||||
view.render();
|
||||
var comment1 = new OCA.Comments.CommentModel({
|
||||
id: 1,
|
||||
actorType: 'users',
|
||||
actorId: 'user1',
|
||||
actorDisplayName: 'User One',
|
||||
objectType: 'files',
|
||||
objectId: 5,
|
||||
message: 'First',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 0)).toUTCString()
|
||||
});
|
||||
var comment2 = new OCA.Comments.CommentModel({
|
||||
id: 2,
|
||||
actorType: 'users',
|
||||
actorId: 'user2',
|
||||
actorDisplayName: 'User Two',
|
||||
objectType: 'files',
|
||||
objectId: 5,
|
||||
message: 'Second\nNewline',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 0, 0)).toUTCString()
|
||||
});
|
||||
var comment3 = new OCA.Comments.CommentModel({
|
||||
id: 3,
|
||||
actorId: 'anotheruser',
|
||||
actorDisplayName: 'Another User',
|
||||
actorType: 'users',
|
||||
verb: 'comment',
|
||||
message: 'Hail to thee, @macbeth. Yours faithfully, @banquo',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString(),
|
||||
mentions: {
|
||||
0: {
|
||||
mentionDisplayName: "Thane of Cawdor",
|
||||
mentionId: "macbeth",
|
||||
mentionTye: "user"
|
||||
},
|
||||
1: {
|
||||
mentionDisplayName: "Lord Banquo",
|
||||
mentionId: "banquo",
|
||||
mentionTye: "user"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
testComments = [comment1, comment2, comment3];
|
||||
});
|
||||
afterEach(function() {
|
||||
view.remove();
|
||||
view = undefined;
|
||||
fetchStub.restore();
|
||||
avatarStub.restore();
|
||||
clock.restore();
|
||||
});
|
||||
describe('rendering', function() {
|
||||
it('reloads matching comments when setting file info model', function() {
|
||||
view.setFileInfo(fileInfoModel);
|
||||
expect(fetchStub.calledOnce).toEqual(true);
|
||||
});
|
||||
|
||||
it('renders loading icon while fetching comments', function() {
|
||||
view.setFileInfo(fileInfoModel);
|
||||
view.collection.trigger('request');
|
||||
|
||||
expect(view.$el.find('.loading').length).toEqual(1);
|
||||
expect(view.$el.find('.comments li').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('renders comments', function() {
|
||||
view.setFileInfo(fileInfoModel);
|
||||
view.collection.set(testComments);
|
||||
|
||||
var $comments = view.$el.find('.comments>li');
|
||||
expect($comments.length).toEqual(3);
|
||||
var $item = $comments.eq(0);
|
||||
expect($item.find('.author').text()).toEqual('User One');
|
||||
expect($item.find('.date').text()).toEqual('seconds ago');
|
||||
expect($item.find('.message').text()).toEqual('First');
|
||||
|
||||
$item = $comments.eq(1);
|
||||
expect($item.find('.author').text()).toEqual('User Two');
|
||||
expect($item.find('.date').text()).toEqual('5 minutes ago');
|
||||
expect($item.find('.message').html()).toEqual('Second<br>Newline');
|
||||
});
|
||||
|
||||
it('renders comments from deleted user differently', function() {
|
||||
testComments[0].set('actorType', 'deleted_users', {silent: true});
|
||||
view.collection.set(testComments);
|
||||
|
||||
var $item = view.$el.find('.comment[data-id=1]');
|
||||
expect($item.find('.author').text()).toEqual('[Deleted user]');
|
||||
expect($item.find('.avatar').attr('data-username')).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('renders mentioned user id to avatar and displayname', function() {
|
||||
view.collection.set(testComments);
|
||||
|
||||
var $comment = view.$el.find('.comment[data-id=3] .message');
|
||||
expect($comment.length).toEqual(1);
|
||||
expect($comment.find('.avatar[data-user=macbeth]').length).toEqual(1);
|
||||
expect($comment.find('strong:first').text()).toEqual('Thane of Cawdor');
|
||||
expect($comment.find('.avatar[data-user=macbeth] ~ .contactsmenu-popover').length).toEqual(1);
|
||||
|
||||
expect($comment.find('.avatar[data-user=banquo]').length).toEqual(1);
|
||||
expect($comment.find('.avatar[data-user=banquo] ~ strong').text()).toEqual('Lord Banquo');
|
||||
expect($comment.find('.avatar[data-user=banquo] ~ .contactsmenu-popover').length).toEqual(1);
|
||||
});
|
||||
|
||||
});
|
||||
describe('more comments', function() {
|
||||
var hasMoreResultsStub;
|
||||
|
||||
beforeEach(function() {
|
||||
view.collection.set(testComments);
|
||||
hasMoreResultsStub = sinon.stub(OCA.Comments.CommentCollection.prototype, 'hasMoreResults');
|
||||
});
|
||||
afterEach(function() {
|
||||
hasMoreResultsStub.restore();
|
||||
});
|
||||
|
||||
it('shows "More comments" button when more comments are available', function() {
|
||||
hasMoreResultsStub.returns(true);
|
||||
view.collection.trigger('sync');
|
||||
|
||||
expect(view.$el.find('.showMore').hasClass('hidden')).toEqual(false);
|
||||
});
|
||||
it('does not show "More comments" button when more comments are available', function() {
|
||||
hasMoreResultsStub.returns(false);
|
||||
view.collection.trigger('sync');
|
||||
|
||||
expect(view.$el.find('.showMore').hasClass('hidden')).toEqual(true);
|
||||
});
|
||||
it('fetches and appends the next page when clicking the "More" button', function() {
|
||||
hasMoreResultsStub.returns(true);
|
||||
|
||||
expect(fetchStub.notCalled).toEqual(true);
|
||||
|
||||
view.$el.find('.showMore').trigger('click');
|
||||
|
||||
expect(fetchStub.calledOnce).toEqual(true);
|
||||
});
|
||||
it('appends comment to the list when added to collection', function() {
|
||||
var comment4 = new OCA.Comments.CommentModel({
|
||||
id: 4,
|
||||
actorType: 'users',
|
||||
actorId: 'user3',
|
||||
actorDisplayName: 'User Three',
|
||||
objectType: 'files',
|
||||
objectId: 5,
|
||||
message: 'Third',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 5, 0, 0)).toUTCString()
|
||||
});
|
||||
|
||||
view.collection.add(comment4);
|
||||
|
||||
expect(view.$el.find('.comments>li').length).toEqual(4);
|
||||
|
||||
var $item = view.$el.find('.comments>li').eq(3);
|
||||
expect($item.find('.author').text()).toEqual('User Three');
|
||||
expect($item.find('.date').text()).toEqual('5 hours ago');
|
||||
expect($item.find('.message').html()).toEqual('Third');
|
||||
});
|
||||
});
|
||||
describe('posting comments', function() {
|
||||
var createStub;
|
||||
var currentUserStub;
|
||||
var $newCommentForm;
|
||||
|
||||
beforeEach(function() {
|
||||
view.collection.set(testComments);
|
||||
createStub = sinon.stub(OCA.Comments.CommentCollection.prototype, 'create');
|
||||
currentUserStub = sinon.stub(OC, 'getCurrentUser');
|
||||
currentUserStub.returns({
|
||||
uid: 'testuser',
|
||||
displayName: 'Test User'
|
||||
});
|
||||
|
||||
$newCommentForm = view.$el.find('.newCommentForm');
|
||||
|
||||
// Required for the absolute selector used to find the new comment
|
||||
// after a successful creation in _onSubmitSuccess.
|
||||
$('#testArea').append(view.$el);
|
||||
});
|
||||
afterEach(function() {
|
||||
createStub.restore();
|
||||
currentUserStub.restore();
|
||||
});
|
||||
|
||||
it('creates a new comment when clicking post button', function() {
|
||||
$newCommentForm.find('.message').text('New message');
|
||||
$newCommentForm.submit();
|
||||
|
||||
expect(createStub.calledOnce).toEqual(true);
|
||||
expect(createStub.lastCall.args[0]).toEqual({
|
||||
actorId: 'testuser',
|
||||
actorDisplayName: 'Test User',
|
||||
actorType: 'users',
|
||||
verb: 'comment',
|
||||
message: 'New message',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
|
||||
});
|
||||
});
|
||||
it('creates a new comment when typing enter', function() {
|
||||
$newCommentForm.find('.message').text('New message');
|
||||
var keydownEvent = new $.Event('keydown', {keyCode: 13});
|
||||
$newCommentForm.find('.message').trigger(keydownEvent);
|
||||
|
||||
expect(createStub.calledOnce).toEqual(true);
|
||||
expect(createStub.lastCall.args[0]).toEqual({
|
||||
actorId: 'testuser',
|
||||
actorDisplayName: 'Test User',
|
||||
actorType: 'users',
|
||||
verb: 'comment',
|
||||
message: 'New message',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
|
||||
});
|
||||
expect(keydownEvent.isDefaultPrevented()).toEqual(true);
|
||||
});
|
||||
it('creates a new mention when typing enter in the autocomplete popover', function() {
|
||||
var autoCompleteStub = sinon.stub(view, '_onAutoComplete');
|
||||
autoCompleteStub.callsArgWith(1, [{"id":"userId", "label":"User Name", "source":"users"}]);
|
||||
|
||||
// Force the autocomplete to be initialized
|
||||
view._initAutoComplete($newCommentForm.find('.message'));
|
||||
|
||||
// PhantomJS does not seem to handle typing in a contenteditable, so
|
||||
// some tricks are needed to show the autocomplete popover.
|
||||
//
|
||||
// Instead of sending key events to type "@u" the characters are
|
||||
// programatically set in the input field.
|
||||
$newCommentForm.find('.message').text('Mention to @u');
|
||||
|
||||
// When focusing on the input field the caret is not guaranteed to
|
||||
// be at the end; instead of calling "focus()" on the input field
|
||||
// the caret is explicitly set at the end of the input field, that
|
||||
// is, after "@u".
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents($newCommentForm.find('.message')[0]);
|
||||
range.collapse(false);
|
||||
var selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
// As PhantomJS does not handle typing in a contenteditable the key
|
||||
// typed here is in practice ignored by At.js, but despite that it
|
||||
// will cause the popover to be shown.
|
||||
$newCommentForm.find('.message').trigger(new $.Event('keydown', {keyCode: 's'}));
|
||||
$newCommentForm.find('.message').trigger(new $.Event('keyup', {keyCode: 's'}));
|
||||
|
||||
expect(autoCompleteStub.calledOnce).toEqual(true);
|
||||
|
||||
var keydownEvent = new $.Event('keydown', {keyCode: 13});
|
||||
$newCommentForm.find('.message').trigger(keydownEvent);
|
||||
|
||||
expect(createStub.calledOnce).toEqual(false);
|
||||
expect($newCommentForm.find('.message').html()).toContain('Mention to <span');
|
||||
expect($newCommentForm.find('.message').html()).toContain('<span class="avatar"');
|
||||
expect($newCommentForm.find('.message').html()).toContain('<strong>User Name</strong>');
|
||||
expect($newCommentForm.find('.message').text()).not.toContain('@');
|
||||
// In this case the default behaviour is prevented by the
|
||||
// "onKeydown" event handler of At.js.
|
||||
expect(keydownEvent.isDefaultPrevented()).toEqual(true);
|
||||
});
|
||||
it('creates a new line when typing shift+enter', function() {
|
||||
$newCommentForm.find('.message').text('New message');
|
||||
var keydownEvent = new $.Event('keydown', {keyCode: 13, shiftKey: true});
|
||||
$newCommentForm.find('.message').trigger(keydownEvent);
|
||||
|
||||
expect(createStub.calledOnce).toEqual(false);
|
||||
// PhantomJS does not seem to handle typing in a contenteditable, so
|
||||
// instead of looking for a new line the best that can be done is
|
||||
// checking that the default behaviour would have been executed.
|
||||
expect($newCommentForm.find('.message').text()).toContain('New message');
|
||||
expect(keydownEvent.isDefaultPrevented()).toEqual(false);
|
||||
});
|
||||
it('creates a new comment with mentions when clicking post button', function() {
|
||||
$newCommentForm.find('.message').text('New message @anotheruser');
|
||||
$newCommentForm.submit();
|
||||
|
||||
var createStubExpectedData = {
|
||||
actorId: 'testuser',
|
||||
actorDisplayName: 'Test User',
|
||||
actorType: 'users',
|
||||
verb: 'comment',
|
||||
message: 'New message @anotheruser',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
|
||||
};
|
||||
|
||||
expect(createStub.calledOnce).toEqual(true);
|
||||
expect(createStub.lastCall.args[0]).toEqual(createStubExpectedData);
|
||||
|
||||
var model = new OCA.Comments.CommentModel(_.extend({id: 4}, createStubExpectedData));
|
||||
var fetchStub = sinon.stub(model, 'fetch');
|
||||
// simulate the fact that create adds the model to the collection
|
||||
view.collection.add(model, {at: 0});
|
||||
createStub.yieldTo('success', model);
|
||||
|
||||
expect(fetchStub.calledOnce).toEqual(true);
|
||||
|
||||
// simulate the fact that fetch sets the attribute
|
||||
model.set('mentions', {
|
||||
0: {
|
||||
mentionDisplayName: "Another User",
|
||||
mentionId: "anotheruser",
|
||||
mentionTye: "user"
|
||||
}
|
||||
});
|
||||
fetchStub.yieldTo('success', model);
|
||||
|
||||
// comment was added to the list
|
||||
var $comment = view.$el.find('.comment[data-id=4]');
|
||||
expect($comment.length).toEqual(1);
|
||||
var $message = $comment.find('.message');
|
||||
expect($message.html()).toContain('New message');
|
||||
expect($message.find('.avatar').length).toEqual(1);
|
||||
expect($message.find('.avatar[data-user=anotheruser]').length).toEqual(1);
|
||||
expect($message.find('.avatar[data-user=anotheruser] ~ strong').text()).toEqual('Another User');
|
||||
expect($message.find('.avatar[data-user=anotheruser] ~ .contactsmenu-popover').length).toEqual(1);
|
||||
});
|
||||
it('does not create a comment if the field is empty', function() {
|
||||
$newCommentForm.find('.message').val(' ');
|
||||
$newCommentForm.submit();
|
||||
|
||||
expect(createStub.notCalled).toEqual(true);
|
||||
});
|
||||
it('does not create a comment if the field length is too large', function() {
|
||||
var bigMessage = '';
|
||||
for (var i = 0; i < view._commentMaxLength * 2; i++) {
|
||||
bigMessage += 'a';
|
||||
}
|
||||
$newCommentForm.find('.message').val(bigMessage);
|
||||
$newCommentForm.submit();
|
||||
|
||||
expect(createStub.notCalled).toEqual(true);
|
||||
});
|
||||
describe('limit indicator', function() {
|
||||
var tooltipStub;
|
||||
var $message;
|
||||
var $submitButton;
|
||||
|
||||
beforeEach(function() {
|
||||
tooltipStub = sinon.stub($.fn, 'tooltip');
|
||||
$message = $newCommentForm.find('.message');
|
||||
$submitButton = $newCommentForm.find('.submit');
|
||||
});
|
||||
afterEach(function() {
|
||||
tooltipStub.restore();
|
||||
});
|
||||
|
||||
it('does not displays tooltip when limit is far away', function() {
|
||||
$message.val(createMessageWithLength(3));
|
||||
$message.trigger('change');
|
||||
|
||||
expect(tooltipStub.calledWith('show')).toEqual(false);
|
||||
expect($submitButton.prop('disabled')).toEqual(false);
|
||||
expect($message.hasClass('error')).toEqual(false);
|
||||
});
|
||||
it('displays tooltip when limit is almost reached', function() {
|
||||
$message.text(createMessageWithLength(view._commentMaxLength - 2));
|
||||
$message.trigger('change');
|
||||
|
||||
expect(tooltipStub.calledWith('show')).toEqual(true);
|
||||
expect($submitButton.prop('disabled')).toEqual(false);
|
||||
expect($message.hasClass('error')).toEqual(false);
|
||||
});
|
||||
it('displays tooltip and disabled button when limit is exceeded', function() {
|
||||
$message.text(createMessageWithLength(view._commentMaxLength + 2));
|
||||
$message.trigger('change');
|
||||
|
||||
expect(tooltipStub.calledWith('show')).toEqual(true);
|
||||
expect($submitButton.prop('disabled')).toEqual(true);
|
||||
expect($message.hasClass('error')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('editing comments', function() {
|
||||
var saveStub;
|
||||
var fetchStub;
|
||||
var currentUserStub;
|
||||
|
||||
beforeEach(function() {
|
||||
saveStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'save');
|
||||
fetchStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'fetch');
|
||||
currentUserStub = sinon.stub(OC, 'getCurrentUser');
|
||||
currentUserStub.returns({
|
||||
uid: 'testuser',
|
||||
displayName: 'Test User'
|
||||
});
|
||||
view.collection.add({
|
||||
id: 1,
|
||||
actorId: 'testuser',
|
||||
actorDisplayName: 'Test User',
|
||||
actorType: 'users',
|
||||
verb: 'comment',
|
||||
message: 'New message',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
|
||||
});
|
||||
view.collection.add({
|
||||
id: 2,
|
||||
actorId: 'anotheruser',
|
||||
actorDisplayName: 'Another User',
|
||||
actorType: 'users',
|
||||
verb: 'comment',
|
||||
message: 'New message from another user',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString(),
|
||||
});
|
||||
view.collection.add({
|
||||
id: 3,
|
||||
actorId: 'testuser',
|
||||
actorDisplayName: 'Test User',
|
||||
actorType: 'users',
|
||||
verb: 'comment',
|
||||
message: 'Hail to thee, @macbeth. Yours faithfully, @banquo',
|
||||
creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString(),
|
||||
mentions: {
|
||||
0: {
|
||||
mentionDisplayName: "Thane of Cawdor",
|
||||
mentionId: "macbeth",
|
||||
mentionTye: "user"
|
||||
},
|
||||
1: {
|
||||
mentionDisplayName: "Lord Banquo",
|
||||
mentionId: "banquo",
|
||||
mentionTye: "user"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
afterEach(function() {
|
||||
saveStub.restore();
|
||||
fetchStub.restore();
|
||||
currentUserStub.restore();
|
||||
});
|
||||
|
||||
it('shows edit link for owner comments', function() {
|
||||
var $comment = view.$el.find('.comment[data-id=1]');
|
||||
expect($comment.length).toEqual(1);
|
||||
$comment.find('.action.more').trigger('click');
|
||||
expect($comment.find('.action.edit').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('does not show edit link for other user\'s comments', function() {
|
||||
var $comment = view.$el.find('.comment[data-id=2]');
|
||||
expect($comment.length).toEqual(1);
|
||||
$comment.find('.action.more').trigger('click');
|
||||
expect($comment.find('.action.edit').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('shows edit form when clicking edit', function() {
|
||||
var $comment = view.$el.find('.comment[data-id=1]');
|
||||
$comment.find('.action.more').trigger('click');
|
||||
$comment.find('.action.edit').trigger('click');
|
||||
|
||||
expect($comment.hasClass('hidden')).toEqual(true);
|
||||
var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
|
||||
expect($formRow.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('saves message and updates comment item when clicking save', function() {
|
||||
var $comment = view.$el.find('.comment[data-id=1]');
|
||||
$comment.find('.action.more').trigger('click');
|
||||
$comment.find('.action.edit').trigger('click');
|
||||
|
||||
var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
|
||||
expect($formRow.length).toEqual(1);
|
||||
|
||||
$formRow.find('div.message').text('modified message');
|
||||
$formRow.find('form').submit();
|
||||
|
||||
expect(saveStub.calledOnce).toEqual(true);
|
||||
expect(saveStub.lastCall.args[0]).toEqual({
|
||||
message: 'modified message'
|
||||
});
|
||||
|
||||
var model = view.collection.get(1);
|
||||
// simulate the fact that save sets the attribute
|
||||
model.set('message', 'modified\nmessage');
|
||||
saveStub.yieldTo('success', model);
|
||||
view.collection.get(model);
|
||||
|
||||
expect(fetchStub.called).toEqual(true);
|
||||
fetchStub.yieldTo('success', model);
|
||||
|
||||
// original comment element is visible again
|
||||
expect($comment.hasClass('hidden')).toEqual(false);
|
||||
// and its message was updated
|
||||
expect($comment.find('.message').html()).toEqual('modified<br>message');
|
||||
|
||||
// form row is gone
|
||||
$formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
|
||||
expect($formRow.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('saves message and updates comment item with mentions when clicking save', function() {
|
||||
var $comment = view.$el.find('.comment[data-id=3]');
|
||||
$comment.find('.action.more').trigger('click');
|
||||
$comment.find('.action.edit').trigger('click');
|
||||
|
||||
var $formRow = view.$el.find('.newCommentRow.comment[data-id=3]');
|
||||
expect($formRow.length).toEqual(1);
|
||||
|
||||
$formRow.find('div.message').text('modified\nmessage @anotheruser');
|
||||
$formRow.find('form').submit();
|
||||
|
||||
expect(saveStub.calledOnce).toEqual(true);
|
||||
expect(saveStub.lastCall.args[0]).toEqual({
|
||||
message: 'modified\nmessage @anotheruser'
|
||||
});
|
||||
|
||||
var model = view.collection.get(3);
|
||||
// simulate the fact that save sets the attribute
|
||||
model.set('message', 'modified\nmessage @anotheruser');
|
||||
saveStub.yieldTo('success', model);
|
||||
|
||||
expect(fetchStub.called).toEqual(true);
|
||||
|
||||
// simulate the fact that fetch sets the attribute
|
||||
model.set('mentions', {
|
||||
0: {
|
||||
mentionDisplayName: "Another User",
|
||||
mentionId: "anotheruser",
|
||||
mentionTye: "user"
|
||||
}
|
||||
});
|
||||
fetchStub.yieldTo('success', model);
|
||||
|
||||
// original comment element is visible again
|
||||
expect($comment.hasClass('hidden')).toEqual(false);
|
||||
// and its message was updated
|
||||
var $message = $comment.find('.message');
|
||||
expect($message.html()).toContain('modified<br>message');
|
||||
expect($message.find('.avatar').length).toEqual(1);
|
||||
expect($message.find('.avatar[data-user=anotheruser]').length).toEqual(1);
|
||||
expect($message.find('.avatar[data-user=anotheruser] ~ strong').text()).toEqual('Another User');
|
||||
expect($message.find('.avatar[data-user=anotheruser] ~ .contactsmenu-popover').length).toEqual(1);
|
||||
|
||||
// form row is gone
|
||||
$formRow = view.$el.find('.newCommentRow.comment[data-id=3]');
|
||||
expect($formRow.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('restores original comment when cancelling', function() {
|
||||
var $comment = view.$el.find('.comment[data-id=1]');
|
||||
$comment.find('.action.more').trigger('click');
|
||||
$comment.find('.action.edit').trigger('click');
|
||||
|
||||
var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
|
||||
expect($formRow.length).toEqual(1);
|
||||
|
||||
$formRow.find('textarea').val('modified\nmessage');
|
||||
$formRow.find('.cancel').trigger('click');
|
||||
|
||||
expect(saveStub.notCalled).toEqual(true);
|
||||
|
||||
// original comment element is visible again
|
||||
expect($comment.hasClass('hidden')).toEqual(false);
|
||||
// and its message was not updated
|
||||
expect($comment.find('.message').html()).toEqual('New message');
|
||||
|
||||
// form row is gone
|
||||
$formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
|
||||
expect($formRow.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('destroys model when clicking delete', function() {
|
||||
var destroyStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'destroy');
|
||||
var $comment = view.$el.find('.comment[data-id=1]');
|
||||
$comment.find('.action.more').trigger('click');
|
||||
$comment.find('.action.delete').trigger('click');
|
||||
|
||||
expect(destroyStub.calledOnce).toEqual(true);
|
||||
expect(destroyStub.thisValues[0].id).toEqual(1);
|
||||
|
||||
destroyStub.yieldTo('success');
|
||||
|
||||
// original comment element is gone
|
||||
$comment = view.$el.find('.comment[data-id=1]');
|
||||
expect($comment.length).toEqual(0);
|
||||
|
||||
destroyStub.restore();
|
||||
});
|
||||
it('does not submit comment if the field is empty', function() {
|
||||
var $comment = view.$el.find('.comment[data-id=1]');
|
||||
$comment.find('.action.edit').trigger('click');
|
||||
$comment.find('.message').val(' ');
|
||||
$comment.find('form').submit();
|
||||
|
||||
expect(saveStub.notCalled).toEqual(true);
|
||||
});
|
||||
it('does not submit comment if the field length is too large', function() {
|
||||
var $comment = view.$el.find('.comment[data-id=1]');
|
||||
$comment.find('.action.edit').trigger('click');
|
||||
$comment.find('.message').val(createMessageWithLength(view._commentMaxLength * 2));
|
||||
$comment.find('form').submit();
|
||||
|
||||
expect(saveStub.notCalled).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('read marker', function() {
|
||||
var updateMarkerStub;
|
||||
|
||||
beforeEach(function() {
|
||||
updateMarkerStub = sinon.stub(OCA.Comments.CommentCollection.prototype, 'updateReadMarker');
|
||||
});
|
||||
afterEach(function() {
|
||||
updateMarkerStub.restore();
|
||||
});
|
||||
|
||||
it('resets the read marker after REPORT', function() {
|
||||
testComments[0].set('isUnread', true, {silent: true});
|
||||
testComments[1].set('isUnread', true, {silent: true});
|
||||
view.collection.set(testComments);
|
||||
view.collection.trigger('sync', 'REPORT');
|
||||
|
||||
expect(updateMarkerStub.calledOnce).toEqual(true);
|
||||
expect(updateMarkerStub.lastCall.args[0]).toBeFalsy();
|
||||
});
|
||||
it('does not reset the read marker if there was no unread comments', function() {
|
||||
view.collection.set(testComments);
|
||||
view.collection.trigger('sync', 'REPORT');
|
||||
|
||||
expect(updateMarkerStub.notCalled).toEqual(true);
|
||||
});
|
||||
it('does not reset the read marker when posting comments', function() {
|
||||
testComments[0].set('isUnread', true, {silent: true});
|
||||
testComments[1].set('isUnread', true, {silent: true});
|
||||
view.collection.set(testComments);
|
||||
view.collection.trigger('sync', 'POST');
|
||||
|
||||
expect(updateMarkerStub.notCalled).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue