Add system tags filter section for files app

This commit is contained in:
Vincent Petry 2016-02-08 11:43:42 +01:00
parent ae367c7e97
commit e378a757ff
14 changed files with 897 additions and 2 deletions

View File

@ -39,9 +39,11 @@ $eventDispatcher->addListener(
\OCP\Util::addScript('systemtags/systemtagscollection');
\OCP\Util::addScript('systemtags/systemtagsinputfield');
\OCP\Util::addScript('systemtags', 'app');
\OCP\Util::addScript('systemtags', 'systemtagsfilelist');
\OCP\Util::addScript('systemtags', 'filesplugin');
\OCP\Util::addScript('systemtags', 'systemtagsinfoview');
\OCP\Util::addStyle('systemtags');
\OCP\Util::addStyle('systemtags', 'systemtagsfilelist');
}
);
@ -73,3 +75,15 @@ $mapperListener = function(MapperEvent $event) use ($activityManager) {
$eventDispatcher->addListener(MapperEvent::EVENT_ASSIGN, $mapperListener);
$eventDispatcher->addListener(MapperEvent::EVENT_UNASSIGN, $mapperListener);
$l = \OC::$server->getL10N('files_sharing');
\OCA\Files\App::getNavigationManager()->add(
array(
'id' => 'systemtagsfilter',
'appname' => 'systemtags',
'script' => 'list.php',
'order' => 9,
'name' => $l->t('Tags')
)
);

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2016
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
#app-content-systemtagsfilter .select2-container {
width: 30%;
}
#app-content-systemtagsfilter .select2-choices {
white-space: nowrap;
text-overflow: ellipsis;
background: #fff;
color: #555;
box-sizing: content-box;
border-radius: 3px;
border: 1px solid #ddd;
margin: 3px 3px 3px 0;
padding: 0;
min-height: auto;
}
.nav-icon-systemtagsfilter {
background-image: url('../img/tag.svg');
}

BIN
apps/systemtags/img/tag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<rect style="color:#000000" fill-opacity="0" height="97.986" width="163.31" y="-32.993" x="-62.897"/>
<path opacity=".5" style="color:#000000" d="m6 1c-2.7614 0-5 2.2386-5 5s2.2386 5 5 5c0.98478 0 1.8823-0.28967 2.6562-0.78125l4.4688 4.625c0.09558 0.10527 0.22619 0.16452 0.375 0.15625 0.14882-0.0083 0.3031-0.07119 0.40625-0.1875l0.9375-1.0625c0.19194-0.22089 0.19549-0.53592 0-0.71875l-4.594-4.406c0.478-0.7663 0.75-1.6555 0.75-2.625 0-2.7614-2.2386-5-5-5zm0 2c1.6569 0 3 1.3431 3 3s-1.3431 3-3 3-3-1.3431-3-3 1.3431-3 3-3z"/>
</svg>

After

Width:  |  Height:  |  Size: 813 B

View File

@ -16,5 +16,92 @@
OCA.SystemTags = {};
}
OCA.SystemTags.App = {
initFileList: function($el) {
if (this._fileList) {
return this._fileList;
}
this._fileList = new OCA.SystemTags.FileList(
$el,
{
id: 'systemtags',
scrollContainer: $('#app-content'),
fileActions: this._createFileActions()
}
);
this._fileList.appName = t('systemtags', 'Tags');
return this._fileList;
},
removeFileList: function() {
if (this._fileList) {
this._fileList.$fileList.empty();
}
},
_createFileActions: function() {
// inherit file actions from the files app
var fileActions = new OCA.Files.FileActions();
// note: not merging the legacy actions because legacy apps are not
// compatible with the sharing overview and need to be adapted first
fileActions.registerDefaultActions();
fileActions.merge(OCA.Files.fileActions);
if (!this._globalActionsInitialized) {
// in case actions are registered later
this._onActionsUpdated = _.bind(this._onActionsUpdated, this);
OCA.Files.fileActions.on('setDefault.app-systemtags', this._onActionsUpdated);
OCA.Files.fileActions.on('registerAction.app-systemtags', this._onActionsUpdated);
this._globalActionsInitialized = true;
}
// when the user clicks on a folder, redirect to the corresponding
// folder in the files app instead of opening it directly
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
OCA.Files.App.setActiveView('files', {silent: true});
OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true);
});
fileActions.setDefault('dir', 'Open');
return fileActions;
},
_onActionsUpdated: function(ev) {
if (!this._fileList) {
return;
}
if (ev.action) {
this._fileList.fileActions.registerAction(ev.action);
} else if (ev.defaultAction) {
this._fileList.fileActions.setDefault(
ev.defaultAction.mime,
ev.defaultAction.name
);
}
},
/**
* Destroy the app
*/
destroy: function() {
OCA.Files.fileActions.off('setDefault.app-systemtags', this._onActionsUpdated);
OCA.Files.fileActions.off('registerAction.app-systemtags', this._onActionsUpdated);
this.removeFileList();
this._fileList = null;
delete this._globalActionsInitialized;
}
};
})();
$(document).ready(function() {
$('#app-content-systemtagsfilter').on('show', function(e) {
OCA.SystemTags.App.initFileList($(e.target));
});
$('#app-content-systemtagsfilter').on('hide', function() {
OCA.SystemTags.App.removeFileList();
});
});

