Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>tags/v28.0.0beta1
@@ -20,7 +20,6 @@ | |||
"newfilemenu.js", | |||
"operationprogressbar.js", | |||
"recentfilelist.js", | |||
"recentplugin.js", | |||
"semaphore.js", | |||
"sidebarpreviewmanager.js", | |||
"sidebarpreviewtext.js", |
@@ -1,121 +0,0 @@ | |||
/* | |||
* Copyright (c) 2014 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 (OCA) { | |||
/** | |||
* Registers the recent file list from the files app sidebar. | |||
* | |||
* @namespace OCA.Files.RecentPlugin | |||
*/ | |||
OCA.Files.RecentPlugin = { | |||
name: 'Recent', | |||
/** | |||
* @type OCA.Files.RecentFileList | |||
*/ | |||
recentFileList: null, | |||
attach: function () { | |||
var self = this; | |||
$('#app-content-recent').on('show.plugin-recent', function (e) { | |||
self.showFileList($(e.target)); | |||
}); | |||
$('#app-content-recent').on('hide.plugin-recent', function () { | |||
self.hideFileList(); | |||
}); | |||
}, | |||
detach: function () { | |||
if (this.recentFileList) { | |||
this.recentFileList.destroy(); | |||
OCA.Files.fileActions.off('setDefault.plugin-recent', this._onActionsUpdated); | |||
OCA.Files.fileActions.off('registerAction.plugin-recent', this._onActionsUpdated); | |||
$('#app-content-recent').off('.plugin-recent'); | |||
this.recentFileList = null; | |||
} | |||
}, | |||
showFileList: function ($el) { | |||
if (!this.recentFileList) { | |||
this.recentFileList = this._createRecentFileList($el); | |||
} | |||
return this.recentFileList; | |||
}, | |||
hideFileList: function () { | |||
if (this.recentFileList) { | |||
this.recentFileList.$fileList.empty(); | |||
} | |||
}, | |||
/** | |||
* Creates the recent file list. | |||
* | |||
* @param $el container for the file list | |||
* @return {OCA.Files.RecentFileList} file list | |||
*/ | |||
_createRecentFileList: function ($el) { | |||
var fileActions = this._createFileActions(); | |||
// register recent list for sidebar section | |||
return new OCA.Files.RecentFileList( | |||
$el, { | |||
fileActions: fileActions, | |||
// 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 | |||
} | |||
); | |||
}, | |||
_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.plugin-recent', this._onActionsUpdated); | |||
OCA.Files.fileActions.on('registerAction.plugin-recent', 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}); | |||
var path = OC.joinPaths(context.$file.attr('data-path'), filename); | |||
OCA.Files.App.fileList.changeDirectory(path, true, true); | |||
}); | |||
fileActions.setDefault('dir', 'Open'); | |||
return fileActions; | |||
}, | |||
_onActionsUpdated: function (ev) { | |||
if (ev.action) { | |||
this.recentFileList.fileActions.registerAction(ev.action); | |||
} else if (ev.defaultAction) { | |||
this.recentFileList.fileActions.setDefault( | |||
ev.defaultAction.mime, | |||
ev.defaultAction.name | |||
); | |||
} | |||
} | |||
}; | |||
})(OCA); | |||
OC.Plugins.register('OCA.Files.App', OCA.Files.RecentPlugin); | |||
@@ -163,15 +163,6 @@ class Application extends App implements IBootstrap { | |||
'name' => $l10n->t('All files') | |||
]; | |||
}); | |||
\OCA\Files\App::getNavigationManager()->add(function () use ($l10n) { | |||
return [ | |||
'id' => 'recent', | |||
'appname' => 'files', | |||
'script' => 'recentlist.php', | |||
'order' => 2, | |||
'name' => $l10n->t('Recent') | |||
]; | |||
}); | |||
} | |||
private function registerHooks(): void { |
@@ -1,37 +0,0 @@ | |||
<?php | |||
/** | |||
* @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl> | |||
* | |||
* @author Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* @author John Molakvoæ <skjnldsv@protonmail.com> | |||
* @author Morris Jobke <hey@morrisjobke.de> | |||
* @author Robin Appelman <robin@icewind.nl> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* 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/>. | |||
* | |||
*/ | |||
$config = \OC::$server->getConfig(); | |||
$userSession = \OC::$server->getUserSession(); | |||
$showgridview = $config->getUserValue($userSession->getUser()->getUID(), 'files', 'show_grid', false); | |||
$tmpl = new OCP\Template('files', 'recentlist', ''); | |||
// gridview not available for ie | |||
$tmpl->assign('showgridview', $showgridview); | |||
$tmpl->printPage(); |
@@ -0,0 +1,103 @@ | |||
/** | |||
* @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 { action } from './openInFilesAction' | |||
import { expect } from '@jest/globals' | |||
import { File, Folder, Permission } from '@nextcloud/files' | |||
import { DefaultType, FileAction } from '../../../files/src/services/FileAction' | |||
import type { Navigation } from '../../../files/src/services/Navigation' | |||
const view = { | |||
id: 'files', | |||
name: 'Files', | |||
} as Navigation | |||
const recentView = { | |||
id: 'recent', | |||
name: 'Recent', | |||
} as Navigation | |||
describe('Open in files action conditions tests', () => { | |||
test('Default values', () => { | |||
expect(action).toBeInstanceOf(FileAction) | |||
expect(action.id).toBe('open-in-files-recent') | |||
expect(action.displayName([], recentView)).toBe('Open in Files') | |||
expect(action.iconSvgInline([], recentView)).toBe('') | |||
expect(action.default).toBe(DefaultType.HIDDEN) | |||
expect(action.order).toBe(-1000) | |||
expect(action.inline).toBeUndefined() | |||
}) | |||
}) | |||
describe('Open in files action enabled tests', () => { | |||
test('Enabled with on valid view', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([], recentView)).toBe(true) | |||
}) | |||
test('Disabled on wrong view', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([], view)).toBe(false) | |||
}) | |||
}) | |||
describe('Open in files action execute tests', () => { | |||
test('Open in files', async () => { | |||
const goToRouteMock = jest.fn() | |||
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } } | |||
const file = new File({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/foobar.txt', | |||
owner: 'admin', | |||
mime: 'text/plain', | |||
root: '/files/admin', | |||
permissions: Permission.ALL, | |||
}) | |||
const exec = await action.exec(file, view, '/') | |||
// Silent action | |||
expect(exec).toBe(null) | |||
expect(goToRouteMock).toBeCalledTimes(1) | |||
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { fileid: 1, dir: '/Foo', openfile: true }) | |||
}) | |||
test('Open in files with folder', async () => { | |||
const goToRouteMock = jest.fn() | |||
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } } | |||
const file = new Folder({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/Bar', | |||
owner: 'admin', | |||
root: '/files/admin', | |||
permissions: Permission.ALL, | |||
}) | |||
const exec = await action.exec(file, view, '/') | |||
// Silent action | |||
expect(exec).toBe(null) | |||
expect(goToRouteMock).toBeCalledTimes(1) | |||
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { fileid: 1, dir: '/Foo/Bar', openfile: true }) | |||
}) | |||
}) |
@@ -0,0 +1,57 @@ | |||
/** | |||
* @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 { translate as t } from '@nextcloud/l10n' | |||
import { FileType, type Node } from '@nextcloud/files' | |||
import { registerFileAction, FileAction, DefaultType } from '../../../files/src/services/FileAction' | |||
/** | |||
* TODO: Move away from a redirect and handle | |||
* navigation straight out of the recent view | |||
*/ | |||
export const action = new FileAction({ | |||
id: 'open-in-files-recent', | |||
displayName: () => t('files', 'Open in Files'), | |||
iconSvgInline: () => '', | |||
enabled: (nodes, view) => view.id === 'recent', | |||
async exec(node: Node) { | |||
let dir = node.dirname | |||
if (node.type === FileType.Folder) { | |||
dir = dir + '/' + node.basename | |||
} | |||
window.OCP.Files.Router.goToRoute( | |||
null, // use default route | |||
{ view: 'files', fileid: node.fileid }, | |||
{ dir, fileid: node.fileid, openfile: true }, | |||
) | |||
return null | |||
}, | |||
// Before openFolderAction | |||
order: -1000, | |||
default: DefaultType.HIDDEN, | |||
}) | |||
registerFileAction(action) |
@@ -159,6 +159,7 @@ import { formatFileSize, Permission } from '@nextcloud/files' | |||
import { Fragment } from 'vue-frag' | |||
import { showError, showSuccess } from '@nextcloud/dialogs' | |||
import { translate } from '@nextcloud/l10n' | |||
import { generateUrl } from '@nextcloud/router' | |||
import { vOnClickOutside } from '@vueuse/components' | |||
import axios from '@nextcloud/axios' | |||
import CancelablePromise from 'cancelable-promise' | |||
@@ -367,10 +368,16 @@ export default Vue.extend({ | |||
}, | |||
previewUrl() { | |||
try { | |||
const url = new URL(window.location.origin + this.source.attributes.previewUrl) | |||
const previewUrl = this.source.attributes.previewUrl | |||
|| generateUrl('/core/preview?fileId={fileid}', { | |||
fileid: this.source.fileid, | |||
}) | |||
const url = new URL(window.location.origin + previewUrl) | |||
// Request tiny previews | |||
url.searchParams.set('x', '32') | |||
url.searchParams.set('y', '32') | |||
// Handle cropping | |||
url.searchParams.set('a', this.cropPreviews === true ? '0' : '1') | |||
return url.href |
@@ -6,6 +6,7 @@ import './actions/downloadAction' | |||
import './actions/editLocallyAction' | |||
import './actions/favoriteAction' | |||
import './actions/openFolderAction' | |||
import './actions/openInFilesAction.js' | |||
import './actions/renameAction' | |||
import './actions/sidebarAction' | |||
import './actions/viewInFolderAction' | |||
@@ -18,6 +19,7 @@ import NavigationService from './services/Navigation' | |||
import NavigationView from './views/Navigation.vue' | |||
import processLegacyFilesViews from './legacy/navigationMapper.js' | |||
import registerFavoritesView from './views/favorites' | |||
import registerRecentView from './views/recent' | |||
import registerPreviewServiceWorker from './services/ServiceWorker.js' | |||
import router from './router/router.js' | |||
import RouterService from './services/RouterService' | |||
@@ -78,6 +80,7 @@ FilesList.$mount('#app-content-vue') | |||
// Init legacy and new files views | |||
processLegacyFilesViews() | |||
registerFavoritesView() | |||
registerRecentView() | |||
// Register preview service worker | |||
registerPreviewServiceWorker() |
@@ -0,0 +1,148 @@ | |||
/** | |||
* @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 { File, Folder, Permission, parseWebdavPermissions } from '@nextcloud/files' | |||
import { generateRemoteUrl } from '@nextcloud/router' | |||
import { getClient, rootPath } from './WebdavClient' | |||
import { getCurrentUser } from '@nextcloud/auth' | |||
import { getDavNameSpaces, getDavProperties } from './DavProperties' | |||
import type { ContentsWithRoot } from './Navigation' | |||
import type { FileStat, ResponseDataDetailed, DAVResultResponseProps } from 'webdav' | |||
const client = getClient(generateRemoteUrl('dav')) | |||
const lastTwoWeeksTimestamp = Math.round((Date.now() / 1000) - (60 * 60 * 24 * 14)) | |||
const searchPayload = `<?xml version="1.0" encoding="UTF-8"?> | |||
<d:searchrequest ${getDavNameSpaces()} | |||
xmlns:ns="https://github.com/icewind1991/SearchDAV/ns"> | |||
<d:basicsearch> | |||
<d:select> | |||
<d:prop> | |||
${getDavProperties()} | |||
</d:prop> | |||
</d:select> | |||
<d:from> | |||
<d:scope> | |||
<d:href>/files/${getCurrentUser()?.uid}/</d:href> | |||
<d:depth>infinity</d:depth> | |||
</d:scope> | |||
</d:from> | |||
<d:where> | |||
<d:and> | |||
<d:or> | |||
<d:not> | |||
<d:eq> | |||
<d:prop> | |||
<d:getcontenttype/> | |||
</d:prop> | |||
<d:literal>httpd/unix-directory</d:literal> | |||
</d:eq> | |||
</d:not> | |||
<d:eq> | |||
<d:prop> | |||
<oc:size/> | |||
</d:prop> | |||
<d:literal>0</d:literal> | |||
</d:eq> | |||
</d:or> | |||
<d:gt> | |||
<d:prop> | |||
<d:getlastmodified/> | |||
</d:prop> | |||
<d:literal>${lastTwoWeeksTimestamp}</d:literal> | |||
</d:gt> | |||
</d:and> | |||
</d:where> | |||
<d:orderby> | |||
<d:order> | |||
<d:prop> | |||
<d:getlastmodified/> | |||
</d:prop> | |||
<d:descending/> | |||
</d:order> | |||
</d:orderby> | |||
<d:limit> | |||
<d:nresults>100</d:nresults> | |||
<ns:firstresult>0</ns:firstresult> | |||
</d:limit> | |||
</d:basicsearch> | |||
</d:searchrequest>` | |||
interface ResponseProps extends DAVResultResponseProps { | |||
permissions: string, | |||
fileid: number, | |||
size: number, | |||
} | |||
const resultToNode = function(node: FileStat): File | Folder { | |||
const props = node.props as ResponseProps | |||
const permissions = parseWebdavPermissions(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, | |||
data: searchPayload, | |||
headers: { | |||
// Patched in WebdavClient.ts | |||
method: 'SEARCH', | |||
// Somehow it's needed to get the correct response | |||
'Content-Type': 'application/xml; charset=utf-8', | |||
}, | |||
deep: true, | |||
}) as ResponseDataDetailed<FileStat[]> | |||
const contents = contentsResponse.data | |||
return { | |||
folder: new Folder({ | |||
id: 0, | |||
source: generateRemoteUrl('dav' + rootPath), | |||
root: rootPath, | |||
owner: getCurrentUser()?.uid || null, | |||
permissions: Permission.READ, | |||
}), | |||
contents: contents.map(resultToNode), | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
/** | |||
* @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 NavigationService from '../services/Navigation' | |||
import type { Navigation } from '../services/Navigation' | |||
import { translate as t } from '@nextcloud/l10n' | |||
import HistorySvg from '@mdi/svg/svg/history.svg?raw' | |||
import { getContents } from '../services/Recent' | |||
export default () => { | |||
const Navigation = window.OCP.Files.Navigation as NavigationService | |||
Navigation.register({ | |||
id: 'recent', | |||
name: t('files', 'Recent'), | |||
caption: t('files', 'List of recently modified files and folders.'), | |||
emptyTitle: t('files', 'No recently modified files'), | |||
emptyCaption: t('files', 'Files and folders you recently modified will show up here.'), | |||
icon: HistorySvg, | |||
order: 2, | |||
defaultSortKey: 'mtime', | |||
getContents, | |||
} as Navigation) | |||
} |
@@ -1,41 +0,0 @@ | |||
<?php /** @var \OCP\IL10N $l */ ?> | |||
<div class="emptyfilelist emptycontent hidden"></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 class="files-filestable list-container <?php p($_['showgridview'] ? 'view-grid' : '') ?>"> | |||
<thead> | |||
<tr> | |||
<th class="hidden column-name"> | |||
<div class="column-name-container"> | |||
<a class="name sort columntitle" href="#" onclick="event.preventDefault()" | |||
data-sort="name"><span><?php p($l->t('Name')); ?></span></a> | |||
</div> | |||
</th> | |||
<th class="hidden column-size"> | |||
<a class="size sort columntitle" href="#" onclick="event.preventDefault()" | |||
data-sort="size"><span><?php p($l->t('Size')); ?></span></a> | |||
</th> | |||
<th class="hidden column-mtime"> | |||
<a class="columntitle" href="#" onclick="event.preventDefault()" | |||
data-sort="mtime"><span><?php p($l->t('Modified')); ?></span><span | |||
class="sort-indicator"></span></a> | |||
<span class="selectedActions"> | |||
<a href="#" onclick="event.preventDefault()" class="delete-selected"> | |||
<span class="icon icon-delete"></span> | |||
<span><?php p($l->t('Delete')) ?></span> | |||
</a> | |||
</span> | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody class="files-fileList"> | |||
</tbody> | |||
<tfoot> | |||
</tfoot> | |||
</table> |
@@ -185,19 +185,6 @@ class ViewControllerTest extends TestCase { | |||
'expanded' => false, | |||
'unread' => 0, | |||
], | |||
'recent' => [ | |||
'id' => 'recent', | |||
'appname' => 'files', | |||
'script' => 'recentlist.php', | |||
'order' => 2, | |||
'name' => \OC::$server->getL10N('files')->t('Recent'), | |||
'active' => false, | |||
'icon' => '', | |||
'type' => 'link', | |||
'classes' => '', | |||
'expanded' => false, | |||
'unread' => 0, | |||
], | |||
'systemtagsfilter' => [ | |||
'id' => 'systemtagsfilter', | |||
'appname' => 'systemtags', | |||
@@ -233,10 +220,6 @@ class ViewControllerTest extends TestCase { | |||
'id' => 'files', | |||
'content' => null, | |||
], | |||
'recent' => [ | |||
'id' => 'recent', | |||
'content' => null, | |||
], | |||
'systemtagsfilter' => [ | |||
'id' => 'systemtagsfilter', | |||
'content' => null, |
@@ -110,12 +110,27 @@ describe('Enter credentials action enabled tests', () => { | |||
}, | |||
}) | |||
const notAStorage = new Folder({ | |||
const missingConfig = new Folder({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/', | |||
owner: 'admin', | |||
root: '/files/admin', | |||
permissions: Permission.ALL, | |||
attributes: { | |||
scope: 'system', | |||
backend: 'SFTP', | |||
config: { | |||
} as StorageConfig, | |||
}, | |||
}) | |||
const notAStorage = new File({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/test.txt', | |||
mime: 'text/plain', | |||
owner: 'admin', | |||
root: '/files/admin', | |||
permissions: Permission.ALL, | |||
}) | |||
test('Disabled with on success storage', () => { | |||
@@ -138,6 +153,11 @@ describe('Enter credentials action enabled tests', () => { | |||
expect(action.enabled!([globalAuthUserStorage], view)).toBe(true) | |||
}) | |||
test('Disabled for missing config', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([missingConfig], view)).toBe(false) | |||
}) | |||
test('Disabled for normal nodes', () => { | |||
expect(action.enabled).toBeDefined() | |||
expect(action.enabled!([notAStorage], view)).toBe(false) |
@@ -1,9 +1,9 @@ | |||
.files-list__row-status { | |||
display: flex; | |||
width: 44px; | |||
justify-content: center; | |||
align-items: center; | |||
height: 100%; | |||
width: 44px; | |||
justify-content: center; | |||
align-items: center; | |||
height: 100%; | |||
svg { | |||
width: 24px; | |||
@@ -33,4 +33,4 @@ | |||
&--warning { | |||
background: var(--color-warning); | |||
} | |||
} | |||
} |
@@ -35,7 +35,7 @@ export const rootPath = `/files/${getCurrentUser()?.uid}` | |||
export type StorageConfig = { | |||
applicableUsers?: string[] | |||
applicableGroups?: string[] | |||
applicableGroups?: string[] | |||
authMechanism: string | |||
backend: string | |||
backendOptions: Record<string, string> |
@@ -0,0 +1,91 @@ | |||
/** | |||
* @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 { File, Folder, Permission } from '@nextcloud/files' | |||
import { isNodeExternalStorage } from './externalStorageUtils' | |||
import { expect } from '@jest/globals' | |||
describe('Is node an external storage', () => { | |||
test('A Folder with a backend and a valid scope is an external storage', () => { | |||
const folder = new Folder({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/', | |||
owner: 'admin', | |||
permissions: Permission.ALL, | |||
attributes: { | |||
scope: 'personal', | |||
backend: 'SFTP', | |||
}, | |||
}) | |||
expect(isNodeExternalStorage(folder)).toBe(true) | |||
}) | |||
test('a File is not a valid storage', () => { | |||
const file = new File({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', | |||
owner: 'admin', | |||
mime: 'text/plain', | |||
permissions: Permission.ALL, | |||
}) | |||
expect(isNodeExternalStorage(file)).toBe(false) | |||
}) | |||
test('A Folder without a backend is not a storage', () => { | |||
const folder = new Folder({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/', | |||
owner: 'admin', | |||
permissions: Permission.ALL, | |||
attributes: { | |||
scope: 'personal', | |||
}, | |||
}) | |||
expect(isNodeExternalStorage(folder)).toBe(false) | |||
}) | |||
test('A Folder without a scope is not a storage', () => { | |||
const folder = new Folder({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/', | |||
owner: 'admin', | |||
permissions: Permission.ALL, | |||
attributes: { | |||
backend: 'SFTP', | |||
}, | |||
}) | |||
expect(isNodeExternalStorage(folder)).toBe(false) | |||
}) | |||
test('A Folder with an invalid scope is not a storage', () => { | |||
const folder = new Folder({ | |||
id: 1, | |||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/', | |||
owner: 'admin', | |||
permissions: Permission.ALL, | |||
attributes: { | |||
scope: 'null', | |||
backend: 'SFTP', | |||
}, | |||
}) | |||
expect(isNodeExternalStorage(folder)).toBe(false) | |||
}) | |||
}) |
@@ -313,7 +313,6 @@ describe('SharingService share to Node mapping', () => { | |||
expect(file.root).toBe('/files/test') | |||
expect(file.attributes).toBeInstanceOf(Object) | |||
expect(file.attributes['has-preview']).toBe(true) | |||
expect(file.attributes.previewUrl).toBe('/index.php/core/preview?fileId=530936&x=32&y=32&forceIcon=0') | |||
expect(file.attributes.favorite).toBe(0) | |||
}) | |||
@@ -53,7 +53,6 @@ const ocsEntryToNode = function(ocsEntry: any): Folder | File | null { | |||
const Node = isFolder ? Folder : File | |||
const fileid = ocsEntry.file_source | |||
const previewUrl = hasPreview ? generateUrl('/core/preview?fileId={fileid}&x=32&y=32&forceIcon=0', { fileid }) : undefined | |||
// Generate path and strip double slashes | |||
const path = ocsEntry?.path || ocsEntry.file_target | |||
@@ -76,7 +75,6 @@ const ocsEntryToNode = function(ocsEntry: any): Folder | File | null { | |||
root: rootPath, | |||
attributes: { | |||
...ocsEntry, | |||
previewUrl, | |||
'has-preview': hasPreview, | |||
favorite: ocsEntry?.tags?.includes(window.OC.TAG_FAVORITE) ? 1 : 0, | |||
}, |
@@ -28,7 +28,7 @@ import AccountGroupSvg from '@mdi/svg/svg/account-group.svg?raw' | |||
import AccountSvg from '@mdi/svg/svg/account.svg?raw' | |||
import DeleteSvg from '@mdi/svg/svg/delete.svg?raw' | |||
import LinkSvg from '@mdi/svg/svg/link.svg?raw' | |||
import AccouontPlusSvg from '@mdi/svg/svg/account-plus.svg?raw' | |||
import AccountPlusSvg from '@mdi/svg/svg/account-plus.svg?raw' | |||
import { getContents } from '../services/SharingService' | |||
@@ -49,7 +49,7 @@ export default () => { | |||
emptyTitle: t('files_sharing', 'No shares'), | |||
emptyCaption: t('files_sharing', 'Files and folders you shared or have been shared with you will show up here'), | |||
icon: AccouontPlusSvg, | |||
icon: AccountPlusSvg, | |||
order: 20, | |||
columns: [], |