@conduction/nextcloud-vue 2.56.0-beta.3 → 2.56.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +21 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +49 -5
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +2 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +2 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +78 -7
- package/dist/esm/utils/actionLink.js +5 -0
- package/dist/esm/utils/actionLink.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +16 -1
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/linkNavigation.js +6 -3
- package/dist/esm/utils/linkNavigation.js.map +1 -1
- package/dist/esm/utils/rowAuxClick.js +30 -5
- package/dist/esm/utils/rowAuxClick.js.map +1 -1
- package/dist/esm/utils/safeHref.js +18 -3
- package/dist/esm/utils/safeHref.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +885 -393
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +78 -7
- package/l10n/en.json +2 -0
- package/l10n/nl.json +2 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
- package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +31 -2
- package/src/components/CnBoardView/CnBoardView.vue +13 -5
- package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
- package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +131 -17
- package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
- package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
- package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
- package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
- package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
- package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
- package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
- package/src/components/CnObjectList/CnObjectList.vue +19 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
- package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
- package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
- package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
- package/src/components/CnRowActions/CnRowActions.vue +7 -2
- package/src/components/CnSetupWizard/CnSetupWizard.vue +57 -6
- package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
- package/src/components/CnTabs/CnTabs.vue +7 -3
- package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
- package/src/css/table.css +26 -0
- package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
- package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
- package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
- package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
- package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
- package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
- package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
- package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
- package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
- package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
- package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
- package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
- package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
- package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
- package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
- package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
- package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
- package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
- package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
- package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
- package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
- package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
- package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
- package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
- package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
- package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
- package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
- package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
- package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
- package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
- package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
- package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
- package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
- package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
- package/src/schemas/app-manifest-v2.schema.json +2 -1
- package/src/utils/actionLink.js +5 -0
- package/src/utils/actionsDispatcher.js +15 -0
- package/src/utils/linkNavigation.js +6 -3
- package/src/utils/rowAuxClick.js +29 -4
- package/src/utils/safeHref.js +18 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFlowTab.vue2.js","sources":["../../../../../src/integrations/builtin/flow/CnFlowTab.vue"],"sourcesContent":["<!--\n CnFlowTab — bespoke sidebar tab for the `flow` (automation) integration.\n\n Renders a list of NC Flow (workflowengine) operations bound to the\n parent OR object via the Tier-2 `openregister_flow_links` table.\n Each row surfaces the operation name, the target entity class\n (short form), the operation type, a chips set of trigger events, a\n checks-count, and a (display-only) enabled toggle. A trailing\n \"Open in Flow settings\" deep-link points to\n `/index.php/settings/admin/workflow` (NC Flow is admin-gated, so\n edits happen in the admin settings, not in the sidebar tab).\n\n Talks to the Tier-2 link-table endpoints:\n GET /api/objects/{r}/{s}/{id}/flow (read, all users)\n POST /api/objects/{r}/{s}/{id}/flow (link, admin-only)\n DELETE /api/objects/{r}/{s}/{id}/flow/{operationId} (unlink, admin-only)\n\n Payload contract (per FlowProvider + FlowLinkService):\n { operationId, operationName, operationClass, entityType,\n enabled, url, linkId, events, checks, operation }\n + envelope `{ results, total, isAdmin }`\n\n Surface behaviour:\n - Admins see: \"Link existing automation\" button → CnFlowOperationPicker\n + per-row unlink action.\n - Non-admins see: read-only list, no link/unlink controls. The\n \"Open Flow settings\" deep-link is still surfaced so the user\n knows where automations live.\n - Loading + 503 \"currently unavailable\" + generic error states\n match CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-flow/` for the spec\n delta and ADR-019 (registry mechanism) / ADR-023 (action authorisation).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-flow-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-flow-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div v-if=\"isAdmin && !loading && !error\" class=\"cn-flow-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" data-testid=\"cn-flow-tab-link\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing automation') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-flow-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"operations.length === 0\" class=\"cn-sidebar-tab__empty cn-flow-tab__empty\">\n\t\t\t<SitemapOutline :size=\"32\" class=\"cn-flow-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"flowSettingsUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<SitemapOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openSettingsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-flow-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"op in operations\"\n\t\t\t\t:key=\"opKey(op)\"\n\t\t\t\tclass=\"cn-flow-tab__row\"\n\t\t\t\t:class=\"{ 'cn-flow-tab__row--disabled': isDisabled(op) }\"\n\t\t\t\t:name=\"opTitle(op)\"\n\t\t\t\t:href=\"opUrl(op)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:forceDisplayActions=\"isAdmin\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-flow-tab__avatar\" :class=\"enabledClass(op)\" :aria-hidden=\"true\">\n\t\t\t\t\t\t<SitemapOutline :size=\"20\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span v-if=\"opSummary(op)\" class=\"cn-flow-tab__summary\">{{ opSummary(op) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #indicator>\n\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\tclass=\"cn-flow-tab__enabled\"\n\t\t\t\t\t\t:class=\"enabledClass(op)\"\n\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t:variant=\"enabledVariant(op)\"\n\t\t\t\t\t\t:label=\"enabledLabel(op)\"\n\t\t\t\t\t\t:aria-label=\"enabledAriaLabel(op)\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"events(op).length > 0 || opMeta(op)\" #details>\n\t\t\t\t\t<span class=\"cn-flow-tab__details\">\n\t\t\t\t\t\t<span v-if=\"events(op).length > 0\" class=\"cn-flow-tab__events\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"evt in events(op)\"\n\t\t\t\t\t\t\t\t:key=\"evt\"\n\t\t\t\t\t\t\t\tclass=\"cn-flow-tab__chip\">{{ shortEvent(evt) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"opMeta(op)\" class=\"cn-flow-tab__meta\">{{ opMeta(op) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"isAdmin\" #actions>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-flow-tab__unlink\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Unlink automation')\"\n\t\t\t\t\t\tdata-testid=\"cn-flow-tab-unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"confirmUnlink(op)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink automation') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnFlowOperationPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:flowSettingsUrl=\"flowSettingsUrl\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLink\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport SitemapOutline from 'vue-material-design-icons/SitemapOutline.vue'\nimport CnFlowOperationPicker from '../../../components/CnFlowOperationPicker/CnFlowOperationPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnFlowTab — bespoke automation-rule list for the `flow` integration.\n *\n * Admins can link/unlink operations via the picker; non-admins see\n * a read-only list with a deep-link to NC Workflow Settings.\n */\nexport default {\n\tname: 'CnFlowTab',\n\n\tcomponents: { NcActionButton, NcButton, NcListItem, NcLoadingIcon, AlertCircleOutline, Close, Plus, SitemapOutline, CnFlowOperationPicker, CnStatusBadge },\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'flow'`). */\n\t\tintegrationId: { type: String, default: 'flow' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No automations linked yet') },\n\t\t/** Pre-translated label for the \"Open Flow settings\" CTA. */\n\t\topenSettingsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Workflow settings') },\n\t\t/** Pre-translated banner when Flow is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Flow is currently unavailable.') },\n\t\t/** URL of the NC Flow admin settings page. */\n\t\tflowSettingsUrl: { type: String, default: '/index.php/settings/admin/workflow' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\toperations: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tisAdmin: false,\n\t\t\tpickerOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchOperations()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchOperations()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchOperations()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/flow`\n\t\t},\n\n\t\topKey(op) {\n\t\t\treturn op.operationId ?? op.id ?? op.title ?? ''\n\t\t},\n\n\t\topTitle(op) {\n\t\t\treturn op.operationName || op.title || op.name || this.shortClass(op.operationClass) || t('nextcloud-vue', 'Untitled automation')\n\t\t},\n\n\t\topUrl(op) {\n\t\t\treturn op.url || this.flowSettingsUrl\n\t\t},\n\n\t\tshortClass(className) {\n\t\t\tif (!className) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst parts = String(className).split('\\\\')\n\t\t\treturn parts[parts.length - 1] || className\n\t\t},\n\n\t\tentityLabel(op) {\n\t\t\tconst entity = op.entityType || op.entity\n\t\t\tif (!entity) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.shortClass(entity)\n\t\t},\n\n\t\toperationLabel(op) {\n\t\t\treturn op.operation || ''\n\t\t},\n\n\t\topSummary(op) {\n\t\t\tconst parts = []\n\t\t\tconst entity = this.entityLabel(op)\n\t\t\tif (entity !== '') {\n\t\t\t\tparts.push(t('nextcloud-vue', 'Entity: {entity}', { entity }))\n\t\t\t}\n\t\t\tconst opLabel = this.operationLabel(op)\n\t\t\tif (opLabel !== '') {\n\t\t\t\tparts.push(t('nextcloud-vue', 'Operation: {op}', { op: opLabel }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tevents(op) {\n\t\t\tconst raw = op.events ?? op.data?.events ?? []\n\t\t\tif (Array.isArray(raw) === true) {\n\t\t\t\treturn raw.filter((e) => typeof e === 'string' && e !== '')\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tshortEvent(evt) {\n\t\t\tconst colon = String(evt).lastIndexOf(':')\n\t\t\tif (colon === -1) {\n\t\t\t\treturn String(evt)\n\t\t\t}\n\t\t\treturn String(evt).slice(colon + 1) || String(evt)\n\t\t},\n\n\t\tchecksCount(op) {\n\t\t\tconst raw = op.checks ?? op.data?.checks ?? []\n\t\t\tif (Array.isArray(raw) === true) {\n\t\t\t\treturn raw.length\n\t\t\t}\n\t\t\tif (typeof raw === 'string' && raw.startsWith('[') === true) {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(raw)\n\t\t\t\t\treturn Array.isArray(parsed) === true ? parsed.length : 0\n\t\t\t\t} catch {\n\t\t\t\t\treturn 0\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\topMeta(op) {\n\t\t\tconst parts = []\n\t\t\tconst checks = this.checksCount(op)\n\t\t\tif (checks > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} conditions', { n: checks }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tisDisabled(op) {\n\t\t\tconst v = op.enabled ?? op.data?.enabled\n\t\t\tif (v === undefined || v === null) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn v === false || v === 0 || v === '0'\n\t\t},\n\n\t\tenabledLabel(op) {\n\t\t\treturn this.isDisabled(op) === true\n\t\t\t\t? t('nextcloud-vue', 'Disabled')\n\t\t\t\t: t('nextcloud-vue', 'Enabled')\n\t\t},\n\n\t\tenabledClass(op) {\n\t\t\treturn this.isDisabled(op) === true\n\t\t\t\t? 'cn-flow-tab__enabled--off'\n\t\t\t\t: 'cn-flow-tab__enabled--on'\n\t\t},\n\n\t\tenabledVariant(op) {\n\t\t\treturn this.isDisabled(op) === true ? 'default' : 'success'\n\t\t},\n\n\t\tenabledAriaLabel(op) {\n\t\t\treturn this.enabledLabel(op)\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\tasync fetchOperations() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.operations = rows\n\t\t\t\t\tthis.isAdmin = Boolean(data.isAdmin)\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.degraded = t('nextcloud-vue', 'NC Workflow Engine is not installed.')\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load automations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] failed to fetch operations', err)\n\t\t\t\tthis.operations = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load automations.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync onLink({ operationId }) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ operationId }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchOperations()\n\t\t\t\t} else if (response.status === 403) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Only administrators can link automations.')\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Automation is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link automation.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link automation.')\n\t\t\t}\n\t\t},\n\n\t\tasync confirmUnlink(op) {\n\t\t\tconst opId = op.operationId ?? op.id\n\t\t\tif (!opId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (typeof window !== 'undefined' && typeof window.confirm === 'function') {\n\t\t\t\tconst ok = window.confirm(t('nextcloud-vue', 'Remove this automation link?'))\n\t\t\t\tif (!ok) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(opId)}`), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchOperations()\n\t\t\t\t} else if (response.status === 403) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Only administrators can unlink automations.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink automation.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink automation.')\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-flow-tab__actions {\n\tmargin-bottom: 8px;\n}\n\n.cn-flow-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-flow-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-flow-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Mirror NC Flow's rule rows: a round robot avatar, the bold rule\n name, a trigger/condition subline, and an enabled/disabled chip. */\n.cn-flow-tab__row--disabled {\n\topacity: 0.7;\n}\n\n.cn-flow-tab__avatar {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 36px;\n\theight: 36px;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-flow-tab__avatar.cn-flow-tab__enabled--on {\n\tbackground: var(--color-primary-element-light, var(--color-background-dark));\n\tcolor: var(--color-primary-element, #0082c9);\n}\n\n.cn-flow-tab__avatar.cn-flow-tab__enabled--off {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__summary {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-flow-tab__meta {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__details {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tflex-wrap: wrap;\n\tgap: 6px;\n}\n\n.cn-flow-tab__events {\n\tdisplay: inline-flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n\n.cn-flow-tab__chip {\n\tdisplay: inline-block;\n\tpadding: 1px 8px;\n\tborder-radius: 10px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 0.75em;\n}\n</style>\n"],"names":["CnFlowOperationPicker","CnStatusBadge","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFlowTab.vue2.js","sources":["../../../../../src/integrations/builtin/flow/CnFlowTab.vue"],"sourcesContent":["<!--\n CnFlowTab — bespoke sidebar tab for the `flow` (automation) integration.\n\n Renders a list of NC Flow (workflowengine) operations bound to the\n parent OR object via the Tier-2 `openregister_flow_links` table.\n Each row surfaces the operation name, the target entity class\n (short form), the operation type, a chips set of trigger events, a\n checks-count, and a (display-only) enabled toggle. A trailing\n \"Open in Flow settings\" deep-link points to\n `/index.php/settings/admin/workflow` (NC Flow is admin-gated, so\n edits happen in the admin settings, not in the sidebar tab).\n\n Talks to the Tier-2 link-table endpoints:\n GET /api/objects/{r}/{s}/{id}/flow (read, all users)\n POST /api/objects/{r}/{s}/{id}/flow (link, admin-only)\n DELETE /api/objects/{r}/{s}/{id}/flow/{operationId} (unlink, admin-only)\n\n Payload contract (per FlowProvider + FlowLinkService):\n { operationId, operationName, operationClass, entityType,\n enabled, url, linkId, events, checks, operation }\n + envelope `{ results, total, isAdmin }`\n\n Surface behaviour:\n - Admins see: \"Link existing automation\" button → CnFlowOperationPicker\n + per-row unlink action.\n - Non-admins see: read-only list, no link/unlink controls. The\n \"Open Flow settings\" deep-link is still surfaced so the user\n knows where automations live.\n - Loading + 503 \"currently unavailable\" + generic error states\n match CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-flow/` for the spec\n delta and ADR-019 (registry mechanism) / ADR-023 (action authorisation).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-flow-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-flow-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div v-if=\"isAdmin && !loading && !error\" class=\"cn-flow-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" data-testid=\"cn-flow-tab-link\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing automation') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-flow-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"operations.length === 0\" class=\"cn-sidebar-tab__empty cn-flow-tab__empty\">\n\t\t\t<SitemapOutline :size=\"32\" class=\"cn-flow-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"flowSettingsUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<SitemapOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openSettingsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-flow-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"op in operations\"\n\t\t\t\t:key=\"opKey(op)\"\n\t\t\t\tclass=\"cn-flow-tab__row\"\n\t\t\t\t:class=\"{ 'cn-flow-tab__row--disabled': isDisabled(op) }\"\n\t\t\t\t:name=\"opTitle(op)\"\n\t\t\t\t:href=\"opUrl(op)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:forceDisplayActions=\"isAdmin\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-flow-tab__avatar\" :class=\"enabledClass(op)\" :aria-hidden=\"true\">\n\t\t\t\t\t\t<SitemapOutline :size=\"20\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span v-if=\"opSummary(op)\" class=\"cn-flow-tab__summary\">{{ opSummary(op) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #indicator>\n\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\tclass=\"cn-flow-tab__enabled\"\n\t\t\t\t\t\t:class=\"enabledClass(op)\"\n\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t:variant=\"enabledVariant(op)\"\n\t\t\t\t\t\t:label=\"enabledLabel(op)\"\n\t\t\t\t\t\t:aria-label=\"enabledAriaLabel(op)\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"events(op).length > 0 || opMeta(op)\" #details>\n\t\t\t\t\t<span class=\"cn-flow-tab__details\">\n\t\t\t\t\t\t<span v-if=\"events(op).length > 0\" class=\"cn-flow-tab__events\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"evt in events(op)\"\n\t\t\t\t\t\t\t\t:key=\"evt\"\n\t\t\t\t\t\t\t\tclass=\"cn-flow-tab__chip\">{{ shortEvent(evt) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"opMeta(op)\" class=\"cn-flow-tab__meta\">{{ opMeta(op) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"isAdmin\" #actions>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-flow-tab__unlink\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Unlink automation')\"\n\t\t\t\t\t\tdata-testid=\"cn-flow-tab-unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"confirmUnlink(op)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink automation') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnFlowOperationPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:flowSettingsUrl=\"flowSettingsUrl\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLink\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport SitemapOutline from 'vue-material-design-icons/SitemapOutline.vue'\nimport CnFlowOperationPicker from '../../../components/CnFlowOperationPicker/CnFlowOperationPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnFlowTab — bespoke automation-rule list for the `flow` integration.\n *\n * Admins can link/unlink operations via the picker; non-admins see\n * a read-only list with a deep-link to NC Workflow Settings.\n */\nexport default {\n\tname: 'CnFlowTab',\n\n\tcomponents: { NcActionButton, NcButton, NcListItem, NcLoadingIcon, AlertCircleOutline, Close, Plus, SitemapOutline, CnFlowOperationPicker, CnStatusBadge },\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'flow'`). */\n\t\tintegrationId: { type: String, default: 'flow' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No automations linked yet') },\n\t\t/** Pre-translated label for the \"Open Flow settings\" CTA. */\n\t\topenSettingsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Workflow settings') },\n\t\t/** Pre-translated banner when Flow is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Flow is currently unavailable.') },\n\t\t/** URL of the NC Flow admin settings page. */\n\t\tflowSettingsUrl: { type: String, default: '/index.php/settings/admin/workflow' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\toperations: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tisAdmin: false,\n\t\t\tpickerOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchOperations()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchOperations()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchOperations()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/flow`\n\t\t},\n\n\t\topKey(op) {\n\t\t\treturn op.operationId ?? op.id ?? op.title ?? ''\n\t\t},\n\n\t\topTitle(op) {\n\t\t\treturn op.operationName || op.title || op.name || this.shortClass(op.operationClass) || t('nextcloud-vue', 'Untitled automation')\n\t\t},\n\n\t\topUrl(op) {\n\t\t\treturn op.url && safeHref(op.url) !== '#' ? op.url : this.flowSettingsUrl\n\t\t},\n\n\t\tshortClass(className) {\n\t\t\tif (!className) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst parts = String(className).split('\\\\')\n\t\t\treturn parts[parts.length - 1] || className\n\t\t},\n\n\t\tentityLabel(op) {\n\t\t\tconst entity = op.entityType || op.entity\n\t\t\tif (!entity) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.shortClass(entity)\n\t\t},\n\n\t\toperationLabel(op) {\n\t\t\treturn op.operation || ''\n\t\t},\n\n\t\topSummary(op) {\n\t\t\tconst parts = []\n\t\t\tconst entity = this.entityLabel(op)\n\t\t\tif (entity !== '') {\n\t\t\t\tparts.push(t('nextcloud-vue', 'Entity: {entity}', { entity }))\n\t\t\t}\n\t\t\tconst opLabel = this.operationLabel(op)\n\t\t\tif (opLabel !== '') {\n\t\t\t\tparts.push(t('nextcloud-vue', 'Operation: {op}', { op: opLabel }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tevents(op) {\n\t\t\tconst raw = op.events ?? op.data?.events ?? []\n\t\t\tif (Array.isArray(raw) === true) {\n\t\t\t\treturn raw.filter((e) => typeof e === 'string' && e !== '')\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tshortEvent(evt) {\n\t\t\tconst colon = String(evt).lastIndexOf(':')\n\t\t\tif (colon === -1) {\n\t\t\t\treturn String(evt)\n\t\t\t}\n\t\t\treturn String(evt).slice(colon + 1) || String(evt)\n\t\t},\n\n\t\tchecksCount(op) {\n\t\t\tconst raw = op.checks ?? op.data?.checks ?? []\n\t\t\tif (Array.isArray(raw) === true) {\n\t\t\t\treturn raw.length\n\t\t\t}\n\t\t\tif (typeof raw === 'string' && raw.startsWith('[') === true) {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(raw)\n\t\t\t\t\treturn Array.isArray(parsed) === true ? parsed.length : 0\n\t\t\t\t} catch {\n\t\t\t\t\treturn 0\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\topMeta(op) {\n\t\t\tconst parts = []\n\t\t\tconst checks = this.checksCount(op)\n\t\t\tif (checks > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} conditions', { n: checks }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tisDisabled(op) {\n\t\t\tconst v = op.enabled ?? op.data?.enabled\n\t\t\tif (v === undefined || v === null) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn v === false || v === 0 || v === '0'\n\t\t},\n\n\t\tenabledLabel(op) {\n\t\t\treturn this.isDisabled(op) === true\n\t\t\t\t? t('nextcloud-vue', 'Disabled')\n\t\t\t\t: t('nextcloud-vue', 'Enabled')\n\t\t},\n\n\t\tenabledClass(op) {\n\t\t\treturn this.isDisabled(op) === true\n\t\t\t\t? 'cn-flow-tab__enabled--off'\n\t\t\t\t: 'cn-flow-tab__enabled--on'\n\t\t},\n\n\t\tenabledVariant(op) {\n\t\t\treturn this.isDisabled(op) === true ? 'default' : 'success'\n\t\t},\n\n\t\tenabledAriaLabel(op) {\n\t\t\treturn this.enabledLabel(op)\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\tasync fetchOperations() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.operations = rows\n\t\t\t\t\tthis.isAdmin = Boolean(data.isAdmin)\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.degraded = t('nextcloud-vue', 'NC Workflow Engine is not installed.')\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load automations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] failed to fetch operations', err)\n\t\t\t\tthis.operations = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load automations.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync onLink({ operationId }) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ operationId }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchOperations()\n\t\t\t\t} else if (response.status === 403) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Only administrators can link automations.')\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Automation is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link automation.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link automation.')\n\t\t\t}\n\t\t},\n\n\t\tasync confirmUnlink(op) {\n\t\t\tconst opId = op.operationId ?? op.id\n\t\t\tif (!opId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (typeof window !== 'undefined' && typeof window.confirm === 'function') {\n\t\t\t\tconst ok = window.confirm(t('nextcloud-vue', 'Remove this automation link?'))\n\t\t\t\tif (!ok) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(opId)}`), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchOperations()\n\t\t\t\t} else if (response.status === 403) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Only administrators can unlink automations.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink automation.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink automation.')\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-flow-tab__actions {\n\tmargin-bottom: 8px;\n}\n\n.cn-flow-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-flow-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-flow-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Mirror NC Flow's rule rows: a round robot avatar, the bold rule\n name, a trigger/condition subline, and an enabled/disabled chip. */\n.cn-flow-tab__row--disabled {\n\topacity: 0.7;\n}\n\n.cn-flow-tab__avatar {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 36px;\n\theight: 36px;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-flow-tab__avatar.cn-flow-tab__enabled--on {\n\tbackground: var(--color-primary-element-light, var(--color-background-dark));\n\tcolor: var(--color-primary-element, #0082c9);\n}\n\n.cn-flow-tab__avatar.cn-flow-tab__enabled--off {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__summary {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-flow-tab__meta {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__details {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tflex-wrap: wrap;\n\tgap: 6px;\n}\n\n.cn-flow-tab__events {\n\tdisplay: inline-flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n\n.cn-flow-tab__chip {\n\tdisplay: inline-block;\n\tpadding: 1px 8px;\n\tborder-radius: 10px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 0.75em;\n}\n</style>\n"],"names":["CnFlowOperationPicker","CnStatusBadge","t"],"mappings":";;;;;;;;;;;AA8IA;;AAEC;;;;AAID,aAAe;AACd,CAAA,IAAI,EAAE,WAAW;;AAEjB,CAAA,UAAU,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,kBAAkB,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,yBAAEA,UAAqB,iBAAEC,cAAe;;AAE1J,CAAA,KAAK,EAAE;AACN;AACA;AACA,EAAA,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ;AAChD;AACA;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;AAC1C;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;AACvC;AACA,EAAA,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;AACrC;EACA,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAuB,EAAG;AAC5D;AACA,EAAA,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,2BAA2B,GAAG;;AAE5F,EAAA,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,wBAAwB,GAAG;;AAEhG,EAAA,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,mCAAmC,GAAG;AAC1G;AACA,EAAA,eAAe,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,sCAAsC;EAChF;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,UAAU,EAAE,EAAE;GACd,OAAO,EAAE,KAAK;AACd,GAAA,KAAK,EAAE,EAAE;GACT,QAAQ,EAAE,EAAE;GACZ,OAAO,EAAE,KAAK;AACd,GAAA,UAAU,EAAE,KAAK;AAClB,GAAA;EACA;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE;AACxC,GAAA,IAAI,EAAE,EAAE;AACP,IAAA,IAAI,CAAC,eAAe,GAAC;AACtB,IAAA;AACD,GAAA,EAAG;;EAEH,QAAQ,GAAG;AACV,GAAA,IAAI,CAAC,eAAe,GAAC;GACrB;;AAED,EAAA,MAAM,GAAG;AACR,GAAA,IAAI,CAAC,eAAe,GAAC;GACrB;EACD;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;;AAED,EAAA,OAAO,GAAG;AACT,GAAA,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAA;GACrF;;AAED,EAAA,KAAK,CAAC,EAAE,EAAE;AACT,GAAA,OAAO,EAAE,CAAC,WAAU,IAAK,EAAE,CAAC,EAAC,IAAK,EAAE,CAAC,KAAM,IAAG,EAAC;GAC/C;;EAED,OAAO,CAAC,EAAE,EAAE;GACX,OAAO,EAAE,CAAC,aAAY,IAAK,EAAE,CAAC,KAAM,IAAG,EAAE,CAAC,IAAG,IAAK,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,cAAc,CAAE,IAAGA,SAAC,CAAC,eAAe,EAAE,qBAAqB,CAAA;GAChI;;AAED,EAAA,KAAK,CAAC,EAAE,EAAE;AACT,GAAA,OAAO,EAAE,CAAC,GAAI,IAAG,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAE,KAAI,GAAI,GAAE,EAAE,CAAC,GAAE,GAAI,IAAI,CAAC,eAAc;GACxE;;AAED,EAAA,UAAU,CAAC,SAAS,EAAE;AACrB,GAAA,IAAI,CAAC,SAAS,EAAE;AACf,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,MAAM,KAAM,GAAE,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,EAAA;AAC1C,GAAA,OAAO,KAAK,CAAC,KAAK,CAAC,MAAO,GAAE,CAAC,CAAE,IAAG,SAAQ;GAC1C;;AAED,EAAA,WAAW,CAAC,EAAE,EAAE;AACf,GAAA,MAAM,MAAO,GAAE,EAAE,CAAC,UAAW,IAAG,EAAE,CAAC,OAAK;GACxC,IAAI,CAAC,MAAM,EAAE;AACZ,IAAA,OAAO,EAAC;AACT,IAAA;GACA,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAA;GAC7B;;AAED,EAAA,cAAc,CAAC,EAAE,EAAE;AAClB,GAAA,OAAO,EAAE,CAAC,SAAU,IAAG,EAAC;GACxB;;EAED,SAAS,CAAC,EAAE,EAAE;GACb,MAAM,KAAM,GAAE,GAAC;AACf,GAAA,MAAM,MAAK,GAAI,IAAI,CAAC,WAAW,CAAC,EAAE,EAAA;AAClC,GAAA,IAAI,MAAK,KAAM,EAAE,EAAE;IAClB,KAAK,CAAC,IAAI,CAACA,SAAC,CAAC,eAAe,EAAE,kBAAkB,EAAE,EAAE,MAAO,EAAC,CAAC,EAAA;AAC9D,IAAA;AACA,GAAA,MAAM,OAAM,GAAI,IAAI,CAAC,cAAc,CAAC,EAAE,EAAA;AACtC,GAAA,IAAI,OAAM,KAAM,EAAE,EAAE;IACnB,KAAK,CAAC,IAAI,CAACA,SAAC,CAAC,eAAe,EAAE,iBAAiB,EAAE,EAAE,EAAE,EAAE,OAAQ,EAAC,CAAC,EAAA;AAClE,IAAA;AACA,GAAA,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAA;GACvB;;EAED,MAAM,CAAC,EAAE,EAAE;AACV,GAAA,MAAM,GAAI,GAAE,EAAE,CAAC,MAAO,IAAG,EAAE,CAAC,IAAI,EAAE,MAAO,IAAG,GAAC;GAC7C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAE,KAAI,IAAI,EAAE;IAChC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAE,KAAI,YAAY,MAAM,EAAE,CAAA;AAC3D,IAAA;AACA,GAAA,OAAO,EAAC;GACR;;AAED,EAAA,UAAU,CAAC,GAAG,EAAE;AACf,GAAA,MAAM,KAAM,GAAE,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,GAAG,EAAA;AACzC,GAAA,IAAI,KAAM,KAAI,CAAC,CAAC,EAAE;AACjB,IAAA,OAAO,MAAM,CAAC,GAAG,CAAA;AAClB,IAAA;AACA,GAAA,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,KAAM,GAAE,CAAC,CAAE,IAAG,MAAM,CAAC,GAAG,CAAA;GACjD;;AAED,EAAA,WAAW,CAAC,EAAE,EAAE;AACf,GAAA,MAAM,GAAI,GAAE,EAAE,CAAC,MAAO,IAAG,EAAE,CAAC,IAAI,EAAE,MAAO,IAAG,GAAC;GAC7C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAE,KAAI,IAAI,EAAE;AAChC,IAAA,OAAO,GAAG,CAAC,MAAK;AACjB,IAAA;GACA,IAAI,OAAO,GAAE,KAAM,QAAS,IAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAA,KAAM,IAAI,EAAE;AAC5D,IAAA,IAAI;KACH,MAAM,MAAK,GAAI,IAAI,CAAC,KAAK,CAAC,GAAG,EAAA;KAC7B,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAA,KAAM,IAAK,GAAE,MAAM,CAAC,MAAK,GAAI,CAAA;AACzD,KAAA,CAAE,MAAM;AACP,KAAA,OAAO,CAAA;AACR,KAAA;AACD,IAAA;AACA,GAAA,OAAO,CAAA;GACP;;EAED,MAAM,CAAC,EAAE,EAAE;GACV,MAAM,KAAM,GAAE,GAAC;AACf,GAAA,MAAM,MAAK,GAAI,IAAI,CAAC,WAAW,CAAC,EAAE,EAAA;GAClC,IAAI,MAAK,GAAI,CAAC,EAAE;IACf,KAAK,CAAC,IAAI,CAACA,SAAC,CAAC,eAAe,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,MAAO,EAAC,CAAC,EAAA;AAC/D,IAAA;AACA,GAAA,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAA;GACvB;;AAED,EAAA,UAAU,CAAC,EAAE,EAAE;AACd,GAAA,MAAM,IAAI,EAAE,CAAC,OAAM,IAAK,EAAE,CAAC,IAAI,EAAE,QAAM;GACvC,IAAI,CAAA,KAAM,SAAQ,IAAK,CAAE,KAAI,IAAI,EAAE;IAClC,OAAO,KAAI;AACZ,IAAA;AACA,GAAA,OAAO,CAAA,KAAM,KAAI,IAAK,CAAE,KAAI,CAAE,IAAG,CAAA,KAAM,GAAE;GACzC;;AAED,EAAA,YAAY,CAAC,EAAE,EAAE;AAChB,GAAA,OAAO,IAAI,CAAC,UAAU,CAAC,EAAE,CAAA,KAAM,IAAG;MAC/BA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;MAC7BA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;GAC/B;;AAED,EAAA,YAAY,CAAC,EAAE,EAAE;AAChB,GAAA,OAAO,IAAI,CAAC,UAAU,CAAC,EAAE,CAAA,KAAM,IAAG;MAC/B,2BAA0B;MAC1B,0BAAyB;GAC5B;;AAED,EAAA,cAAc,CAAC,EAAE,EAAE;GAClB,OAAO,IAAI,CAAC,UAAU,CAAC,EAAE,CAAA,KAAM,OAAO,SAAU,GAAE,SAAQ;GAC1D;;AAED,EAAA,gBAAgB,CAAC,EAAE,EAAE;GACpB,OAAO,IAAI,CAAC,YAAY,CAAC,EAAE,CAAA;GAC3B;;EAED,UAAU,GAAG;AACZ,GAAA,IAAI,CAAC,aAAa,KAAG;GACrB;;EAED,MAAM,eAAe,GAAG;GACvB,IAAI,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE;IACrD,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;GAClB,IAAI,CAAC,KAAM,GAAE,GAAC;AACd,GAAA,IAAI,CAAC,QAAO,GAAI,GAAC;AACjB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AACnF,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;AACjC,KAAA,MAAM,IAAG,GAAI,IAAI,CAAC,OAAM,IAAK,IAAI,CAAC,KAAI,KAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAE,GAAE,IAAK,GAAE,EAAE,CAAE,IAAG,GAAC;AACjF,KAAA,IAAI,CAAC,aAAa,KAAG;AACrB,KAAA,IAAI,CAAC,OAAQ,GAAE,OAAO,CAAC,IAAI,CAAC,OAAO,EAAA;AACpC,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,UAAS,GAAI,GAAC;AACnB,KAAA,IAAI,CAAC,QAAO,GAAIA,SAAC,CAAC,eAAe,EAAE,sCAAsC,EAAA;AAC1E,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,UAAS,GAAI,GAAC;AACnB,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;AACN,KAAA,IAAI,CAAC,UAAS,GAAI,GAAC;KACnB,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,6BAA6B,EAAA;AAC9D,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,GAAG,EAAA;AAC3D,IAAA,IAAI,CAAC,UAAS,GAAI,GAAC;IACnB,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,6BAA6B,EAAA;AAC9D,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;EAED,MAAM,MAAM,CAAC,EAAE,WAAY,EAAC,EAAE;AAC7B,GAAA,IAAI,CAAC,UAAS,GAAI,MAAI;AACtB,GAAA,IAAI;IACH,MAAM,WAAW,MAAM,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE;KACvD,MAAM,EAAE,MAAM;KACd,OAAO,EAAE,EAAE,GAAG,YAAY,EAAE,EAAE,cAAc,EAAE,kBAAiB,EAAG;AAClE,KAAA,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,WAAU,EAAG,CAAC;KACrC,EAAA;AACD,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAI,CAAC,eAAe,GAAC;AAC5B,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,2CAA2C,EAAA;AAC5E,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;KACnC,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,+BAA+B,EAAA;KAC9D,MAAK;KACN,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,4BAA4B,EAAA;AAC7D,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;AACA,IAAA,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,EAAA;IAC5C,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,4BAA4B,EAAA;AAC7D,IAAA;GACA;;EAED,MAAM,aAAa,CAAC,EAAE,EAAE;AACvB,GAAA,MAAM,IAAG,GAAI,EAAE,CAAC,WAAY,IAAG,EAAE,CAAC,GAAC;AACnC,GAAA,IAAI,CAAC,IAAI,EAAE;IACV,MAAK;AACN,IAAA;AACA,GAAA,IAAI,OAAO,MAAK,KAAM,WAAU,IAAK,OAAO,MAAM,CAAC,OAAM,KAAM,UAAU,EAAE;AAC1E,IAAA,MAAM,EAAC,GAAI,MAAM,CAAC,OAAO,CAACA,SAAC,CAAC,eAAe,EAAE,8BAA8B,CAAC,EAAA;AAC5E,IAAA,IAAI,CAAC,EAAE,EAAE;KACR,MAAK;AACN,KAAA;AACD,IAAA;AACA,GAAA,IAAI;AACH,IAAA,MAAM,QAAS,GAAE,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;AACxF,KAAA,MAAM,EAAE,QAAQ;KAChB,OAAO,EAAE,YAAY,EAAE;KACvB,EAAA;AACD,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAI,CAAC,eAAe,GAAC;AAC5B,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,KAAI,GAAIA,SAAC,CAAC,eAAe,EAAE,6CAA6C,EAAA;KAC5E,MAAK;KACN,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,8BAA8B,EAAA;AAC/D,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;AACA,IAAA,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,EAAA;IAC9C,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,8BAA8B,EAAA;AAC/D,IAAA;GACA;EACD;AACF;;;;"}
|
|
@@ -87,8 +87,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
87
87
|
createVNode(_component_NcButton, {
|
|
88
88
|
variant: "primary",
|
|
89
89
|
href: $props.flowSettingsUrl,
|
|
90
|
-
target: "_blank"
|
|
91
|
-
rel: "noopener noreferrer"
|
|
90
|
+
target: "_blank"
|
|
92
91
|
}, {
|
|
93
92
|
icon: withCtx(() => [
|
|
94
93
|
createVNode(_component_SitemapOutline, { size: 20 })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFlowTab.vue3.js","sources":["../../../../../src/integrations/builtin/flow/CnFlowTab.vue?vue&type=template&id=15f62f6e&lang.js"],"sourcesContent":["<!--\n CnFlowTab — bespoke sidebar tab for the `flow` (automation) integration.\n\n Renders a list of NC Flow (workflowengine) operations bound to the\n parent OR object via the Tier-2 `openregister_flow_links` table.\n Each row surfaces the operation name, the target entity class\n (short form), the operation type, a chips set of trigger events, a\n checks-count, and a (display-only) enabled toggle. A trailing\n \"Open in Flow settings\" deep-link points to\n `/index.php/settings/admin/workflow` (NC Flow is admin-gated, so\n edits happen in the admin settings, not in the sidebar tab).\n\n Talks to the Tier-2 link-table endpoints:\n GET /api/objects/{r}/{s}/{id}/flow (read, all users)\n POST /api/objects/{r}/{s}/{id}/flow (link, admin-only)\n DELETE /api/objects/{r}/{s}/{id}/flow/{operationId} (unlink, admin-only)\n\n Payload contract (per FlowProvider + FlowLinkService):\n { operationId, operationName, operationClass, entityType,\n enabled, url, linkId, events, checks, operation }\n + envelope `{ results, total, isAdmin }`\n\n Surface behaviour:\n - Admins see: \"Link existing automation\" button → CnFlowOperationPicker\n + per-row unlink action.\n - Non-admins see: read-only list, no link/unlink controls. The\n \"Open Flow settings\" deep-link is still surfaced so the user\n knows where automations live.\n - Loading + 503 \"currently unavailable\" + generic error states\n match CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-flow/` for the spec\n delta and ADR-019 (registry mechanism) / ADR-023 (action authorisation).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-flow-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-flow-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div v-if=\"isAdmin && !loading && !error\" class=\"cn-flow-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" data-testid=\"cn-flow-tab-link\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing automation') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-flow-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"operations.length === 0\" class=\"cn-sidebar-tab__empty cn-flow-tab__empty\">\n\t\t\t<SitemapOutline :size=\"32\" class=\"cn-flow-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"flowSettingsUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<SitemapOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openSettingsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-flow-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"op in operations\"\n\t\t\t\t:key=\"opKey(op)\"\n\t\t\t\tclass=\"cn-flow-tab__row\"\n\t\t\t\t:class=\"{ 'cn-flow-tab__row--disabled': isDisabled(op) }\"\n\t\t\t\t:name=\"opTitle(op)\"\n\t\t\t\t:href=\"opUrl(op)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:forceDisplayActions=\"isAdmin\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-flow-tab__avatar\" :class=\"enabledClass(op)\" :aria-hidden=\"true\">\n\t\t\t\t\t\t<SitemapOutline :size=\"20\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span v-if=\"opSummary(op)\" class=\"cn-flow-tab__summary\">{{ opSummary(op) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #indicator>\n\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\tclass=\"cn-flow-tab__enabled\"\n\t\t\t\t\t\t:class=\"enabledClass(op)\"\n\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t:variant=\"enabledVariant(op)\"\n\t\t\t\t\t\t:label=\"enabledLabel(op)\"\n\t\t\t\t\t\t:aria-label=\"enabledAriaLabel(op)\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"events(op).length > 0 || opMeta(op)\" #details>\n\t\t\t\t\t<span class=\"cn-flow-tab__details\">\n\t\t\t\t\t\t<span v-if=\"events(op).length > 0\" class=\"cn-flow-tab__events\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"evt in events(op)\"\n\t\t\t\t\t\t\t\t:key=\"evt\"\n\t\t\t\t\t\t\t\tclass=\"cn-flow-tab__chip\">{{ shortEvent(evt) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"opMeta(op)\" class=\"cn-flow-tab__meta\">{{ opMeta(op) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"isAdmin\" #actions>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-flow-tab__unlink\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Unlink automation')\"\n\t\t\t\t\t\tdata-testid=\"cn-flow-tab-unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"confirmUnlink(op)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink automation') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnFlowOperationPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:flowSettingsUrl=\"flowSettingsUrl\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLink\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport SitemapOutline from 'vue-material-design-icons/SitemapOutline.vue'\nimport CnFlowOperationPicker from '../../../components/CnFlowOperationPicker/CnFlowOperationPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnFlowTab — bespoke automation-rule list for the `flow` integration.\n *\n * Admins can link/unlink operations via the picker; non-admins see\n * a read-only list with a deep-link to NC Workflow Settings.\n */\nexport default {\n\tname: 'CnFlowTab',\n\n\tcomponents: { NcActionButton, NcButton, NcListItem, NcLoadingIcon, AlertCircleOutline, Close, Plus, SitemapOutline, CnFlowOperationPicker, CnStatusBadge },\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'flow'`). */\n\t\tintegrationId: { type: String, default: 'flow' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No automations linked yet') },\n\t\t/** Pre-translated label for the \"Open Flow settings\" CTA. */\n\t\topenSettingsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Workflow settings') },\n\t\t/** Pre-translated banner when Flow is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Flow is currently unavailable.') },\n\t\t/** URL of the NC Flow admin settings page. */\n\t\tflowSettingsUrl: { type: String, default: '/index.php/settings/admin/workflow' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\toperations: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tisAdmin: false,\n\t\t\tpickerOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchOperations()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchOperations()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchOperations()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/flow`\n\t\t},\n\n\t\topKey(op) {\n\t\t\treturn op.operationId ?? op.id ?? op.title ?? ''\n\t\t},\n\n\t\topTitle(op) {\n\t\t\treturn op.operationName || op.title || op.name || this.shortClass(op.operationClass) || t('nextcloud-vue', 'Untitled automation')\n\t\t},\n\n\t\topUrl(op) {\n\t\t\treturn op.url || this.flowSettingsUrl\n\t\t},\n\n\t\tshortClass(className) {\n\t\t\tif (!className) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst parts = String(className).split('\\\\')\n\t\t\treturn parts[parts.length - 1] || className\n\t\t},\n\n\t\tentityLabel(op) {\n\t\t\tconst entity = op.entityType || op.entity\n\t\t\tif (!entity) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.shortClass(entity)\n\t\t},\n\n\t\toperationLabel(op) {\n\t\t\treturn op.operation || ''\n\t\t},\n\n\t\topSummary(op) {\n\t\t\tconst parts = []\n\t\t\tconst entity = this.entityLabel(op)\n\t\t\tif (entity !== '') {\n\t\t\t\tparts.push(t('nextcloud-vue', 'Entity: {entity}', { entity }))\n\t\t\t}\n\t\t\tconst opLabel = this.operationLabel(op)\n\t\t\tif (opLabel !== '') {\n\t\t\t\tparts.push(t('nextcloud-vue', 'Operation: {op}', { op: opLabel }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tevents(op) {\n\t\t\tconst raw = op.events ?? op.data?.events ?? []\n\t\t\tif (Array.isArray(raw) === true) {\n\t\t\t\treturn raw.filter((e) => typeof e === 'string' && e !== '')\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tshortEvent(evt) {\n\t\t\tconst colon = String(evt).lastIndexOf(':')\n\t\t\tif (colon === -1) {\n\t\t\t\treturn String(evt)\n\t\t\t}\n\t\t\treturn String(evt).slice(colon + 1) || String(evt)\n\t\t},\n\n\t\tchecksCount(op) {\n\t\t\tconst raw = op.checks ?? op.data?.checks ?? []\n\t\t\tif (Array.isArray(raw) === true) {\n\t\t\t\treturn raw.length\n\t\t\t}\n\t\t\tif (typeof raw === 'string' && raw.startsWith('[') === true) {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(raw)\n\t\t\t\t\treturn Array.isArray(parsed) === true ? parsed.length : 0\n\t\t\t\t} catch {\n\t\t\t\t\treturn 0\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\topMeta(op) {\n\t\t\tconst parts = []\n\t\t\tconst checks = this.checksCount(op)\n\t\t\tif (checks > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} conditions', { n: checks }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tisDisabled(op) {\n\t\t\tconst v = op.enabled ?? op.data?.enabled\n\t\t\tif (v === undefined || v === null) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn v === false || v === 0 || v === '0'\n\t\t},\n\n\t\tenabledLabel(op) {\n\t\t\treturn this.isDisabled(op) === true\n\t\t\t\t? t('nextcloud-vue', 'Disabled')\n\t\t\t\t: t('nextcloud-vue', 'Enabled')\n\t\t},\n\n\t\tenabledClass(op) {\n\t\t\treturn this.isDisabled(op) === true\n\t\t\t\t? 'cn-flow-tab__enabled--off'\n\t\t\t\t: 'cn-flow-tab__enabled--on'\n\t\t},\n\n\t\tenabledVariant(op) {\n\t\t\treturn this.isDisabled(op) === true ? 'default' : 'success'\n\t\t},\n\n\t\tenabledAriaLabel(op) {\n\t\t\treturn this.enabledLabel(op)\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\tasync fetchOperations() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.operations = rows\n\t\t\t\t\tthis.isAdmin = Boolean(data.isAdmin)\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.degraded = t('nextcloud-vue', 'NC Workflow Engine is not installed.')\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load automations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] failed to fetch operations', err)\n\t\t\t\tthis.operations = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load automations.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync onLink({ operationId }) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ operationId }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchOperations()\n\t\t\t\t} else if (response.status === 403) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Only administrators can link automations.')\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Automation is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link automation.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link automation.')\n\t\t\t}\n\t\t},\n\n\t\tasync confirmUnlink(op) {\n\t\t\tconst opId = op.operationId ?? op.id\n\t\t\tif (!opId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (typeof window !== 'undefined' && typeof window.confirm === 'function') {\n\t\t\t\tconst ok = window.confirm(t('nextcloud-vue', 'Remove this automation link?'))\n\t\t\t\tif (!ok) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(opId)}`), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchOperations()\n\t\t\t\t} else if (response.status === 403) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Only administrators can unlink automations.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink automation.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink automation.')\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-flow-tab__actions {\n\tmargin-bottom: 8px;\n}\n\n.cn-flow-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-flow-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-flow-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Mirror NC Flow's rule rows: a round robot avatar, the bold rule\n name, a trigger/condition subline, and an enabled/disabled chip. */\n.cn-flow-tab__row--disabled {\n\topacity: 0.7;\n}\n\n.cn-flow-tab__avatar {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 36px;\n\theight: 36px;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-flow-tab__avatar.cn-flow-tab__enabled--on {\n\tbackground: var(--color-primary-element-light, var(--color-background-dark));\n\tcolor: var(--color-primary-element, #0082c9);\n}\n\n.cn-flow-tab__avatar.cn-flow-tab__enabled--off {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__summary {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-flow-tab__meta {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__details {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tflex-wrap: wrap;\n\tgap: 6px;\n}\n\n.cn-flow-tab__events {\n\tdisplay: inline-flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n\n.cn-flow-tab__chip {\n\tdisplay: inline-block;\n\tpadding: 1px 8px;\n\tborder-radius: 10px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 0.75em;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass","_createElementVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFlowTab.vue3.js","sources":["../../../../../src/integrations/builtin/flow/CnFlowTab.vue?vue&type=template&id=15f62f6e&lang.js"],"sourcesContent":["<!--\n CnFlowTab — bespoke sidebar tab for the `flow` (automation) integration.\n\n Renders a list of NC Flow (workflowengine) operations bound to the\n parent OR object via the Tier-2 `openregister_flow_links` table.\n Each row surfaces the operation name, the target entity class\n (short form), the operation type, a chips set of trigger events, a\n checks-count, and a (display-only) enabled toggle. A trailing\n \"Open in Flow settings\" deep-link points to\n `/index.php/settings/admin/workflow` (NC Flow is admin-gated, so\n edits happen in the admin settings, not in the sidebar tab).\n\n Talks to the Tier-2 link-table endpoints:\n GET /api/objects/{r}/{s}/{id}/flow (read, all users)\n POST /api/objects/{r}/{s}/{id}/flow (link, admin-only)\n DELETE /api/objects/{r}/{s}/{id}/flow/{operationId} (unlink, admin-only)\n\n Payload contract (per FlowProvider + FlowLinkService):\n { operationId, operationName, operationClass, entityType,\n enabled, url, linkId, events, checks, operation }\n + envelope `{ results, total, isAdmin }`\n\n Surface behaviour:\n - Admins see: \"Link existing automation\" button → CnFlowOperationPicker\n + per-row unlink action.\n - Non-admins see: read-only list, no link/unlink controls. The\n \"Open Flow settings\" deep-link is still surfaced so the user\n knows where automations live.\n - Loading + 503 \"currently unavailable\" + generic error states\n match CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-flow/` for the spec\n delta and ADR-019 (registry mechanism) / ADR-023 (action authorisation).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-flow-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-flow-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div v-if=\"isAdmin && !loading && !error\" class=\"cn-flow-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" data-testid=\"cn-flow-tab-link\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing automation') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-flow-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"operations.length === 0\" class=\"cn-sidebar-tab__empty cn-flow-tab__empty\">\n\t\t\t<SitemapOutline :size=\"32\" class=\"cn-flow-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"flowSettingsUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<SitemapOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openSettingsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-flow-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"op in operations\"\n\t\t\t\t:key=\"opKey(op)\"\n\t\t\t\tclass=\"cn-flow-tab__row\"\n\t\t\t\t:class=\"{ 'cn-flow-tab__row--disabled': isDisabled(op) }\"\n\t\t\t\t:name=\"opTitle(op)\"\n\t\t\t\t:href=\"opUrl(op)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:forceDisplayActions=\"isAdmin\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-flow-tab__avatar\" :class=\"enabledClass(op)\" :aria-hidden=\"true\">\n\t\t\t\t\t\t<SitemapOutline :size=\"20\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span v-if=\"opSummary(op)\" class=\"cn-flow-tab__summary\">{{ opSummary(op) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #indicator>\n\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\tclass=\"cn-flow-tab__enabled\"\n\t\t\t\t\t\t:class=\"enabledClass(op)\"\n\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t:variant=\"enabledVariant(op)\"\n\t\t\t\t\t\t:label=\"enabledLabel(op)\"\n\t\t\t\t\t\t:aria-label=\"enabledAriaLabel(op)\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"events(op).length > 0 || opMeta(op)\" #details>\n\t\t\t\t\t<span class=\"cn-flow-tab__details\">\n\t\t\t\t\t\t<span v-if=\"events(op).length > 0\" class=\"cn-flow-tab__events\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"evt in events(op)\"\n\t\t\t\t\t\t\t\t:key=\"evt\"\n\t\t\t\t\t\t\t\tclass=\"cn-flow-tab__chip\">{{ shortEvent(evt) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"opMeta(op)\" class=\"cn-flow-tab__meta\">{{ opMeta(op) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"isAdmin\" #actions>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-flow-tab__unlink\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Unlink automation')\"\n\t\t\t\t\t\tdata-testid=\"cn-flow-tab-unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"confirmUnlink(op)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink automation') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnFlowOperationPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:flowSettingsUrl=\"flowSettingsUrl\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLink\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport SitemapOutline from 'vue-material-design-icons/SitemapOutline.vue'\nimport CnFlowOperationPicker from '../../../components/CnFlowOperationPicker/CnFlowOperationPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnFlowTab — bespoke automation-rule list for the `flow` integration.\n *\n * Admins can link/unlink operations via the picker; non-admins see\n * a read-only list with a deep-link to NC Workflow Settings.\n */\nexport default {\n\tname: 'CnFlowTab',\n\n\tcomponents: { NcActionButton, NcButton, NcListItem, NcLoadingIcon, AlertCircleOutline, Close, Plus, SitemapOutline, CnFlowOperationPicker, CnStatusBadge },\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'flow'`). */\n\t\tintegrationId: { type: String, default: 'flow' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No automations linked yet') },\n\t\t/** Pre-translated label for the \"Open Flow settings\" CTA. */\n\t\topenSettingsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Workflow settings') },\n\t\t/** Pre-translated banner when Flow is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Flow is currently unavailable.') },\n\t\t/** URL of the NC Flow admin settings page. */\n\t\tflowSettingsUrl: { type: String, default: '/index.php/settings/admin/workflow' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\toperations: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tisAdmin: false,\n\t\t\tpickerOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchOperations()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchOperations()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchOperations()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/flow`\n\t\t},\n\n\t\topKey(op) {\n\t\t\treturn op.operationId ?? op.id ?? op.title ?? ''\n\t\t},\n\n\t\topTitle(op) {\n\t\t\treturn op.operationName || op.title || op.name || this.shortClass(op.operationClass) || t('nextcloud-vue', 'Untitled automation')\n\t\t},\n\n\t\topUrl(op) {\n\t\t\treturn op.url && safeHref(op.url) !== '#' ? op.url : this.flowSettingsUrl\n\t\t},\n\n\t\tshortClass(className) {\n\t\t\tif (!className) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst parts = String(className).split('\\\\')\n\t\t\treturn parts[parts.length - 1] || className\n\t\t},\n\n\t\tentityLabel(op) {\n\t\t\tconst entity = op.entityType || op.entity\n\t\t\tif (!entity) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.shortClass(entity)\n\t\t},\n\n\t\toperationLabel(op) {\n\t\t\treturn op.operation || ''\n\t\t},\n\n\t\topSummary(op) {\n\t\t\tconst parts = []\n\t\t\tconst entity = this.entityLabel(op)\n\t\t\tif (entity !== '') {\n\t\t\t\tparts.push(t('nextcloud-vue', 'Entity: {entity}', { entity }))\n\t\t\t}\n\t\t\tconst opLabel = this.operationLabel(op)\n\t\t\tif (opLabel !== '') {\n\t\t\t\tparts.push(t('nextcloud-vue', 'Operation: {op}', { op: opLabel }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tevents(op) {\n\t\t\tconst raw = op.events ?? op.data?.events ?? []\n\t\t\tif (Array.isArray(raw) === true) {\n\t\t\t\treturn raw.filter((e) => typeof e === 'string' && e !== '')\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tshortEvent(evt) {\n\t\t\tconst colon = String(evt).lastIndexOf(':')\n\t\t\tif (colon === -1) {\n\t\t\t\treturn String(evt)\n\t\t\t}\n\t\t\treturn String(evt).slice(colon + 1) || String(evt)\n\t\t},\n\n\t\tchecksCount(op) {\n\t\t\tconst raw = op.checks ?? op.data?.checks ?? []\n\t\t\tif (Array.isArray(raw) === true) {\n\t\t\t\treturn raw.length\n\t\t\t}\n\t\t\tif (typeof raw === 'string' && raw.startsWith('[') === true) {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(raw)\n\t\t\t\t\treturn Array.isArray(parsed) === true ? parsed.length : 0\n\t\t\t\t} catch {\n\t\t\t\t\treturn 0\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\topMeta(op) {\n\t\t\tconst parts = []\n\t\t\tconst checks = this.checksCount(op)\n\t\t\tif (checks > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} conditions', { n: checks }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tisDisabled(op) {\n\t\t\tconst v = op.enabled ?? op.data?.enabled\n\t\t\tif (v === undefined || v === null) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn v === false || v === 0 || v === '0'\n\t\t},\n\n\t\tenabledLabel(op) {\n\t\t\treturn this.isDisabled(op) === true\n\t\t\t\t? t('nextcloud-vue', 'Disabled')\n\t\t\t\t: t('nextcloud-vue', 'Enabled')\n\t\t},\n\n\t\tenabledClass(op) {\n\t\t\treturn this.isDisabled(op) === true\n\t\t\t\t? 'cn-flow-tab__enabled--off'\n\t\t\t\t: 'cn-flow-tab__enabled--on'\n\t\t},\n\n\t\tenabledVariant(op) {\n\t\t\treturn this.isDisabled(op) === true ? 'default' : 'success'\n\t\t},\n\n\t\tenabledAriaLabel(op) {\n\t\t\treturn this.enabledLabel(op)\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\tasync fetchOperations() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.operations = rows\n\t\t\t\t\tthis.isAdmin = Boolean(data.isAdmin)\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.degraded = t('nextcloud-vue', 'NC Workflow Engine is not installed.')\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.operations = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load automations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] failed to fetch operations', err)\n\t\t\t\tthis.operations = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load automations.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync onLink({ operationId }) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ operationId }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchOperations()\n\t\t\t\t} else if (response.status === 403) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Only administrators can link automations.')\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Automation is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link automation.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link automation.')\n\t\t\t}\n\t\t},\n\n\t\tasync confirmUnlink(op) {\n\t\t\tconst opId = op.operationId ?? op.id\n\t\t\tif (!opId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (typeof window !== 'undefined' && typeof window.confirm === 'function') {\n\t\t\t\tconst ok = window.confirm(t('nextcloud-vue', 'Remove this automation link?'))\n\t\t\t\tif (!ok) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(opId)}`), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchOperations()\n\t\t\t\t} else if (response.status === 403) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Only administrators can unlink automations.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink automation.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFlowTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink automation.')\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-flow-tab__actions {\n\tmargin-bottom: 8px;\n}\n\n.cn-flow-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-flow-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-flow-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Mirror NC Flow's rule rows: a round robot avatar, the bold rule\n name, a trigger/condition subline, and an enabled/disabled chip. */\n.cn-flow-tab__row--disabled {\n\topacity: 0.7;\n}\n\n.cn-flow-tab__avatar {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 36px;\n\theight: 36px;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-flow-tab__avatar.cn-flow-tab__enabled--on {\n\tbackground: var(--color-primary-element-light, var(--color-background-dark));\n\tcolor: var(--color-primary-element, #0082c9);\n}\n\n.cn-flow-tab__avatar.cn-flow-tab__enabled--off {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__summary {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-flow-tab__meta {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-tab__details {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tflex-wrap: wrap;\n\tgap: 6px;\n}\n\n.cn-flow-tab__events {\n\tdisplay: inline-flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n\n.cn-flow-tab__chip {\n\tdisplay: inline-block;\n\tpadding: 1px 8px;\n\tborder-radius: 10px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 0.75em;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass","_createElementVNode"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { translate, translatePlural } from '@nextcloud/l10n';
|
|
|
2
2
|
import { NcLoadingIcon } from '@nextcloud/vue';
|
|
3
3
|
import ClipboardText from 'vue-material-design-icons/ClipboardText.vue';
|
|
4
4
|
import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
|
|
5
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
5
6
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
6
7
|
|
|
7
8
|
const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
|
|
@@ -191,8 +192,9 @@ var script = {
|
|
|
191
192
|
return form.title || form.id || translate('nextcloud-vue', 'Untitled form')
|
|
192
193
|
},
|
|
193
194
|
|
|
195
|
+
// A provider URL is used only when it is a safe scheme; else the app link.
|
|
194
196
|
formUrl(form) {
|
|
195
|
-
if (form.url) {
|
|
197
|
+
if (form.url && safeHref(form.url) !== '#') {
|
|
196
198
|
return form.url
|
|
197
199
|
}
|
|
198
200
|
if (form.hash) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFormsCard.vue2.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsCard.vue"],"sourcesContent":["<!--\n CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n\n Replaces the generic CnIntegrationCard for the `forms` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N forms · M open\"\n secondary line names the most-recent form with its submission\n count.\n - detail-page : compact list of linked forms\n with status pill + submission count per row.\n - single-entity : chip with form title + status\n indicator (referenceType: 'forms').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnFormsTab; for `single-entity` the optional `value` prop addresses a\n single form by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-forms/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-forms-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"formUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ formTitle(entity) }}</a>\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-forms-card__chip-status\"\n\t\t\t\t\t:class=\"statusClass(entity)\">{{ statusLabel(entity) }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-forms-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-forms-card__headline\">\n\t\t\t\t<div class=\"cn-forms-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-forms-card__headline-total\">\n\t\t\t\t\t{{ submissionsHeadline }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-forms-card__headline-recent\">\n\t\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"formUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ formTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-forms-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"form in displayedForms\"\n\t\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\t\tclass=\"cn-forms-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-forms-card__row--closed': isClosed(form) }\">\n\t\t\t\t\t<div class=\"cn-forms-card__row-header\">\n\t\t\t\t\t\t<ClipboardText :size=\"16\" class=\"cn-forms-card__row-icon\" />\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-forms-card__title\">{{ formTitle(form) }}</a>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-forms-card__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\">{{ statusLabel(form) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(form)\" class=\"cn-forms-card__subtitle\">{{ rowMeta(form) }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n *\n * Renders Forms-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnFormsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ClipboardText },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (form id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Forms') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ClipboardText },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No forms linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tentityRow: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tentity() {\n\t\t\tif (this.entityRow === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normaliseFormRow(this.entityRow)\n\t\t},\n\n\t\tdisplayedForms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.forms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.forms\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + (this.isClosed(f) ? 0 : 1), 0)\n\t\t},\n\n\t\ttotalSubmissions() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + this.submissionCount(f), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.forms.length\n\t\t\tconst open = this.openCount\n\t\t\tif (total === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} form (all closed)', '{count} forms (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} form', '{count} forms', total, { count: total })\n\t\t\tconst openFragment = n('nextcloud-vue', '{count} open', '{count} open', open, { count: open })\n\t\t\treturn `${totalFragment} · ${openFragment}`\n\t\t},\n\n\t\tsubmissionsHeadline() {\n\t\t\tconst total = this.totalSubmissions\n\t\t\treturn n('nextcloud-vue', '{count} submission', '{count} submissions', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.forms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.forms].sort((a, b) => {\n\t\t\t\treturn this.sortTimestamp(b) - this.sortTimestamp(a)\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformUrl(form) {\n\t\t\tif (form.url) {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(form) {\n\t\t\tif (form.status === 'closed') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-card__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-card__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-card__status--open'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tsortTimestamp(form) {\n\t\t\tconst lu = Number(form.lastUpdated ?? 0)\n\t\t\tif (Number.isFinite(lu) && lu > 0) {\n\t\t\t\treturn lu < 1e12 ? lu * 1000 : lu\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\trowMeta(form) {\n\t\t\tconst parts = []\n\t\t\tconst count = this.submissionCount(form)\n\t\t\tif (count > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} submissions', { n: count }))\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\tconst diff = ms - Date.now()\n\t\t\t\tif (diff > 0) {\n\t\t\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\t\t\tif (days <= 1) {\n\t\t\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} hours', { n: hours }))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} days', { n: days }))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tchipSubtitle(form) {\n\t\t\treturn this.statusLabel(form) || this.formTitle(form)\n\t\t},\n\n\t\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.rawRows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFormsCard] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entityRow = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entityRow = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFormsCard] failed to fetch single form', err)\n\t\t\t\tthis.entityRow = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-forms-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-forms-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-forms-card__headline-total {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-forms-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-forms-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip-status {\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.75em;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-forms-card__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-forms-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-forms-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-forms-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-forms-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-forms-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__status,\n.cn-forms-card__chip-status {\n\tflex-shrink: 0;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n}\n\n.cn-forms-card__status--open {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-primary-element-text, #ffffff);\n}\n\n.cn-forms-card__status--closed {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__status--draft {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-forms-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFormsCard.vue2.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsCard.vue"],"sourcesContent":["<!--\n CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n\n Replaces the generic CnIntegrationCard for the `forms` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N forms · M open\"\n secondary line names the most-recent form with its submission\n count.\n - detail-page : compact list of linked forms\n with status pill + submission count per row.\n - single-entity : chip with form title + status\n indicator (referenceType: 'forms').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnFormsTab; for `single-entity` the optional `value` prop addresses a\n single form by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-forms/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-forms-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"formUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ formTitle(entity) }}</a>\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-forms-card__chip-status\"\n\t\t\t\t\t:class=\"statusClass(entity)\">{{ statusLabel(entity) }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-forms-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-forms-card__headline\">\n\t\t\t\t<div class=\"cn-forms-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-forms-card__headline-total\">\n\t\t\t\t\t{{ submissionsHeadline }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-forms-card__headline-recent\">\n\t\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"formUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ formTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-forms-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"form in displayedForms\"\n\t\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\t\tclass=\"cn-forms-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-forms-card__row--closed': isClosed(form) }\">\n\t\t\t\t\t<div class=\"cn-forms-card__row-header\">\n\t\t\t\t\t\t<ClipboardText :size=\"16\" class=\"cn-forms-card__row-icon\" />\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-forms-card__title\">{{ formTitle(form) }}</a>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-forms-card__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\">{{ statusLabel(form) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(form)\" class=\"cn-forms-card__subtitle\">{{ rowMeta(form) }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n *\n * Renders Forms-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnFormsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ClipboardText },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (form id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Forms') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ClipboardText },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No forms linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tentityRow: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tentity() {\n\t\t\tif (this.entityRow === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normaliseFormRow(this.entityRow)\n\t\t},\n\n\t\tdisplayedForms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.forms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.forms\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + (this.isClosed(f) ? 0 : 1), 0)\n\t\t},\n\n\t\ttotalSubmissions() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + this.submissionCount(f), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.forms.length\n\t\t\tconst open = this.openCount\n\t\t\tif (total === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} form (all closed)', '{count} forms (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} form', '{count} forms', total, { count: total })\n\t\t\tconst openFragment = n('nextcloud-vue', '{count} open', '{count} open', open, { count: open })\n\t\t\treturn `${totalFragment} · ${openFragment}`\n\t\t},\n\n\t\tsubmissionsHeadline() {\n\t\t\tconst total = this.totalSubmissions\n\t\t\treturn n('nextcloud-vue', '{count} submission', '{count} submissions', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.forms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.forms].sort((a, b) => {\n\t\t\t\treturn this.sortTimestamp(b) - this.sortTimestamp(a)\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\tformUrl(form) {\n\t\t\tif (form.url && safeHref(form.url) !== '#') {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(form) {\n\t\t\tif (form.status === 'closed') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-card__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-card__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-card__status--open'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tsortTimestamp(form) {\n\t\t\tconst lu = Number(form.lastUpdated ?? 0)\n\t\t\tif (Number.isFinite(lu) && lu > 0) {\n\t\t\t\treturn lu < 1e12 ? lu * 1000 : lu\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\trowMeta(form) {\n\t\t\tconst parts = []\n\t\t\tconst count = this.submissionCount(form)\n\t\t\tif (count > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} submissions', { n: count }))\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\tconst diff = ms - Date.now()\n\t\t\t\tif (diff > 0) {\n\t\t\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\t\t\tif (days <= 1) {\n\t\t\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} hours', { n: hours }))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} days', { n: days }))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tchipSubtitle(form) {\n\t\t\treturn this.statusLabel(form) || this.formTitle(form)\n\t\t},\n\n\t\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.rawRows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFormsCard] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entityRow = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entityRow = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFormsCard] failed to fetch single form', err)\n\t\t\t\tthis.entityRow = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-forms-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-forms-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-forms-card__headline-total {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-forms-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-forms-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip-status {\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.75em;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-forms-card__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-forms-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-forms-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-forms-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-forms-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-forms-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__status,\n.cn-forms-card__chip-status {\n\tflex-shrink: 0;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n}\n\n.cn-forms-card__status--open {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-primary-element-text, #ffffff);\n}\n\n.cn-forms-card__status--closed {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__status--draft {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-forms-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFormsCard.vue3.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsCard.vue?vue&type=template&id=0c622af1&lang.js"],"sourcesContent":["<!--\n CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n\n Replaces the generic CnIntegrationCard for the `forms` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N forms · M open\"\n secondary line names the most-recent form with its submission\n count.\n - detail-page : compact list of linked forms\n with status pill + submission count per row.\n - single-entity : chip with form title + status\n indicator (referenceType: 'forms').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnFormsTab; for `single-entity` the optional `value` prop addresses a\n single form by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-forms/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-forms-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"formUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ formTitle(entity) }}</a>\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-forms-card__chip-status\"\n\t\t\t\t\t:class=\"statusClass(entity)\">{{ statusLabel(entity) }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-forms-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-forms-card__headline\">\n\t\t\t\t<div class=\"cn-forms-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-forms-card__headline-total\">\n\t\t\t\t\t{{ submissionsHeadline }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-forms-card__headline-recent\">\n\t\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"formUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ formTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-forms-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"form in displayedForms\"\n\t\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\t\tclass=\"cn-forms-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-forms-card__row--closed': isClosed(form) }\">\n\t\t\t\t\t<div class=\"cn-forms-card__row-header\">\n\t\t\t\t\t\t<ClipboardText :size=\"16\" class=\"cn-forms-card__row-icon\" />\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-forms-card__title\">{{ formTitle(form) }}</a>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-forms-card__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\">{{ statusLabel(form) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(form)\" class=\"cn-forms-card__subtitle\">{{ rowMeta(form) }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n *\n * Renders Forms-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnFormsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ClipboardText },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (form id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Forms') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ClipboardText },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No forms linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tentityRow: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tentity() {\n\t\t\tif (this.entityRow === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normaliseFormRow(this.entityRow)\n\t\t},\n\n\t\tdisplayedForms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.forms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.forms\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + (this.isClosed(f) ? 0 : 1), 0)\n\t\t},\n\n\t\ttotalSubmissions() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + this.submissionCount(f), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.forms.length\n\t\t\tconst open = this.openCount\n\t\t\tif (total === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} form (all closed)', '{count} forms (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} form', '{count} forms', total, { count: total })\n\t\t\tconst openFragment = n('nextcloud-vue', '{count} open', '{count} open', open, { count: open })\n\t\t\treturn `${totalFragment} · ${openFragment}`\n\t\t},\n\n\t\tsubmissionsHeadline() {\n\t\t\tconst total = this.totalSubmissions\n\t\t\treturn n('nextcloud-vue', '{count} submission', '{count} submissions', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.forms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.forms].sort((a, b) => {\n\t\t\t\treturn this.sortTimestamp(b) - this.sortTimestamp(a)\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformUrl(form) {\n\t\t\tif (form.url) {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(form) {\n\t\t\tif (form.status === 'closed') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-card__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-card__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-card__status--open'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tsortTimestamp(form) {\n\t\t\tconst lu = Number(form.lastUpdated ?? 0)\n\t\t\tif (Number.isFinite(lu) && lu > 0) {\n\t\t\t\treturn lu < 1e12 ? lu * 1000 : lu\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\trowMeta(form) {\n\t\t\tconst parts = []\n\t\t\tconst count = this.submissionCount(form)\n\t\t\tif (count > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} submissions', { n: count }))\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\tconst diff = ms - Date.now()\n\t\t\t\tif (diff > 0) {\n\t\t\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\t\t\tif (days <= 1) {\n\t\t\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} hours', { n: hours }))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} days', { n: days }))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tchipSubtitle(form) {\n\t\t\treturn this.statusLabel(form) || this.formTitle(form)\n\t\t},\n\n\t\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.rawRows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFormsCard] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entityRow = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entityRow = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFormsCard] failed to fetch single form', err)\n\t\t\t\tthis.entityRow = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-forms-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-forms-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-forms-card__headline-total {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-forms-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-forms-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip-status {\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.75em;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-forms-card__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-forms-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-forms-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-forms-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-forms-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-forms-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__status,\n.cn-forms-card__chip-status {\n\tflex-shrink: 0;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n}\n\n.cn-forms-card__status--open {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-primary-element-text, #ffffff);\n}\n\n.cn-forms-card__status--closed {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__status--draft {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-forms-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_normalizeClass","_openBlock"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFormsCard.vue3.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsCard.vue?vue&type=template&id=0c622af1&lang.js"],"sourcesContent":["<!--\n CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n\n Replaces the generic CnIntegrationCard for the `forms` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N forms · M open\"\n secondary line names the most-recent form with its submission\n count.\n - detail-page : compact list of linked forms\n with status pill + submission count per row.\n - single-entity : chip with form title + status\n indicator (referenceType: 'forms').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnFormsTab; for `single-entity` the optional `value` prop addresses a\n single form by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-forms/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-forms-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"formUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ formTitle(entity) }}</a>\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-forms-card__chip-status\"\n\t\t\t\t\t:class=\"statusClass(entity)\">{{ statusLabel(entity) }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-forms-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-forms-card__headline\">\n\t\t\t\t<div class=\"cn-forms-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-forms-card__headline-total\">\n\t\t\t\t\t{{ submissionsHeadline }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-forms-card__headline-recent\">\n\t\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"formUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ formTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-forms-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"form in displayedForms\"\n\t\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\t\tclass=\"cn-forms-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-forms-card__row--closed': isClosed(form) }\">\n\t\t\t\t\t<div class=\"cn-forms-card__row-header\">\n\t\t\t\t\t\t<ClipboardText :size=\"16\" class=\"cn-forms-card__row-icon\" />\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-forms-card__title\">{{ formTitle(form) }}</a>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-forms-card__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\">{{ statusLabel(form) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(form)\" class=\"cn-forms-card__subtitle\">{{ rowMeta(form) }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n *\n * Renders Forms-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnFormsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ClipboardText },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (form id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Forms') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ClipboardText },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No forms linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tentityRow: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tentity() {\n\t\t\tif (this.entityRow === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normaliseFormRow(this.entityRow)\n\t\t},\n\n\t\tdisplayedForms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.forms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.forms\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + (this.isClosed(f) ? 0 : 1), 0)\n\t\t},\n\n\t\ttotalSubmissions() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + this.submissionCount(f), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.forms.length\n\t\t\tconst open = this.openCount\n\t\t\tif (total === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} form (all closed)', '{count} forms (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} form', '{count} forms', total, { count: total })\n\t\t\tconst openFragment = n('nextcloud-vue', '{count} open', '{count} open', open, { count: open })\n\t\t\treturn `${totalFragment} · ${openFragment}`\n\t\t},\n\n\t\tsubmissionsHeadline() {\n\t\t\tconst total = this.totalSubmissions\n\t\t\treturn n('nextcloud-vue', '{count} submission', '{count} submissions', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.forms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.forms].sort((a, b) => {\n\t\t\t\treturn this.sortTimestamp(b) - this.sortTimestamp(a)\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\tformUrl(form) {\n\t\t\tif (form.url && safeHref(form.url) !== '#') {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(form) {\n\t\t\tif (form.status === 'closed') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-card__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-card__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-card__status--open'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tsortTimestamp(form) {\n\t\t\tconst lu = Number(form.lastUpdated ?? 0)\n\t\t\tif (Number.isFinite(lu) && lu > 0) {\n\t\t\t\treturn lu < 1e12 ? lu * 1000 : lu\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\trowMeta(form) {\n\t\t\tconst parts = []\n\t\t\tconst count = this.submissionCount(form)\n\t\t\tif (count > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} submissions', { n: count }))\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\tconst diff = ms - Date.now()\n\t\t\t\tif (diff > 0) {\n\t\t\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\t\t\tif (days <= 1) {\n\t\t\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} hours', { n: hours }))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} days', { n: days }))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tchipSubtitle(form) {\n\t\t\treturn this.statusLabel(form) || this.formTitle(form)\n\t\t},\n\n\t\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.rawRows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFormsCard] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entityRow = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entityRow = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFormsCard] failed to fetch single form', err)\n\t\t\t\tthis.entityRow = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-forms-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-forms-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-forms-card__headline-total {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-forms-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-forms-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip-status {\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.75em;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-forms-card__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-forms-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-forms-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-forms-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-forms-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-forms-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__status,\n.cn-forms-card__chip-status {\n\tflex-shrink: 0;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n}\n\n.cn-forms-card__status--open {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-primary-element-text, #ffffff);\n}\n\n.cn-forms-card__status--closed {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__status--draft {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-forms-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_normalizeClass","_openBlock"],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
|
|
|
10
10
|
import __sfc_main$1 from './CnFormCreate.vue.js';
|
|
11
11
|
import __sfc_main$2 from './CnFormPicker.vue.js';
|
|
12
12
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
13
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.
|
|
@@ -173,8 +174,9 @@ var script = {
|
|
|
173
174
|
return form.description || ''
|
|
174
175
|
},
|
|
175
176
|
|
|
177
|
+
// A provider URL is used only when it is a safe scheme; else the app link.
|
|
176
178
|
formUrl(form) {
|
|
177
|
-
if (form.url) {
|
|
179
|
+
if (form.url && safeHref(form.url) !== '#') {
|
|
178
180
|
return form.url
|
|
179
181
|
}
|
|
180
182
|
if (form.hash) {
|