refactor(f2v): Migrate unread comments action to the new FileAction API

Signed-off-by: Lucas Azevedo <lhs_azevedo@hotmail.com>
pull/40409/head
Lucas Azevedo 9 months ago committed by John Molakvoæ (skjnldsv)
parent f8ccaf8d58
commit 03ece129bf
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF

@ -0,0 +1,193 @@
/**
* @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 './inlineUnreadCommentsAction'
import { expect } from '@jest/globals'
import { File, Permission, View, FileAction } from '@nextcloud/files'
import logger from '../logger'
const view = {
id: 'files',
name: 'Files',
} as View
describe('Inline unread comments action display name 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,
attributes: {
'comments-unread': 1,
},
})
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('comments-unread')
expect(action.displayName([file], view)).toBe('1 new comment')
expect(action.iconSvgInline([], view)).toBe('<svg>SvgMock</svg>')
expect(action.enabled!([file], view)).toBe(true)
expect(action.inline!(file, view)).toBe(true)
expect(action.default).toBeUndefined()
expect(action.order).toBe(-140)
})
test('Display name when file has two new comments', () => {
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,
attributes: {
'comments-unread': 2,
},
})
expect(action.displayName([file], view)).toBe('2 new comments')
})
})
describe('Inline unread comments action enabled tests', () => {
test('Action is disabled when comments-unread attribute is missing', () => {
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,
attributes: { },
})
expect(action.enabled!([file], view)).toBe(false)
})
test('Action is disabled when file does not have unread comments', () => {
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,
attributes: {
'comments-unread': 0,
},
})
expect(action.enabled!([file], view)).toBe(false)
})
test('Action is enabled when file has a single unread comment', () => {
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,
attributes: {
'comments-unread': 1,
},
})
expect(action.enabled!([file], view)).toBe(true)
})
test('Action is enabled when file has a two unread comments', () => {
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,
attributes: {
'comments-unread': 2,
},
})
expect(action.enabled!([file], view)).toBe(true)
})
})
describe('Inline unread comments action execute tests', () => {
test('Action opens sidebar', async () => {
const openMock = jest.fn()
const setActiveTabMock = jest.fn()
window.OCA = {
Files: {
Sidebar: {
open: openMock,
setActiveTab: setActiveTabMock,
},
},
}
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,
attributes: {
'comments-unread': 1,
},
})
const result = await action.exec!(file, view, '/')
expect(result).toBe(null)
expect(setActiveTabMock).toBeCalledWith('comments')
expect(openMock).toBeCalledWith('/foobar.txt')
})
test('Action handles sidebar open failure', async () => {
const openMock = jest.fn(() => { throw new Error('Mock error') })
const setActiveTabMock = jest.fn()
window.OCA = {
Files: {
Sidebar: {
open: openMock,
setActiveTab: setActiveTabMock,
},
},
}
jest.spyOn(logger, 'error').mockImplementation(() => jest.fn())
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,
attributes: {
'comments-unread': 1,
},
})
const result = await action.exec!(file, view, '/')
expect(result).toBe(false)
expect(setActiveTabMock).toBeCalledWith('comments')
expect(openMock).toBeCalledWith('/foobar.txt')
expect(logger.error).toBeCalledTimes(1)
})
})

@ -0,0 +1,61 @@
/**
* @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, registerFileAction } from '@nextcloud/files'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import commentProcessingSvg from '@mdi/svg/svg/comment-processing.svg?raw'
import logger from '../logger'
export const action = new FileAction({
id: 'comments-unread',
displayName(nodes: Node[]) {
const unread = nodes[0].attributes['comments-unread'] as number
if (unread >= 0) {
return n('comments', '1 new comment', '{unread} new comments', unread, { unread })
}
return t('comments', 'Comment')
},
iconSvgInline: () => commentProcessingSvg,
enabled(nodes: Node[]) {
const unread = nodes[0].attributes['comments-unread'] as number|undefined
return typeof unread === 'number' && unread > 0
},
async exec(node: Node) {
try {
window.OCA.Files.Sidebar.setActiveTab('comments')
await window.OCA.Files.Sidebar.open(node.path)
return null
} catch (error) {
logger.error('Error while opening sidebar', { error })
return false
}
},
inline: () => true,
order: -140,
})
registerFileAction(action)

@ -23,7 +23,7 @@
import './app.js'
import './templates.js'
import './filesplugin.js'
import './activitytabviewplugin.js'
import './actions/inlineUnreadCommentsAction.ts'
window.OCA.Comments = OCA.Comments

@ -1,141 +0,0 @@
/**
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com>
*
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Michael Jobst <mjobst+github@tecratech.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @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() {
_.extend(OC.Files.Client, {
PROPERTY_COMMENTS_UNREAD: '{' + OC.Files.Client.NS_OWNCLOUD + '}comments-unread',
})
OCA.Comments = _.extend({}, OCA.Comments)
if (!OCA.Comments) {
/**
* @namespace
*/
OCA.Comments = {}
}
/**
* @namespace
*/
OCA.Comments.FilesPlugin = {
ignoreLists: [
'trashbin',
'files.public',
],
_formatCommentCount(count) {
return OCA.Comments.Templates.filesplugin({
count,
countMessage: n('comments', '%n unread comment', '%n unread comments', count),
iconUrl: OC.imagePath('core', 'actions/comment'),
})
},
attach(fileList) {
const self = this
if (this.ignoreLists.indexOf(fileList.id) >= 0) {
return
}
const oldGetWebdavProperties = fileList._getWebdavProperties
fileList._getWebdavProperties = function() {
const props = oldGetWebdavProperties.apply(this, arguments)
props.push(OC.Files.Client.PROPERTY_COMMENTS_UNREAD)
return props
}
fileList.filesClient.addFileInfoParser(function(response) {
const data = {}
const props = response.propStat[0].properties
const commentsUnread = props[OC.Files.Client.PROPERTY_COMMENTS_UNREAD]
if (!_.isUndefined(commentsUnread) && commentsUnread !== '') {
data.commentsUnread = parseInt(commentsUnread, 10)
}
return data
})
fileList.$el.addClass('has-comments')
const oldCreateRow = fileList._createRow
fileList._createRow = function(fileData) {
const $tr = oldCreateRow.apply(this, arguments)
if (fileData.commentsUnread) {
$tr.attr('data-comments-unread', fileData.commentsUnread)
}
return $tr
}
// register "comment" action for reading comments
fileList.fileActions.registerAction({
name: 'Comment',
displayName(context) {
if (context && context.$file) {
const unread = parseInt(context.$file.data('comments-unread'), 10)
if (unread >= 0) {
return n('comments', '1 new comment', '{unread} new comments', unread, { unread })
}
}
return t('comments', 'Comment')
},
mime: 'all',
order: -140,
iconClass: 'icon-comment',
permissions: OC.PERMISSION_READ,
type: OCA.Files.FileActions.TYPE_INLINE,
render(actionSpec, isDefault, context) {
const $file = context.$file
const unreadComments = $file.data('comments-unread')
if (unreadComments) {
const $actionLink = $(self._formatCommentCount(unreadComments))
context.$file.find('a.name>span.fileactions').append($actionLink)
return $actionLink
}
return ''
},
actionHandler(fileName, context) {
context.$file.find('.action-comment').tooltip('hide')
// open sidebar in comments section
OCA.Files.Sidebar.setActiveTab('comments')
OCA.Files.Sidebar.open(context.dir + '/' + fileName)
},
})
// add attribute to "elementToFile"
const oldElementToFile = fileList.elementToFile
fileList.elementToFile = function($el) {
const fileInfo = oldElementToFile.apply(this, arguments)
const commentsUnread = $el.data('comments-unread')
if (commentsUnread) {
fileInfo.commentsUnread = commentsUnread
}
return fileInfo
}
},
}
})()
OC.Plugins.register('OCA.Files.FileList', OCA.Comments.FilesPlugin)

