From a2275cebed94a67611e6cc4efabf35eac5081c7e Mon Sep 17 00:00:00 2001 From: Andrey Borysenko Date: Wed, 10 Jan 2024 19:37:23 +0300 Subject: [PATCH] refactor filesplugin to split NC27 and NC28+ version (#205) Signed-off-by: Andrey Borysenko --- lib/Listener/LoadFilesPluginListener.php | 7 +- src/filesplugin.js | 173 +++++------------------ src/filesplugin28.js | 114 +++++++++++++++ webpack.js | 1 + 4 files changed, 157 insertions(+), 138 deletions(-) create mode 100644 src/filesplugin28.js diff --git a/lib/Listener/LoadFilesPluginListener.php b/lib/Listener/LoadFilesPluginListener.php index 2969c939..43bcafe0 100644 --- a/lib/Listener/LoadFilesPluginListener.php +++ b/lib/Listener/LoadFilesPluginListener.php @@ -36,7 +36,12 @@ public function handle(Event $event): void { 'fileActions' => $exFilesActions, 'instanceId' => $this->config->getSystemValue('instanceid'), ]); - Util::addScript(Application::APP_ID, Application::APP_ID . '-filesplugin'); + $ncVersion = $this->config->getSystemValueString('version', '0.0.0'); + if (version_compare($ncVersion, '28.0', '<')) { + Util::addScript(Application::APP_ID, Application::APP_ID . '-filesplugin'); + } else { + Util::addScript(Application::APP_ID, Application::APP_ID . '-filesplugin28'); + } Util::addStyle(Application::APP_ID, 'filesactions'); } } diff --git a/src/filesplugin.js b/src/filesplugin.js index 7d8d945e..ffa70466 100644 --- a/src/filesplugin.js +++ b/src/filesplugin.js @@ -2,29 +2,10 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' import { loadState } from '@nextcloud/initial-state' import { translate as t } from '@nextcloud/l10n' -import { registerFileAction, FileAction } from '@nextcloud/files' import { getCurrentUser } from '@nextcloud/auth' const state = loadState('app_api', 'ex_files_actions_menu') -function loadStaticAppAPIInlineSvgIcon() { - return '' -} - -function loadExAppInlineSvgIcon(appId, route) { - const url = generateAppAPIProxyUrl(appId, route) - return axios.get(url).then((response) => { - // Check content type to be svg image - if (response.headers['content-type'] !== 'image/svg+xml') { - return null - } - return response.data - }).catch((error) => { - console.error('Failed to load ExApp FileAction icon inline svg', error) - return null - }) -} - function generateAppAPIProxyUrl(appId, route) { return generateUrl(`/apps/app_api/proxy/${appId}/${route}`) } @@ -32,6 +13,41 @@ function generateAppAPIProxyUrl(appId, route) { if (OCA.Files && OCA.Files.fileActions) { // NC 27 state.fileActions.forEach(fileAction => { const mimes = fileAction.mime.split(',').map(mime => mime.trim()) // multiple mimes are separated by comma + + const actionHandler = (fileName, context) => { + const file = context.$file[0] + const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler) + axios.post(exAppFileActionHandler, { + fileId: Number(file.dataset.id), + name: fileName, + directory: file.dataset.path, + etag: file.dataset.etag, + mime: file.dataset.mime, + favorite: file.dataset.favorite || 'false', + permissions: Number(file.dataset.permissions), + fileType: file.dataset.type, + size: Number(file.dataset.size), + mtime: Number(file.dataset.mtime) / 1000, // convert ms to s + shareTypes: file.dataset?.shareTypes || null, + shareAttributes: file.dataset?.shareAttributes || null, + sharePermissions: file.dataset?.sharePermissions || null, + shareOwner: file.dataset?.shareOwner || null, + shareOwnerId: file.dataset?.shareOwnerId || null, + userId: getCurrentUser().uid, + instanceId: state.instanceId, + }).then((response) => { + if (response.status === 200) { + OC.dialogs.info(t('app_api', 'Action request sent to ExApp'), t(fileAction.appid, fileAction.display_name)) + } else { + console.debug(response) + OC.dialogs.info(t('app_api', 'Error while sending File action request to ExApp'), t(fileAction.appid, fileAction.display_name)) + } + }).catch((error) => { + console.error('error', error) + OC.dialogs.info(t('app_api', 'Error while sending File action request to ExApp'), t(fileAction.appid, fileAction.display_name)) + }) + } + mimes.forEach((mimeType) => { const action = { name: fileAction.name, @@ -41,126 +57,9 @@ if (OCA.Files && OCA.Files.fileActions) { // NC 27 order: Number(fileAction.order), icon: fileAction.icon !== '' ? generateAppAPIProxyUrl(fileAction.appid, fileAction.icon) : null, iconClass: fileAction.icon === '' ? 'icon-app-api' : '', - actionHandler: (fileName, context) => { - const file = context.$file[0] - const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler) - axios.post(exAppFileActionHandler, { - fileId: Number(file.dataset.id), - name: fileName, - directory: file.dataset.path, - etag: file.dataset.etag, - mime: file.dataset.mime, - favorite: file.dataset.favorite || 'false', - permissions: Number(file.dataset.permissions), - fileType: file.dataset.type, - size: Number(file.dataset.size), - mtime: Number(file.dataset.mtime) / 1000, // convert ms to s - shareTypes: file.dataset?.shareTypes || null, - shareAttributes: file.dataset?.shareAttributes || null, - sharePermissions: file.dataset?.sharePermissions || null, - shareOwner: file.dataset?.shareOwner || null, - shareOwnerId: file.dataset?.shareOwnerId || null, - userId: getCurrentUser().uid, - instanceId: state.instanceId, - }).then((response) => { - if (response.status === 200) { - OC.dialogs.info(t('app_api', 'Action request sent to ExApp'), t(fileAction.appid, fileAction.display_name)) - } else { - console.debug(response) - OC.dialogs.info(t('app_api', 'Error while sending File action request to ExApp'), t(fileAction.appid, fileAction.display_name)) - } - }).catch((error) => { - console.error('error', error) - OC.dialogs.info(t('app_api', 'Error while sending File action request to ExApp'), t(fileAction.appid, fileAction.display_name)) - }) - }, + actionHandler, } OCA.Files.fileActions.registerAction(action) }) }) -} else { // NC 28+ - state.fileActions.forEach(fileAction => { - if (fileAction.icon === '') { - const inlineSvgIcon = loadStaticAppAPIInlineSvgIcon() - registerFileAction28(fileAction, inlineSvgIcon) - } else { - loadExAppInlineSvgIcon(fileAction.appid, fileAction.icon).then((svg) => { - if (svg !== null) { - // Set css filter for theming - const parser = new DOMParser() - const icon = parser.parseFromString(svg, 'image/svg+xml') - icon.documentElement.setAttribute('style', 'filter: var(--background-invert-if-dark);') - // Convert back to inline string - const inlineSvgIcon = icon.documentElement.outerHTML - registerFileAction28(fileAction, inlineSvgIcon) - } - }) - } - }) -} - -function registerFileAction28(fileAction, inlineSvgIcon) { - const action = new FileAction({ - id: fileAction.name, - displayName: () => fileAction.display_name, - iconSvgInline: () => inlineSvgIcon, - order: Number(fileAction.order), - enabled(files, view) { - if (files.length === 1) { - // Check for multiple mimes separated by comma - let isMimeMatch = false - fileAction.mime.split(',').forEach((mime) => { - if (files[0].mime.indexOf(mime.trim()) !== -1) { - isMimeMatch = true - } - }) - return isMimeMatch - } else if (files.length > 1) { - // Check all files match fileAction mime - return files.every((file) => { - // Check for multiple mimes separated by comma - let isMimeMatch = false - fileAction.mime.split(',').forEach((mime) => { - if (file.mime.indexOf(mime.trim()) !== -1) { - isMimeMatch = true - } - }) - return isMimeMatch - }) - } - }, - async exec(node, view, dir) { - const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler) - return axios.post(exAppFileActionHandler, { - fileId: node.fileid, - name: node.basename, - directory: node.dirname, - etag: node.attributes.etag, - mime: node.mime, - favorite: Boolean(node.attributes.favorite).toString(), - permissions: node.permissions, - fileType: node.type, - size: Number(node.size), - mtime: new Date(node.mtime).getTime() / 1000, // convert ms to s - shareTypes: node.attributes.shareTypes || null, - shareAttributes: node.attributes.shareAttributes || null, - sharePermissions: node.attributes.sharePermissions || null, - shareOwner: node.attributes.ownerDisplayName || null, - shareOwnerId: node.attributes.ownerId || null, - userId: getCurrentUser().uid, - instanceId: state.instanceId, - }).then((response) => { - return true - }).catch((error) => { - console.error('Failed to send FileAction request to ExApp', error) - return false - }) - }, - async execBatch(nodes, view, dir) { - return Promise.all(nodes.map((node) => { - return this.exec(node, view, dir) - })) - }, - }) - registerFileAction(action) } diff --git a/src/filesplugin28.js b/src/filesplugin28.js new file mode 100644 index 00000000..1eb723a0 --- /dev/null +++ b/src/filesplugin28.js @@ -0,0 +1,114 @@ +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' +import { loadState } from '@nextcloud/initial-state' +import { registerFileAction, FileAction } from '@nextcloud/files' +import { getCurrentUser } from '@nextcloud/auth' + +const state = loadState('app_api', 'ex_files_actions_menu') + +function loadStaticAppAPIInlineSvgIcon() { + return '' +} + +function loadExAppInlineSvgIcon(appId, route) { + const url = generateAppAPIProxyUrl(appId, route) + return axios.get(url).then((response) => { + // Check content type to be svg image + if (response.headers['content-type'] !== 'image/svg+xml') { + return null + } + return response.data + }).catch((error) => { + console.error('Failed to load ExApp FileAction icon inline svg', error) + return null + }) +} + +function generateAppAPIProxyUrl(appId, route) { + return generateUrl(`/apps/app_api/proxy/${appId}/${route}`) +} + +state.fileActions.forEach(fileAction => { + if (fileAction.icon === '') { + const inlineSvgIcon = loadStaticAppAPIInlineSvgIcon() + registerFileAction28(fileAction, inlineSvgIcon) + } else { + loadExAppInlineSvgIcon(fileAction.appid, fileAction.icon).then((svg) => { + if (svg !== null) { + // Set css filter for theming + const parser = new DOMParser() + const icon = parser.parseFromString(svg, 'image/svg+xml') + icon.documentElement.setAttribute('style', 'filter: var(--background-invert-if-dark);') + // Convert back to inline string + const inlineSvgIcon = icon.documentElement.outerHTML + registerFileAction28(fileAction, inlineSvgIcon) + } + }) + } +}) + +function registerFileAction28(fileAction, inlineSvgIcon) { + const action = new FileAction({ + id: fileAction.name, + displayName: () => fileAction.display_name, + iconSvgInline: () => inlineSvgIcon, + order: Number(fileAction.order), + enabled(files, view) { + if (files.length === 1) { + // Check for multiple mimes separated by comma + let isMimeMatch = false + fileAction.mime.split(',').forEach((mime) => { + if (files[0].mime.indexOf(mime.trim()) !== -1) { + isMimeMatch = true + } + }) + return isMimeMatch + } else if (files.length > 1) { + // Check all files match fileAction mime + return files.every((file) => { + // Check for multiple mimes separated by comma + let isMimeMatch = false + fileAction.mime.split(',').forEach((mime) => { + if (file.mime.indexOf(mime.trim()) !== -1) { + isMimeMatch = true + } + }) + return isMimeMatch + }) + } + }, + async exec(node, view, dir) { + const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler) + return axios.post(exAppFileActionHandler, { + fileId: node.fileid, + name: node.basename, + directory: node.dirname, + etag: node.attributes.etag, + mime: node.mime, + favorite: Boolean(node.attributes.favorite).toString(), + permissions: node.permissions, + fileType: node.type, + size: Number(node.size), + mtime: new Date(node.mtime).getTime() / 1000, // convert ms to s + shareTypes: node.attributes.shareTypes || null, + shareAttributes: node.attributes.shareAttributes || null, + sharePermissions: node.attributes.sharePermissions || null, + shareOwner: node.attributes.ownerDisplayName || null, + shareOwnerId: node.attributes.ownerId || null, + userId: getCurrentUser().uid, + instanceId: state.instanceId, + }).then((response) => { + return true + }).catch((error) => { + console.error('Failed to send FileAction request to ExApp', error) + return false + }) + }, + async execBatch(nodes, view, dir) { + return Promise.all(nodes.map((node) => { + return this.exec(node, view, dir) + })) + }, + }) + registerFileAction(action) +} diff --git a/webpack.js b/webpack.js index 203ae358..a40f03fc 100644 --- a/webpack.js +++ b/webpack.js @@ -22,6 +22,7 @@ webpackConfig.entry = { main: { import: path.join(__dirname, 'src', 'main.js'), filename: appId + '-main.js' }, adminSettings: { import: path.join(__dirname, 'src', 'adminSettings.js'), filename: appId + '-adminSettings.js' }, filesplugin: { import: path.join(__dirname, 'src', 'filesplugin.js'), filename: appId + '-filesplugin.js' }, + filesplugin28: { import: path.join(__dirname, 'src', 'filesplugin28.js'), filename: appId + '-filesplugin28.js' }, } webpackConfig.plugins.push(