Merge pull request #40475 from nextcloud/feat/f2v/systemtags
commit
085568b75c
@ -0,0 +1,40 @@ |
||||
<?php |
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> |
||||
* |
||||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
||||
* |
||||
* @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/> |
||||
* |
||||
*/ |
||||
namespace OCA\SystemTags; |
||||
|
||||
use OCP\Capabilities\ICapability; |
||||
|
||||
class Capabilities implements ICapability { |
||||
/** |
||||
* @return array{systemtags: array{enabled: true}} |
||||
*/ |
||||
public function getCapabilities() { |
||||
$capabilities = [ |
||||
'systemtags' => [ |
||||
'enabled' => true, |
||||
] |
||||
]; |
||||
return $capabilities; |
||||
} |
||||
} |
@ -1,131 +0,0 @@ |
||||
/** |
||||
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> |
||||
* |
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* @author Daniel Calviño Sánchez <danxuliu@gmail.com> |
||||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
||||
* @author Vincent Petry <vincent@nextcloud.com> |
||||
* |
||||
* @license AGPL-3.0-or-later |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* 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 |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
(function() { |
||||
if (!OCA.SystemTags) { |
||||
/** |
||||
* @namespace |
||||
*/ |
||||
OCA.SystemTags = {} |
||||
} |
||||
|
||||
OCA.SystemTags.App = { |
||||
|
||||
initFileList($el) { |
||||
if (this._fileList) { |
||||
return this._fileList |
||||
} |
||||
|
||||
const tagsParam = (new URL(window.location.href)).searchParams.get('tags') |
||||
const initialTags = tagsParam ? tagsParam.split(',').map(parseInt) : [] |
||||
|
||||
this._fileList = new OCA.SystemTags.FileList( |
||||
$el, |
||||
{ |
||||
id: 'systemtags', |
||||
fileActions: this._createFileActions(), |
||||
config: OCA.Files.App.getFilesConfig(), |
||||
// The file list is created when a "show" event is handled,
|
||||
// so it should be marked as "shown" like it would have been
|
||||
// done if handling the event with the file list already
|
||||
// created.
|
||||
shown: true, |
||||
systemTagIds: initialTags, |
||||
} |
||||
) |
||||
|
||||
this._fileList.appName = t('systemtags', 'Tags') |
||||
return this._fileList |
||||
}, |
||||
|
||||
removeFileList() { |
||||
if (this._fileList) { |
||||
this._fileList.$fileList.empty() |
||||
} |
||||
}, |
||||
|
||||
_createFileActions() { |
||||
// inherit file actions from the files app
|
||||
const 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(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() { |
||||
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 |
||||
}, |
||||
} |
||||
|
||||
})() |
||||
|
||||
window.addEventListener('DOMContentLoaded', function() { |
||||
$('#app-content-systemtagsfilter').on('show', function(e) { |
||||
OCA.SystemTags.App.initFileList($(e.target)) |
||||
}) |
||||
$('#app-content-systemtagsfilter').on('hide', function() { |
||||
OCA.SystemTags.App.removeFileList() |
||||
}) |
||||
}) |
@ -1,22 +0,0 @@ |
||||
/* |
||||
* 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%; |
||||
margin-left: 10px; |
||||
} |
||||
|
||||
#app-sidebar .app-sidebar-header__action .tag-label { |
||||
cursor: pointer; |
||||
padding: 13px 0; |
||||
display: flex; |
||||
color: var(--color-text-light); |
||||
position: relative; |
||||
margin-top: -20px; |
||||
} |
@ -0,0 +1,97 @@ |
||||
/** |
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> |
||||
* |
||||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
||||
* |
||||
* @license AGPL-3.0-or-later |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* 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 |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
import type { FileStat, ResponseDataDetailed } from 'webdav' |
||||
import type { TagWithId } from '../types' |
||||
|
||||
import { Folder, type ContentsWithRoot, Permission, getDavNameSpaces, getDavProperties } from '@nextcloud/files' |
||||
import { generateRemoteUrl } from '@nextcloud/router' |
||||
import { getCurrentUser } from '@nextcloud/auth' |
||||
|
||||
import { fetchTags } from './api' |
||||
import { getClient } from '../../../files/src/services/WebdavClient' |
||||
import { resultToNode } from '../../../files/src/services/Files' |
||||
|
||||
const formatReportPayload = (tagId: number) => `<?xml version="1.0"?>
|
||||
<oc:filter-files ${getDavNameSpaces()}> |
||||
<d:prop> |
||||
${getDavProperties()} |
||||
</d:prop> |
||||
<oc:filter-rules> |
||||
<oc:systemtag>${tagId}</oc:systemtag> |
||||
</oc:filter-rules> |
||||
</oc:filter-files>` |
||||
|
||||
const tagToNode = function(tag: TagWithId): Folder { |
||||
return new Folder({ |
||||
id: tag.id, |
||||
source: generateRemoteUrl('dav/systemtags/' + tag.id), |
||||
owner: getCurrentUser()?.uid as string, |
||||
root: '/systemtags', |
||||
permissions: Permission.READ, |
||||
attributes: { |
||||
...tag, |
||||
'is-tag': true, |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
export const getContents = async (path = '/'): Promise<ContentsWithRoot> => { |
||||
// List tags in the root
|
||||
const tagsCache = (await fetchTags()).filter(tag => tag.userVisible) as TagWithId[] |
||||
|
||||
if (path === '/') { |
||||
return { |
||||
folder: new Folder({ |
||||
id: 0, |
||||
source: generateRemoteUrl('dav/systemtags'), |
||||
owner: getCurrentUser()?.uid as string, |
||||
root: '/systemtags', |
||||
permissions: Permission.NONE, |
||||
}), |
||||
contents: tagsCache.map(tagToNode), |
||||
} |
||||
} |
||||
|
||||
const tagId = parseInt(path.replace('/', ''), 10) |
||||
const tag = tagsCache.find(tag => tag.id === tagId) |
||||
|
||||
if (!tag) { |
||||
throw new Error('Tag not found') |
||||
} |
||||
|
||||
const folder = tagToNode(tag) |
||||
const contentsResponse = await getClient().getDirectoryContents('/', { |
||||
details: true, |
||||
// Only filter favorites if we're at the root
|
||||
data: formatReportPayload(tagId), |
||||
headers: { |
||||
// Patched in WebdavClient.ts
|
||||
method: 'REPORT', |
||||
}, |
||||
}) as ResponseDataDetailed<FileStat[]> |
||||
|
||||
return { |
||||
folder, |
||||
contents: contentsResponse.data.map(resultToNode), |
||||
} |
||||
|
||||
} |
@ -1,355 +0,0 @@ |
||||
/** |
||||
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com> |
||||
* |
||||
* @author Joas Schilling <coding@schilljs.com> |
||||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
||||
* @author Vincent Petry <vincent@nextcloud.com> |
||||
* |
||||
* @license AGPL-3.0-or-later |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* 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 |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
(function() { |
||||
/** |
||||
* @class OCA.SystemTags.FileList |
||||
* @augments OCA.Files.FileList |
||||
* |
||||
* @classdesc SystemTags file list. |
||||
* Contains a list of files filtered by system tags. |
||||
* |
||||
* @param {object} $el container element with existing markup for the .files-controls and a table |
||||
* @param {Array} [options] map of options, see other parameters |
||||
* @param {Array.<string>} [options.systemTagIds] array of system tag ids to |
||||
* filter by |
||||
*/ |
||||
const 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: [], |
||||
_lastUsedTags: [], |
||||
|
||||
_clientSideSort: true, |
||||
_allowSelection: false, |
||||
|
||||
_filterField: null, |
||||
|
||||
/** |
||||
* @private |
||||
* @param {object} $el container element |
||||
* @param {object} [options] map of options, see other parameters |
||||
*/ |
||||
initialize($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) |
||||
|
||||
const $controls = this.$el.find('.files-controls').empty() |
||||
|
||||
_.defer(_.bind(this._getLastUsedTags, this)) |
||||
this._initFilterField($controls) |
||||
}, |
||||
|
||||
destroy() { |
||||
this.$filterField.remove() |
||||
|
||||
OCA.Files.FileList.prototype.destroy.apply(this, arguments) |
||||
}, |
||||
|
||||
_getLastUsedTags() { |
||||
const self = this |
||||
$.ajax({ |
||||
type: 'GET', |
||||
url: OC.generateUrl('/apps/systemtags/lastused'), |
||||
success(response) { |
||||
self._lastUsedTags = response |
||||
}, |
||||
}) |
||||
}, |
||||
|
||||
_initFilterField($container) { |
||||
const self = this |
||||
this.$filterField = $('<input type="hidden" name="tags"/>') |
||||
this.$filterField.val(this._systemTagIds.join(',')) |
||||
$container.append(this.$filterField) |
||||
this.$filterField.select2({ |
||||
placeholder: t('systemtags', 'Select tags to filter by'), |
||||
allowClear: false, |
||||
multiple: true, |
||||
toggleSelect: true, |
||||
separator: ',', |
||||
query: _.bind(this._queryTagsAutocomplete, this), |
||||
|
||||
id(tag) { |
||||
return tag.id |
||||
}, |
||||
|
||||
initSelection(element, callback) { |
||||
const val = $(element) |
||||
.val() |
||||
.trim() |
||||
if (val) { |
||||
const tagIds = val.split(',') |
||||
const tags = [] |
||||
|
||||
OC.SystemTags.collection.fetch({ |
||||
success() { |
||||
_.each(tagIds, function(tagId) { |
||||
const tag = OC.SystemTags.collection.get( |
||||
tagId |
||||
) |
||||
if (!_.isUndefined(tag)) { |
||||
tags.push(tag.toJSON()) |
||||
} |
||||
}) |
||||
callback(tags) |
||||
self._onTagsChanged({ target: element }) |
||||
}, |
||||
}) |
||||
} else { |
||||
// eslint-disable-next-line n/no-callback-literal
|
||||
callback([]) |
||||
} |
||||
}, |
||||
|
||||
formatResult(tag) { |
||||
return OC.SystemTags.getDescriptiveTag(tag) |
||||
}, |
||||
|
||||
formatSelection(tag) { |
||||
return OC.SystemTags.getDescriptiveTag(tag).outerHTML |
||||
}, |
||||
|
||||
sortResults(results) { |
||||
results.sort(function(a, b) { |
||||
const aLastUsed = self._lastUsedTags.indexOf(a.id) |
||||
const bLastUsed = self._lastUsedTags.indexOf(b.id) |
||||
|
||||
if (aLastUsed !== bLastUsed) { |
||||
if (bLastUsed === -1) { |
||||
return -1 |
||||
} |
||||
if (aLastUsed === -1) { |
||||
return 1 |
||||
} |
||||
return aLastUsed < bLastUsed ? -1 : 1 |
||||
} |
||||
|
||||
// Both not found
|
||||
return OC.Util.naturalSortCompare(a.name, b.name) |
||||
}) |
||||
return results |
||||
}, |
||||
|
||||
escapeMarkup(m) { |
||||
// prevent double markup escape
|
||||
return m |
||||
}, |
||||
formatNoMatches() { |
||||
return t('systemtags', 'No tags found') |
||||
}, |
||||
}) |
||||
this.$filterField.parent().children('.select2-container').attr('aria-expanded', 'false') |
||||
this.$filterField.on('select2-open', () => { |
||||
this.$filterField.parent().children('.select2-container').attr('aria-expanded', 'true') |
||||
}) |
||||
this.$filterField.on('select2-close', () => { |
||||
this.$filterField.parent().children('.select2-container').attr('aria-expanded', 'false') |
||||
}) |
||||
this.$filterField.on( |
||||
'change', |
||||
_.bind(this._onTagsChanged, this) |
||||
) |
||||
return this.$filterField |
||||
}, |
||||
|
||||
/** |
||||
* Autocomplete function for dropdown results |
||||
* |
||||
* @param {object} query select2 query object |
||||
*/ |
||||
_queryTagsAutocomplete(query) { |
||||
OC.SystemTags.collection.fetch({ |
||||
success() { |
||||
const results = OC.SystemTags.collection.filterByName( |
||||
query.term |
||||
) |
||||
|
||||
query.callback({ |
||||
results: _.invoke(results, 'toJSON'), |
||||
}) |
||||
}, |
||||
}) |
||||
}, |
||||
|
||||
/** |
||||
* Event handler for when the URL changed |
||||
* |
||||
* @param {Event} e the urlchanged event |
||||
*/ |
||||
_onUrlChanged(e) { |
||||
if (e.dir) { |
||||
const tags = _.filter(e.dir.split('/'), function(val) { |
||||
return val.trim() !== '' |
||||
}) |
||||
this.$filterField.select2('val', tags || []) |
||||
this._systemTagIds = tags |
||||
this.reload() |
||||
} |
||||
}, |
||||
|
||||
_onTagsChanged(ev) { |
||||
const val = $(ev.target) |
||||
.val() |
||||
.trim() |
||||
if (val !== '') { |
||||
this._systemTagIds = val.split(',') |
||||
} else { |
||||
this._systemTagIds = [] |
||||
} |
||||
|
||||
this.$el.trigger( |
||||
$.Event('changeDirectory', { |
||||
dir: this._systemTagIds.join('/'), |
||||
}) |
||||
) |
||||
this.reload() |
||||
}, |
||||
|
||||
updateEmptyContent() { |
||||
const dir = this.getCurrentDirectory() |
||||
if (dir === '/') { |
||||
// root has special permissions
|
||||
if (!this._systemTagIds.length) { |
||||
// no tags selected
|
||||
this.$el |
||||
.find('.emptyfilelist.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('.emptyfilelist.emptycontent') |
||||
.html( |
||||
'<div class="icon-systemtags"></div>' |
||||
+ '<h2>' |
||||
+ t( |
||||
'systemtags', |
||||
'No files found for the selected tags' |
||||
) |
||||
+ '</h2>' |
||||
) |
||||
} |
||||
this.$el |
||||
.find('.emptyfilelist.emptycontent') |
||||
.toggleClass('hidden', !this.isEmpty) |
||||
this.$el |
||||
.find('.files-filestable thead th') |
||||
.toggleClass('hidden', this.isEmpty) |
||||
} else { |
||||
OCA.Files.FileList.prototype.updateEmptyContent.apply( |
||||
this, |
||||
arguments |
||||
) |
||||
} |
||||
}, |
||||
|
||||
getDirectoryPermissions() { |
||||
return OC.PERMISSION_READ | OC.PERMISSION_DELETE |
||||
}, |
||||
|
||||
updateStorageStatistics() { |
||||
// no op because it doesn't have
|
||||
// storage info like free space / used space
|
||||
}, |
||||
|
||||
reload() { |
||||
// there is only root
|
||||
this._setCurrentDir('/', false) |
||||
|
||||
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) |
||||
} |
||||
const callBack = this.reloadCallback.bind(this) |
||||
return this._reloadCall.then(callBack, callBack) |
||||
}, |
||||
|
||||
reloadCallback(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 |
||||
})() |
@ -1,240 +0,0 @@ |
||||
/** |
||||
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com> |
||||
* |
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* @author Vincent Petry <vincent@nextcloud.com> |
||||
* |
||||
* @license AGPL-3.0-or-later |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* 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 |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
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">' + |
||||
// init horrible parameters
|
||||
'<input type="hidden" id="permissions" value="31"></input>' + |
||||
'<div class="files-controls"></div>' + |
||||
// dummy table
|
||||
// TODO: at some point this will be rendered by the fileList class itself!
|
||||
'<table class="files-filestable">' + |
||||
'<thead><tr>' + |
||||
'<th 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"></span>' + |
||||
'</th>' + |
||||
'<th class="hidden column-mtime">' + |
||||
'<a class="columntitle" data-sort="mtime"><span class="sort-indicator"></span></a>' + |
||||
'</th>' + |
||||
'</tr></thead>' + |
||||
'<tbody class="files-fileList"></tbody>' + |
||||
'<tfoot></tfoot>' + |
||||
'</table>' + |
||||
'<div class="emptyfilelist 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'), { |
||||
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'), { |
||||
systemTagIds: [] |
||||
} |
||||
); |
||||
|
||||
fileList.reload(); |
||||
|
||||
expect(fileList.$el.find('.emptyfilelist.emptycontent').hasClass('hidden')).toEqual(false); |
||||
|
||||
expect(getFilteredFilesSpec.notCalled).toEqual(true); |
||||
}); |
||||
|
||||
it('render files', function(done) { |
||||
fileList = new OCA.SystemTags.FileList( |
||||
$('#app-content'), { |
||||
systemTagIds: ['123', '456'] |
||||
} |
||||
); |
||||
|
||||
var reloading = 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); |
||||
|
||||
return reloading.then(function() { |
||||
expect(fileList.$el.find('.emptyfilelist.emptycontent').hasClass('hidden')).toEqual(true); |
||||
expect(fileList.$el.find('tbody>tr').length).toEqual(4); |
||||
}).then(done, done); |
||||
}); |
||||
}); |
||||
}); |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue