From 6e4d79be140fd33c28c03b5120d6ae0c8a002a90 Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Wed, 21 Jan 2026 12:53:58 +0545 Subject: [PATCH] feat: Add NC 33 Files API compatibility with backward support - Update @nextcloud/files to support v4.0.0-beta.9+ for NC 33+ - Implement new sidebar tab registration using registerSidebarTab API - Create custom element wrapper for Vue 2 compatibility with NC 33+ - Add automatic fallback to legacy Tab API for NC < 33 - Add defensive check for Sidebar API in file actions - Maintain full backward compatibility with older NC versions Implements support for breaking changes in NC 33: - File sidebar tab now uses custom DOM elements - Uses new registerSidebarTab from @nextcloud/files - File actions already use compatible API Signed-off-by: Saw-jan --- package-lock.json | 6 +- package.json | 2 +- src/filesPlugin/filesPlugin.js | 8 ++- src/projectTab.js | 114 ++++++++++++++++++++++++++++++++- 4 files changed, 123 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 06e02e75b..38adc5f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "integration_openproject", - "version": "2.10.1", + "version": "2.11.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "integration_openproject", - "version": "2.10.1", + "version": "2.11.0", "license": "AGPL-3.0-or-later", "dependencies": { "@mdi/svg": "^7.3.67", "@nextcloud/auth": "^2.3.0", "@nextcloud/axios": "^2.5.0", "@nextcloud/dialogs": "^5.3.4", - "@nextcloud/files": "^3.5.1", + "@nextcloud/files": "^4.0.0-beta.9 || ^3.5.1", "@nextcloud/initial-state": "^2.2.0", "@nextcloud/l10n": "^2.2.0", "@nextcloud/moment": "^1.3.1", diff --git a/package.json b/package.json index a64856685..354c8f092 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@nextcloud/auth": "^2.3.0", "@nextcloud/axios": "^2.5.0", "@nextcloud/dialogs": "^5.3.4", - "@nextcloud/files": "^3.5.1", + "@nextcloud/files": "^4.0.0-beta.9 || ^3.5.1", "@nextcloud/initial-state": "^2.2.0", "@nextcloud/l10n": "^2.2.0", "@nextcloud/moment": "^1.3.1", diff --git a/src/filesPlugin/filesPlugin.js b/src/filesPlugin/filesPlugin.js index 5de0ac8be..874def22c 100644 --- a/src/filesPlugin/filesPlugin.js +++ b/src/filesPlugin/filesPlugin.js @@ -47,8 +47,12 @@ const singleFileAction = new FileAction({ }, iconSvgInline: () => OpenProjectIcon, async exec(node, view, dir) { - window.OCA.Files.Sidebar.setActiveTab('open-project') - await window.OCA.Files.Sidebar.open(node.path) + // Open the sidebar with the OpenProject tab + // In NC 33+, OCA.Files.Sidebar still exists for programmatic access + if (window.OCA?.Files?.Sidebar) { + window.OCA.Files.Sidebar.setActiveTab('open-project') + await window.OCA.Files.Sidebar.open(node.path) + } return null }, }) diff --git a/src/projectTab.js b/src/projectTab.js index 01eab3090..fdd0493ec 100644 --- a/src/projectTab.js +++ b/src/projectTab.js @@ -5,9 +5,9 @@ */ import Vue from 'vue' - import './bootstrap.js' import ProjectsTab from './views/ProjectsTab.vue' +import OpenProjectIcon from '../img/app-dark.svg' // Init OpenProject Tab Service if (!window.OCA.OpenProject) { @@ -17,6 +17,18 @@ if (!window.OCA.OpenProject) { const View = Vue.extend(ProjectsTab) let TabInstance = null +// Try to import NC 33+ APIs +let registerSidebarTab +try { + // Dynamic import for NC 33+ API + const filesModule = await import('@nextcloud/files') + registerSidebarTab = filesModule.registerSidebarTab +} catch (e) { + // NC < 33: API not available + registerSidebarTab = null +} + +// For NC < 33: Use old Tab API const projectTab = new OCA.Files.Sidebar.Tab({ id: 'open-project', name: t('integration_openproject', 'OpenProject'), @@ -43,8 +55,108 @@ const projectTab = new OCA.Files.Sidebar.Tab({ }, }) +// Setup custom element for NC 33+ (Vue 2 approach) +function setupCustomElement() { + const tagName = 'openproject-sidebar-tab' + + if (!window.customElements) { + return false + } + + if (window.customElements.get(tagName)) { + // Already defined + return true + } + + try { + // Custom element wrapper for Vue 2 + class OpenProjectTabElement extends HTMLElement { + + constructor() { + super() + this._vueInstance = null + } + + connectedCallback() { + // Get props from the element + const node = this.node || {} + + // Create Vue instance + this._vueInstance = new View() + + // Mount the Vue component + this._vueInstance.$mount(this) + + // Update with file info if node is provided + if (node.fileid) { + this._vueInstance.update({ + id: node.fileid, + name: node.basename, + path: node.path, + ...node, + }) + } + } + + disconnectedCallback() { + if (this._vueInstance) { + this._vueInstance.$destroy() + this._vueInstance = null + } + } + + // Allow setting node dynamically + set node(value) { + this._node = value + if (this._vueInstance && value && value.fileid) { + this._vueInstance.update({ + id: value.fileid, + name: value.basename, + path: value.path, + ...value, + }) + } + } + + get node() { + return this._node + } + + } + + window.customElements.define(tagName, OpenProjectTabElement) + return true + } catch (e) { + console.error('Failed to define custom element for OpenProject tab', e) + return false + } +} + window.addEventListener('DOMContentLoaded', function() { if (OCA.Files && OCA.Files.Sidebar) { + // Try NC 33+ API first + if (registerSidebarTab && setupCustomElement()) { + try { + registerSidebarTab({ + id: 'open-project', + order: 90, + displayName: t('integration_openproject', 'OpenProject'), + iconSvgInline: OpenProjectIcon, + tagName: 'openproject-sidebar-tab', + enabled({ node }) { + // Enable for all files + return true + }, + }) + console.debug('OpenProject: Registered sidebar tab using NC 33+ API') + return + } catch (e) { + console.warn('OpenProject: Failed to register sidebar tab with NC 33+ API, falling back', e) + } + } + + // Fallback to old API for NC < 33 OCA.Files.Sidebar.registerTab(projectTab) + console.debug('OpenProject: Registered sidebar tab using legacy API') } })