View File

@ -23,7 +23,8 @@
OCA.SystemTags.FilesPlugin = {
allowedLists: [
'files',
'favorites'
'favorites',
'systemtagsfilter'
],
attach: function(fileList) {

View File

@ -0,0 +1,240 @@
/*
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com>
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function() {
/**
* @class OCA.SystemTags.FileList
* @augments OCA.Files.FileList
*
* @classdesc SystemTags file list.
* Contains a list of files filtered by system tags.
*
* @param $el container element with existing markup for the #controls
* and a table
* @param [options] map of options, see other parameters
* @param {Array.<string>} [options.systemTagIds] array of system tag ids to
* filter by
*/
var FileList = function($el, options) {
this.initialize($el, options);
};
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
/** @lends OCA.SystemTags.FileList.prototype */ {
id: 'systemtagsfilter',
appName: t('systemtags', 'Tagged files'),
/**
* Array of system tag ids to filter by
*
* @type Array.<string>
*/
_systemTagIds: [],
_clientSideSort: true,
_allowSelection: false,
_filterField: null,
/**
* @private
*/
initialize: function($el, options) {
OCA.Files.FileList.prototype.initialize.apply(this, arguments);
if (this.initialized) {
return;
}
if (options && options.systemTagIds) {
this._systemTagIds = options.systemTagIds;
}
OC.Plugins.attach('OCA.SystemTags.FileList', this);
var $controls = this.$el.find('#controls').empty();
this._initFilterField($controls);
},
destroy: function() {
this.$filterField.remove();
OCA.Files.FileList.prototype.destroy.apply(this, arguments);
},
_initFilterField: function($container) {
this.$filterField = $('<input type="hidden" name="tags"/>');
$container.append(this.$filterField);
this.$filterField.select2({
placeholder: t('systemtags', 'Select tags to filter by'),
allowClear: false,
multiple: true,
separator: ',',
query: _.bind(this._queryTagsAutocomplete, this),
id: function(tag) {
return tag.id;
},
initSelection: function(element, callback) {
var val = $(element).val().trim();
if (val) {
var tagIds = val.split(','),
tags = [];
OC.SystemTags.collection.fetch({
success: function() {
_.each(tagIds, function(tagId) {
var tag = OC.SystemTags.collection.get(tagId);
if (!_.isUndefined(tag)) {
tags.push(tag.toJSON());
}
});
callback(tags);
}
});
} else {
callback([]);
}
},
formatResult: function (tag) {
return OC.SystemTags.getDescriptiveTag(tag);
},
formatSelection: function (tag) {
return OC.SystemTags.getDescriptiveTag(tag)[0].outerHTML;
},
escapeMarkup: function(m) {
// prevent double markup escape
return m;
}
});
this.$filterField.on('change', _.bind(this._onTagsChanged, this));
return this.$filterField;
},
/**
* Autocomplete function for dropdown results
*
* @param {Object} query select2 query object
*/
_queryTagsAutocomplete: function(query) {
OC.SystemTags.collection.fetch({
success: function() {
var results = OC.SystemTags.collection.filterByName(query.term);
query.callback({
results: _.invoke(results, 'toJSON')
});
}
});
},
/**
* Event handler for when the URL changed
*/
_onUrlChanged: function(e) {
if (e.dir) {
var tags = _.filter(e.dir.split('/'), function(val) { return val.trim() !== ''; });
this.$filterField.select2('val', tags || []);
this._systemTagIds = tags;
this.reload();
}
},
_onTagsChanged: function(ev) {
var val = $(ev.target).val().trim();
if (val !== '') {
this._systemTagIds = val.split(',');
} else {
this._systemTagIds = [];
}
this.$el.trigger(jQuery.Event('changeDirectory', {
dir: this._systemTagIds.join('/')
}));
this.reload();
},
updateEmptyContent: function() {
var dir = this.getCurrentDirectory();
if (dir === '/') {
// root has special permissions
if (!this._systemTagIds.length) {
// no tags selected
this.$el.find('#emptycontent').html('<div class="icon-systemtags"></div>' +
'<h2>' + t('systemtags', 'Please select tags to filter by') + '</h2>');
} else {
// tags selected but no results
this.$el.find('#emptycontent').html('<div class="icon-systemtags"></div>' +
'<h2>' + t('systemtags', 'No files found for the selected tags') + '</h2>');
}
this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
}
else {
OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments);
}
},
getDirectoryPermissions: function() {
return OC.PERMISSION_READ | OC.PERMISSION_DELETE;
},
updateStorageStatistics: function() {
// no op because it doesn't have
// storage info like free space / used space
},
reload: function() {
if (!this._systemTagIds.length) {
// don't reload
this.updateEmptyContent();
this.setFiles([]);
return $.Deferred().resolve();
}
this._selectedFiles = {};
this._selectionSummary.clear();
if (this._currentFileModel) {
this._currentFileModel.off();
}
this._currentFileModel = null;
this.$el.find('.select-all').prop('checked', false);
this.showMask();
this._reloadCall = this.filesClient.getFilteredFiles(
{
systemTagIds: this._systemTagIds
},
{
properties: this._getWebdavProperties()
}
);
if (this._detailsView) {
// close sidebar
this._updateDetailsView(null);
}
var callBack = this.reloadCallback.bind(this);
return this._reloadCall.then(callBack, callBack);
},
reloadCallback: function(status, result) {
if (result) {
// prepend empty dir info because original handler
result.unshift({});
}
return OCA.Files.FileList.prototype.reloadCallback.call(this, status, result);
}
});
OCA.SystemTags.FileList = FileList;
})();

