summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/systemtags/appinfo/app.php14
-rw-r--r--apps/systemtags/css/systemtagsfilelist.css29
-rw-r--r--apps/systemtags/img/tag.pngbin0 -> 293 bytes
-rw-r--r--apps/systemtags/img/tag.svg5
-rw-r--r--apps/systemtags/js/app.js87
-rw-r--r--apps/systemtags/js/filesplugin.js3
-rw-r--r--apps/systemtags/js/systemtagsfilelist.js240
-rw-r--r--apps/systemtags/list.php25
-rw-r--r--apps/systemtags/templates/list.php38
-rw-r--r--apps/systemtags/tests/js/systemtagsfilelistSpec.js226
-rw-r--r--core/js/files/client.js73
-rw-r--r--core/js/systemtags/systemtagsinputfield.js4
-rw-r--r--core/js/tests/specs/files/clientSpec.js154
-rw-r--r--tests/karma.config.js1
14 files changed, 897 insertions, 2 deletions
diff --git a/apps/systemtags/appinfo/app.php b/apps/systemtags/appinfo/app.php
index 0bb57e1227b..6bcbae4d0da 100644
--- a/apps/systemtags/appinfo/app.php
+++ b/apps/systemtags/appinfo/app.php
@@ -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')
+ )
+);
diff --git a/apps/systemtags/css/systemtagsfilelist.css b/apps/systemtags/css/systemtagsfilelist.css
new file mode 100644
index 00000000000..e8fb665e26b
--- /dev/null
+++ b/apps/systemtags/css/systemtagsfilelist.css
@@ -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');
+}
diff --git a/apps/systemtags/img/tag.png b/apps/systemtags/img/tag.png
new file mode 100644
index 00000000000..5f4767a6f46
--- /dev/null
+++ b/apps/systemtags/img/tag.png
Binary files differ
diff --git a/apps/systemtags/img/tag.svg b/apps/systemtags/img/tag.svg
new file mode 100644
index 00000000000..6024607dd0a
--- /dev/null
+++ b/apps/systemtags/img/tag.svg
@@ -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>
diff --git a/apps/systemtags/js/app.js b/apps/systemtags/js/app.js
index f55aa5c9a6e..d28514358c1 100644
--- a/apps/systemtags/js/app.js
+++ b/apps/systemtags/js/app.js
@@ -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();
+ });
+});
diff --git a/apps/systemtags/js/filesplugin.js b/apps/systemtags/js/filesplugin.js
index 471440c2e09..588037455ae 100644
--- a/apps/systemtags/js/filesplugin.js
+++ b/apps/systemtags/js/filesplugin.js
@@ -23,7 +23,8 @@
OCA.SystemTags.FilesPlugin = {
allowedLists: [
'files',
- 'favorites'
+ 'favorites',
+ 'systemtagsfilter'
],
attach: function(fileList) {
diff --git a/apps/systemtags/js/systemtagsfilelist.js b/apps/systemtags/js/systemtagsfilelist.js
new file mode 100644
index 00000000000..56838018a2c
--- /dev/null
+++ b/apps/systemtags/js/systemtagsfilelist.js
@@ -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;
+})();
diff --git a/apps/systemtags/list.php b/apps/systemtags/list.php
new file mode 100644
index 00000000000..dd4fe01e767
--- /dev/null
+++ b/apps/systemtags/list.php
@@ -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();
diff --git a/apps/systemtags/templates/list.php b/apps/systemtags/templates/list.php
new file mode 100644
index 00000000000..841ce7b5b6d
--- /dev/null
+++ b/apps/systemtags/templates/list.php
@@ -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="" />
+
diff --git a/apps/systemtags/tests/js/systemtagsfilelistSpec.js b/apps/systemtags/tests/js/systemtagsfilelistSpec.js
new file mode 100644
index 00000000000..ba41d347ca4
--- /dev/null
+++ b/apps/systemtags/tests/js/systemtagsfilelistSpec.js
@@ -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);
+ });
+ });
+});
diff --git a/core/js/files/client.js b/core/js/files/client.js
index b736447d65e..55a8e2c485a 100644
--- a/core/js/files/client.js
+++ b/core/js/files/client.js
@@ -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;
}
@@ -415,6 +415,77 @@
},
/**
+ * 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.
*
* @param {String} path path
diff --git a/core/js/systemtags/systemtagsinputfield.js b/core/js/systemtags/systemtagsinputfield.js
index 48fc98c6188..a64e5386102 100644
--- a/core/js/systemtags/systemtagsinputfield.js
+++ b/core/js/systemtags/systemtagsinputfield.js
@@ -425,6 +425,10 @@
}
},
+ getValues: function() {
+ this.$tagsField.select2('val');
+ },
+
setValues: function(values) {
this.$tagsField.select2('val', values);
},
diff --git a/core/js/tests/specs/files/clientSpec.js b/core/js/tests/specs/files/clientSpec.js
index b945e1bb4da..7673ec6e0fc 100644
--- a/core/js/tests/specs/files/clientSpec.js
+++ b/core/js/tests/specs/files/clientSpec.js
@@ -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"?>' +
diff --git a/tests/karma.config.js b/tests/karma.config.js
index 2b569fb7584..111af7a1559 100644
--- a/tests/karma.config.js
+++ b/tests/karma.config.js
@@ -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']