diff --git a/Makefile b/Makefile
index 9f5b5cdd..301b3c3e 100644
--- a/Makefile
+++ b/Makefile
@@ -15,32 +15,20 @@ help:
@echo " "
@echo " Next commands are only for dev environment with nextcloud-docker-dev!"
@echo " Daemon register(Linux, socket):"
- @echo " dock-sock create docker daemon for Nextcloud 29, 28, 27 (/var/run/docker.sock)"
- @echo " dock-sock27 create docker daemon for Nextcloud 27 (/var/run/docker.sock)"
- @echo " dock-sock27-gpu create docker daemon with GPU for Nextcloud 27 (/var/run/docker.sock)"
+ @echo " dock-sock create docker daemon for Nextcloud 30, 29, 28 (/var/run/docker.sock)"
@echo " dock-sock28 create docker daemon for Nextcloud 28 (/var/run/docker.sock)"
@echo " dock-sock28-gpu create docker daemon with GPU for Nextcloud 28 (/var/run/docker.sock)"
+ @echo " dock-sock29 create docker daemon for Nextcloud 29 (/var/run/docker.sock)"
+ @echo " dock-sock29-gpu create docker daemon with GPU for Nextcloud 29 (/var/run/docker.sock)"
@echo " dock-sock create docker daemon for Nextcloud Last (/var/run/docker.sock)"
@echo " dock-sock-gpu create docker daemon with GPU for Nextcloud Last (/var/run/docker.sock)"
@echo " "
@echo " Daemon register(any OS, host:port)"
@echo " dock2port will map docker socket to port. first use this!"
- @echo " dock-port27 create docker daemon for Nextcloud 27 (host.docker.internal:8443)"
@echo " dock-port28 create docker daemon for Nextcloud 28 (host.docker.internal:8443)"
+ @echo " dock-port29 create docker daemon for Nextcloud 29 (host.docker.internal:8443)"
@echo " dock-port create docker daemons for Nextcloud Last (host.docker.internal:8443)"
-.PHONY: dock-sock27
-dock-sock27:
- @echo "creating daemon for nextcloud 'stable27' container"
- docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:register \
- docker_dev Docker docker-install http /var/run/docker.sock http://stable27.local/index.php --net=master_default
-
-.PHONY: dock-sock27-gpu
-dock-sock27-gpu:
- @echo "creating daemon with NVIDIA gpu for nextcloud 'stable27' container"
- docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:register \
- docker_dev_gpu "Docker with GPU" docker-install http /var/run/docker.sock http://stable27.local/index.php --net=master_default --gpu --set-default
-
.PHONY: dock-sock28
dock-sock28:
@echo "creating daemon for nextcloud 'stable28' container"
@@ -53,6 +41,18 @@ dock-sock28-gpu:
docker exec master-stable28-1 sudo -u www-data php occ app_api:daemon:register \
docker_dev_gpu "Docker with GPU" docker-install http /var/run/docker.sock http://stable28.local/index.php --net=master_default --gpu --set-default
+.PHONY: dock-sock29
+dock-sock29:
+ @echo "creating daemon for nextcloud 'stable29' container"
+ docker exec master-stable29-1 sudo -u www-data php occ app_api:daemon:register \
+ docker_dev Docker docker-install http /var/run/docker.sock http://stable29.local/index.php --net=master_default
+
+.PHONY: dock-sock29-gpu
+dock-sock29-gpu:
+ @echo "creating daemon with NVIDIA gpu for nextcloud 'stable29' container"
+ docker exec master-stable29-1 sudo -u www-data php occ app_api:daemon:register \
+ docker_dev_gpu "Docker with GPU" docker-install http /var/run/docker.sock http://stable29.local/index.php --net=master_default --gpu --set-default
+
.PHONY: dock-sock
dock-sock:
@echo "creating daemon for nextcloud 'master' container"
@@ -74,20 +74,20 @@ dock2port:
--net=master_default \
--restart unless-stopped --privileged -d ghcr.io/cloud-py-api/nextcloud-appapi-dsp:latest
-.PHONY: dock-port27
-dock-port27:
- @echo "creating daemon for nextcloud '27' container"
- docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:register \
- docker_dev Docker docker-install http nextcloud-appapi-dsp:2375 http://stable27.local/index.php \
- --net=master_default --haproxy_password="some_secure_password"
-
.PHONY: dock-port28
dock-port28:
- @echo "creating daemon for nextcloud '27' container"
+ @echo "creating daemon for nextcloud '28' container"
docker exec master-stable28-1 sudo -u www-data php occ app_api:daemon:register \
docker_dev Docker docker-install http nextcloud-appapi-dsp:2375 http://stable28.local/index.php \
--net=master_default --haproxy_password="some_secure_password"
+.PHONY: dock-port29
+dock-port29:
+ @echo "creating daemon for nextcloud '29' container"
+ docker exec master-stable29-1 sudo -u www-data php occ app_api:daemon:register \
+ docker_dev Docker docker-install http nextcloud-appapi-dsp:2375 http://stable29.local/index.php \
+ --net=master_default --haproxy_password="some_secure_password"
+
.PHONY: dock-port
dock-port:
@echo "creating daemon for nextcloud 'master' container"
diff --git a/appinfo/info.xml b/appinfo/info.xml
index d7b32089..259a2889 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -63,7 +63,7 @@ to join us in shaping a more versatile, stable, and secure app landscape.
https://raw.githubusercontent.com/cloud-py-api/app_api/main/screenshots/app_api_4.png
-
+
OCA\AppAPI\BackgroundJob\ExAppInitStatusCheckJob
diff --git a/lib/Listener/LoadFilesPluginListener.php b/lib/Listener/LoadFilesPluginListener.php
index 43bcafe0..2969c939 100644
--- a/lib/Listener/LoadFilesPluginListener.php
+++ b/lib/Listener/LoadFilesPluginListener.php
@@ -36,12 +36,7 @@ public function handle(Event $event): void {
'fileActions' => $exFilesActions,
'instanceId' => $this->config->getSystemValue('instanceid'),
]);
- $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::addScript(Application::APP_ID, Application::APP_ID . '-filesplugin');
Util::addStyle(Application::APP_ID, 'filesactions');
}
}
diff --git a/src/filesplugin.js b/src/filesplugin.js
index 97e5707c..1f606322 100644
--- a/src/filesplugin.js
+++ b/src/filesplugin.js
@@ -1,67 +1,159 @@
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'
+import { translate as t } from '@nextcloud/l10n'
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}`)
}
-document.addEventListener('DOMContentLoaded', () => {
- 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
+function generateExAppUIPageUrl(appId, route) {
+ return generateUrl(`/apps/app_api/embedded/${appId}/${route}`)
+}
- 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))
+function registerFileAction28(fileAction, inlineSvgIcon) {
+ const action = new FileAction({
+ id: fileAction.name,
+ displayName: () => t(fileAction.appid, 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
}
- }).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))
})
+ 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)
+ if ('version' in fileAction && fileAction.version === '2.0') {
+ return axios.post(exAppFileActionHandler, { files: [buildNodeInfo(node)] })
+ .then((response) => {
+ if (typeof response.data === 'object' && 'redirect_handler' in response.data) {
+ const redirectPage = generateExAppUIPageUrl(fileAction.appid, response.data.redirect_handler)
+ window.location.assign(`${redirectPage}?fileIds=${node.fileid}`)
+ return true
+ }
+ return true
+ }).catch((error) => {
+ console.error('Failed to send FileAction request to ExApp', error)
+ return false
+ })
}
+ return axios.post(exAppFileActionHandler, buildNodeInfo(node))
+ .then(() => {
+ return true
+ })
+ .catch((error) => {
+ console.error('Failed to send FileAction request to ExApp', error)
+ return false
+ })
+ },
+ async execBatch(nodes, view, dir) {
+ if ('version' in fileAction && fileAction.version === '2.0') {
+ const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler)
+ const nodesDataList = nodes.map(buildNodeInfo)
+ return axios.post(exAppFileActionHandler, { files: nodesDataList })
+ .then((response) => {
+ if (typeof response.data === 'object' && 'redirect_handler' in response.data) {
+ const redirectPage = generateExAppUIPageUrl(fileAction.appid, response.data.redirect_handler)
+ const fileIds = nodes.map((node) => node.fileid).join(',')
+ window.location.assign(`${redirectPage}?fileIds=${fileIds}`)
+ }
+ return nodes.map(_ => true)
+ })
+ .catch((error) => {
+ console.error('Failed to send FileAction request to ExApp', error)
+ return nodes.map(_ => false)
+ })
+ }
+ // for version 1.0 behavior is not changed
+ return Promise.all(nodes.map((node) => {
+ return this.exec(node, view, dir)
+ }))
+ },
+ })
+ registerFileAction(action)
+}
- mimes.forEach((mimeType) => {
- const action = {
- name: fileAction.name,
- displayName: t(fileAction.appid, fileAction.display_name),
- mime: mimeType,
- permissions: Number(fileAction.permissions),
- order: Number(fileAction.order),
- icon: fileAction.icon !== '' ? generateAppAPIProxyUrl(fileAction.appid, fileAction.icon) : null,
- iconClass: fileAction.icon === '' ? 'icon-app-api' : '',
- actionHandler,
+function buildNodeInfo(node) {
+ return {
+ 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,
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ 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)
}
- OCA.Files.fileActions.registerAction(action)
})
- })
- }
+ }
+ })
})
diff --git a/src/filesplugin28.js b/src/filesplugin28.js
deleted file mode 100644
index 1f606322..00000000
--- a/src/filesplugin28.js
+++ /dev/null
@@ -1,159 +0,0 @@
-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'
-import { translate as t } from '@nextcloud/l10n'
-
-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}`)
-}
-
-function generateExAppUIPageUrl(appId, route) {
- return generateUrl(`/apps/app_api/embedded/${appId}/${route}`)
-}
-
-function registerFileAction28(fileAction, inlineSvgIcon) {
- const action = new FileAction({
- id: fileAction.name,
- displayName: () => t(fileAction.appid, 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)
- if ('version' in fileAction && fileAction.version === '2.0') {
- return axios.post(exAppFileActionHandler, { files: [buildNodeInfo(node)] })
- .then((response) => {
- if (typeof response.data === 'object' && 'redirect_handler' in response.data) {
- const redirectPage = generateExAppUIPageUrl(fileAction.appid, response.data.redirect_handler)
- window.location.assign(`${redirectPage}?fileIds=${node.fileid}`)
- return true
- }
- return true
- }).catch((error) => {
- console.error('Failed to send FileAction request to ExApp', error)
- return false
- })
- }
- return axios.post(exAppFileActionHandler, buildNodeInfo(node))
- .then(() => {
- return true
- })
- .catch((error) => {
- console.error('Failed to send FileAction request to ExApp', error)
- return false
- })
- },
- async execBatch(nodes, view, dir) {
- if ('version' in fileAction && fileAction.version === '2.0') {
- const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler)
- const nodesDataList = nodes.map(buildNodeInfo)
- return axios.post(exAppFileActionHandler, { files: nodesDataList })
- .then((response) => {
- if (typeof response.data === 'object' && 'redirect_handler' in response.data) {
- const redirectPage = generateExAppUIPageUrl(fileAction.appid, response.data.redirect_handler)
- const fileIds = nodes.map((node) => node.fileid).join(',')
- window.location.assign(`${redirectPage}?fileIds=${fileIds}`)
- }
- return nodes.map(_ => true)
- })
- .catch((error) => {
- console.error('Failed to send FileAction request to ExApp', error)
- return nodes.map(_ => false)
- })
- }
- // for version 1.0 behavior is not changed
- return Promise.all(nodes.map((node) => {
- return this.exec(node, view, dir)
- }))
- },
- })
- registerFileAction(action)
-}
-
-function buildNodeInfo(node) {
- return {
- 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,
- }
-}
-
-document.addEventListener('DOMContentLoaded', () => {
- 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)
- }
- })
- }
- })
-})
diff --git a/webpack.js b/webpack.js
index a40f03fc..203ae358 100644
--- a/webpack.js
+++ b/webpack.js
@@ -22,7 +22,6 @@ 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(