Browse Source

feat(files): add systemtags view

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
John Molakvoæ (skjnldsv) 7 months ago
parent
commit
2845319187

+ 2 - 2
apps/dav/lib/SystemTag/SystemTagsInUseCollection.php

@@ -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;

+ 7 - 0
apps/files/src/components/FileEntry.vue

@@ -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)) {

+ 1 - 28
apps/files/src/services/Favorites.ts

@@ -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()
 

+ 1 - 1
apps/files/src/services/Files.ts

@@ -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

+ 1 - 28
apps/files/src/services/Recent.ts

@@ -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,

+ 11 - 1
apps/files/src/views/FilesList.vue

@@ -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()
@@ -373,7 +382,8 @@ export default Vue.extend({
 					this.pathsStore.addPath({ service: currentView.id, fileid: node.fileid, path: join(dir, node.basename) })
 				})
 			} catch (error) {
-				logger.error('Error while fetching content', { error })
+				throw error
+				// logger.error('Error while fetching content', { error })
 			} finally {
 				this.loading = false
 			}

+ 1 - 1
apps/systemtags/lib/AppInfo/Application.php

@@ -56,7 +56,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');
 				}
 			);
 

+ 0 - 131
apps/systemtags/src/app.js

@@ -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()
-	})
-})

+ 20 - 5
apps/systemtags/src/systemtags.js → apps/systemtags/src/init.ts

@@ -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: 'systemtags',
+	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,
+}))

+ 3 - 5
apps/systemtags/src/services/api.ts

@@ -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>

+ 98 - 0
apps/systemtags/src/services/systemtags.ts

@@ -0,0 +1,98 @@
+/**
+ * @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'
+
+let tagsCache = [] as TagWithId[]
+
+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
+	tagsCache = await fetchTags()
+
+	if (path === '/') {
+		return {
+			folder: new Folder({
+				id: 0,
+				source: generateRemoteUrl('dav/systemtags'),
+				owner: getCurrentUser()?.uid as string,
+				root: '/systemtags',
+			}),
+			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),
+	}
+
+}

+ 0 - 355
apps/systemtags/src/systemtagsfilelist.js

@@ -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 - 1
webpack.modules.js

@@ -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'),