@ -0,0 +1,28 @@
/**
* @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 { getLoggerBuilder } from '@nextcloud/logger'
export default getLoggerBuilder()
.setApp('comments')
.detectUser()
.build()

@ -20,20 +20,15 @@
*
*/
import { getLoggerBuilder } from '@nextcloud/logger'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import { getRequestToken } from '@nextcloud/auth'
import CommentsApp from '../views/Comments.vue'
import Vue from 'vue'
import CommentsApp from '../views/Comments.vue'
import logger from '../logger.js'
// eslint-disable-next-line camelcase
__webpack_nonce__ = btoa(getRequestToken())
const logger = getLoggerBuilder()
.setApp('comments')
.detectUser()
.build()
// Add translates functions
Vue.mixin({
data() {

@ -1,117 +0,0 @@
/**
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.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/>.
*
*/
describe('OCA.Comments.FilesPlugin tests', function() {
var fileList;
var testFiles;
beforeEach(function() {
var $content = $('<div id="app-content"></div>');
$('#testArea').append($content);
// dummy file list
var $div = $(
'<div>' +
'<table class="files-filestable">' +
'<thead></thead>' +
'<tbody class="files-fileList"></tbody>' +
'</table>' +
'</div>');
$('#app-content').append($div);
fileList = new OCA.Files.FileList($div);
OCA.Comments.FilesPlugin.attach(fileList);
testFiles = [{
id: 1,
type: 'file',
name: 'One.txt',
path: '/subdir',
mimetype: 'text/plain',
size: 12,
permissions: OC.PERMISSION_ALL,
etag: 'abc',
shareOwner: 'User One',
isShareMountPoint: false,
commentsUnread: 3
}];
});
afterEach(function() {
fileList.destroy();
fileList = null;
});
describe('Comment icon', function() {
it('does not render icon when no unread comments available', function() {
testFiles[0].commentsUnread = 0;
fileList.setFiles(testFiles);
var $tr = fileList.findFileEl('One.txt');
expect($tr.find('.action-comment').length).toEqual(0);
});
it('renders comment icon and extra data', function() {
var $action, $tr;
fileList.setFiles(testFiles);
$tr = fileList.findFileEl('One.txt');
$action = $tr.find('.action-comment');
expect($action.length).toEqual(1);
expect($action.hasClass('permanent')).toEqual(true);
expect($tr.attr('data-comments-unread')).toEqual('3');
});
it('clicking icon opens sidebar', function() {
var sidebarTabStub = sinon.stub(OCA.Files.Sidebar, 'setActiveTab');
var sidebarStub = sinon.stub(OCA.Files.Sidebar, 'open');
var $action, $tr;
fileList.setFiles(testFiles);
$tr = fileList.findFileEl('One.txt');
$action = $tr.find('.action-comment');
$action.click();
expect(sidebarTabStub.calledOnce).toEqual(true);
expect(sidebarTabStub.lastCall.args[0]).toEqual('comments');
expect(sidebarStub.calledOnce).toEqual(true);
expect(sidebarStub.lastCall.args[0]).toEqual('/subdir/One.txt');
});
});
describe('elementToFile', function() {
it('returns comment count', function() {
fileList.setFiles(testFiles);
var $tr = fileList.findFileEl('One.txt');
var data = fileList.elementToFile($tr);
expect(data.commentsUnread).toEqual(3);
});
it('does not set comment count when not set', function() {
delete testFiles[0].commentsUnread;
fileList.setFiles(testFiles);
var $tr = fileList.findFileEl('One.txt');
var data = fileList.elementToFile($tr);
expect(data.commentsUnread).not.toBeDefined();
});
it('does not set comment count when zero', function() {
testFiles[0].commentsUnread = 0;
fileList.setFiles(testFiles);
var $tr = fileList.findFileEl('One.txt');
var data = fileList.elementToFile($tr);
expect(data.commentsUnread).not.toBeDefined();
});
});
});

@ -119,7 +119,6 @@
:boundaries-element="getBoundariesElement()"
:container="getBoundariesElement()"
:disabled="source._loading"
:force-name="true"
:force-menu="enabledInlineActions.length === 0 /* forceMenu only if no inline actions */"
:inline="enabledInlineActions.length"
:open.sync="openedMenu">

4
dist/6898-6898.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,3 +1,5 @@
/*! 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>
*
@ -21,6 +23,28 @@
*
*/
/**
* @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/>.
*
*/
/**
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com>
*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/core-main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -49,13 +49,6 @@ module.exports = function(config) {
return [
'files',
'files_versions',
{
name: 'comments',
srcFiles: [
'dist/comments-comments.js'
],
testFiles: ['apps/comments/tests/js/**/*.js']
},
{
name: 'files_sharing',
srcFiles: [

Loading…
Cancel
Save