25
apps/systemtags/list.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/**
* @author Vincent Petry <pvince81@owncloud.com>
*
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
// Check if we are a user
OCP\User::checkLoggedIn();
$tmpl = new OCP\Template('systemtags', 'list', '');
$tmpl->printPage();

View File

@ -0,0 +1,38 @@
<div id="controls">
</div>
<div id="emptycontent" class="hidden">
<div class="icon-folder"></div>
<h2><?php p($l->t('No files in here')); ?></h2>
<p class="uploadmessage hidden"></p>
</div>
<div class="nofilterresults emptycontent hidden">
<div class="icon-search"></div>
<h2><?php p($l->t('No entries found in this folder')); ?></h2>
<p></p>
</div>
<table id="filestable" data-preview-x="32" data-preview-y="32">
<thead>
<tr>
<th id='headerName' class="hidden column-name">
<div id="headerName-container">
<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a>
</div>
</th>
<th id="headerSize" class="hidden column-size">
<a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a>
</th>
<th id="headerDate" class="hidden column-mtime">
<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a>
</th>
</tr>
</thead>
<tbody id="fileList">
</tbody>
<tfoot>
</tfoot>
</table>
<input type="hidden" name="dir" id="dir" value="" />

View File

@ -0,0 +1,226 @@
/*
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com>
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
describe('OCA.SystemTags.FileList tests', function() {
var FileInfo = OC.Files.FileInfo;
var fileList;
beforeEach(function() {
// init parameters and test table elements
$('#testArea').append(
'<div id="app-content-container">' +
// init horrible parameters
'<input type="hidden" id="dir" value="/"></input>' +
'<input type="hidden" id="permissions" value="31"></input>' +
'<div id="controls"></div>' +
// dummy table
// TODO: at some point this will be rendered by the fileList class itself!
'<table id="filestable">' +
'<thead><tr>' +
'<th id="headerName" class="hidden column-name">' +
'<input type="checkbox" id="select_all_files" class="select-all">' +
'<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' +
'<span class="selectedActions hidden">' +
'</th>' +
'<th class="hidden column-mtime">' +
'<a class="columntitle" data-sort="mtime"><span class="sort-indicator"></span></a>' +
'</th>' +
'</tr></thead>' +
'<tbody id="fileList"></tbody>' +
'<tfoot></tfoot>' +
'</table>' +
'<div id="emptycontent">Empty content message</div>' +
'</div>'
);
});
afterEach(function() {
fileList.destroy();
fileList = undefined;
});
describe('filter field', function() {
var select2Stub, oldCollection, fetchTagsStub;
var $tagsField;
beforeEach(function() {
fetchTagsStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'fetch');
select2Stub = sinon.stub($.fn, 'select2');
oldCollection = OC.SystemTags.collection;
OC.SystemTags.collection = new OC.SystemTags.SystemTagsCollection([
{
id: '123',
name: 'abc'
},
{
id: '456',
name: 'def'
}
]);
fileList = new OCA.SystemTags.FileList(
$('#app-content-container'), {
systemTagIds: []
}
);
$tagsField = fileList.$el.find('[name=tags]');
});
afterEach(function() {
select2Stub.restore();
fetchTagsStub.restore();
OC.SystemTags.collection = oldCollection;
});
it('inits select2 on filter field', function() {
expect(select2Stub.calledOnce).toEqual(true);
});
it('uses global system tags collection', function() {
var callback = sinon.stub();
var opts = select2Stub.firstCall.args[0];
$tagsField.val('123');
opts.initSelection($tagsField, callback);
expect(callback.notCalled).toEqual(true);
expect(fetchTagsStub.calledOnce).toEqual(true);
fetchTagsStub.yieldTo('success', fetchTagsStub.thisValues[0]);
expect(callback.calledOnce).toEqual(true);
expect(callback.lastCall.args[0]).toEqual([
OC.SystemTags.collection.get('123').toJSON()
]);
});
it('fetches tag list from the global collection', function() {
var callback = sinon.stub();
var opts = select2Stub.firstCall.args[0];
$tagsField.val('123');
opts.query({
term: 'de',
callback: callback
});
expect(fetchTagsStub.calledOnce).toEqual(true);
expect(callback.notCalled).toEqual(true);
fetchTagsStub.yieldTo('success', fetchTagsStub.thisValues[0]);
expect(callback.calledOnce).toEqual(true);
expect(callback.lastCall.args[0]).toEqual({
results: [
OC.SystemTags.collection.get('456').toJSON()
]
});
});
it('reloads file list after selection', function() {
var reloadStub = sinon.stub(fileList, 'reload');
$tagsField.val('456,123').change();
expect(reloadStub.calledOnce).toEqual(true);
reloadStub.restore();
});
it('updates URL after selection', function() {
var handler = sinon.stub();
fileList.$el.on('changeDirectory', handler);
$tagsField.val('456,123').change();
expect(handler.calledOnce).toEqual(true);
expect(handler.lastCall.args[0].dir).toEqual('456/123');
});
it('updates tag selection when url changed', function() {
fileList.$el.trigger(new $.Event('urlChanged', {dir: '456/123'}));
expect(select2Stub.lastCall.args[0]).toEqual('val');
expect(select2Stub.lastCall.args[1]).toEqual(['456', '123']);
});
});
describe('loading results', function() {
var getFilteredFilesSpec, requestDeferred;
beforeEach(function() {
requestDeferred = new $.Deferred();
getFilteredFilesSpec = sinon.stub(OC.Files.Client.prototype, 'getFilteredFiles')
.returns(requestDeferred.promise());
});
afterEach(function() {
getFilteredFilesSpec.restore();
});
it('renders empty message when no tags were set', function() {
fileList = new OCA.SystemTags.FileList(
$('#app-content-container'), {
systemTagIds: []
}
);
fileList.reload();
expect(fileList.$el.find('#emptycontent').hasClass('hidden')).toEqual(false);
expect(getFilteredFilesSpec.notCalled).toEqual(true);
});
it('render files', function() {
fileList = new OCA.SystemTags.FileList(
$('#app-content-container'), {
systemTagIds: ['123', '456']
}
);
fileList.reload();
expect(getFilteredFilesSpec.calledOnce).toEqual(true);
expect(getFilteredFilesSpec.lastCall.args[0].systemTagIds).toEqual(['123', '456']);
var testFiles = [new FileInfo({
id: 1,
type: 'file',
name: 'One.txt',
mimetype: 'text/plain',
mtime: 123456789,
size: 12,
etag: 'abc',
permissions: OC.PERMISSION_ALL
}), new FileInfo({
id: 2,
type: 'file',
name: 'Two.jpg',
mimetype: 'image/jpeg',
mtime: 234567890,
size: 12049,
etag: 'def',
permissions: OC.PERMISSION_ALL
}), new FileInfo({
id: 3,
type: 'file',
name: 'Three.pdf',
mimetype: 'application/pdf',
mtime: 234560000,
size: 58009,
etag: '123',
permissions: OC.PERMISSION_ALL
}), new FileInfo({
id: 4,
type: 'dir',
name: 'somedir',
mimetype: 'httpd/unix-directory',
mtime: 134560000,
size: 250,
etag: '456',
permissions: OC.PERMISSION_ALL
})];
requestDeferred.resolve(207, testFiles);
expect(fileList.$el.find('#emptycontent').hasClass('hidden')).toEqual(true);
expect(fileList.$el.find('tbody>tr').length).toEqual(4);
});
});
});

View File

@ -241,7 +241,7 @@
path = decodeURIComponent(path);
if (response.propStat.length === 1 && response.propStat[0].status !== 200) {
if (response.propStat.length === 0 || response.propStat[0].status !== 'HTTP/1.1 200 OK') {
return null;
}
@ -414,6 +414,77 @@
return promise;
},
/**
* Fetches a flat list of files filtered by a given filter criteria.
* (currently only system tags is supported)
*
* @param {Object} filter filter criteria
* @param {Object} [filter.systemTagIds] list of system tag ids to filter by
* @param {Object} [options] options
* @param {Array} [options.properties] list of Webdav properties to retrieve
*
* @return {Promise} promise
*/
getFilteredFiles: function(filter, options) {
options = options || {};
var self = this;
var deferred = $.Deferred();
var promise = deferred.promise();
var properties;
if (_.isUndefined(options.properties)) {
properties = this.getPropfindProperties();
} else {
properties = options.properties;
}
if (!filter || !filter.systemTagIds || !filter.systemTagIds.length) {
throw 'Missing filter argument';
}
var headers = _.extend({}, this._defaultHeaders);
// root element with namespaces
var body = '<oc:filter-files ';
var namespace;
for (namespace in this._client.xmlNamespaces) {
body += ' xmlns:' + this._client.xmlNamespaces[namespace] + '="' + namespace + '"';
}
body += '>\n';
// properties query
body += ' <' + this._client.xmlNamespaces['DAV:'] + ':prop>\n';
_.each(properties, function(prop) {
var property = self._client.parseClarkNotation(prop);
body += ' <' + self._client.xmlNamespaces[property.namespace] + ':' + property.name + ' />\n';
});
body += ' </' + this._client.xmlNamespaces['DAV:'] + ':prop>\n';
// rules block
body += ' <oc:filter-rules>\n';
_.each(filter.systemTagIds, function(systemTagIds) {
body += ' <oc:systemtag>' + escapeHTML(systemTagIds) + '</oc:systemtag>\n';
});
body += ' </oc:filter-rules>\n';
// end of root
body += '</oc:filter-files>\n';
this._client.request(
'REPORT',
this._buildUrl(),
headers,
body
).then(function(result) {
if (self._isSuccessStatus(result.status)) {
var results = self._parseResult(result.body);
deferred.resolve(result.status, results);
} else {
deferred.reject(result.status);
}
});
return promise;
},
/**
* Returns the file info of a given path.
*

View File

@ -425,6 +425,10 @@
}
},
getValues: function() {
this.$tagsField.select2('val');
},
setValues: function(values) {
this.$tagsField.select2('val', values);
},

View File

@ -318,6 +318,160 @@ describe('OC.Files.Client tests', function() {
});
});
describe('file filtering', function() {
// TODO: switch this to the already parsed structure
var folderContentsXml = dav.Client.prototype.parseMultiStatus(
'<?xml version="1.0" encoding="utf-8"?>' +
'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
makeResponseBlock(
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/',
{
'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
'd:getetag': '"56cfcabd79abb"',
'd:resourcetype': '<d:collection/>',
'oc:id': '00000011oc2d13a6a068',
'oc:fileid': '11',
'oc:permissions': 'RDNVCK',
'oc:size': '120'
},
[
'd:getcontenttype',
'd:getcontentlength'
]
) +
makeResponseBlock(
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt',
{
'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT',
'd:getetag': '"559fcabd79a38"',
'd:getcontenttype': 'text/plain',
'd:getcontentlength': 250,
'd:resourcetype': '',
'oc:id': '00000051oc2d13a6a068',
'oc:fileid': '51',
'oc:permissions': 'RDNVW'
},
[
'oc:size',
]
) +
makeResponseBlock(
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/sub',
{
'd:getlastmodified': 'Fri, 10 Jul 2015 14:00:00 GMT',
'd:getetag': '"66cfcabd79abb"',
'd:resourcetype': '<d:collection/>',
'oc:id': '00000015oc2d13a6a068',
'oc:fileid': '15',
'oc:permissions': 'RDNVCK',
'oc:size': '100'
},
[
'd:getcontenttype',
'd:getcontentlength'
]
) +
'</d:multistatus>'
);
it('sends REPORT with filter information', function() {
client.getFilteredFiles({
systemTagIds: ['123', '456']
});
expect(requestStub.calledOnce).toEqual(true);
expect(requestStub.lastCall.args[0]).toEqual('REPORT');
expect(requestStub.lastCall.args[1]).toEqual(baseUrl);
var body = requestStub.lastCall.args[3];
var doc = (new window.DOMParser()).parseFromString(
body,
'application/xml'
);
var ns = 'http://owncloud.org/ns';
expect(doc.documentElement.localName).toEqual('filter-files');
expect(doc.documentElement.namespaceURI).toEqual(ns);
var filterRoots = doc.getElementsByTagNameNS(ns, 'filter-rules');
var rulesList = filterRoots[0] = doc.getElementsByTagNameNS(ns, 'systemtag');
expect(rulesList.length).toEqual(2);
expect(rulesList[0].localName).toEqual('systemtag');
expect(rulesList[0].namespaceURI).toEqual(ns);
expect(rulesList[0].textContent).toEqual('123');
expect(rulesList[1].localName).toEqual('systemtag');
expect(rulesList[1].namespaceURI).toEqual(ns);
expect(rulesList[1].textContent).toEqual('456');
});
it('sends REPORT with explicit properties to filter file list', function() {
client.getFilteredFiles({
systemTagIds: ['123', '456']
});
expect(requestStub.calledOnce).toEqual(true);
expect(requestStub.lastCall.args[0]).toEqual('REPORT');
expect(requestStub.lastCall.args[1]).toEqual(baseUrl);
var props = getRequestedProperties(requestStub.lastCall.args[3]);
expect(props).toContain('{DAV:}getlastmodified');
expect(props).toContain('{DAV:}getcontentlength');
expect(props).toContain('{DAV:}getcontenttype');
expect(props).toContain('{DAV:}getetag');
expect(props).toContain('{DAV:}resourcetype');
expect(props).toContain('{http://owncloud.org/ns}fileid');
expect(props).toContain('{http://owncloud.org/ns}size');
expect(props).toContain('{http://owncloud.org/ns}permissions');
});
it('parses the result list into a FileInfo array', function() {
var promise = client.getFilteredFiles({
systemTagIds: ['123', '456']
});
expect(requestStub.calledOnce).toEqual(true);
requestDeferred.resolve({
status: 207,
body: folderContentsXml
});
promise.then(function(status, response) {
expect(status).toEqual(207);
expect(_.isArray(response)).toEqual(true);
// returns all entries
expect(response.length).toEqual(3);
// file entry
var info = response[0];
expect(info instanceof OC.Files.FileInfo).toEqual(true);
expect(info.id).toEqual(11);
// file entry
var info = response[1];
expect(info instanceof OC.Files.FileInfo).toEqual(true);
expect(info.id).toEqual(51);
// sub entry
info = response[2];
expect(info instanceof OC.Files.FileInfo).toEqual(true);
expect(info.id).toEqual(15);
});
});
it('throws exception if arguments are missing', function() {
var thrown = null;
try {
client.getFilteredFiles({
systemTagIds: []
});
} catch (e) {
thrown = true;
}
expect(thrown).toEqual(true);
});
});
describe('file info', function() {
var responseXml = dav.Client.prototype.parseMultiStatus(
'<?xml version="1.0" encoding="utf-8"?>' +

View File

@ -101,6 +101,7 @@ module.exports = function(config) {
// need to enforce loading order...
'apps/systemtags/js/app.js',
'apps/systemtags/js/systemtagsinfoview.js',
'apps/systemtags/js/systemtagsfilelist.js',
'apps/systemtags/js/filesplugin.js'
],
testFiles: ['apps/systemtags/tests/js/**/*.js']