mirror of
https://github.com/nextcloud/server.git
synced 2026-06-08 16:26:59 -04:00
Merge pull request #40475 from nextcloud/feat/f2v/systemtags
This commit is contained in:
commit
085568b75c
32 changed files with 218 additions and 874 deletions
|
|
@ -99,8 +99,8 @@ class SystemTagsInUseCollection extends SimpleCollection {
|
|||
$tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']);
|
||||
// read only, so we can submit the isAdmin parameter as false generally
|
||||
$node = new SystemTagNode($tag, $user, false, $this->systemTagManager);
|
||||
$node->setNumberOfFiles($tagData['number_files']);
|
||||
$node->setReferenceFileId($tagData['ref_file_id']);
|
||||
$node->setNumberOfFiles((int) $tagData['number_files']);
|
||||
$node->setReferenceFileId((int) $tagData['ref_file_id']);
|
||||
$children[] = $node;
|
||||
}
|
||||
return $children;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,19 @@ export const action = new FileAction({
|
|||
iconSvgInline: () => ArrowDownSvg,
|
||||
|
||||
enabled(nodes: Node[]) {
|
||||
return nodes.length > 0 && nodes
|
||||
if (nodes.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// We can download direct dav files. But if we have
|
||||
// some folders, we need to use the /apps/files/ajax/download.php
|
||||
// endpoint, which only supports user root folder.
|
||||
if (nodes.some(node => node.type === FileType.Folder)
|
||||
&& nodes.some(node => !node.root?.startsWith('/files'))) {
|
||||
return false
|
||||
}
|
||||
|
||||
return nodes
|
||||
.map(node => node.permissions)
|
||||
.every(permission => (permission & Permission.READ) !== 0)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ import AccountGroupIcon from 'vue-material-design-icons/AccountGroup.vue'
|
|||
import FileIcon from 'vue-material-design-icons/File.vue'
|
||||
import FolderIcon from 'vue-material-design-icons/Folder.vue'
|
||||
import KeyIcon from 'vue-material-design-icons/Key.vue'
|
||||
import TagIcon from 'vue-material-design-icons/Tag.vue'
|
||||
import LinkIcon from 'vue-material-design-icons/Link.vue'
|
||||
import NetworkIcon from 'vue-material-design-icons/Network.vue'
|
||||
import AccountPlusIcon from 'vue-material-design-icons/AccountPlus.vue'
|
||||
|
|
@ -237,6 +238,7 @@ export default Vue.extend({
|
|||
NcLoadingIcon,
|
||||
NcTextField,
|
||||
NetworkIcon,
|
||||
TagIcon,
|
||||
},
|
||||
|
||||
props: {
|
||||
|
|
@ -381,6 +383,11 @@ export default Vue.extend({
|
|||
return KeyIcon
|
||||
}
|
||||
|
||||
// System tags
|
||||
if (this.source?.attributes?.['is-tag']) {
|
||||
return TagIcon
|
||||
}
|
||||
|
||||
// Link and mail shared folders
|
||||
const shareTypes = Object.values(this.source?.attributes?.['share-types'] || {}).flat() as number[]
|
||||
if (shareTypes.some(type => type === ShareType.SHARE_TYPE_LINK || type === ShareType.SHARE_TYPE_EMAIL)) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { getCurrentUser } from '@nextcloud/auth'
|
|||
|
||||
import { getClient, rootPath } from './WebdavClient'
|
||||
import { getDavNameSpaces, getDavProperties, getDefaultPropfind } from './DavProperties'
|
||||
import { resultToNode } from './Files'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
|
|
@ -47,34 +48,6 @@ interface ResponseProps extends DAVResultResponseProps {
|
|||
size: number,
|
||||
}
|
||||
|
||||
const resultToNode = function(node: FileStat): File | Folder {
|
||||
const props = node.props as ResponseProps
|
||||
const permissions = davParsePermissions(props?.permissions)
|
||||
const owner = getCurrentUser()?.uid as string
|
||||
|
||||
const nodeData = {
|
||||
id: props?.fileid as number || 0,
|
||||
source: generateRemoteUrl('dav' + rootPath + node.filename),
|
||||
mtime: new Date(node.lastmod),
|
||||
mime: node.mime as string,
|
||||
size: props?.size as number || 0,
|
||||
permissions,
|
||||
owner,
|
||||
root: rootPath,
|
||||
attributes: {
|
||||
...node,
|
||||
...props,
|
||||
hasPreview: props?.['has-preview'],
|
||||
},
|
||||
}
|
||||
|
||||
delete nodeData.attributes.props
|
||||
|
||||
return node.type === 'file'
|
||||
? new File(nodeData)
|
||||
: new Folder(nodeData)
|
||||
}
|
||||
|
||||
export const getContents = async (path = '/'): Promise<ContentsWithRoot> => {
|
||||
const propfindPayload = getDefaultPropfind()
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ interface ResponseProps extends DAVResultResponseProps {
|
|||
size: number,
|
||||
}
|
||||
|
||||
const resultToNode = function(node: FileStat): File | Folder {
|
||||
export const resultToNode = function(node: FileStat): File | Folder {
|
||||
const props = node.props as ResponseProps
|
||||
const permissions = davParsePermissions(props?.permissions)
|
||||
const owner = getCurrentUser()?.uid as string
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { getCurrentUser } from '@nextcloud/auth'
|
|||
|
||||
import { getClient, rootPath } from './WebdavClient'
|
||||
import { getDavNameSpaces, getDavProperties } from './DavProperties'
|
||||
import { resultToNode } from './Files'
|
||||
|
||||
const client = getClient(generateRemoteUrl('dav'))
|
||||
|
||||
|
|
@ -94,34 +95,6 @@ interface ResponseProps extends DAVResultResponseProps {
|
|||
size: number,
|
||||
}
|
||||
|
||||
const resultToNode = function(node: FileStat): File | Folder {
|
||||
const props = node.props as ResponseProps
|
||||
const permissions = davParsePermissions(props?.permissions)
|
||||
const owner = getCurrentUser()?.uid as string
|
||||
|
||||
const nodeData = {
|
||||
id: props?.fileid as number || 0,
|
||||
source: generateRemoteUrl('dav' + node.filename),
|
||||
mtime: new Date(node.lastmod),
|
||||
mime: node.mime as string,
|
||||
size: props?.size as number || 0,
|
||||
permissions,
|
||||
owner,
|
||||
root: rootPath,
|
||||
attributes: {
|
||||
...node,
|
||||
...props,
|
||||
hasPreview: props?.['has-preview'],
|
||||
},
|
||||
}
|
||||
|
||||
delete nodeData.attributes.props
|
||||
|
||||
return node.type === 'file'
|
||||
? new File(nodeData)
|
||||
: new Folder(nodeData)
|
||||
}
|
||||
|
||||
export const getContents = async (path = '/'): Promise<ContentsWithRoot> => {
|
||||
const contentsResponse = await client.getDirectoryContents(path, {
|
||||
details: true,
|
||||
|
|
|
|||
|
|
@ -328,12 +328,21 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchContent()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchContent() {
|
||||
this.loading = true
|
||||
const dir = this.dir
|
||||
const currentView = this.currentView
|
||||
|
||||
if (!currentView) {
|
||||
logger.debug('The current view doesn\'t exists or is not ready.', { currentView })
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a cancellable promise ongoing, cancel it
|
||||
if (typeof this.promise?.cancel === 'function') {
|
||||
this.promise.cancel()
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@
|
|||
import { emit } from '@nextcloud/event-bus'
|
||||
import { encodePath } from '@nextcloud/paths'
|
||||
import { File, Folder } from '@nextcloud/files'
|
||||
import { getCapabilities } from '@nextcloud/capabilities'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { Type as ShareTypes } from '@nextcloud/sharing'
|
||||
import $ from 'jquery'
|
||||
|
|
@ -299,7 +300,7 @@ export default {
|
|||
},
|
||||
|
||||
isSystemTagsEnabled() {
|
||||
return OCA && 'SystemTags' in OCA
|
||||
return getCapabilities()?.systemtags?.enabled === true
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ return array(
|
|||
'OCA\\SystemTags\\Activity\\Provider' => $baseDir . '/../lib/Activity/Provider.php',
|
||||
'OCA\\SystemTags\\Activity\\Setting' => $baseDir . '/../lib/Activity/Setting.php',
|
||||
'OCA\\SystemTags\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\SystemTags\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
|
||||
'OCA\\SystemTags\\Controller\\LastUsedController' => $baseDir . '/../lib/Controller/LastUsedController.php',
|
||||
'OCA\\SystemTags\\Search\\TagSearchProvider' => $baseDir . '/../lib/Search/TagSearchProvider.php',
|
||||
'OCA\\SystemTags\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class ComposerStaticInitSystemTags
|
|||
'OCA\\SystemTags\\Activity\\Provider' => __DIR__ . '/..' . '/../lib/Activity/Provider.php',
|
||||
'OCA\\SystemTags\\Activity\\Setting' => __DIR__ . '/..' . '/../lib/Activity/Setting.php',
|
||||
'OCA\\SystemTags\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\SystemTags\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
|
||||
'OCA\\SystemTags\\Controller\\LastUsedController' => __DIR__ . '/..' . '/../lib/Controller/LastUsedController.php',
|
||||
'OCA\\SystemTags\\Search\\TagSearchProvider' => __DIR__ . '/..' . '/../lib/Search/TagSearchProvider.php',
|
||||
'OCA\\SystemTags\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ namespace OCA\SystemTags\AppInfo;
|
|||
use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
||||
use OCA\SystemTags\Search\TagSearchProvider;
|
||||
use OCA\SystemTags\Activity\Listener;
|
||||
use OCA\SystemTags\Capabilities;
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
|
|
@ -45,6 +46,7 @@ class Application extends App implements IBootstrap {
|
|||
|
||||
public function register(IRegistrationContext $context): void {
|
||||
$context->registerSearchProvider(TagSearchProvider::class);
|
||||
$context->registerCapability(Capabilities::class);
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void {
|
||||
|
|
@ -56,7 +58,7 @@ class Application extends App implements IBootstrap {
|
|||
LoadAdditionalScriptsEvent::class,
|
||||
function () {
|
||||
\OCP\Util::addScript('core', 'systemtags');
|
||||
\OCP\Util::addScript(self::APP_ID, 'systemtags');
|
||||
\OCP\Util::addInitScript(self::APP_ID, 'init');
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -77,16 +79,5 @@ class Application extends App implements IBootstrap {
|
|||
$dispatcher->addListener(MapperEvent::EVENT_ASSIGN, $mapperListener);
|
||||
$dispatcher->addListener(MapperEvent::EVENT_UNASSIGN, $mapperListener);
|
||||
});
|
||||
|
||||
\OCA\Files\App::getNavigationManager()->add(function () {
|
||||
$l = \OC::$server->getL10N(self::APP_ID);
|
||||
return [
|
||||
'id' => 'systemtagsfilter',
|
||||
'appname' => self::APP_ID,
|
||||
'script' => 'list.php',
|
||||
'order' => 25,
|
||||
'name' => $l->t('Tags'),
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
40
apps/systemtags/lib/Capabilities.php
Normal file
40
apps/systemtags/lib/Capabilities.php
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -20,10 +20,25 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import './actions/inlineSystemTagsAction.js'
|
||||
|
||||
import './app.js'
|
||||
import './systemtagsfilelist.js'
|
||||
import './css/systemtagsfilelist.scss'
|
||||
import './actions/inlineSystemTagsAction.ts'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { Column, Node, View, getNavigation } from '@nextcloud/files'
|
||||
import TagMultipleSvg from '@mdi/svg/svg/tag-multiple.svg?raw'
|
||||
|
||||
window.OCA.SystemTags = OCA.SystemTags
|
||||
import { getContents } from './services/systemtags.js'
|
||||
|
||||
const Navigation = getNavigation()
|
||||
Navigation.register(new View({
|
||||
id: 'tags',
|
||||
name: t('systemtags', 'Tags'),
|
||||
caption: t('systemtags', 'List of tags and their associated files and folders.'),
|
||||
|
||||
emptyTitle: t('systemtags', 'No tags found'),
|
||||
emptyCaption: t('systemtags', 'Tags you have created will show up here.'),
|
||||
|
||||
icon: TagMultipleSvg,
|
||||
order: 25,
|
||||
|
||||
getContents,
|
||||
}))
|
||||
|
|
@ -19,19 +19,17 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import type { FileStat, ResponseDataDetailed } from 'webdav'
|
||||
import type { ServerTag, Tag, TagWithId } from '../types.js'
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
|
||||
import { davClient } from './davClient.js'
|
||||
import { formatTag, parseIdFromLocation, parseTags } from '../utils.js'
|
||||
import { formatTag, parseIdFromLocation, parseTags } from '../utils'
|
||||
import { logger } from '../logger.js'
|
||||
|
||||
import type { FileStat, ResponseDataDetailed } from 'webdav'
|
||||
|
||||
import type { ServerTag, Tag, TagWithId } from '../types.js'
|
||||
|
||||
const fetchTagsBody = `<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:prop>
|
||||
|
|
|
|||
97
apps/systemtags/src/services/systemtags.ts
Normal file
97
apps/systemtags/src/services/systemtags.ts
Normal file
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
4
dist/core-common.js
vendored
4
dist/core-common.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-common.js.map
vendored
2
dist/core-common.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-main.js
vendored
4
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-sidebar.js
vendored
4
dist/files-sidebar.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-sidebar.js.map
vendored
2
dist/files-sidebar.js.map
vendored
File diff suppressed because one or more lines are too long
3
dist/systemtags-init.js
vendored
Normal file
3
dist/systemtags-init.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,10 +1,9 @@
|
|||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
|
|
@ -15,7 +14,7 @@
|
|||
*
|
||||
* 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
|
||||
* 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
|
||||
|
|
@ -44,27 +43,3 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
1
dist/systemtags-init.js.map
vendored
Normal file
1
dist/systemtags-init.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3
dist/systemtags-systemtags.js
vendored
3
dist/systemtags-systemtags.js
vendored
File diff suppressed because one or more lines are too long
1
dist/systemtags-systemtags.js.map
vendored
1
dist/systemtags-systemtags.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -65,7 +65,6 @@ module.exports = function(config) {
|
|||
],
|
||||
testFiles: ['apps/files_sharing/tests/js/*.js']
|
||||
},
|
||||
'systemtags',
|
||||
'files_trashbin',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ module.exports = {
|
|||
'vue-settings-admin-sharebymail': path.join(__dirname, 'apps/sharebymail/src', 'main-admin.js'),
|
||||
},
|
||||
systemtags: {
|
||||
systemtags: path.join(__dirname, 'apps/systemtags/src', 'systemtags.js'),
|
||||
init: path.join(__dirname, 'apps/systemtags/src', 'init.ts'),
|
||||
},
|
||||
theming: {
|
||||
'personal-theming': path.join(__dirname, 'apps/theming/src', 'personal-settings.js'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue