-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #32250 from owncloud/feature/webdav-locking-frontend
Feature/webdav locking frontend
- Loading branch information
Showing
21 changed files
with
2,146 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/* | ||
* Copyright (c) 2018 Thomas Müller <thomas.mueller@tmit.eu> | ||
* | ||
* This file is licensed under the Affero General Public License version 3 | ||
* or later. | ||
* | ||
* See the COPYING-README file. | ||
* | ||
*/ | ||
|
||
(function(OCA) { | ||
var NS_DAV = OC.Files.Client.NS_DAV; | ||
|
||
var TEMPLATE_LOCK_STATUS_ACTION = | ||
'<a class="action action-lock-status permanent" title="{{message}}" href="#">' + | ||
'<span class="icon icon-lock-open" />' + | ||
'</a>'; | ||
|
||
/** | ||
* Parses an XML lock node | ||
* | ||
* @param {Node} xmlvalue node to parse | ||
* @return {Object} parsed values in associative array | ||
*/ | ||
function parseLockNode(xmlvalue) { | ||
return { | ||
lockscope: getChildNodeLocalName(xmlvalue.getElementsByTagNameNS(NS_DAV, 'lockscope')[0]), | ||
locktype: getChildNodeLocalName(xmlvalue.getElementsByTagNameNS(NS_DAV, 'locktype')[0]), | ||
lockroot: getHrefNodeContents(xmlvalue.getElementsByTagNameNS(NS_DAV, 'lockroot')[0]), | ||
// string, as it can also be "infinite" | ||
depth: xmlvalue.getElementsByTagNameNS(NS_DAV, 'depth')[0].textContent, | ||
timeout: xmlvalue.getElementsByTagNameNS(NS_DAV, 'timeout')[0].textContent, | ||
locktoken: getHrefNodeContents(xmlvalue.getElementsByTagNameNS(NS_DAV, 'locktoken')[0]), | ||
owner: xmlvalue.getElementsByTagNameNS(NS_DAV, 'owner')[0].textContent | ||
}; | ||
} | ||
|
||
function getHrefNodeContents(node) { | ||
var nodes = node.getElementsByTagNameNS(NS_DAV, 'href'); | ||
if (!nodes.length) { | ||
return null; | ||
} | ||
return nodes[0].textContent; | ||
} | ||
|
||
/** | ||
* Filter out text nodes from a list of XML nodes | ||
* | ||
* @param {Array.<Node>} nodes nodes to filter | ||
* @return {Array.<Node>} filtered array of nodes | ||
*/ | ||
function getChildNodeLocalName(node) { | ||
for (var i = 0; i < node.childNodes.length; i++) { | ||
// skip pure text nodes | ||
if (node.childNodes[i].nodeType === 1) { | ||
return node.childNodes[i].localName; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
OCA.Files = OCA.Files || {}; | ||
|
||
/** | ||
* @namespace OCA.Files.LockPlugin | ||
*/ | ||
OCA.Files.LockPlugin = { | ||
|
||
/** | ||
* @param fileList | ||
*/ | ||
attach: function(fileList) { | ||
this._extendFileActions(fileList); | ||
|
||
var oldCreateRow = fileList._createRow; | ||
fileList._createRow = function(fileData) { | ||
var $tr = oldCreateRow.apply(this, arguments); | ||
if (fileData.activeLocks) { | ||
$tr.attr('data-activelocks', JSON.stringify(fileData.activeLocks)); | ||
} | ||
return $tr; | ||
}; | ||
var oldElementToFile = fileList.elementToFile; | ||
fileList.elementToFile = function($el) { | ||
var fileInfo = oldElementToFile.apply(this, arguments); | ||
var activeLocks = $el.attr('data-activelocks'); | ||
if (_.isUndefined(activeLocks)) { | ||
activeLocks = '[]'; | ||
} | ||
fileInfo.activeLocks = JSON.parse(activeLocks); | ||
return fileInfo; | ||
}; | ||
|
||
var oldGetWebdavProperties = fileList._getWebdavProperties; | ||
fileList._getWebdavProperties = function() { | ||
var props = oldGetWebdavProperties.apply(this, arguments); | ||
props.push('{DAV:}lockdiscovery'); | ||
return props; | ||
}; | ||
|
||
var lockTab = new OCA.Files.LockTabView('lockTabView', {order: -20}); | ||
fileList.registerTabView(lockTab); | ||
|
||
fileList.filesClient.addFileInfoParser(function(response) { | ||
var data = {}; | ||
var props = response.propStat[0].properties; | ||
var activeLocks = props['{DAV:}lockdiscovery']; | ||
if (!_.isUndefined(activeLocks) && activeLocks !== '') { | ||
data.activeLocks = _.chain(activeLocks).filter(function(xmlvalue) { | ||
return (xmlvalue.namespaceURI === NS_DAV && xmlvalue.nodeName.split(':')[1] === 'activelock'); | ||
}).map(function(xmlvalue) { | ||
return parseLockNode(xmlvalue); | ||
}).value(); | ||
|
||
} | ||
return data; | ||
}); | ||
|
||
|
||
}, | ||
|
||
/** | ||
* @param fileList | ||
* @private | ||
*/ | ||
_extendFileActions: function(fileList) { | ||
var self = this; | ||
fileList.fileActions.registerAction({ | ||
name: 'lock-status', | ||
displayName: t('files', 'Lock status'), | ||
mime: 'all', | ||
permissions: OC.PERMISSION_READ, | ||
type: OCA.Files.FileActions.TYPE_INLINE, | ||
render: function(actionSpec, isDefault, context) { | ||
var $file = context.$file; | ||
var isLocked = $file.data('activelocks'); | ||
if (isLocked && isLocked.length > 0) { | ||
var $actionLink = $(self.renderLink()); | ||
context.$file.find('a.name>span.fileactions').append($actionLink); | ||
return $actionLink; | ||
} | ||
return ''; | ||
}, | ||
actionHandler: function(fileName) { | ||
fileList.showDetailsView(fileName, 'lockTabView'); | ||
} | ||
}); | ||
|
||
}, | ||
|
||
renderLink: function () { | ||
if (!this._template) { | ||
this._template = Handlebars.compile(TEMPLATE_LOCK_STATUS_ACTION); | ||
} | ||
return this._template({ | ||
message: t('files', 'This resource is locked. Click to see more details.') | ||
}); | ||
} | ||
|
||
}; | ||
|
||
})(OCA); | ||
|
||
OC.Plugins.register('OCA.Files.FileList', OCA.Files.LockPlugin); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
* Copyright (c) 2018 | ||
* | ||
* This file is licensed under the Affero General Public License version 3 | ||
* or later. | ||
* | ||
* See the COPYING-README file. | ||
* | ||
*/ | ||
|
||
(function () { | ||
var TEMPLATE = | ||
'<ul class="locks"></ul>' + | ||
'<div class="clear-float"></div>' + | ||
'{{#each locks}}' + | ||
'<div class="lock-entry" data-index="{{index}}">' + | ||
'<div style="display: inline;">{{displayText}}</div>' + | ||
// TODO: no inline css | ||
'<a href="#" class="unlock" style="float: right" title="{{unlockLabel}}">' + | ||
'<span class="icon icon-lock-open" style="display: block" /></a>' + | ||
'</div>' + | ||
'{{else}}' + | ||
'<div class="empty">{{emptyResultLabel}}</div>' + | ||
'{{/each}}' + | ||
''; | ||
|
||
function formatLocks(locks) { | ||
var client = OC.Files.getClient(); | ||
|
||
return _.map(locks, function(lock, index) { | ||
var path = client.getRelativePath(lock.lockroot) || lock.lockroot; | ||
|
||
// TODO: what if user in root doesn't match ? | ||
|
||
return { | ||
index: index, | ||
displayText: t('files', '{owner} has locked this resource via {path}', {owner: lock.owner, path: path}), | ||
locktoken: lock.locktoken, | ||
lockroot: lock.lockroot | ||
}; | ||
}); | ||
} | ||
|
||
/** | ||
* @memberof OCA.Files | ||
*/ | ||
var LockTabView = OCA.Files.DetailTabView.extend( | ||
/** @lends OCA.Files.LockTabView.prototype */ { | ||
id: 'lockTabView', | ||
className: 'tab lockTabView', | ||
|
||
events: { | ||
'click a.unlock': '_onClickUnlock' | ||
}, | ||
|
||
_onClickUnlock: function (event) { | ||
var self = this; | ||
var $target = $(event.target).closest('.lock-entry'); | ||
var lockIndex = parseInt($target.attr('data-index'), 10); | ||
|
||
var currentLock = this.model.get('activeLocks')[lockIndex]; | ||
|
||
// FIXME: move to FileInfoModel | ||
this.model._filesClient.getClient().request('UNLOCK', | ||
currentLock.lockroot, | ||
{ | ||
'Lock-Token': currentLock.locktoken | ||
}).then(function (result) { | ||
if (result.status === 204) { | ||
// implicit clone of array else backbone doesn't fire change event | ||
var locks = _.without(self.model.get('activeLocks') || [], currentLock); | ||
self.model.set('activeLocks', locks); | ||
self.render(); | ||
} | ||
else if (result.status === 403) { | ||
OC.Notification.show(t('files', 'Could not unlock, please contact the lock owner {owner}', {owner: currentLock.owner})); | ||
} else { | ||
// TODO: add more information | ||
OC.Notification.show(t('files', 'Unlock failed with status {status}', {status: result.status})); | ||
} | ||
}); | ||
}, | ||
|
||
getLabel: function () { | ||
return t('files', 'Locks'); | ||
}, | ||
|
||
template: function (data) { | ||
if (!this._template) { | ||
this._template = Handlebars.compile(TEMPLATE); | ||
} | ||
|
||
return this._template(data); | ||
}, | ||
|
||
/** | ||
* Renders this details view | ||
*/ | ||
render: function () { | ||
if (!this.model) { | ||
return; | ||
} | ||
this.$el.html(this.template({ | ||
emptyResultLabel: t('files', 'Resource is not locked'), | ||
locks: formatLocks(this.model.get('activeLocks')) | ||
})); | ||
}, | ||
|
||
/** | ||
* Returns whether the current tab is able to display | ||
* the given file info, for example based on mime type. | ||
* | ||
* @param {OCA.Files.FileInfoModel} fileInfo file info model | ||
* @return {bool} whether to display this tab | ||
*/ | ||
canDisplay: function(fileInfo) { | ||
// don't display if no lock is set | ||
return fileInfo && fileInfo.get('activeLocks') && fileInfo.get('activeLocks').length > 0; | ||
} | ||
}); | ||
|
||
OCA.Files.LockTabView = LockTabView; | ||
})(); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.