Merge pull request #40284 from lhsazevedo/inline-system-tags

This commit is contained in:
John Molakvoæ 2023-09-13 10:53:13 +02:00 committed by GitHub
commit 165c54d620
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 317 additions and 163 deletions

22
__mocks__/css.js Normal file
View file

@ -0,0 +1,22 @@
/**
* @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/>.
*
*/
export default {}

View file

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

View file

@ -24,6 +24,5 @@
"sidebarpreviewmanager.js",
"sidebarpreviewtext.js",
"tagsplugin.js",
"systemtagsplugin.js",
"templates.js"
]

View file

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

View file

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

View file

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

View file

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

View file

@ -24,5 +24,6 @@
import './app.js'
import './systemtagsfilelist.js'
import './css/systemtagsfilelist.scss'
import './actions/inlineSystemTagsAction.ts'
window.OCA.SystemTags = OCA.SystemTags

View file

@ -71,6 +71,7 @@ const config: Config = {
// Allow mocking svg files
moduleNameMapper: {
'^.+\\.svg(\\?raw)?$': '<rootDir>/__mocks__/svg.js',
'\\.s?css$': '<rootDir>/__mocks__/css.js',
},
modulePathIgnorePatterns: [
'<rootDir>/apps2/',