diff options
author | John Molakvoæ <skjnldsv@users.noreply.github.com> | 2023-09-13 10:53:13 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-13 10:53:13 +0200 |
commit | 165c54d62079e3ec3459dddc1b9f7e23f2140720 (patch) | |
tree | c879d75fdcd9dc33547735d4c9d95d92e9ee1085 /apps | |
parent | 4f0a151eb4661070c97a726b68511f0c4456c66b (diff) | |
parent | f5865216d6aa46424bedf5cb912495a6abe5618d (diff) | |
download | nextcloud-server-165c54d62079e3ec3459dddc1b9f7e23f2140720.tar.gz nextcloud-server-165c54d62079e3ec3459dddc1b9f7e23f2140720.zip |
Merge pull request #40284 from lhsazevedo/inline-system-tags
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files/css/files.scss | 34 | ||||
-rw-r--r-- | apps/files/js/merged-index.json | 1 | ||||
-rw-r--r-- | apps/files/js/systemtagsplugin.js | 128 | ||||
-rw-r--r-- | apps/systemtags/src/actions/inlineSystemTagsAction.spec.ts | 143 | ||||
-rw-r--r-- | apps/systemtags/src/actions/inlineSystemTagsAction.ts | 90 | ||||
-rw-r--r-- | apps/systemtags/src/css/fileEntryInlineSystemTags.scss | 60 | ||||
-rw-r--r-- | apps/systemtags/src/systemtags.js | 1 |
7 files changed, 294 insertions, 163 deletions
diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss index 1b5d10e6cd1..194d1542de9 100644 --- a/apps/files/css/files.scss +++ b/apps/files/css/files.scss @@ -406,40 +406,6 @@ table { z-index: 10; padding: 0 20px 0 0; } - - /* System tags */ - .system-tags { - --min-size: 32px; - display: flex; - justify-content: center; - align-items: center; - min-width: calc(var(--min-size) * 2); - max-width: 300px; - - .system-tags__tag { - padding: 5px 10px; - border: 1px solid; - border-radius: var(--border-radius-pill); - border-color: var(--color-border); - color: var(--color-text-maxcontrast); - height: var(--min-size); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 22px; // min-size - 2 * 5px padding - text-align: center; - - &--more { - overflow: visible; - text-overflow: initial; - } - - // Proper spacing if multiple shown - & + .system-tags__tag { - margin-left: 5px; - } - } - } } } diff --git a/apps/files/js/merged-index.json b/apps/files/js/merged-index.json index 293aea30d5c..870a807f718 100644 --- a/apps/files/js/merged-index.json +++ b/apps/files/js/merged-index.json @@ -24,6 +24,5 @@ "sidebarpreviewmanager.js", "sidebarpreviewtext.js", "tagsplugin.js", - "systemtagsplugin.js", "templates.js" ] diff --git a/apps/files/js/systemtagsplugin.js b/apps/files/js/systemtagsplugin.js deleted file mode 100644 index f92e6879a3f..00000000000 --- a/apps/files/js/systemtagsplugin.js +++ /dev/null @@ -1,128 +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. - * - */ - -/* global Handlebars */ - -(function (OCA) { - - _.extend(OC.Files.Client, { - PROPERTY_SYSTEM_TAGS: '{' + OC.Files.Client.NS_NEXTCLOUD + '}system-tags', - }); - - OCA.Files = OCA.Files || {}; - - /** - * Extends the file actions and file list to add system tags inline - * - * @namespace OCA.Files.SystemTagsPlugin - */ - OCA.Files.SystemTagsPlugin = { - name: 'SystemTags', - - allowedLists: [ - 'files', - 'favorites', - 'shares.self', - 'shares.others', - 'shares.link' - ], - - _buildTagSpan: function(tag, isMore = false) { - var $tag = $('<li class="system-tags__tag"></li>'); - $tag.text(tag).addClass(isMore ? 'system-tags__tag--more' : ''); - return $tag; - }, - - _buildTagsUI: function(tags) { - $systemTags = $('<ul class="system-tags"></ul>'); - if (tags.length === 1) { - $systemTags.attr('aria-label', t('files', 'This file has the tag {tag}', { tag: tags[0] })); - } else if (tags.length > 1) { - var firstTags = tags.slice(0, -1).join(', '); - var lastTag = tags[tags.length - 1]; - $systemTags.attr('aria-label', t('files', 'This file has the tags {firstTags} and {lastTag}', { firstTags, lastTag })); - } - - if (tags.length > 0) { - $systemTags.append(this._buildTagSpan(tags[0])); - } - - // More tags than the one we're showing - if (tags.length > 1) { - $moreTag = this._buildTagSpan('+' + (tags.length - 1), true) - $moreTag.attr('title', tags.slice(1).join(', ')); - $systemTags.append($moreTag); - } - - return $systemTags; - }, - - _extendFileList: function(fileList) { - var self = this; - - // extend row prototype - var oldCreateRow = fileList._createRow; - fileList._createRow = function(fileData) { - var $tr = oldCreateRow.apply(this, arguments); - var systemTags = fileData.systemTags || []; - - // Update tr data list - $tr.attr('data-systemTags', systemTags.join('|')); - - // No tags, no need to do anything - if (systemTags.length === 0) { - return $tr; - } - - // Build tags ui and inject - $systemTags = self._buildTagsUI.apply(self, [systemTags]) - $systemTags.insertAfter($tr.find('td.filename .nametext')); - return $tr; - }; - - var oldElementToFile = fileList.elementToFile; - fileList.elementToFile = function ($el) { - var fileInfo = oldElementToFile.apply(this, arguments); - var systemTags = $el.attr('data-systemTags'); - fileInfo.systemTags = systemTags?.split?.('|') || []; - return fileInfo; - }; - - var oldGetWebdavProperties = fileList._getWebdavProperties; - fileList._getWebdavProperties = function () { - var props = oldGetWebdavProperties.apply(this, arguments); - props.push(OC.Files.Client.PROPERTY_SYSTEM_TAGS); - return props; - }; - - fileList.filesClient.addFileInfoParser(function (response) { - var data = {}; - var props = response.propStat[0].properties; - var systemTags = props[OC.Files.Client.PROPERTY_SYSTEM_TAGS] || []; - if (systemTags && systemTags.length) { - data.systemTags = systemTags - .filter(xmlvalue => xmlvalue.namespaceURI === OC.Files.Client.NS_NEXTCLOUD && xmlvalue.nodeName.split(':')[1] === 'system-tag') - .map(xmlvalue => xmlvalue.textContent || xmlvalue.text); - } - return data; - }); - }, - - attach: function(fileList) { - if (this.allowedLists.indexOf(fileList.id) < 0) { - return; - } - this._extendFileList(fileList); - }, - }; -}) -(OCA); - -OC.Plugins.register('OCA.Files.FileList', OCA.Files.SystemTagsPlugin); diff --git a/apps/systemtags/src/actions/inlineSystemTagsAction.spec.ts b/apps/systemtags/src/actions/inlineSystemTagsAction.spec.ts new file mode 100644 index 00000000000..0910a6039dd --- /dev/null +++ b/apps/systemtags/src/actions/inlineSystemTagsAction.spec.ts @@ -0,0 +1,143 @@ +/** + * @copyright Copyright (c) 2023 Lucas Azevedo <lhs_azevedo@hotmail.com> + * + * @author Lucas Azevedo <lhs_azevedo@hotmail.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 './inlineSystemTagsAction' +import { expect } from '@jest/globals' +import { File, Permission, View, FileAction } from '@nextcloud/files' + +const view = { + id: 'files', + name: 'Files', +} as View + +describe('Inline system tags action conditions tests', () => { + test('Default values', () => { + 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(action).toBeInstanceOf(FileAction) + expect(action.id).toBe('system-tags') + expect(action.displayName([file], view)).toBe('') + expect(action.iconSvgInline([], view)).toBe('') + expect(action.default).toBeUndefined() + expect(action.enabled).toBeUndefined() + expect(action.order).toBe(0) + }) +}) + +describe('Inline system tags action render tests', () => { + test('Render nothing when Node does not have system tags', async () => { + const file = new File({ + id: 1, + source: 'http://localhost/remote.php/dav/files/admin/foobar.txt', + owner: 'admin', + mime: 'text/plain', + permissions: Permission.ALL, + }) + + const result = await action.renderInline!(file, view) + expect(result).toBeNull() + }) + + test('Render a single system tag', async () => { + const file = new File({ + id: 1, + source: 'http://localhost/remote.php/dav/files/admin/foobar.txt', + owner: 'admin', + mime: 'text/plain', + permissions: Permission.ALL, + attributes: { + 'system-tags': { + 'system-tag': 'Confidential', + }, + }, + }) + + const result = await action.renderInline!(file, view) + expect(result).toBeInstanceOf(HTMLElement) + expect(result!.outerHTML).toBe( + '<ul class="files-list__system-tags" aria-label="This file has the tag Confidential">' + + '<li class="files-list__system-tag">Confidential</li>' + + '</ul>', + ) + }) + + test('Render two system tags', async () => { + const file = new File({ + id: 1, + source: 'http://localhost/remote.php/dav/files/admin/foobar.txt', + owner: 'admin', + mime: 'text/plain', + permissions: Permission.ALL, + attributes: { + 'system-tags': { + 'system-tag': [ + 'Important', + 'Confidential', + ], + }, + }, + }) + + const result = await action.renderInline!(file, view) + expect(result).toBeInstanceOf(HTMLElement) + expect(result!.outerHTML).toBe( + '<ul class="files-list__system-tags" aria-label="This file has the tags Important and Confidential">' + + '<li class="files-list__system-tag">Important</li>' + + '<li class="files-list__system-tag files-list__system-tag--more" title="Confidential">+1</li>' + + '</ul>', + ) + }) + + test('Render multiple system tags', async () => { + const file = new File({ + id: 1, + source: 'http://localhost/remote.php/dav/files/admin/foobar.txt', + owner: 'admin', + mime: 'text/plain', + permissions: Permission.ALL, + attributes: { + 'system-tags': { + 'system-tag': [ + 'Important', + 'Confidential', + 'Secret', + 'Classified', + ], + }, + }, + }) + + const result = await action.renderInline!(file, view) + expect(result).toBeInstanceOf(HTMLElement) + expect(result!.outerHTML).toBe( + '<ul class="files-list__system-tags" aria-label="This file has the tags Important, Confidential, Secret and Classified">' + + '<li class="files-list__system-tag">Important</li>' + + '<li class="files-list__system-tag files-list__system-tag--more" title="Confidential, Secret, Classified">+3</li>' + + '</ul>', + ) + }) +}) diff --git a/apps/systemtags/src/actions/inlineSystemTagsAction.ts b/apps/systemtags/src/actions/inlineSystemTagsAction.ts new file mode 100644 index 00000000000..c92b5cbe13b --- /dev/null +++ b/apps/systemtags/src/actions/inlineSystemTagsAction.ts @@ -0,0 +1,90 @@ +/** + * @copyright Copyright (c) 2023 Lucas Azevedo <lhs_azevedo@hotmail.com> + * + * @author Lucas Azevedo <lhs_azevedo@hotmail.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 { FileAction, Node, registerDavProperty, registerFileAction } from '@nextcloud/files' +import { translate as t } from '@nextcloud/l10n' + +import '../css/fileEntryInlineSystemTags.scss' + +const getNodeSystemTags = function(node: Node): string[] { + const tags = node.attributes?.['system-tags']?.['system-tag'] as string|string[]|undefined + + if (tags === undefined) { + return [] + } + + return [tags].flat() +} + +const renderTag = function(tag: string, isMore = false): HTMLElement { + const tagElement = document.createElement('li') + tagElement.classList.add('files-list__system-tag') + tagElement.textContent = tag + + if (isMore) { + tagElement.classList.add('files-list__system-tag--more') + } + + return tagElement +} + +export const action = new FileAction({ + id: 'system-tags', + displayName: () => '', + iconSvgInline: () => '', + exec: async () => null, + + async renderInline(node: Node) { + // Ensure we have the system tags as an array + const tags = getNodeSystemTags(node) + + if (tags.length === 0) { + return null + } + + const systemTagsElement = document.createElement('ul') + systemTagsElement.classList.add('files-list__system-tags') + + if (tags.length === 1) { + systemTagsElement.setAttribute('aria-label', t('files', 'This file has the tag {tag}', { tag: tags[0] })) + } else { + const firstTags = tags.slice(0, -1).join(', ') + const lastTag = tags[tags.length - 1] + systemTagsElement.setAttribute('aria-label', t('files', 'This file has the tags {firstTags} and {lastTag}', { firstTags, lastTag })) + } + + systemTagsElement.append(renderTag(tags[0])) + + // More tags than the one we're showing + if (tags.length > 1) { + const moreTagElement = renderTag('+' + (tags.length - 1), true) + moreTagElement.setAttribute('title', tags.slice(1).join(', ')) + systemTagsElement.append(moreTagElement) + } + + return systemTagsElement + }, + + order: 0, +}) + +registerDavProperty('nc:system-tags') +registerFileAction(action) diff --git a/apps/systemtags/src/css/fileEntryInlineSystemTags.scss b/apps/systemtags/src/css/fileEntryInlineSystemTags.scss new file mode 100644 index 00000000000..040ac1f850d --- /dev/null +++ b/apps/systemtags/src/css/fileEntryInlineSystemTags.scss @@ -0,0 +1,60 @@ +/** + * @copyright Copyright (c) 2023 Lucas Azevedo <lhs_azevedo@hotmail.com> + * + * @author Lucas Azevedo <lhs_azevedo@hotmail.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/>. + * + */ + +.files-list__system-tags { + --min-size: 32px; + display: none; + justify-content: center; + align-items: center; + min-width: calc(var(--min-size) * 2); + max-width: 300px; +} + +.files-list__system-tag { + padding: 5px 10px; + border: 1px solid; + border-radius: var(--border-radius-pill); + border-color: var(--color-border); + color: var(--color-text-maxcontrast); + height: var(--min-size); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 22px; // min-size - 2 * 5px padding + text-align: center; + + &--more { + overflow: visible; + text-overflow: initial; + } + + // Proper spacing if multiple shown + & + .files-list__system-tag { + margin-left: 5px; + } +} + +@media (min-width: 512px) { + .files-list__system-tags { + display: flex; + } +} diff --git a/apps/systemtags/src/systemtags.js b/apps/systemtags/src/systemtags.js index ab2f8ff4126..b4f767e0f12 100644 --- a/apps/systemtags/src/systemtags.js +++ b/apps/systemtags/src/systemtags.js @@ -24,5 +24,6 @@ import './app.js' import './systemtagsfilelist.js' import './css/systemtagsfilelist.scss' +import './actions/inlineSystemTagsAction.ts' window.OCA.SystemTags = OCA.SystemTags |