@conduction/nextcloud-vue 2.58.0 → 2.59.0
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/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/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +100 -3
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.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/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 +1 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +1 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +71 -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/nextcloud-vue.cjs.js +909 -386
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +71 -7
- package/l10n/en.json +1 -0
- package/l10n/nl.json +1 -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/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/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +100 -3
- package/src/components/CnRowActions/CnRowActions.vue +7 -2
- 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/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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnTasksWidget.vue2.js","sources":["../../../../src/components/CnTasksWidget/CnTasksWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-tasks-widget\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-tasks-widget__error\">\n\t\t\t{{ tr('Could not load the tasks') }}\n\t\t</p>\n\t\t<div v-else-if=\"loading && rows.length === 0\" class=\"cn-tasks-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- An empty inbox is the GOOD state, not an error and not a void:\n\t\t one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0\" class=\"cn-tasks-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<!-- The count states the server TOTAL, never the rendered length\n\t\t\t (ADR-062): the number a person acts on is how much work\n\t\t\t exists, not how much fits the cell. -->\n\t\t\t<p class=\"cn-tasks-widget__count\">\n\t\t\t\t{{ countLabel }}\n\t\t\t</p>\n\t\t\t<ul class=\"cn-tasks-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"task in rows\"\n\t\t\t\t\t:key=\"task.uuid\"\n\t\t\t\t\tclass=\"cn-tasks-widget__row\"\n\t\t\t\t\t:data-state=\"task.state\"\n\t\t\t\t\t@click=\"onRowClick(task, $event)\"\n\t\t\t\t\t@auxclick=\"onRowAuxClick(task, $event)\">\n\t\t\t\t\t<span class=\"cn-tasks-widget__body\">\n\t\t\t\t\t\t<span class=\"cn-tasks-widget__name\">{{ titleOf(task) }}</span>\n\t\t\t\t\t\t<span class=\"cn-tasks-widget__meta\">{{ metaLine(task) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- Overdue is carried by the WORDING (taskDueLabel) and by\n\t\t\t\t\t weight; the colour only reinforces it. -->\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-tasks-widget__due\"\n\t\t\t\t\t\t:class=\"{ 'cn-tasks-widget__due--overdue': task.overdue === true }\">\n\t\t\t\t\t\t{{ dueLabel(task) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- Only verbs the contract can accept are offered; the\n\t\t\t\t\t server still authorizes, and a refusal is toasted in\n\t\t\t\t\t its own words. Stopped clicks: the menu must not also\n\t\t\t\t\t open the row. -->\n\t\t\t\t\t<NcActions\n\t\t\t\t\t\tv-if=\"canClaim(task) || canComplete(task)\"\n\t\t\t\t\t\tclass=\"cn-tasks-widget__actions\"\n\t\t\t\t\t\t:forceMenu=\"true\"\n\t\t\t\t\t\t:aria-label=\"tr('Task actions')\"\n\t\t\t\t\t\t@click.stop>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"canClaim(task)\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"claim(task)\">\n\t\t\t\t\t\t\t{{ tr('Claim') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<template v-if=\"canComplete(task)\">\n\t\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\t\tv-for=\"outcome in outcomesOf(task)\"\n\t\t\t\t\t\t\t\t:key=\"outcomeId(outcome)\"\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t@click=\"complete(task, outcomeId(outcome))\">\n\t\t\t\t\t\t\t\t{{ completeLabel(outcome) }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-tasks-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { showError } from '@nextcloud/dialogs'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcActionButton, NcActions, NcLoadingIcon } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport { taskDeepLink, taskDueLabel } from '../../composables/indexSources.js'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { isRowMiddleClick } from '../../utils/rowAuxClick.js'\n\n/**\n * The OpenRegister inbox read (openregister flow-task-entity). One endpoint\n * for the whole fleet: every app's human tasks live on OpenRegister's one\n * task store, so the widget is app-agnostic and ships no controller.\n *\n * @type {string}\n */\nconst FLOW_TASKS_URL = '/apps/openregister/api/flow-tasks'\n\n/**\n * The CMMN states, in the reading a dashboard needs. Labels double as the\n * meta-line words, so state never rests on styling.\n *\n * @type {Record<string, string>}\n */\nconst STATE_LABELS = {\n\tavailable: 'Available',\n\tenabled: 'Ready to claim',\n\tactive: 'In progress',\n\tcompleted: 'Completed',\n\tterminated: 'Terminated',\n\tdisabled: 'Disabled',\n}\n\n/**\n * The priority vocabulary (openregister Task::PRIORITIES).\n *\n * @type {Record<string, string>}\n */\nconst PRIORITY_LABELS = {\n\tlow: 'Low',\n\tnormal: 'Normal',\n\thigh: 'High',\n\turgent: 'Urgent',\n}\n\n/**\n * CnTasksWidget — the viewer's open tasks, from OpenRegister's task inbox.\n *\n * Reads `flow-tasks` with `isTerminal=false` (an inbox widget that showed\n * finished tasks would never drain), scoped by `content.scope` and sorted by\n * due date so the most urgent row sits on top. Placed by its registry type\n * key `tasks`:\n *\n * ```js\n * { id: 'my-tasks', type: 'tasks', title: 'My tasks',\n * content: { scope: 'assigned', limit: 6, pollSeconds: 30 } }\n * ```\n *\n * Why it polls: tasks are handed out, claimed and completed by OTHER people\n * and by flows, so a widget that only reflected its mount moment would be\n * wrong within minutes and look identical to being right. Polling pauses\n * while the tab is hidden.\n *\n * Quick actions offer only what the row's contract can accept: claim on a\n * pooled row, complete (per declared outcome) on the viewer's own open row.\n * The server still authorizes; a refusal surfaces its `error` message as a\n * toast and the widget refetches, so a lost claim race corrects the row.\n *\n * @spec openspec/changes/cn-tasks-entity-source/specs/cn-tasks-entity-source/spec.md\n */\nexport default {\n\tname: 'CnTasksWidget',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `scope` — whose relationship to list: `assigned` (default),\n\t\t * `pooled`, `watched` or `all`. Whose INBOX it is stays the\n\t\t * endpoint's decision; scope never names another user.\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The count\n\t\t * line always states the server total regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 30; `0` disables).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click,\n\t\t * receiving the task uuid as `:id`. Omitted = the click opens the\n\t\t * task's openregister deep link instead.\n\t\t * - `emptyText` — override for the empty-inbox line.\n\t\t *\n\t\t * @type {{scope?: string, limit?: number, pollSeconds?: number, rowRoute?: string, emptyText?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the\n\t\t// same binding CnFlowRunsWidget uses, so a page-level Refresh updates\n\t\t// this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\t\tconst source = () => ({\n\t\t\turl: FLOW_TASKS_URL,\n\t\t\tmethod: 'GET',\n\t\t\tparams: {\n\t\t\t\tscope: normaliseScope(props.content && props.content.scope),\n\t\t\t\t// An inbox drains: finished tasks leave the widget. The index\n\t\t\t\t// page's \"everything\" tab is the place to read history.\n\t\t\t\tisTerminal: 'false',\n\t\t\t\t// Ascending due date: the most urgent (overdue first) on top.\n\t\t\t\tsort: 'dueAt',\n\t\t\t\tlimit: normaliseLimit(props.content && props.content.limit),\n\t\t\t},\n\t\t})\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tinjectedTranslate,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The signed-in viewer's uid, for the per-row action offer. Empty\n\t\t * outside a Nextcloud session (the offer then collapses to claim on\n\t\t * pooled rows only, which is the safe reading).\n\t\t *\n\t\t * @return {string} The uid, or ''.\n\t\t */\n\t\tviewerUid() {\n\t\t\treturn getCurrentUser()?.uid || ''\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The task rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * The server's honest total, independent of the rendered page.\n\t\t *\n\t\t * @return {number} The open-task total (never negative).\n\t\t */\n\t\ttotal() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total)\n\t\t},\n\n\t\t/** The count line above the rows. */\n\t\tcountLabel() {\n\t\t\tif (this.total === 1) {\n\t\t\t\treturn this.tr('1 open task')\n\t\t\t}\n\t\t\treturn this.tr('{count} open tasks').replace('{count}', String(this.total))\n\t\t},\n\n\t\t/**\n\t\t * How many open tasks exist beyond the rendered rows.\n\t\t *\n\t\t * @return {number} The hidden task count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\treturn Math.max(0, this.total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** The empty-inbox line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No open tasks')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off. Floored at 5s so a\n\t\t * mis-authored `pollSeconds: 0.1` cannot turn a dashboard into a\n\t\t * request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 30 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.stopPolling()\n\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is off.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetch(true)\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume on\n\t\t * return, so the dashboard shows current work rather than whatever\n\t\t * was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetch(true)\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * The row's display title (server-synthesized when untitled).\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The title.\n\t\t */\n\t\ttitleOf(task) {\n\t\t\treturn task.displayTitle || task.title || String(task.uuid || '')\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: subject, state and priority — whichever of\n\t\t * those the row actually carries, in words.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(task) {\n\t\t\tconst parts = []\n\t\t\tif (task.subject && task.subject.title) {\n\t\t\t\tparts.push(String(task.subject.title))\n\t\t\t}\n\t\t\tconst state = STATE_LABELS[task.state]\n\t\t\tparts.push(state ? this.tr(state) : String(task.state || ''))\n\t\t\tconst priority = PRIORITY_LABELS[task.priority]\n\t\t\tif (priority && task.priority !== 'normal') {\n\t\t\t\tparts.push(this.tr(priority))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * The due wording for a row, from the server's derived projection.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The due label, or '' without a due date.\n\t\t */\n\t\tdueLabel(task) {\n\t\t\treturn taskDueLabel(task)\n\t\t},\n\n\t\t/**\n\t\t * Whether the row offers claim: pooled (no assignee) and still open.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {boolean} True when claim is offered.\n\t\t */\n\t\tcanClaim(task) {\n\t\t\treturn !task.assignee && task.isTerminal !== true\n\t\t},\n\n\t\t/**\n\t\t * Whether the row offers complete: the viewer's own open task.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {boolean} True when complete is offered.\n\t\t */\n\t\tcanComplete(task) {\n\t\t\treturn this.viewerUid !== ''\n\t\t\t\t&& task.assignee === this.viewerUid\n\t\t\t\t&& task.isTerminal !== true\n\t\t},\n\n\t\t/**\n\t\t * The outcomes to offer on complete: the row's declared list, else\n\t\t * one entry for the server's default outcome.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {Array<object|string>} The outcome entries.\n\t\t */\n\t\toutcomesOf(task) {\n\t\t\tif (Array.isArray(task.outcomes) && task.outcomes.length > 0) {\n\t\t\t\treturn task.outcomes\n\t\t\t}\n\t\t\treturn ['done']\n\t\t},\n\n\t\t/**\n\t\t * The wire value of one outcome entry (a string, or `{id, label}`).\n\t\t *\n\t\t * @param {object|string} outcome The outcome entry.\n\t\t * @return {string} The outcome id.\n\t\t */\n\t\toutcomeId(outcome) {\n\t\t\tif (outcome && typeof outcome === 'object') {\n\t\t\t\treturn String(outcome.id ?? outcome.value ?? outcome.label ?? '')\n\t\t\t}\n\t\t\treturn String(outcome ?? '')\n\t\t},\n\n\t\t/**\n\t\t * The menu label for one complete entry.\n\t\t *\n\t\t * @param {object|string} outcome The outcome entry.\n\t\t * @return {string} The label.\n\t\t */\n\t\tcompleteLabel(outcome) {\n\t\t\tconst id = this.outcomeId(outcome)\n\t\t\tconst label = (outcome && typeof outcome === 'object' && outcome.label)\n\t\t\t\t? String(outcome.label)\n\t\t\t\t: id\n\t\t\tif (id === 'done') {\n\t\t\t\treturn this.tr('Complete')\n\t\t\t}\n\t\t\treturn this.tr('Complete: {outcome}').replace('{outcome}', label)\n\t\t},\n\n\t\t/**\n\t\t * Claim a pooled task.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tclaim(task) {\n\t\t\treturn this.postVerb(task, 'claim', {})\n\t\t},\n\n\t\t/**\n\t\t * Complete the viewer's own task with an outcome.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @param {string} outcome The chosen outcome id.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tcomplete(task, outcome) {\n\t\t\treturn this.postVerb(task, 'complete', { outcome })\n\t\t},\n\n\t\t/**\n\t\t * POST one lifecycle verb and refetch either way: on success the row\n\t\t * moved, and on a refusal (a lost claim race, a revoked task) the\n\t\t * list is stale in exactly the way the refusal proves. The refusal\n\t\t * itself is toasted in the SERVER'S words (ADR-062 forbids leaking\n\t\t * the raw status line, and the endpoint's `error` string is written\n\t\t * for people).\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @param {string} verb The lifecycle verb.\n\t\t * @param {object} body The verb body.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tasync postVerb(task, verb, body) {\n\t\t\tconst uuid = String(task.uuid || '')\n\t\t\tif (uuid === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`${FLOW_TASKS_URL}/${uuid}/${verb}`), body)\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error?.response?.data?.error\n\t\t\t\tshowError(typeof message === 'string' && message !== ''\n\t\t\t\t\t? message\n\t\t\t\t\t: this.tr('The task action was refused'))\n\t\t\t}\n\t\t\tthis.refetch(true)\n\t\t},\n\n\t\t/**\n\t\t * Open the clicked task: the configured route with the uuid as `id`,\n\t\t * else the task's openregister deep link. A ctrl/cmd/shift or middle\n\t\t * click opens it in a new tab.\n\t\t *\n\t\t * @param {object} task The clicked task row.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(task, event) {\n\t\t\tconst uuid = String(task.uuid || '')\n\t\t\tif (uuid === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst rowRoute = this.content.rowRoute\n\t\t\tif (typeof rowRoute === 'string' && rowRoute !== '' && this.$router) {\n\t\t\t\topenRowTarget(event, { name: rowRoute, params: { id: uuid } }, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\topenRowTarget(event, taskDeepLink(uuid))\n\t\t},\n\n\t\t/**\n\t\t * Middle click on a task row: open it in a new tab.\n\t\t *\n\t\t * @param {object} task The clicked task row.\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\tonRowAuxClick(task, event) {\n\t\t\tif (isRowMiddleClick(event)) {\n\t\t\t\tthis.onRowClick(task, event)\n\t\t\t}\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {unknown} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n\n/**\n * Clamp a configured scope onto the endpoint's vocabulary.\n *\n * @param {unknown} raw The configured scope.\n * @return {string} One of assigned, pooled, watched, all (default assigned).\n */\nfunction normaliseScope(raw) {\n\treturn ['assigned', 'pooled', 'watched', 'all'].includes(raw) ? raw : 'assigned'\n}\n</script>\n\n<style scoped>\n.cn-tasks-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-tasks-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-tasks-widget__empty,\n.cn-tasks-widget__error,\n.cn-tasks-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-tasks-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-tasks-widget__count {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n.cn-tasks-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-tasks-widget__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n\tcursor: pointer;\n}\n\n.cn-tasks-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-tasks-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-tasks-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-tasks-widget__row:hover .cn-tasks-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-tasks-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-tasks-widget__due {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* Overdue: weight AND colour on top of the wording, which already says\n \"Overdue by N days\" — the signal survives monochrome. */\n.cn-tasks-widget__due--overdue {\n\tcolor: var(--color-error, #d91f2d);\n\tfont-weight: 600;\n}\n\n.cn-tasks-widget__actions {\n\tflex: 0 0 auto;\n}\n</style>\n"],"names":["t","axios"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnTasksWidget.vue2.js","sources":["../../../../src/components/CnTasksWidget/CnTasksWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-tasks-widget\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-tasks-widget__error\">\n\t\t\t{{ tr('Could not load the tasks') }}\n\t\t</p>\n\t\t<div v-else-if=\"loading && rows.length === 0\" class=\"cn-tasks-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- An empty inbox is the GOOD state, not an error and not a void:\n\t\t one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0\" class=\"cn-tasks-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<!-- The count states the server TOTAL, never the rendered length\n\t\t\t (ADR-062): the number a person acts on is how much work\n\t\t\t exists, not how much fits the cell. -->\n\t\t\t<p class=\"cn-tasks-widget__count\">\n\t\t\t\t{{ countLabel }}\n\t\t\t</p>\n\t\t\t<ul class=\"cn-tasks-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"task in rows\"\n\t\t\t\t\t:key=\"task.uuid\"\n\t\t\t\t\tclass=\"cn-tasks-widget__row\"\n\t\t\t\t\t:data-state=\"task.state\"\n\t\t\t\t\t@click=\"onRowClick(task, $event)\"\n\t\t\t\t\t@mousedown=\"preventMiddleClickAutoscroll\"\n\t\t\t\t\t@auxclick=\"onRowAuxClick(task, $event)\">\n\t\t\t\t\t<span class=\"cn-tasks-widget__body\">\n\t\t\t\t\t\t<span class=\"cn-tasks-widget__name\">{{ titleOf(task) }}</span>\n\t\t\t\t\t\t<span class=\"cn-tasks-widget__meta\">{{ metaLine(task) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- Overdue is carried by the WORDING (taskDueLabel) and by\n\t\t\t\t\t weight; the colour only reinforces it. -->\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-tasks-widget__due\"\n\t\t\t\t\t\t:class=\"{ 'cn-tasks-widget__due--overdue': task.overdue === true }\">\n\t\t\t\t\t\t{{ dueLabel(task) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- Only verbs the contract can accept are offered; the\n\t\t\t\t\t server still authorizes, and a refusal is toasted in\n\t\t\t\t\t its own words. Stopped clicks: the menu must not also\n\t\t\t\t\t open the row. -->\n\t\t\t\t\t<NcActions\n\t\t\t\t\t\tv-if=\"canClaim(task) || canComplete(task)\"\n\t\t\t\t\t\tclass=\"cn-tasks-widget__actions\"\n\t\t\t\t\t\t:forceMenu=\"true\"\n\t\t\t\t\t\t:aria-label=\"tr('Task actions')\"\n\t\t\t\t\t\t@click.stop>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"canClaim(task)\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"claim(task)\">\n\t\t\t\t\t\t\t{{ tr('Claim') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<template v-if=\"canComplete(task)\">\n\t\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\t\tv-for=\"outcome in outcomesOf(task)\"\n\t\t\t\t\t\t\t\t:key=\"outcomeId(outcome)\"\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t@click=\"complete(task, outcomeId(outcome))\">\n\t\t\t\t\t\t\t\t{{ completeLabel(outcome) }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-tasks-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { showError } from '@nextcloud/dialogs'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcActionButton, NcActions, NcLoadingIcon } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport { taskDeepLink, taskDueLabel } from '../../composables/indexSources.js'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'\n\n/**\n * The OpenRegister inbox read (openregister flow-task-entity). One endpoint\n * for the whole fleet: every app's human tasks live on OpenRegister's one\n * task store, so the widget is app-agnostic and ships no controller.\n *\n * @type {string}\n */\nconst FLOW_TASKS_URL = '/apps/openregister/api/flow-tasks'\n\n/**\n * The CMMN states, in the reading a dashboard needs. Labels double as the\n * meta-line words, so state never rests on styling.\n *\n * @type {Record<string, string>}\n */\nconst STATE_LABELS = {\n\tavailable: 'Available',\n\tenabled: 'Ready to claim',\n\tactive: 'In progress',\n\tcompleted: 'Completed',\n\tterminated: 'Terminated',\n\tdisabled: 'Disabled',\n}\n\n/**\n * The priority vocabulary (openregister Task::PRIORITIES).\n *\n * @type {Record<string, string>}\n */\nconst PRIORITY_LABELS = {\n\tlow: 'Low',\n\tnormal: 'Normal',\n\thigh: 'High',\n\turgent: 'Urgent',\n}\n\n/**\n * CnTasksWidget — the viewer's open tasks, from OpenRegister's task inbox.\n *\n * Reads `flow-tasks` with `isTerminal=false` (an inbox widget that showed\n * finished tasks would never drain), scoped by `content.scope` and sorted by\n * due date so the most urgent row sits on top. Placed by its registry type\n * key `tasks`:\n *\n * ```js\n * { id: 'my-tasks', type: 'tasks', title: 'My tasks',\n * content: { scope: 'assigned', limit: 6, pollSeconds: 30 } }\n * ```\n *\n * Why it polls: tasks are handed out, claimed and completed by OTHER people\n * and by flows, so a widget that only reflected its mount moment would be\n * wrong within minutes and look identical to being right. Polling pauses\n * while the tab is hidden.\n *\n * Quick actions offer only what the row's contract can accept: claim on a\n * pooled row, complete (per declared outcome) on the viewer's own open row.\n * The server still authorizes; a refusal surfaces its `error` message as a\n * toast and the widget refetches, so a lost claim race corrects the row.\n *\n * @spec openspec/changes/cn-tasks-entity-source/specs/cn-tasks-entity-source/spec.md\n */\nexport default {\n\tname: 'CnTasksWidget',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `scope` — whose relationship to list: `assigned` (default),\n\t\t * `pooled`, `watched` or `all`. Whose INBOX it is stays the\n\t\t * endpoint's decision; scope never names another user.\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The count\n\t\t * line always states the server total regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 30; `0` disables).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click,\n\t\t * receiving the task uuid as `:id`. Omitted = the click opens the\n\t\t * task's openregister deep link instead.\n\t\t * - `emptyText` — override for the empty-inbox line.\n\t\t *\n\t\t * @type {{scope?: string, limit?: number, pollSeconds?: number, rowRoute?: string, emptyText?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the\n\t\t// same binding CnFlowRunsWidget uses, so a page-level Refresh updates\n\t\t// this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\t\tconst source = () => ({\n\t\t\turl: FLOW_TASKS_URL,\n\t\t\tmethod: 'GET',\n\t\t\tparams: {\n\t\t\t\tscope: normaliseScope(props.content && props.content.scope),\n\t\t\t\t// An inbox drains: finished tasks leave the widget. The index\n\t\t\t\t// page's \"everything\" tab is the place to read history.\n\t\t\t\tisTerminal: 'false',\n\t\t\t\t// Ascending due date: the most urgent (overdue first) on top.\n\t\t\t\tsort: 'dueAt',\n\t\t\t\tlimit: normaliseLimit(props.content && props.content.limit),\n\t\t\t},\n\t\t})\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tinjectedTranslate,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The signed-in viewer's uid, for the per-row action offer. Empty\n\t\t * outside a Nextcloud session (the offer then collapses to claim on\n\t\t * pooled rows only, which is the safe reading).\n\t\t *\n\t\t * @return {string} The uid, or ''.\n\t\t */\n\t\tviewerUid() {\n\t\t\treturn getCurrentUser()?.uid || ''\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The task rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * The server's honest total, independent of the rendered page.\n\t\t *\n\t\t * @return {number} The open-task total (never negative).\n\t\t */\n\t\ttotal() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total)\n\t\t},\n\n\t\t/** The count line above the rows. */\n\t\tcountLabel() {\n\t\t\tif (this.total === 1) {\n\t\t\t\treturn this.tr('1 open task')\n\t\t\t}\n\t\t\treturn this.tr('{count} open tasks').replace('{count}', String(this.total))\n\t\t},\n\n\t\t/**\n\t\t * How many open tasks exist beyond the rendered rows.\n\t\t *\n\t\t * @return {number} The hidden task count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\treturn Math.max(0, this.total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** The empty-inbox line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No open tasks')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off. Floored at 5s so a\n\t\t * mis-authored `pollSeconds: 0.1` cannot turn a dashboard into a\n\t\t * request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 30 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.stopPolling()\n\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tmethods: {\n\t\tpreventMiddleClickAutoscroll,\n\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is off.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetch(true)\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume on\n\t\t * return, so the dashboard shows current work rather than whatever\n\t\t * was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetch(true)\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * The row's display title (server-synthesized when untitled).\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The title.\n\t\t */\n\t\ttitleOf(task) {\n\t\t\treturn task.displayTitle || task.title || String(task.uuid || '')\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: subject, state and priority — whichever of\n\t\t * those the row actually carries, in words.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(task) {\n\t\t\tconst parts = []\n\t\t\tif (task.subject && task.subject.title) {\n\t\t\t\tparts.push(String(task.subject.title))\n\t\t\t}\n\t\t\tconst state = STATE_LABELS[task.state]\n\t\t\tparts.push(state ? this.tr(state) : String(task.state || ''))\n\t\t\tconst priority = PRIORITY_LABELS[task.priority]\n\t\t\tif (priority && task.priority !== 'normal') {\n\t\t\t\tparts.push(this.tr(priority))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * The due wording for a row, from the server's derived projection.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The due label, or '' without a due date.\n\t\t */\n\t\tdueLabel(task) {\n\t\t\treturn taskDueLabel(task)\n\t\t},\n\n\t\t/**\n\t\t * Whether the row offers claim: pooled (no assignee) and still open.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {boolean} True when claim is offered.\n\t\t */\n\t\tcanClaim(task) {\n\t\t\treturn !task.assignee && task.isTerminal !== true\n\t\t},\n\n\t\t/**\n\t\t * Whether the row offers complete: the viewer's own open task.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {boolean} True when complete is offered.\n\t\t */\n\t\tcanComplete(task) {\n\t\t\treturn this.viewerUid !== ''\n\t\t\t\t&& task.assignee === this.viewerUid\n\t\t\t\t&& task.isTerminal !== true\n\t\t},\n\n\t\t/**\n\t\t * The outcomes to offer on complete: the row's declared list, else\n\t\t * one entry for the server's default outcome.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {Array<object|string>} The outcome entries.\n\t\t */\n\t\toutcomesOf(task) {\n\t\t\tif (Array.isArray(task.outcomes) && task.outcomes.length > 0) {\n\t\t\t\treturn task.outcomes\n\t\t\t}\n\t\t\treturn ['done']\n\t\t},\n\n\t\t/**\n\t\t * The wire value of one outcome entry (a string, or `{id, label}`).\n\t\t *\n\t\t * @param {object|string} outcome The outcome entry.\n\t\t * @return {string} The outcome id.\n\t\t */\n\t\toutcomeId(outcome) {\n\t\t\tif (outcome && typeof outcome === 'object') {\n\t\t\t\treturn String(outcome.id ?? outcome.value ?? outcome.label ?? '')\n\t\t\t}\n\t\t\treturn String(outcome ?? '')\n\t\t},\n\n\t\t/**\n\t\t * The menu label for one complete entry.\n\t\t *\n\t\t * @param {object|string} outcome The outcome entry.\n\t\t * @return {string} The label.\n\t\t */\n\t\tcompleteLabel(outcome) {\n\t\t\tconst id = this.outcomeId(outcome)\n\t\t\tconst label = (outcome && typeof outcome === 'object' && outcome.label)\n\t\t\t\t? String(outcome.label)\n\t\t\t\t: id\n\t\t\tif (id === 'done') {\n\t\t\t\treturn this.tr('Complete')\n\t\t\t}\n\t\t\treturn this.tr('Complete: {outcome}').replace('{outcome}', label)\n\t\t},\n\n\t\t/**\n\t\t * Claim a pooled task.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tclaim(task) {\n\t\t\treturn this.postVerb(task, 'claim', {})\n\t\t},\n\n\t\t/**\n\t\t * Complete the viewer's own task with an outcome.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @param {string} outcome The chosen outcome id.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tcomplete(task, outcome) {\n\t\t\treturn this.postVerb(task, 'complete', { outcome })\n\t\t},\n\n\t\t/**\n\t\t * POST one lifecycle verb and refetch either way: on success the row\n\t\t * moved, and on a refusal (a lost claim race, a revoked task) the\n\t\t * list is stale in exactly the way the refusal proves. The refusal\n\t\t * itself is toasted in the SERVER'S words (ADR-062 forbids leaking\n\t\t * the raw status line, and the endpoint's `error` string is written\n\t\t * for people).\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @param {string} verb The lifecycle verb.\n\t\t * @param {object} body The verb body.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tasync postVerb(task, verb, body) {\n\t\t\tconst uuid = String(task.uuid || '')\n\t\t\tif (uuid === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`${FLOW_TASKS_URL}/${uuid}/${verb}`), body)\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error?.response?.data?.error\n\t\t\t\tshowError(typeof message === 'string' && message !== ''\n\t\t\t\t\t? message\n\t\t\t\t\t: this.tr('The task action was refused'))\n\t\t\t}\n\t\t\tthis.refetch(true)\n\t\t},\n\n\t\t/**\n\t\t * Open the clicked task: the configured route with the uuid as `id`,\n\t\t * else the task's openregister deep link. A ctrl/cmd/shift or middle\n\t\t * click opens it in a new tab.\n\t\t *\n\t\t * @param {object} task The clicked task row.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(task, event) {\n\t\t\tconst uuid = String(task.uuid || '')\n\t\t\tif (uuid === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst rowRoute = this.content.rowRoute\n\t\t\tif (typeof rowRoute === 'string' && rowRoute !== '' && this.$router) {\n\t\t\t\topenRowTarget(event, { name: rowRoute, params: { id: uuid } }, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\topenRowTarget(event, taskDeepLink(uuid))\n\t\t},\n\n\t\t/**\n\t\t * Middle click on a task row: open it in a new tab.\n\t\t *\n\t\t * @param {object} task The clicked task row.\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\tonRowAuxClick(task, event) {\n\t\t\tif (isRowMiddleClick(event)) {\n\t\t\t\tthis.onRowClick(task, event)\n\t\t\t}\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {unknown} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n\n/**\n * Clamp a configured scope onto the endpoint's vocabulary.\n *\n * @param {unknown} raw The configured scope.\n * @return {string} One of assigned, pooled, watched, all (default assigned).\n */\nfunction normaliseScope(raw) {\n\treturn ['assigned', 'pooled', 'watched', 'all'].includes(raw) ? raw : 'assigned'\n}\n</script>\n\n<style scoped>\n.cn-tasks-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-tasks-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-tasks-widget__empty,\n.cn-tasks-widget__error,\n.cn-tasks-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-tasks-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-tasks-widget__count {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n.cn-tasks-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-tasks-widget__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n\tcursor: pointer;\n}\n\n.cn-tasks-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-tasks-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-tasks-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-tasks-widget__row:hover .cn-tasks-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-tasks-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-tasks-widget__due {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* Overdue: weight AND colour on top of the wording, which already says\n \"Overdue by N days\" — the signal survives monochrome. */\n.cn-tasks-widget__due--overdue {\n\tcolor: var(--color-error, #d91f2d);\n\tfont-weight: 600;\n}\n\n.cn-tasks-widget__actions {\n\tflex: 0 0 auto;\n}\n</style>\n"],"names":["t","axios"],"mappings":"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"}
|
|
@@ -47,6 +47,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
47
47
|
class: "cn-tasks-widget__row",
|
|
48
48
|
"data-state": task.state,
|
|
49
49
|
onClick: $event => ($options.onRowClick(task, $event)),
|
|
50
|
+
onMousedown: _cache[1] || (_cache[1] = (...args) => ($options.preventMiddleClickAutoscroll && $options.preventMiddleClickAutoscroll(...args))),
|
|
50
51
|
onAuxclick: $event => ($options.onRowAuxClick(task, $event))
|
|
51
52
|
}, [
|
|
52
53
|
createElementVNode("span", _hoisted_8, [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnTasksWidget.vue3.js","sources":["../../../../src/components/CnTasksWidget/CnTasksWidget.vue?vue&type=template&id=ff399342&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-tasks-widget\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-tasks-widget__error\">\n\t\t\t{{ tr('Could not load the tasks') }}\n\t\t</p>\n\t\t<div v-else-if=\"loading && rows.length === 0\" class=\"cn-tasks-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- An empty inbox is the GOOD state, not an error and not a void:\n\t\t one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0\" class=\"cn-tasks-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<!-- The count states the server TOTAL, never the rendered length\n\t\t\t (ADR-062): the number a person acts on is how much work\n\t\t\t exists, not how much fits the cell. -->\n\t\t\t<p class=\"cn-tasks-widget__count\">\n\t\t\t\t{{ countLabel }}\n\t\t\t</p>\n\t\t\t<ul class=\"cn-tasks-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"task in rows\"\n\t\t\t\t\t:key=\"task.uuid\"\n\t\t\t\t\tclass=\"cn-tasks-widget__row\"\n\t\t\t\t\t:data-state=\"task.state\"\n\t\t\t\t\t@click=\"onRowClick(task, $event)\"\n\t\t\t\t\t@auxclick=\"onRowAuxClick(task, $event)\">\n\t\t\t\t\t<span class=\"cn-tasks-widget__body\">\n\t\t\t\t\t\t<span class=\"cn-tasks-widget__name\">{{ titleOf(task) }}</span>\n\t\t\t\t\t\t<span class=\"cn-tasks-widget__meta\">{{ metaLine(task) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- Overdue is carried by the WORDING (taskDueLabel) and by\n\t\t\t\t\t weight; the colour only reinforces it. -->\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-tasks-widget__due\"\n\t\t\t\t\t\t:class=\"{ 'cn-tasks-widget__due--overdue': task.overdue === true }\">\n\t\t\t\t\t\t{{ dueLabel(task) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- Only verbs the contract can accept are offered; the\n\t\t\t\t\t server still authorizes, and a refusal is toasted in\n\t\t\t\t\t its own words. Stopped clicks: the menu must not also\n\t\t\t\t\t open the row. -->\n\t\t\t\t\t<NcActions\n\t\t\t\t\t\tv-if=\"canClaim(task) || canComplete(task)\"\n\t\t\t\t\t\tclass=\"cn-tasks-widget__actions\"\n\t\t\t\t\t\t:forceMenu=\"true\"\n\t\t\t\t\t\t:aria-label=\"tr('Task actions')\"\n\t\t\t\t\t\t@click.stop>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"canClaim(task)\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"claim(task)\">\n\t\t\t\t\t\t\t{{ tr('Claim') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<template v-if=\"canComplete(task)\">\n\t\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\t\tv-for=\"outcome in outcomesOf(task)\"\n\t\t\t\t\t\t\t\t:key=\"outcomeId(outcome)\"\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t@click=\"complete(task, outcomeId(outcome))\">\n\t\t\t\t\t\t\t\t{{ completeLabel(outcome) }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-tasks-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { showError } from '@nextcloud/dialogs'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcActionButton, NcActions, NcLoadingIcon } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport { taskDeepLink, taskDueLabel } from '../../composables/indexSources.js'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { isRowMiddleClick } from '../../utils/rowAuxClick.js'\n\n/**\n * The OpenRegister inbox read (openregister flow-task-entity). One endpoint\n * for the whole fleet: every app's human tasks live on OpenRegister's one\n * task store, so the widget is app-agnostic and ships no controller.\n *\n * @type {string}\n */\nconst FLOW_TASKS_URL = '/apps/openregister/api/flow-tasks'\n\n/**\n * The CMMN states, in the reading a dashboard needs. Labels double as the\n * meta-line words, so state never rests on styling.\n *\n * @type {Record<string, string>}\n */\nconst STATE_LABELS = {\n\tavailable: 'Available',\n\tenabled: 'Ready to claim',\n\tactive: 'In progress',\n\tcompleted: 'Completed',\n\tterminated: 'Terminated',\n\tdisabled: 'Disabled',\n}\n\n/**\n * The priority vocabulary (openregister Task::PRIORITIES).\n *\n * @type {Record<string, string>}\n */\nconst PRIORITY_LABELS = {\n\tlow: 'Low',\n\tnormal: 'Normal',\n\thigh: 'High',\n\turgent: 'Urgent',\n}\n\n/**\n * CnTasksWidget — the viewer's open tasks, from OpenRegister's task inbox.\n *\n * Reads `flow-tasks` with `isTerminal=false` (an inbox widget that showed\n * finished tasks would never drain), scoped by `content.scope` and sorted by\n * due date so the most urgent row sits on top. Placed by its registry type\n * key `tasks`:\n *\n * ```js\n * { id: 'my-tasks', type: 'tasks', title: 'My tasks',\n * content: { scope: 'assigned', limit: 6, pollSeconds: 30 } }\n * ```\n *\n * Why it polls: tasks are handed out, claimed and completed by OTHER people\n * and by flows, so a widget that only reflected its mount moment would be\n * wrong within minutes and look identical to being right. Polling pauses\n * while the tab is hidden.\n *\n * Quick actions offer only what the row's contract can accept: claim on a\n * pooled row, complete (per declared outcome) on the viewer's own open row.\n * The server still authorizes; a refusal surfaces its `error` message as a\n * toast and the widget refetches, so a lost claim race corrects the row.\n *\n * @spec openspec/changes/cn-tasks-entity-source/specs/cn-tasks-entity-source/spec.md\n */\nexport default {\n\tname: 'CnTasksWidget',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `scope` — whose relationship to list: `assigned` (default),\n\t\t * `pooled`, `watched` or `all`. Whose INBOX it is stays the\n\t\t * endpoint's decision; scope never names another user.\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The count\n\t\t * line always states the server total regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 30; `0` disables).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click,\n\t\t * receiving the task uuid as `:id`. Omitted = the click opens the\n\t\t * task's openregister deep link instead.\n\t\t * - `emptyText` — override for the empty-inbox line.\n\t\t *\n\t\t * @type {{scope?: string, limit?: number, pollSeconds?: number, rowRoute?: string, emptyText?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the\n\t\t// same binding CnFlowRunsWidget uses, so a page-level Refresh updates\n\t\t// this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\t\tconst source = () => ({\n\t\t\turl: FLOW_TASKS_URL,\n\t\t\tmethod: 'GET',\n\t\t\tparams: {\n\t\t\t\tscope: normaliseScope(props.content && props.content.scope),\n\t\t\t\t// An inbox drains: finished tasks leave the widget. The index\n\t\t\t\t// page's \"everything\" tab is the place to read history.\n\t\t\t\tisTerminal: 'false',\n\t\t\t\t// Ascending due date: the most urgent (overdue first) on top.\n\t\t\t\tsort: 'dueAt',\n\t\t\t\tlimit: normaliseLimit(props.content && props.content.limit),\n\t\t\t},\n\t\t})\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tinjectedTranslate,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The signed-in viewer's uid, for the per-row action offer. Empty\n\t\t * outside a Nextcloud session (the offer then collapses to claim on\n\t\t * pooled rows only, which is the safe reading).\n\t\t *\n\t\t * @return {string} The uid, or ''.\n\t\t */\n\t\tviewerUid() {\n\t\t\treturn getCurrentUser()?.uid || ''\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The task rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * The server's honest total, independent of the rendered page.\n\t\t *\n\t\t * @return {number} The open-task total (never negative).\n\t\t */\n\t\ttotal() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total)\n\t\t},\n\n\t\t/** The count line above the rows. */\n\t\tcountLabel() {\n\t\t\tif (this.total === 1) {\n\t\t\t\treturn this.tr('1 open task')\n\t\t\t}\n\t\t\treturn this.tr('{count} open tasks').replace('{count}', String(this.total))\n\t\t},\n\n\t\t/**\n\t\t * How many open tasks exist beyond the rendered rows.\n\t\t *\n\t\t * @return {number} The hidden task count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\treturn Math.max(0, this.total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** The empty-inbox line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No open tasks')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off. Floored at 5s so a\n\t\t * mis-authored `pollSeconds: 0.1` cannot turn a dashboard into a\n\t\t * request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 30 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.stopPolling()\n\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is off.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetch(true)\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume on\n\t\t * return, so the dashboard shows current work rather than whatever\n\t\t * was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetch(true)\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * The row's display title (server-synthesized when untitled).\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The title.\n\t\t */\n\t\ttitleOf(task) {\n\t\t\treturn task.displayTitle || task.title || String(task.uuid || '')\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: subject, state and priority — whichever of\n\t\t * those the row actually carries, in words.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(task) {\n\t\t\tconst parts = []\n\t\t\tif (task.subject && task.subject.title) {\n\t\t\t\tparts.push(String(task.subject.title))\n\t\t\t}\n\t\t\tconst state = STATE_LABELS[task.state]\n\t\t\tparts.push(state ? this.tr(state) : String(task.state || ''))\n\t\t\tconst priority = PRIORITY_LABELS[task.priority]\n\t\t\tif (priority && task.priority !== 'normal') {\n\t\t\t\tparts.push(this.tr(priority))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * The due wording for a row, from the server's derived projection.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The due label, or '' without a due date.\n\t\t */\n\t\tdueLabel(task) {\n\t\t\treturn taskDueLabel(task)\n\t\t},\n\n\t\t/**\n\t\t * Whether the row offers claim: pooled (no assignee) and still open.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {boolean} True when claim is offered.\n\t\t */\n\t\tcanClaim(task) {\n\t\t\treturn !task.assignee && task.isTerminal !== true\n\t\t},\n\n\t\t/**\n\t\t * Whether the row offers complete: the viewer's own open task.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {boolean} True when complete is offered.\n\t\t */\n\t\tcanComplete(task) {\n\t\t\treturn this.viewerUid !== ''\n\t\t\t\t&& task.assignee === this.viewerUid\n\t\t\t\t&& task.isTerminal !== true\n\t\t},\n\n\t\t/**\n\t\t * The outcomes to offer on complete: the row's declared list, else\n\t\t * one entry for the server's default outcome.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {Array<object|string>} The outcome entries.\n\t\t */\n\t\toutcomesOf(task) {\n\t\t\tif (Array.isArray(task.outcomes) && task.outcomes.length > 0) {\n\t\t\t\treturn task.outcomes\n\t\t\t}\n\t\t\treturn ['done']\n\t\t},\n\n\t\t/**\n\t\t * The wire value of one outcome entry (a string, or `{id, label}`).\n\t\t *\n\t\t * @param {object|string} outcome The outcome entry.\n\t\t * @return {string} The outcome id.\n\t\t */\n\t\toutcomeId(outcome) {\n\t\t\tif (outcome && typeof outcome === 'object') {\n\t\t\t\treturn String(outcome.id ?? outcome.value ?? outcome.label ?? '')\n\t\t\t}\n\t\t\treturn String(outcome ?? '')\n\t\t},\n\n\t\t/**\n\t\t * The menu label for one complete entry.\n\t\t *\n\t\t * @param {object|string} outcome The outcome entry.\n\t\t * @return {string} The label.\n\t\t */\n\t\tcompleteLabel(outcome) {\n\t\t\tconst id = this.outcomeId(outcome)\n\t\t\tconst label = (outcome && typeof outcome === 'object' && outcome.label)\n\t\t\t\t? String(outcome.label)\n\t\t\t\t: id\n\t\t\tif (id === 'done') {\n\t\t\t\treturn this.tr('Complete')\n\t\t\t}\n\t\t\treturn this.tr('Complete: {outcome}').replace('{outcome}', label)\n\t\t},\n\n\t\t/**\n\t\t * Claim a pooled task.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tclaim(task) {\n\t\t\treturn this.postVerb(task, 'claim', {})\n\t\t},\n\n\t\t/**\n\t\t * Complete the viewer's own task with an outcome.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @param {string} outcome The chosen outcome id.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tcomplete(task, outcome) {\n\t\t\treturn this.postVerb(task, 'complete', { outcome })\n\t\t},\n\n\t\t/**\n\t\t * POST one lifecycle verb and refetch either way: on success the row\n\t\t * moved, and on a refusal (a lost claim race, a revoked task) the\n\t\t * list is stale in exactly the way the refusal proves. The refusal\n\t\t * itself is toasted in the SERVER'S words (ADR-062 forbids leaking\n\t\t * the raw status line, and the endpoint's `error` string is written\n\t\t * for people).\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @param {string} verb The lifecycle verb.\n\t\t * @param {object} body The verb body.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tasync postVerb(task, verb, body) {\n\t\t\tconst uuid = String(task.uuid || '')\n\t\t\tif (uuid === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`${FLOW_TASKS_URL}/${uuid}/${verb}`), body)\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error?.response?.data?.error\n\t\t\t\tshowError(typeof message === 'string' && message !== ''\n\t\t\t\t\t? message\n\t\t\t\t\t: this.tr('The task action was refused'))\n\t\t\t}\n\t\t\tthis.refetch(true)\n\t\t},\n\n\t\t/**\n\t\t * Open the clicked task: the configured route with the uuid as `id`,\n\t\t * else the task's openregister deep link. A ctrl/cmd/shift or middle\n\t\t * click opens it in a new tab.\n\t\t *\n\t\t * @param {object} task The clicked task row.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(task, event) {\n\t\t\tconst uuid = String(task.uuid || '')\n\t\t\tif (uuid === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst rowRoute = this.content.rowRoute\n\t\t\tif (typeof rowRoute === 'string' && rowRoute !== '' && this.$router) {\n\t\t\t\topenRowTarget(event, { name: rowRoute, params: { id: uuid } }, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\topenRowTarget(event, taskDeepLink(uuid))\n\t\t},\n\n\t\t/**\n\t\t * Middle click on a task row: open it in a new tab.\n\t\t *\n\t\t * @param {object} task The clicked task row.\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\tonRowAuxClick(task, event) {\n\t\t\tif (isRowMiddleClick(event)) {\n\t\t\t\tthis.onRowClick(task, event)\n\t\t\t}\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {unknown} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n\n/**\n * Clamp a configured scope onto the endpoint's vocabulary.\n *\n * @param {unknown} raw The configured scope.\n * @return {string} One of assigned, pooled, watched, all (default assigned).\n */\nfunction normaliseScope(raw) {\n\treturn ['assigned', 'pooled', 'watched', 'all'].includes(raw) ? raw : 'assigned'\n}\n</script>\n\n<style scoped>\n.cn-tasks-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-tasks-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-tasks-widget__empty,\n.cn-tasks-widget__error,\n.cn-tasks-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-tasks-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-tasks-widget__count {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n.cn-tasks-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-tasks-widget__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n\tcursor: pointer;\n}\n\n.cn-tasks-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-tasks-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-tasks-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-tasks-widget__row:hover .cn-tasks-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-tasks-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-tasks-widget__due {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* Overdue: weight AND colour on top of the wording, which already says\n \"Overdue by N days\" — the signal survives monochrome. */\n.cn-tasks-widget__due--overdue {\n\tcolor: var(--color-error, #d91f2d);\n\tfont-weight: 600;\n}\n\n.cn-tasks-widget__actions {\n\tflex: 0 0 auto;\n}\n</style>\n"],"names":["_toDisplayString","_openBlock","_createElementBlock","_createVNode","_createElementVNode","_Fragment","_renderList","_normalizeClass","_createBlock","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnTasksWidget.vue3.js","sources":["../../../../src/components/CnTasksWidget/CnTasksWidget.vue?vue&type=template&id=ff399342&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-tasks-widget\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-tasks-widget__error\">\n\t\t\t{{ tr('Could not load the tasks') }}\n\t\t</p>\n\t\t<div v-else-if=\"loading && rows.length === 0\" class=\"cn-tasks-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- An empty inbox is the GOOD state, not an error and not a void:\n\t\t one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0\" class=\"cn-tasks-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<!-- The count states the server TOTAL, never the rendered length\n\t\t\t (ADR-062): the number a person acts on is how much work\n\t\t\t exists, not how much fits the cell. -->\n\t\t\t<p class=\"cn-tasks-widget__count\">\n\t\t\t\t{{ countLabel }}\n\t\t\t</p>\n\t\t\t<ul class=\"cn-tasks-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"task in rows\"\n\t\t\t\t\t:key=\"task.uuid\"\n\t\t\t\t\tclass=\"cn-tasks-widget__row\"\n\t\t\t\t\t:data-state=\"task.state\"\n\t\t\t\t\t@click=\"onRowClick(task, $event)\"\n\t\t\t\t\t@mousedown=\"preventMiddleClickAutoscroll\"\n\t\t\t\t\t@auxclick=\"onRowAuxClick(task, $event)\">\n\t\t\t\t\t<span class=\"cn-tasks-widget__body\">\n\t\t\t\t\t\t<span class=\"cn-tasks-widget__name\">{{ titleOf(task) }}</span>\n\t\t\t\t\t\t<span class=\"cn-tasks-widget__meta\">{{ metaLine(task) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- Overdue is carried by the WORDING (taskDueLabel) and by\n\t\t\t\t\t weight; the colour only reinforces it. -->\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-tasks-widget__due\"\n\t\t\t\t\t\t:class=\"{ 'cn-tasks-widget__due--overdue': task.overdue === true }\">\n\t\t\t\t\t\t{{ dueLabel(task) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- Only verbs the contract can accept are offered; the\n\t\t\t\t\t server still authorizes, and a refusal is toasted in\n\t\t\t\t\t its own words. Stopped clicks: the menu must not also\n\t\t\t\t\t open the row. -->\n\t\t\t\t\t<NcActions\n\t\t\t\t\t\tv-if=\"canClaim(task) || canComplete(task)\"\n\t\t\t\t\t\tclass=\"cn-tasks-widget__actions\"\n\t\t\t\t\t\t:forceMenu=\"true\"\n\t\t\t\t\t\t:aria-label=\"tr('Task actions')\"\n\t\t\t\t\t\t@click.stop>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"canClaim(task)\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"claim(task)\">\n\t\t\t\t\t\t\t{{ tr('Claim') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<template v-if=\"canComplete(task)\">\n\t\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\t\tv-for=\"outcome in outcomesOf(task)\"\n\t\t\t\t\t\t\t\t:key=\"outcomeId(outcome)\"\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t@click=\"complete(task, outcomeId(outcome))\">\n\t\t\t\t\t\t\t\t{{ completeLabel(outcome) }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-tasks-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { showError } from '@nextcloud/dialogs'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcActionButton, NcActions, NcLoadingIcon } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport { taskDeepLink, taskDueLabel } from '../../composables/indexSources.js'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'\n\n/**\n * The OpenRegister inbox read (openregister flow-task-entity). One endpoint\n * for the whole fleet: every app's human tasks live on OpenRegister's one\n * task store, so the widget is app-agnostic and ships no controller.\n *\n * @type {string}\n */\nconst FLOW_TASKS_URL = '/apps/openregister/api/flow-tasks'\n\n/**\n * The CMMN states, in the reading a dashboard needs. Labels double as the\n * meta-line words, so state never rests on styling.\n *\n * @type {Record<string, string>}\n */\nconst STATE_LABELS = {\n\tavailable: 'Available',\n\tenabled: 'Ready to claim',\n\tactive: 'In progress',\n\tcompleted: 'Completed',\n\tterminated: 'Terminated',\n\tdisabled: 'Disabled',\n}\n\n/**\n * The priority vocabulary (openregister Task::PRIORITIES).\n *\n * @type {Record<string, string>}\n */\nconst PRIORITY_LABELS = {\n\tlow: 'Low',\n\tnormal: 'Normal',\n\thigh: 'High',\n\turgent: 'Urgent',\n}\n\n/**\n * CnTasksWidget — the viewer's open tasks, from OpenRegister's task inbox.\n *\n * Reads `flow-tasks` with `isTerminal=false` (an inbox widget that showed\n * finished tasks would never drain), scoped by `content.scope` and sorted by\n * due date so the most urgent row sits on top. Placed by its registry type\n * key `tasks`:\n *\n * ```js\n * { id: 'my-tasks', type: 'tasks', title: 'My tasks',\n * content: { scope: 'assigned', limit: 6, pollSeconds: 30 } }\n * ```\n *\n * Why it polls: tasks are handed out, claimed and completed by OTHER people\n * and by flows, so a widget that only reflected its mount moment would be\n * wrong within minutes and look identical to being right. Polling pauses\n * while the tab is hidden.\n *\n * Quick actions offer only what the row's contract can accept: claim on a\n * pooled row, complete (per declared outcome) on the viewer's own open row.\n * The server still authorizes; a refusal surfaces its `error` message as a\n * toast and the widget refetches, so a lost claim race corrects the row.\n *\n * @spec openspec/changes/cn-tasks-entity-source/specs/cn-tasks-entity-source/spec.md\n */\nexport default {\n\tname: 'CnTasksWidget',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `scope` — whose relationship to list: `assigned` (default),\n\t\t * `pooled`, `watched` or `all`. Whose INBOX it is stays the\n\t\t * endpoint's decision; scope never names another user.\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The count\n\t\t * line always states the server total regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 30; `0` disables).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click,\n\t\t * receiving the task uuid as `:id`. Omitted = the click opens the\n\t\t * task's openregister deep link instead.\n\t\t * - `emptyText` — override for the empty-inbox line.\n\t\t *\n\t\t * @type {{scope?: string, limit?: number, pollSeconds?: number, rowRoute?: string, emptyText?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the\n\t\t// same binding CnFlowRunsWidget uses, so a page-level Refresh updates\n\t\t// this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\t\tconst source = () => ({\n\t\t\turl: FLOW_TASKS_URL,\n\t\t\tmethod: 'GET',\n\t\t\tparams: {\n\t\t\t\tscope: normaliseScope(props.content && props.content.scope),\n\t\t\t\t// An inbox drains: finished tasks leave the widget. The index\n\t\t\t\t// page's \"everything\" tab is the place to read history.\n\t\t\t\tisTerminal: 'false',\n\t\t\t\t// Ascending due date: the most urgent (overdue first) on top.\n\t\t\t\tsort: 'dueAt',\n\t\t\t\tlimit: normaliseLimit(props.content && props.content.limit),\n\t\t\t},\n\t\t})\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tinjectedTranslate,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The signed-in viewer's uid, for the per-row action offer. Empty\n\t\t * outside a Nextcloud session (the offer then collapses to claim on\n\t\t * pooled rows only, which is the safe reading).\n\t\t *\n\t\t * @return {string} The uid, or ''.\n\t\t */\n\t\tviewerUid() {\n\t\t\treturn getCurrentUser()?.uid || ''\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The task rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * The server's honest total, independent of the rendered page.\n\t\t *\n\t\t * @return {number} The open-task total (never negative).\n\t\t */\n\t\ttotal() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total)\n\t\t},\n\n\t\t/** The count line above the rows. */\n\t\tcountLabel() {\n\t\t\tif (this.total === 1) {\n\t\t\t\treturn this.tr('1 open task')\n\t\t\t}\n\t\t\treturn this.tr('{count} open tasks').replace('{count}', String(this.total))\n\t\t},\n\n\t\t/**\n\t\t * How many open tasks exist beyond the rendered rows.\n\t\t *\n\t\t * @return {number} The hidden task count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\treturn Math.max(0, this.total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** The empty-inbox line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No open tasks')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off. Floored at 5s so a\n\t\t * mis-authored `pollSeconds: 0.1` cannot turn a dashboard into a\n\t\t * request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 30 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.stopPolling()\n\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tmethods: {\n\t\tpreventMiddleClickAutoscroll,\n\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is off.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetch(true)\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume on\n\t\t * return, so the dashboard shows current work rather than whatever\n\t\t * was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetch(true)\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * The row's display title (server-synthesized when untitled).\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The title.\n\t\t */\n\t\ttitleOf(task) {\n\t\t\treturn task.displayTitle || task.title || String(task.uuid || '')\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: subject, state and priority — whichever of\n\t\t * those the row actually carries, in words.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(task) {\n\t\t\tconst parts = []\n\t\t\tif (task.subject && task.subject.title) {\n\t\t\t\tparts.push(String(task.subject.title))\n\t\t\t}\n\t\t\tconst state = STATE_LABELS[task.state]\n\t\t\tparts.push(state ? this.tr(state) : String(task.state || ''))\n\t\t\tconst priority = PRIORITY_LABELS[task.priority]\n\t\t\tif (priority && task.priority !== 'normal') {\n\t\t\t\tparts.push(this.tr(priority))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * The due wording for a row, from the server's derived projection.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {string} The due label, or '' without a due date.\n\t\t */\n\t\tdueLabel(task) {\n\t\t\treturn taskDueLabel(task)\n\t\t},\n\n\t\t/**\n\t\t * Whether the row offers claim: pooled (no assignee) and still open.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {boolean} True when claim is offered.\n\t\t */\n\t\tcanClaim(task) {\n\t\t\treturn !task.assignee && task.isTerminal !== true\n\t\t},\n\n\t\t/**\n\t\t * Whether the row offers complete: the viewer's own open task.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {boolean} True when complete is offered.\n\t\t */\n\t\tcanComplete(task) {\n\t\t\treturn this.viewerUid !== ''\n\t\t\t\t&& task.assignee === this.viewerUid\n\t\t\t\t&& task.isTerminal !== true\n\t\t},\n\n\t\t/**\n\t\t * The outcomes to offer on complete: the row's declared list, else\n\t\t * one entry for the server's default outcome.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {Array<object|string>} The outcome entries.\n\t\t */\n\t\toutcomesOf(task) {\n\t\t\tif (Array.isArray(task.outcomes) && task.outcomes.length > 0) {\n\t\t\t\treturn task.outcomes\n\t\t\t}\n\t\t\treturn ['done']\n\t\t},\n\n\t\t/**\n\t\t * The wire value of one outcome entry (a string, or `{id, label}`).\n\t\t *\n\t\t * @param {object|string} outcome The outcome entry.\n\t\t * @return {string} The outcome id.\n\t\t */\n\t\toutcomeId(outcome) {\n\t\t\tif (outcome && typeof outcome === 'object') {\n\t\t\t\treturn String(outcome.id ?? outcome.value ?? outcome.label ?? '')\n\t\t\t}\n\t\t\treturn String(outcome ?? '')\n\t\t},\n\n\t\t/**\n\t\t * The menu label for one complete entry.\n\t\t *\n\t\t * @param {object|string} outcome The outcome entry.\n\t\t * @return {string} The label.\n\t\t */\n\t\tcompleteLabel(outcome) {\n\t\t\tconst id = this.outcomeId(outcome)\n\t\t\tconst label = (outcome && typeof outcome === 'object' && outcome.label)\n\t\t\t\t? String(outcome.label)\n\t\t\t\t: id\n\t\t\tif (id === 'done') {\n\t\t\t\treturn this.tr('Complete')\n\t\t\t}\n\t\t\treturn this.tr('Complete: {outcome}').replace('{outcome}', label)\n\t\t},\n\n\t\t/**\n\t\t * Claim a pooled task.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tclaim(task) {\n\t\t\treturn this.postVerb(task, 'claim', {})\n\t\t},\n\n\t\t/**\n\t\t * Complete the viewer's own task with an outcome.\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @param {string} outcome The chosen outcome id.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tcomplete(task, outcome) {\n\t\t\treturn this.postVerb(task, 'complete', { outcome })\n\t\t},\n\n\t\t/**\n\t\t * POST one lifecycle verb and refetch either way: on success the row\n\t\t * moved, and on a refusal (a lost claim race, a revoked task) the\n\t\t * list is stale in exactly the way the refusal proves. The refusal\n\t\t * itself is toasted in the SERVER'S words (ADR-062 forbids leaking\n\t\t * the raw status line, and the endpoint's `error` string is written\n\t\t * for people).\n\t\t *\n\t\t * @param {object} task The task row.\n\t\t * @param {string} verb The lifecycle verb.\n\t\t * @param {object} body The verb body.\n\t\t * @return {Promise<void>} Resolves when the verb settled.\n\t\t */\n\t\tasync postVerb(task, verb, body) {\n\t\t\tconst uuid = String(task.uuid || '')\n\t\t\tif (uuid === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`${FLOW_TASKS_URL}/${uuid}/${verb}`), body)\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error?.response?.data?.error\n\t\t\t\tshowError(typeof message === 'string' && message !== ''\n\t\t\t\t\t? message\n\t\t\t\t\t: this.tr('The task action was refused'))\n\t\t\t}\n\t\t\tthis.refetch(true)\n\t\t},\n\n\t\t/**\n\t\t * Open the clicked task: the configured route with the uuid as `id`,\n\t\t * else the task's openregister deep link. A ctrl/cmd/shift or middle\n\t\t * click opens it in a new tab.\n\t\t *\n\t\t * @param {object} task The clicked task row.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(task, event) {\n\t\t\tconst uuid = String(task.uuid || '')\n\t\t\tif (uuid === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst rowRoute = this.content.rowRoute\n\t\t\tif (typeof rowRoute === 'string' && rowRoute !== '' && this.$router) {\n\t\t\t\topenRowTarget(event, { name: rowRoute, params: { id: uuid } }, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\topenRowTarget(event, taskDeepLink(uuid))\n\t\t},\n\n\t\t/**\n\t\t * Middle click on a task row: open it in a new tab.\n\t\t *\n\t\t * @param {object} task The clicked task row.\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\tonRowAuxClick(task, event) {\n\t\t\tif (isRowMiddleClick(event)) {\n\t\t\t\tthis.onRowClick(task, event)\n\t\t\t}\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {unknown} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n\n/**\n * Clamp a configured scope onto the endpoint's vocabulary.\n *\n * @param {unknown} raw The configured scope.\n * @return {string} One of assigned, pooled, watched, all (default assigned).\n */\nfunction normaliseScope(raw) {\n\treturn ['assigned', 'pooled', 'watched', 'all'].includes(raw) ? raw : 'assigned'\n}\n</script>\n\n<style scoped>\n.cn-tasks-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-tasks-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-tasks-widget__empty,\n.cn-tasks-widget__error,\n.cn-tasks-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-tasks-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-tasks-widget__count {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n.cn-tasks-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-tasks-widget__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n\tcursor: pointer;\n}\n\n.cn-tasks-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-tasks-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-tasks-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-tasks-widget__row:hover .cn-tasks-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-tasks-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-tasks-widget__due {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* Overdue: weight AND colour on top of the wording, which already says\n \"Overdue by N days\" — the signal survives monochrome. */\n.cn-tasks-widget__due--overdue {\n\tcolor: var(--color-error, #d91f2d);\n\tfont-weight: 600;\n}\n\n.cn-tasks-widget__actions {\n\tflex: 0 0 auto;\n}\n</style>\n"],"names":["_toDisplayString","_openBlock","_createElementBlock","_createVNode","_createElementVNode","_Fragment","_renderList","_normalizeClass","_createBlock","_createTextVNode"],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@ import ChartPie from 'vue-material-design-icons/ChartPie.vue';
|
|
|
6
6
|
import TableIcon from 'vue-material-design-icons/Table.vue';
|
|
7
7
|
import ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue';
|
|
8
8
|
import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
|
|
9
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
9
10
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
10
11
|
|
|
11
12
|
const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
|
|
@@ -171,8 +172,9 @@ var script = {
|
|
|
171
172
|
return `cn-analytics-card__badge--type-${type ?? 'unknown'}`
|
|
172
173
|
},
|
|
173
174
|
|
|
175
|
+
// A provider URL is used only when it is a safe scheme; else the app link.
|
|
174
176
|
reportUrl(report) {
|
|
175
|
-
if (report && report.url) {
|
|
177
|
+
if (report && report.url && safeHref(report.url) !== '#') {
|
|
176
178
|
return report.url
|
|
177
179
|
}
|
|
178
180
|
const id = this.reportKey(report);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnAnalyticsCard.vue2.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsCard.vue"],"sourcesContent":["<!--\n CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n integration.\n\n Replaces the generic CnIntegrationCard for the `analytics` leaf.\n Branches on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N reports\" + the\n most-recently-modified report's name + type badge.\n - detail-page : a compact list of linked reports\n (type icon + name + type badge, up to COMPACT_LIMIT) with\n \"view all\" trail-off.\n - single-entity : a chip with type icon + name for\n reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnAnalyticsTab; for `single-entity` the optional `value` prop\n addresses a single report by id.\n\n This is the widget LaunchPad adopts to consume the analytics leaf. v1\n deliberately does NOT fetch chart data inline per row — that would be\n N HTTP calls per dashboard. The type-coded icon + badge surface the\n report kind cheaply. Phase D / follow-up can wire one inline chart\n via the existing analytics_facts endpoint if the perf budget allows.\n\n See ADR-019 (registry mechanism), AD-19 (surface fallback), ADR-022\n (consumption principle). Refs openregister#1321.\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-analytics-card__chip\" :title=\"reportTitle(entity)\">\n\t\t\t\t<component :is=\"reportIcon(entity)\" :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"reportUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ reportTitle(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-analytics-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-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-analytics-card__headline\">\n\t\t\t\t<div class=\"cn-analytics-card__headline-line\">\n\t\t\t\t\t<strong>{{ reportCountLabel }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-analytics-card__headline-recent\">\n\t\t\t\t\t<component :is=\"reportIcon(mostRecent)\" :size=\"16\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"reportUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-analytics-card__headline-name\">\n\t\t\t\t\t\t{{ reportTitle(mostRecent) }}\n\t\t\t\t\t</a>\n\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(mostRecent)\">\n\t\t\t\t\t\t{{ reportTypeLabel(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"reports.length > 1\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</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-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-analytics-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in displayedReports\"\n\t\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\t\tclass=\"cn-analytics-card__row\">\n\t\t\t\t\t<div class=\"cn-analytics-card__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"16\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-analytics-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclass=\"cn-analytics-card__title\">{{ reportTitle(report) }}</a>\n\t\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(report)\">\n\t\t\t\t\t\t\t{{ reportTypeLabel(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"reports.length > COMPACT_LIMIT\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\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 ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.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\n\n/**\n * CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n * integration.\n *\n * Renders Analytics-aware metadata (report count, type icon, modified\n * date) across all four AD-19 surfaces. See the file-level docblock\n * for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnAnalyticsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ChartBar, ChartLine, ChartPie, TableIcon, ViewDashboard },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\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 (report id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Analytics') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChartBar },\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 reports linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Analytics') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\treports: [],\n\t\t\tentity: 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\tdisplayedReports() {\n\t\t\treturn this.reports.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\treportCountLabel() {\n\t\t\tconst total = this.reports.length\n\t\t\treturn n('nextcloud-vue', '{count} report', '{count} reports', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.reports.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Most recently modified first; falls back to most recently\n\t\t\t// created when no modified timestamp exists.\n\t\t\tconst sorted = [...this.reports].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.modifiedAt || a.data?.modifiedAt || a.createdAt || a.data?.createdAt || 0) || 0\n\t\t\t\tconst tb = Date.parse(b.modifiedAt || b.data?.modifiedAt || b.createdAt || b.data?.createdAt || 0) || 0\n\t\t\t\treturn tb - ta\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\treportKey(report) {\n\t\t\treturn (report && (report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Remote')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'Internal')\n\t\t\t\tcase 5: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'External')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 3: return TableIcon\n\t\t\t\tcase 4: return ViewDashboard\n\t\t\t\tcase 2: return ChartLine\n\t\t\t\tcase 5: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\tbadgeClass(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\treturn `cn-analytics-card__badge--type-${type ?? 'unknown'}`\n\t\t},\n\n\t\treportUrl(report) {\n\t\t\tif (report && report.url) {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\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.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\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('[CnAnalyticsCard] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\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.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = 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.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = 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('[CnAnalyticsCard] failed to fetch single report', err)\n\t\t\t\tthis.entity = 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-analytics-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-analytics-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-analytics-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-analytics-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}\n\n.cn-analytics-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-analytics-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\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.cn-analytics-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-analytics-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-analytics-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-analytics-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-analytics-card__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\twidth: 18px;\n\theight: 18px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-analytics-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-analytics-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\tfont-size: 13px;\n}\n\na.cn-analytics-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n\tflex-shrink: 0;\n}\n\n.cn-analytics-card__badge--type-3 { background: var(--color-primary-element-light, var(--color-primary-light)); color: var(--color-primary-element-text); }\n\n.cn-analytics-card__badge--type-4 { background: var(--color-success, #46ba61); color: var(--color-main-background); }\n\n.cn-analytics-card__badge--type-2 { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-analytics-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-analytics-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-analytics-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":";;;;;;;;;;AA8HA,MAAM,cAAe,GAAE,CAAC,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAA;AACzF,MAAM,gBAAgB,EAAA;;AAEtB;;;AAGC;;;AAGA;;AAED,aAAe;CACd,IAAI,EAAE,iBAAiB;;AAEvB,CAAA,UAAU,EAAE,gBAAEA,UAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe;;AAEpG,CAAA,KAAK,EAAE;AACN;EACA,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAU,EAAG;AACrD;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;AAC1C;AACA,EAAA,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;AACxC;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;AAE1C,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,aAAa;AACtB,GAAA,SAAS,EAAE,CAAC,CAAC,KAAK,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;GAC5C;;AAED;AACA,EAAA,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI;;AAE9C,EAAA,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,WAAW,CAAA,EAAG;AACvE;AACA,EAAA,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAAU;AAC/C;EACA,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAuB,EAAG;AAC5D;AACA,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM;AAC7C;AACA,EAAA,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,uBAAuB,GAAG;AACxF;EACA,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,wCAAwC,CAAA,EAAG;;AAE/G,EAAA,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,uBAAuB,GAAG;AAC1F;AACA,EAAA,eAAe,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,6BAA6B;EACvE;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,aAAa;GACb,OAAO,EAAE,EAAE;GACX,MAAM,EAAE,IAAI;GACZ,OAAO,EAAE,KAAK;GACd,QAAQ,EAAE,EAAE;AACb,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;EACT,SAAS,GAAG;AACX,GAAA,OAAO,IAAI,CAAC,KAAM,IAAG,IAAI,CAAC,aAAY;GACtC;;EAED,QAAQ,GAAG;AACV,GAAA,OAAO,IAAI,CAAC,IAAG;GACf;;AAED,EAAA,gBAAgB,GAAG;AAClB,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAA;GAC1C;;AAED,EAAA,gBAAgB,GAAG;GAClB,MAAM,KAAI,GAAI,IAAI,CAAC,OAAO,CAAC,OAAK;AAChC,GAAA,OAAOC,eAAC,CAAC,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,CAAA;GACtF;;EAED,UAAU,GAAG;GACZ,IAAI,IAAI,CAAC,OAAO,CAAC,MAAK,KAAM,CAAC,EAAE;AAC9B,IAAA,OAAO,IAAG;AACX,IAAA;;AAEA;AACA,GAAA,MAAM,MAAO,GAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK;AAC/C,IAAA,MAAM,EAAC,GAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAW,IAAG,CAAC,CAAC,IAAI,EAAE,UAAW,IAAG,CAAC,CAAC,SAAQ,IAAK,CAAC,CAAC,IAAI,EAAE,SAAU,IAAG,CAAC,CAAA,IAAK,EAAA;AACtG,IAAA,MAAM,EAAC,GAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAW,IAAG,CAAC,CAAC,IAAI,EAAE,UAAW,IAAG,CAAC,CAAC,SAAQ,IAAK,CAAC,CAAC,IAAI,EAAE,SAAU,IAAG,CAAC,CAAA,IAAK,EAAA;IACtG,OAAO,EAAC,GAAI,EAAC;IACb,EAAA;AACD,GAAA,OAAO,MAAM,CAAC,CAAC,CAAA;GACf;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG;GACtC,IAAI,CAAC,KAAK,GAAC;AACZ,GAAA,EAAG;;AAEH,EAAA,OAAO,GAAG;GACT,IAAI,CAAC,KAAK,GAAC;GACX;;AAED,EAAA,KAAK,GAAG;AACP,GAAA,IAAI,IAAI,CAAC,YAAY,eAAe,EAAE;AACrC,IAAA,IAAI,CAAC,WAAW,GAAC;AAClB,IAAA;GACA;EACD;;AAED,CAAA,OAAO,EAAE;AACR,EAAA,OAAO,GAAG;GACT,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,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,CAAA;GACnH;;AAED,EAAA,SAAS,CAAC,MAAM,EAAE;GACjB,OAAO,CAAC,MAAO,KAAI,MAAM,CAAC,EAAG,IAAG,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,EAAC;GACtD;;AAED,EAAA,WAAW,CAAC,MAAM,EAAE;AACnB,GAAA,MAAM,GAAI,GAAE,CAAC,MAAK,KAAM,MAAM,CAAC,KAAI,IAAK,MAAM,CAAC,IAAG,IAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAC;GAC/E,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC,IAAI,EAAC,IAAKD,SAAC,CAAC,eAAe,EAAE,iBAAiB,CAAA;GACtG;;AAED,EAAA,UAAU,CAAC,MAAM,EAAE;GAClB,MAAM,GAAE,GAAI,MAAM,EAAE,UAAW,IAAG,MAAM,EAAE,IAAG,IAAK,MAAM,EAAE,IAAI,EAAE,KAAG;AACnE,GAAA,IAAI,GAAE,KAAM,IAAK,IAAG,QAAQ,SAAU,IAAG,GAAI,KAAI,EAAE,EAAE;AACpD,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,GAAI,GAAE,MAAM,CAAC,GAAG,EAAA;AACtB,GAAA,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAA,GAAI,GAAE,GAAI,IAAG;GACvC;;EAED,eAAe,CAAC,MAAM,EAAE;AACvB,GAAA,MAAM,IAAG,GAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAA;GACnC,QAAQ,IAAI;AACX,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA;AACzC,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAA;AAC1C,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA;AACxC,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;AAC5C,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;AAC5C,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;AAC5C,IAAA,SAAS,OAAOA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAA;AAC5C,IAAA;GACA;;AAED,EAAA,UAAU,CAAC,MAAM,EAAE;AAClB,GAAA,MAAM,IAAG,GAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAA;GACnC,QAAQ,IAAI;AACX,IAAA,KAAK,CAAC,EAAE,OAAO,SAAQ;IACvB,KAAK,CAAC,EAAE,OAAO,aAAY;AAC3B,IAAA,KAAK,CAAC,EAAE,OAAO,SAAQ;AACvB,IAAA,KAAK,CAAC,EAAE,OAAO,QAAO;AACtB,IAAA,SAAS,OAAO,QAAO;AACxB,IAAA;GACA;;AAED,EAAA,UAAU,CAAC,MAAM,EAAE;AAClB,GAAA,MAAM,IAAG,GAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAA;GACnC,OAAO,CAAC,+BAA+B,EAAE,IAAK,IAAG,SAAS,CAAC,CAAA;GAC3D;;AAED,EAAA,SAAS,CAAC,MAAM,EAAE;GACjB,IAAI,UAAU,MAAM,CAAC,GAAG,EAAE;AACzB,IAAA,OAAO,MAAM,CAAC,GAAE;AACjB,IAAA;GACA,MAAM,EAAG,GAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAA;AAChC,GAAA,OAAO,KAAK,CAAC,8BAA8B,EAAE,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAE,GAAE,IAAI,CAAC,eAAc;GAC3F;;AAED,EAAA,KAAK,GAAG;AACP,GAAA,IAAI,IAAI,CAAC,YAAY,eAAe,EAAE;AACrC,IAAA,IAAI,CAAC,WAAW,GAAC;IACjB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,SAAS,GAAC;GACf;;AAED,EAAA,MAAM,SAAS,GAAG;GACjB,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;AAClB,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,IAAI,CAAC,OAAM,GAAI,IAAI,CAAC,OAAQ,IAAG,IAAI,CAAC,KAAI,KAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAA,GAAI,IAAG,GAAI,EAAE,CAAE,IAAG,GAAC;AACpF,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,OAAQ,GAAE,GAAC;AAChB,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;AACN,KAAA,IAAI,CAAC,OAAQ,GAAE,GAAC;AACjB,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,GAAG,EAAA;AAC9D,IAAA,IAAI,CAAC,OAAQ,GAAE,GAAC;AACjB,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED,EAAA,MAAM,WAAW,GAAG;GACnB,IAAI,IAAI,CAAC,UAAU,EAAG,IAAG,IAAI,CAAC,KAAM,KAAI,SAAU,IAAG,IAAI,CAAC,KAAI,KAAM,QAAQ,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,MAAK,IAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;AAC7H,IAAA,IAAI,CAAC,MAAK,GAAI,KAAG;IACjB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,QAAO,GAAI,GAAC;AACjB,GAAA,IAAI;IACH,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,EAAC,EAAG,EAAA;AAC1H,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;AAChB,KAAA,IAAI,CAAC,MAAK,GAAI,MAAM,QAAQ,CAAC,IAAI,GAAC;AACnC,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AACjB,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;AACN,KAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AAClB,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;AACA,IAAA,OAAO,CAAC,KAAK,CAAC,iDAAiD,EAAE,GAAG,EAAA;AACpE,IAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AAClB,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;EACD;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"CnAnalyticsCard.vue2.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsCard.vue"],"sourcesContent":["<!--\n CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n integration.\n\n Replaces the generic CnIntegrationCard for the `analytics` leaf.\n Branches on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N reports\" + the\n most-recently-modified report's name + type badge.\n - detail-page : a compact list of linked reports\n (type icon + name + type badge, up to COMPACT_LIMIT) with\n \"view all\" trail-off.\n - single-entity : a chip with type icon + name for\n reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnAnalyticsTab; for `single-entity` the optional `value` prop\n addresses a single report by id.\n\n This is the widget LaunchPad adopts to consume the analytics leaf. v1\n deliberately does NOT fetch chart data inline per row — that would be\n N HTTP calls per dashboard. The type-coded icon + badge surface the\n report kind cheaply. Phase D / follow-up can wire one inline chart\n via the existing analytics_facts endpoint if the perf budget allows.\n\n See ADR-019 (registry mechanism), AD-19 (surface fallback), ADR-022\n (consumption principle). Refs openregister#1321.\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-analytics-card__chip\" :title=\"reportTitle(entity)\">\n\t\t\t\t<component :is=\"reportIcon(entity)\" :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"reportUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ reportTitle(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-analytics-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-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-analytics-card__headline\">\n\t\t\t\t<div class=\"cn-analytics-card__headline-line\">\n\t\t\t\t\t<strong>{{ reportCountLabel }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-analytics-card__headline-recent\">\n\t\t\t\t\t<component :is=\"reportIcon(mostRecent)\" :size=\"16\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"reportUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-analytics-card__headline-name\">\n\t\t\t\t\t\t{{ reportTitle(mostRecent) }}\n\t\t\t\t\t</a>\n\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(mostRecent)\">\n\t\t\t\t\t\t{{ reportTypeLabel(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"reports.length > 1\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</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-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-analytics-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in displayedReports\"\n\t\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\t\tclass=\"cn-analytics-card__row\">\n\t\t\t\t\t<div class=\"cn-analytics-card__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"16\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-analytics-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclass=\"cn-analytics-card__title\">{{ reportTitle(report) }}</a>\n\t\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(report)\">\n\t\t\t\t\t\t\t{{ reportTypeLabel(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"reports.length > COMPACT_LIMIT\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\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 ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.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\n\n/**\n * CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n * integration.\n *\n * Renders Analytics-aware metadata (report count, type icon, modified\n * date) across all four AD-19 surfaces. See the file-level docblock\n * for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnAnalyticsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ChartBar, ChartLine, ChartPie, TableIcon, ViewDashboard },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\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 (report id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Analytics') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChartBar },\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 reports linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Analytics') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\treports: [],\n\t\t\tentity: 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\tdisplayedReports() {\n\t\t\treturn this.reports.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\treportCountLabel() {\n\t\t\tconst total = this.reports.length\n\t\t\treturn n('nextcloud-vue', '{count} report', '{count} reports', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.reports.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Most recently modified first; falls back to most recently\n\t\t\t// created when no modified timestamp exists.\n\t\t\tconst sorted = [...this.reports].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.modifiedAt || a.data?.modifiedAt || a.createdAt || a.data?.createdAt || 0) || 0\n\t\t\t\tconst tb = Date.parse(b.modifiedAt || b.data?.modifiedAt || b.createdAt || b.data?.createdAt || 0) || 0\n\t\t\t\treturn tb - ta\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\treportKey(report) {\n\t\t\treturn (report && (report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Remote')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'Internal')\n\t\t\t\tcase 5: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'External')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 3: return TableIcon\n\t\t\t\tcase 4: return ViewDashboard\n\t\t\t\tcase 2: return ChartLine\n\t\t\t\tcase 5: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\tbadgeClass(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\treturn `cn-analytics-card__badge--type-${type ?? 'unknown'}`\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\treportUrl(report) {\n\t\t\tif (report && report.url && safeHref(report.url) !== '#') {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\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.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\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('[CnAnalyticsCard] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\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.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = 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.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = 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('[CnAnalyticsCard] failed to fetch single report', err)\n\t\t\t\tthis.entity = 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-analytics-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-analytics-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-analytics-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-analytics-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}\n\n.cn-analytics-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-analytics-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\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.cn-analytics-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-analytics-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-analytics-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-analytics-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-analytics-card__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\twidth: 18px;\n\theight: 18px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-analytics-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-analytics-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\tfont-size: 13px;\n}\n\na.cn-analytics-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n\tflex-shrink: 0;\n}\n\n.cn-analytics-card__badge--type-3 { background: var(--color-primary-element-light, var(--color-primary-light)); color: var(--color-primary-element-text); }\n\n.cn-analytics-card__badge--type-4 { background: var(--color-success, #46ba61); color: var(--color-main-background); }\n\n.cn-analytics-card__badge--type-2 { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-analytics-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-analytics-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-analytics-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnAnalyticsCard.vue3.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsCard.vue?vue&type=template&id=4000c99f&lang.js"],"sourcesContent":["<!--\n CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n integration.\n\n Replaces the generic CnIntegrationCard for the `analytics` leaf.\n Branches on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N reports\" + the\n most-recently-modified report's name + type badge.\n - detail-page : a compact list of linked reports\n (type icon + name + type badge, up to COMPACT_LIMIT) with\n \"view all\" trail-off.\n - single-entity : a chip with type icon + name for\n reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnAnalyticsTab; for `single-entity` the optional `value` prop\n addresses a single report by id.\n\n This is the widget LaunchPad adopts to consume the analytics leaf. v1\n deliberately does NOT fetch chart data inline per row — that would be\n N HTTP calls per dashboard. The type-coded icon + badge surface the\n report kind cheaply. Phase D / follow-up can wire one inline chart\n via the existing analytics_facts endpoint if the perf budget allows.\n\n See ADR-019 (registry mechanism), AD-19 (surface fallback), ADR-022\n (consumption principle). Refs openregister#1321.\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-analytics-card__chip\" :title=\"reportTitle(entity)\">\n\t\t\t\t<component :is=\"reportIcon(entity)\" :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"reportUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ reportTitle(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-analytics-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-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-analytics-card__headline\">\n\t\t\t\t<div class=\"cn-analytics-card__headline-line\">\n\t\t\t\t\t<strong>{{ reportCountLabel }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-analytics-card__headline-recent\">\n\t\t\t\t\t<component :is=\"reportIcon(mostRecent)\" :size=\"16\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"reportUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-analytics-card__headline-name\">\n\t\t\t\t\t\t{{ reportTitle(mostRecent) }}\n\t\t\t\t\t</a>\n\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(mostRecent)\">\n\t\t\t\t\t\t{{ reportTypeLabel(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"reports.length > 1\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</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-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-analytics-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in displayedReports\"\n\t\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\t\tclass=\"cn-analytics-card__row\">\n\t\t\t\t\t<div class=\"cn-analytics-card__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"16\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-analytics-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclass=\"cn-analytics-card__title\">{{ reportTitle(report) }}</a>\n\t\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(report)\">\n\t\t\t\t\t\t\t{{ reportTypeLabel(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"reports.length > COMPACT_LIMIT\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\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 ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.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\n\n/**\n * CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n * integration.\n *\n * Renders Analytics-aware metadata (report count, type icon, modified\n * date) across all four AD-19 surfaces. See the file-level docblock\n * for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnAnalyticsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ChartBar, ChartLine, ChartPie, TableIcon, ViewDashboard },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\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 (report id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Analytics') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChartBar },\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 reports linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Analytics') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\treports: [],\n\t\t\tentity: 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\tdisplayedReports() {\n\t\t\treturn this.reports.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\treportCountLabel() {\n\t\t\tconst total = this.reports.length\n\t\t\treturn n('nextcloud-vue', '{count} report', '{count} reports', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.reports.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Most recently modified first; falls back to most recently\n\t\t\t// created when no modified timestamp exists.\n\t\t\tconst sorted = [...this.reports].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.modifiedAt || a.data?.modifiedAt || a.createdAt || a.data?.createdAt || 0) || 0\n\t\t\t\tconst tb = Date.parse(b.modifiedAt || b.data?.modifiedAt || b.createdAt || b.data?.createdAt || 0) || 0\n\t\t\t\treturn tb - ta\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\treportKey(report) {\n\t\t\treturn (report && (report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Remote')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'Internal')\n\t\t\t\tcase 5: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'External')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 3: return TableIcon\n\t\t\t\tcase 4: return ViewDashboard\n\t\t\t\tcase 2: return ChartLine\n\t\t\t\tcase 5: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\tbadgeClass(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\treturn `cn-analytics-card__badge--type-${type ?? 'unknown'}`\n\t\t},\n\n\t\treportUrl(report) {\n\t\t\tif (report && report.url) {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\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.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\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('[CnAnalyticsCard] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\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.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = 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.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = 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('[CnAnalyticsCard] failed to fetch single report', err)\n\t\t\t\tthis.entity = 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-analytics-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-analytics-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-analytics-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-analytics-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}\n\n.cn-analytics-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-analytics-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\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.cn-analytics-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-analytics-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-analytics-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-analytics-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-analytics-card__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\twidth: 18px;\n\theight: 18px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-analytics-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-analytics-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\tfont-size: 13px;\n}\n\na.cn-analytics-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n\tflex-shrink: 0;\n}\n\n.cn-analytics-card__badge--type-3 { background: var(--color-primary-element-light, var(--color-primary-light)); color: var(--color-primary-element-text); }\n\n.cn-analytics-card__badge--type-4 { background: var(--color-success, #46ba61); color: var(--color-main-background); }\n\n.cn-analytics-card__badge--type-2 { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-analytics-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-analytics-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-analytics-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_openBlock","_resolveDynamicComponent","_toDisplayString","_createElementVNode","_normalizeClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnAnalyticsCard.vue3.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsCard.vue?vue&type=template&id=4000c99f&lang.js"],"sourcesContent":["<!--\n CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n integration.\n\n Replaces the generic CnIntegrationCard for the `analytics` leaf.\n Branches on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N reports\" + the\n most-recently-modified report's name + type badge.\n - detail-page : a compact list of linked reports\n (type icon + name + type badge, up to COMPACT_LIMIT) with\n \"view all\" trail-off.\n - single-entity : a chip with type icon + name for\n reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnAnalyticsTab; for `single-entity` the optional `value` prop\n addresses a single report by id.\n\n This is the widget LaunchPad adopts to consume the analytics leaf. v1\n deliberately does NOT fetch chart data inline per row — that would be\n N HTTP calls per dashboard. The type-coded icon + badge surface the\n report kind cheaply. Phase D / follow-up can wire one inline chart\n via the existing analytics_facts endpoint if the perf budget allows.\n\n See ADR-019 (registry mechanism), AD-19 (surface fallback), ADR-022\n (consumption principle). Refs openregister#1321.\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-analytics-card__chip\" :title=\"reportTitle(entity)\">\n\t\t\t\t<component :is=\"reportIcon(entity)\" :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"reportUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ reportTitle(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-analytics-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-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-analytics-card__headline\">\n\t\t\t\t<div class=\"cn-analytics-card__headline-line\">\n\t\t\t\t\t<strong>{{ reportCountLabel }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-analytics-card__headline-recent\">\n\t\t\t\t\t<component :is=\"reportIcon(mostRecent)\" :size=\"16\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"reportUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-analytics-card__headline-name\">\n\t\t\t\t\t\t{{ reportTitle(mostRecent) }}\n\t\t\t\t\t</a>\n\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(mostRecent)\">\n\t\t\t\t\t\t{{ reportTypeLabel(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"reports.length > 1\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</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-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-analytics-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in displayedReports\"\n\t\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\t\tclass=\"cn-analytics-card__row\">\n\t\t\t\t\t<div class=\"cn-analytics-card__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"16\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-analytics-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclass=\"cn-analytics-card__title\">{{ reportTitle(report) }}</a>\n\t\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(report)\">\n\t\t\t\t\t\t\t{{ reportTypeLabel(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"reports.length > COMPACT_LIMIT\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\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 ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.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\n\n/**\n * CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n * integration.\n *\n * Renders Analytics-aware metadata (report count, type icon, modified\n * date) across all four AD-19 surfaces. See the file-level docblock\n * for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnAnalyticsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ChartBar, ChartLine, ChartPie, TableIcon, ViewDashboard },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\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 (report id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Analytics') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChartBar },\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 reports linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Analytics') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\treports: [],\n\t\t\tentity: 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\tdisplayedReports() {\n\t\t\treturn this.reports.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\treportCountLabel() {\n\t\t\tconst total = this.reports.length\n\t\t\treturn n('nextcloud-vue', '{count} report', '{count} reports', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.reports.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Most recently modified first; falls back to most recently\n\t\t\t// created when no modified timestamp exists.\n\t\t\tconst sorted = [...this.reports].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.modifiedAt || a.data?.modifiedAt || a.createdAt || a.data?.createdAt || 0) || 0\n\t\t\t\tconst tb = Date.parse(b.modifiedAt || b.data?.modifiedAt || b.createdAt || b.data?.createdAt || 0) || 0\n\t\t\t\treturn tb - ta\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\treportKey(report) {\n\t\t\treturn (report && (report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Remote')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'Internal')\n\t\t\t\tcase 5: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'External')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 3: return TableIcon\n\t\t\t\tcase 4: return ViewDashboard\n\t\t\t\tcase 2: return ChartLine\n\t\t\t\tcase 5: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\tbadgeClass(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\treturn `cn-analytics-card__badge--type-${type ?? 'unknown'}`\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\treportUrl(report) {\n\t\t\tif (report && report.url && safeHref(report.url) !== '#') {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\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.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\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('[CnAnalyticsCard] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\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.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = 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.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = 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('[CnAnalyticsCard] failed to fetch single report', err)\n\t\t\t\tthis.entity = 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-analytics-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-analytics-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-analytics-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-analytics-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}\n\n.cn-analytics-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-analytics-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\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.cn-analytics-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-analytics-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-analytics-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-analytics-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-analytics-card__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\twidth: 18px;\n\theight: 18px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-analytics-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-analytics-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\tfont-size: 13px;\n}\n\na.cn-analytics-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n\tflex-shrink: 0;\n}\n\n.cn-analytics-card__badge--type-3 { background: var(--color-primary-element-light, var(--color-primary-light)); color: var(--color-primary-element-text); }\n\n.cn-analytics-card__badge--type-4 { background: var(--color-success, #46ba61); color: var(--color-main-background); }\n\n.cn-analytics-card__badge--type-2 { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-analytics-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-analytics-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-analytics-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_openBlock","_resolveDynamicComponent","_toDisplayString","_createElementVNode","_normalizeClass"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ import __sfc_main$2 from '../../../components/CnAnalyticsReportCreate/CnAnalytic
|
|
|
15
15
|
import __sfc_main$1 from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue.js';
|
|
16
16
|
import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
|
|
17
17
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
18
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* CnAnalyticsTab — bespoke dataset list for the `analytics` integration.
|
|
@@ -244,8 +245,9 @@ var script = {
|
|
|
244
245
|
return translate('nextcloud-vue', 'Latest value')
|
|
245
246
|
},
|
|
246
247
|
|
|
248
|
+
// A provider URL is used only when it is a safe scheme; else the app link.
|
|
247
249
|
reportUrl(report) {
|
|
248
|
-
if (report && report.url) {
|
|
250
|
+
if (report && report.url && safeHref(report.url) !== '#') {
|
|
249
251
|
return report.url
|
|
250
252
|
}
|
|
251
253
|
const id = this.reportKey(report);
|