@conduction/nextcloud-vue 2.56.0-beta.3 → 2.56.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +21 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +49 -5
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +2 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +2 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +78 -7
- package/dist/esm/utils/actionLink.js +5 -0
- package/dist/esm/utils/actionLink.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +16 -1
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/linkNavigation.js +6 -3
- package/dist/esm/utils/linkNavigation.js.map +1 -1
- package/dist/esm/utils/rowAuxClick.js +30 -5
- package/dist/esm/utils/rowAuxClick.js.map +1 -1
- package/dist/esm/utils/safeHref.js +18 -3
- package/dist/esm/utils/safeHref.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +885 -393
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +78 -7
- package/l10n/en.json +2 -0
- package/l10n/nl.json +2 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
- package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +31 -2
- package/src/components/CnBoardView/CnBoardView.vue +13 -5
- package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
- package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +131 -17
- package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
- package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
- package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
- package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
- package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
- package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
- package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
- package/src/components/CnObjectList/CnObjectList.vue +19 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
- package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
- package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
- package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
- package/src/components/CnRowActions/CnRowActions.vue +7 -2
- package/src/components/CnSetupWizard/CnSetupWizard.vue +57 -6
- package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
- package/src/components/CnTabs/CnTabs.vue +7 -3
- package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
- package/src/css/table.css +26 -0
- package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
- package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
- package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
- package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
- package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
- package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
- package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
- package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
- package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
- package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
- package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
- package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
- package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
- package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
- package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
- package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
- package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
- package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
- package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
- package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
- package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
- package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
- package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
- package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
- package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
- package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
- package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
- package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
- package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
- package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
- package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
- package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
- package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
- package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
- package/src/schemas/app-manifest-v2.schema.json +2 -1
- package/src/utils/actionLink.js +5 -0
- package/src/utils/actionsDispatcher.js +15 -0
- package/src/utils/linkNavigation.js +6 -3
- package/src/utils/rowAuxClick.js +29 -4
- package/src/utils/safeHref.js +18 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -9,6 +9,7 @@ import Plus from 'vue-material-design-icons/Plus.vue';
|
|
|
9
9
|
import __sfc_main$1 from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue.js';
|
|
10
10
|
import __sfc_main from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue.js';
|
|
11
11
|
import { stripMarker } from '../../utils/marker.js';
|
|
12
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
12
13
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -106,8 +107,9 @@ var script = {
|
|
|
106
107
|
return stripMarker(raw) || String(this.roomKey(room))
|
|
107
108
|
},
|
|
108
109
|
|
|
110
|
+
// A provider URL is used only when it is a safe scheme; else the app link.
|
|
109
111
|
roomUrl(room) {
|
|
110
|
-
if (room.url) {
|
|
112
|
+
if (room.url && safeHref(room.url) !== '#') {
|
|
111
113
|
return room.url
|
|
112
114
|
}
|
|
113
115
|
const token = room.roomToken ?? room.token ?? room.id ?? '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnTalkTab.vue2.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkTab.vue"],"sourcesContent":["<!--\n CnTalkTab — bespoke sidebar tab for the `talk` integration.\n\n Replaces the generic CnIntegrationTab for the `talk` leaf: mirrors the\n real NC Talk conversation list — each row is an NcListItem with the\n conversation avatar (NcAvatar keyed on the room name), a bold\n conversation name, a last-message preview as the subname, a relative\n timestamp (NcDateTime) in the details slot, and an unread-count bubble\n (NcCounterBubble) when the room has unread messages. Per-row unlink and\n \"Open in Talk\" live in the row action menu. Tier-2: adds explicit\n \"Link existing room\" + \"Create new room\" actions powered by\n CnTalkRoomPicker / CnTalkRoomCreate; unlink does NOT destroy the\n underlying Talk room.\n\n Talks to the OpenRegister Tier-2 endpoint\n GET /api/objects/{register}/{schema}/{objectId}/talk\n POST /api/objects/{register}/{schema}/{objectId}/talk — link\n POST /api/objects/{register}/{schema}/{objectId}/talk/new — create\n DELETE /api/objects/{register}/{schema}/{objectId}/talk/{roomToken} — unlink\n served by OCA\\OpenRegister\\Controller\\TalkLinksController.\n\n Surface behaviour:\n - Empty state with \"Open Talk\" + Link/Create CTAs when no linked rooms.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-talk-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-talk-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<!-- Tier-2 action bar — always visible when not in error/loading -->\n\t\t<div v-if=\"!loading && !error\" class=\"cn-talk-tab__actions\">\n\t\t\t<NcButton @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing room') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new room') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-talk-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-sidebar-tab__empty cn-talk-tab__empty\">\n\t\t\t<ChatOutline :size=\"32\" class=\"cn-talk-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"talkAppUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChatOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openTalkLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-talk-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"room in rooms\"\n\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\tclass=\"cn-talk-tab__row\"\n\t\t\t\t:class=\"{ 'cn-talk-tab__row--unread': hasUnread(room) }\"\n\t\t\t\t:name=\"roomTitle(room)\"\n\t\t\t\t:bold=\"hasUnread(room)\"\n\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t:size=\"40\"\n\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\thideStatus />\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-talk-tab__preview\">{{ roomSubname(room) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"roomTimestamp(room)\" #details>\n\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\tclass=\"cn-talk-tab__time\"\n\t\t\t\t\t\t:timestamp=\"roomTimestamp(room)\"\n\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"hasUnread(room)\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-talk-tab__badge\"\n\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Talk') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkRoom(room)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink from object (the room stays in Talk)') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<!-- Picker + Create modals (mounted lazily) -->\n\t\t<CnTalkRoomPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onPickerLink\" />\n\n\t\t<CnTalkRoomCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreateSubmit\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcAvatar, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnTalkRoomCreate from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue'\nimport CnTalkRoomPicker from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnTalkTab — bespoke conversation list for the `talk` integration.\n *\n * Renders rows pulled from the OR Tier-2 link table as NcListItem rows\n * styled to mirror NC Talk: conversation avatar, bold name,\n * last-message preview, relative timestamp, and an unread counter\n * bubble. Tier-2: supports link / create / unlink via picker + create\n * dialogs.\n */\nexport default {\n\tname: 'CnTalkTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tChatOutline,\n\t\tClose,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnTalkRoomPicker,\n\t\tCnTalkRoomCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (matches the PHP-side provider id, always `talk`). */\n\t\tintegrationId: { type: String, default: 'talk' },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Empty state shown when no conversation is linked yet. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No conversations linked yet') },\n\t\t/** Label for the link that opens the conversation in Talk. */\n\t\topenTalkLabel: { type: String, default: () => t('nextcloud-vue', 'Open Talk') },\n\t\t/** Message shown when the Talk app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** Base URL of the Talk app, used to build a conversation link. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchRooms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.roomToken ?? room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.roomName ?? room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\n\t\t},\n\n\t\troomUrl(room) {\n\t\t\tif (room.url) {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\t/**\n\t\t * Last-message preview line. Falls back to a participant-count\n\t\t * summary so the subname is never empty in NC-Talk style.\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {string} Preview or participant summary.\n\t\t */\n\t\troomSubname(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\tif (preview) {\n\t\t\t\treturn preview\n\t\t\t}\n\t\t\tconst participants = Number(room.participantCount ?? room.numParticipants ?? 0)\n\t\t\tif (Number.isFinite(participants) && participants > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} participants', { n: participants })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tlet msg = room.lastMessage\n\t\t\t// The provider may hand us the message as a JSON string; parse it\n\t\t\t// so we never render a raw `{\"message\":...}` blob in the preview.\n\t\t\tif (typeof msg === 'string') {\n\t\t\t\tconst trimmed = msg.trim()\n\t\t\t\tif (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmsg = JSON.parse(trimmed)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn this.cleanPreview(trimmed)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn this.cleanPreview(msg)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\t// Talk system messages carry a snake_case key (e.g. `user_added`)\n\t\t\t\t// in `systemMessage`/`message` plus rich `parameters`. Humanise\n\t\t\t\t// them instead of dumping the placeholder/JSON.\n\t\t\t\tconst isSystem = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t\t|| msg.messageType === 'system'\n\t\t\t\tconst raw = msg.message ?? msg.text ?? ''\n\t\t\t\tif (isSystem) {\n\t\t\t\t\treturn this.humaniseSystemMessage(msg)\n\t\t\t\t}\n\t\t\t\treturn this.cleanPreview(typeof raw === 'string' ? raw : '')\n\t\t\t}\n\t\t\treturn this.cleanPreview(room.lastMessageText ?? room.preview ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Turn a Talk system message into readable text. Substitutes rich\n\t\t * `{placeholder}` parameters with their display name; falls back to a\n\t\t * title-cased version of the snake_case key (e.g. `user_added` →\n\t\t * \"User added\").\n\t\t *\n\t\t * @param {object} msg The last-message object.\n\t\t * @return {string} Human-readable line.\n\t\t */\n\t\thumaniseSystemMessage(msg) {\n\t\t\tconst key = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t? msg.systemMessage\n\t\t\t\t: (typeof msg.message === 'string' ? msg.message : '')\n\t\t\tconst template = (typeof msg.message === 'string' && msg.message.indexOf('{') !== -1)\n\t\t\t\t? msg.message\n\t\t\t\t: ''\n\t\t\tconst params = msg.messageParameters || msg.parameters || {}\n\t\t\tif (template !== '' && params && typeof params === 'object') {\n\t\t\t\treturn template.replace(/\\{(\\w+)\\}/g, (whole, name) => {\n\t\t\t\t\tconst p = params[name]\n\t\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\t\treturn p.name || p.displayName || p.id || name\n\t\t\t\t\t}\n\t\t\t\t\treturn (p === undefined || p === null) ? name : String(p)\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Bare key like `user_added` / `conversation_created`.\n\t\t\tif (key !== '') {\n\t\t\t\tconst words = key.replace(/[_-]+/g, ' ').trim()\n\t\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'System message')\n\t\t},\n\n\t\t/**\n\t\t * Guard against a raw JSON blob ever reaching the preview line.\n\t\t *\n\t\t * @param {string} text Candidate preview text.\n\t\t * @return {string} The text, or '' when it still looks like JSON.\n\t\t */\n\t\tcleanPreview(text) {\n\t\t\tconst s = String(text || '').trim()\n\t\t\tif (s.charAt(0) === '{' || s.charAt(0) === '[') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Resolve the room's last-activity moment as an epoch-millisecond\n\t\t * value for NcDateTime. Accepts unix seconds, millisecond numbers,\n\t\t * or an ISO-8601 string; returns 0 when unparseable (no timestamp).\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {number} Epoch milliseconds, or 0 when unknown.\n\t\t */\n\t\troomTimestamp(room) {\n\t\t\tconst raw = room.lastActivity ?? room.lastActivityAt ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\t// Heuristic: treat 10-digit values as unix seconds.\n\t\t\t\treturn raw < 1e12 ? raw * 1000 : raw\n\t\t\t}\n\t\t\tconst parsed = new Date(raw).getTime()\n\t\t\treturn Number.isNaN(parsed) ? 0 : parsed\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ roomToken: payload.roomToken }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This room is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] link room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/new`), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] create room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkRoom(room) {\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id\n\t\t\tif (!token) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(token)}`), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] unlink room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchRooms() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.rooms = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\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-talk-tab {\n\tpadding: 8px 0;\n}\n\n.cn-talk-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-talk-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-talk-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-talk-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-talk-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Last-message preview — single line, muted, like NC Talk. */\n.cn-talk-tab__preview {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnTalkRoomPicker","CnTalkRoomCreate","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnTalkTab.vue2.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkTab.vue"],"sourcesContent":["<!--\n CnTalkTab — bespoke sidebar tab for the `talk` integration.\n\n Replaces the generic CnIntegrationTab for the `talk` leaf: mirrors the\n real NC Talk conversation list — each row is an NcListItem with the\n conversation avatar (NcAvatar keyed on the room name), a bold\n conversation name, a last-message preview as the subname, a relative\n timestamp (NcDateTime) in the details slot, and an unread-count bubble\n (NcCounterBubble) when the room has unread messages. Per-row unlink and\n \"Open in Talk\" live in the row action menu. Tier-2: adds explicit\n \"Link existing room\" + \"Create new room\" actions powered by\n CnTalkRoomPicker / CnTalkRoomCreate; unlink does NOT destroy the\n underlying Talk room.\n\n Talks to the OpenRegister Tier-2 endpoint\n GET /api/objects/{register}/{schema}/{objectId}/talk\n POST /api/objects/{register}/{schema}/{objectId}/talk — link\n POST /api/objects/{register}/{schema}/{objectId}/talk/new — create\n DELETE /api/objects/{register}/{schema}/{objectId}/talk/{roomToken} — unlink\n served by OCA\\OpenRegister\\Controller\\TalkLinksController.\n\n Surface behaviour:\n - Empty state with \"Open Talk\" + Link/Create CTAs when no linked rooms.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-talk-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-talk-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<!-- Tier-2 action bar — always visible when not in error/loading -->\n\t\t<div v-if=\"!loading && !error\" class=\"cn-talk-tab__actions\">\n\t\t\t<NcButton @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing room') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new room') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-talk-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-sidebar-tab__empty cn-talk-tab__empty\">\n\t\t\t<ChatOutline :size=\"32\" class=\"cn-talk-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"talkAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChatOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openTalkLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-talk-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"room in rooms\"\n\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\tclass=\"cn-talk-tab__row\"\n\t\t\t\t:class=\"{ 'cn-talk-tab__row--unread': hasUnread(room) }\"\n\t\t\t\t:name=\"roomTitle(room)\"\n\t\t\t\t:bold=\"hasUnread(room)\"\n\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t:size=\"40\"\n\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\thideStatus />\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-talk-tab__preview\">{{ roomSubname(room) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"roomTimestamp(room)\" #details>\n\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\tclass=\"cn-talk-tab__time\"\n\t\t\t\t\t\t:timestamp=\"roomTimestamp(room)\"\n\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"hasUnread(room)\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-talk-tab__badge\"\n\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Talk') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkRoom(room)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink from object (the room stays in Talk)') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<!-- Picker + Create modals (mounted lazily) -->\n\t\t<CnTalkRoomPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onPickerLink\" />\n\n\t\t<CnTalkRoomCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreateSubmit\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcAvatar, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnTalkRoomCreate from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue'\nimport CnTalkRoomPicker from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnTalkTab — bespoke conversation list for the `talk` integration.\n *\n * Renders rows pulled from the OR Tier-2 link table as NcListItem rows\n * styled to mirror NC Talk: conversation avatar, bold name,\n * last-message preview, relative timestamp, and an unread counter\n * bubble. Tier-2: supports link / create / unlink via picker + create\n * dialogs.\n */\nexport default {\n\tname: 'CnTalkTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tChatOutline,\n\t\tClose,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnTalkRoomPicker,\n\t\tCnTalkRoomCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (matches the PHP-side provider id, always `talk`). */\n\t\tintegrationId: { type: String, default: 'talk' },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Empty state shown when no conversation is linked yet. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No conversations linked yet') },\n\t\t/** Label for the link that opens the conversation in Talk. */\n\t\topenTalkLabel: { type: String, default: () => t('nextcloud-vue', 'Open Talk') },\n\t\t/** Message shown when the Talk app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** Base URL of the Talk app, used to build a conversation link. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchRooms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.roomToken ?? room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.roomName ?? room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\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\troomUrl(room) {\n\t\t\tif (room.url && safeHref(room.url) !== '#') {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\t/**\n\t\t * Last-message preview line. Falls back to a participant-count\n\t\t * summary so the subname is never empty in NC-Talk style.\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {string} Preview or participant summary.\n\t\t */\n\t\troomSubname(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\tif (preview) {\n\t\t\t\treturn preview\n\t\t\t}\n\t\t\tconst participants = Number(room.participantCount ?? room.numParticipants ?? 0)\n\t\t\tif (Number.isFinite(participants) && participants > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} participants', { n: participants })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tlet msg = room.lastMessage\n\t\t\t// The provider may hand us the message as a JSON string; parse it\n\t\t\t// so we never render a raw `{\"message\":...}` blob in the preview.\n\t\t\tif (typeof msg === 'string') {\n\t\t\t\tconst trimmed = msg.trim()\n\t\t\t\tif (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmsg = JSON.parse(trimmed)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn this.cleanPreview(trimmed)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn this.cleanPreview(msg)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\t// Talk system messages carry a snake_case key (e.g. `user_added`)\n\t\t\t\t// in `systemMessage`/`message` plus rich `parameters`. Humanise\n\t\t\t\t// them instead of dumping the placeholder/JSON.\n\t\t\t\tconst isSystem = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t\t|| msg.messageType === 'system'\n\t\t\t\tconst raw = msg.message ?? msg.text ?? ''\n\t\t\t\tif (isSystem) {\n\t\t\t\t\treturn this.humaniseSystemMessage(msg)\n\t\t\t\t}\n\t\t\t\treturn this.cleanPreview(typeof raw === 'string' ? raw : '')\n\t\t\t}\n\t\t\treturn this.cleanPreview(room.lastMessageText ?? room.preview ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Turn a Talk system message into readable text. Substitutes rich\n\t\t * `{placeholder}` parameters with their display name; falls back to a\n\t\t * title-cased version of the snake_case key (e.g. `user_added` →\n\t\t * \"User added\").\n\t\t *\n\t\t * @param {object} msg The last-message object.\n\t\t * @return {string} Human-readable line.\n\t\t */\n\t\thumaniseSystemMessage(msg) {\n\t\t\tconst key = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t? msg.systemMessage\n\t\t\t\t: (typeof msg.message === 'string' ? msg.message : '')\n\t\t\tconst template = (typeof msg.message === 'string' && msg.message.indexOf('{') !== -1)\n\t\t\t\t? msg.message\n\t\t\t\t: ''\n\t\t\tconst params = msg.messageParameters || msg.parameters || {}\n\t\t\tif (template !== '' && params && typeof params === 'object') {\n\t\t\t\treturn template.replace(/\\{(\\w+)\\}/g, (whole, name) => {\n\t\t\t\t\tconst p = params[name]\n\t\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\t\treturn p.name || p.displayName || p.id || name\n\t\t\t\t\t}\n\t\t\t\t\treturn (p === undefined || p === null) ? name : String(p)\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Bare key like `user_added` / `conversation_created`.\n\t\t\tif (key !== '') {\n\t\t\t\tconst words = key.replace(/[_-]+/g, ' ').trim()\n\t\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'System message')\n\t\t},\n\n\t\t/**\n\t\t * Guard against a raw JSON blob ever reaching the preview line.\n\t\t *\n\t\t * @param {string} text Candidate preview text.\n\t\t * @return {string} The text, or '' when it still looks like JSON.\n\t\t */\n\t\tcleanPreview(text) {\n\t\t\tconst s = String(text || '').trim()\n\t\t\tif (s.charAt(0) === '{' || s.charAt(0) === '[') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Resolve the room's last-activity moment as an epoch-millisecond\n\t\t * value for NcDateTime. Accepts unix seconds, millisecond numbers,\n\t\t * or an ISO-8601 string; returns 0 when unparseable (no timestamp).\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {number} Epoch milliseconds, or 0 when unknown.\n\t\t */\n\t\troomTimestamp(room) {\n\t\t\tconst raw = room.lastActivity ?? room.lastActivityAt ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\t// Heuristic: treat 10-digit values as unix seconds.\n\t\t\t\treturn raw < 1e12 ? raw * 1000 : raw\n\t\t\t}\n\t\t\tconst parsed = new Date(raw).getTime()\n\t\t\treturn Number.isNaN(parsed) ? 0 : parsed\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ roomToken: payload.roomToken }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This room is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] link room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/new`), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] create room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkRoom(room) {\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id\n\t\t\tif (!token) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(token)}`), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] unlink room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchRooms() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.rooms = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\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-talk-tab {\n\tpadding: 8px 0;\n}\n\n.cn-talk-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-talk-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-talk-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-talk-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-talk-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Last-message preview — single line, muted, like NC Talk. */\n.cn-talk-tab__preview {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnTalkRoomPicker","CnTalkRoomCreate","t"],"mappings":"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"}
|
|
@@ -89,8 +89,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
89
89
|
createVNode(_component_NcButton, {
|
|
90
90
|
variant: "primary",
|
|
91
91
|
href: $props.talkAppUrl,
|
|
92
|
-
target: "_blank"
|
|
93
|
-
rel: "noopener noreferrer"
|
|
92
|
+
target: "_blank"
|
|
94
93
|
}, {
|
|
95
94
|
icon: withCtx(() => [
|
|
96
95
|
createVNode(_component_ChatOutline, { size: 20 })
|
|
@@ -127,8 +126,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
127
126
|
createVNode(_component_NcActionLink, {
|
|
128
127
|
closeAfterClick: true,
|
|
129
128
|
href: $options.roomUrl(room),
|
|
130
|
-
target: "_blank"
|
|
131
|
-
rel: "noopener noreferrer"
|
|
129
|
+
target: "_blank"
|
|
132
130
|
}, {
|
|
133
131
|
icon: withCtx(() => [
|
|
134
132
|
createVNode(_component_OpenInNew, { size: 20 })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnTalkTab.vue3.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkTab.vue?vue&type=template&id=e4b81da4&lang.js"],"sourcesContent":["<!--\n CnTalkTab — bespoke sidebar tab for the `talk` integration.\n\n Replaces the generic CnIntegrationTab for the `talk` leaf: mirrors the\n real NC Talk conversation list — each row is an NcListItem with the\n conversation avatar (NcAvatar keyed on the room name), a bold\n conversation name, a last-message preview as the subname, a relative\n timestamp (NcDateTime) in the details slot, and an unread-count bubble\n (NcCounterBubble) when the room has unread messages. Per-row unlink and\n \"Open in Talk\" live in the row action menu. Tier-2: adds explicit\n \"Link existing room\" + \"Create new room\" actions powered by\n CnTalkRoomPicker / CnTalkRoomCreate; unlink does NOT destroy the\n underlying Talk room.\n\n Talks to the OpenRegister Tier-2 endpoint\n GET /api/objects/{register}/{schema}/{objectId}/talk\n POST /api/objects/{register}/{schema}/{objectId}/talk — link\n POST /api/objects/{register}/{schema}/{objectId}/talk/new — create\n DELETE /api/objects/{register}/{schema}/{objectId}/talk/{roomToken} — unlink\n served by OCA\\OpenRegister\\Controller\\TalkLinksController.\n\n Surface behaviour:\n - Empty state with \"Open Talk\" + Link/Create CTAs when no linked rooms.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-talk-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-talk-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<!-- Tier-2 action bar — always visible when not in error/loading -->\n\t\t<div v-if=\"!loading && !error\" class=\"cn-talk-tab__actions\">\n\t\t\t<NcButton @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing room') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new room') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-talk-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-sidebar-tab__empty cn-talk-tab__empty\">\n\t\t\t<ChatOutline :size=\"32\" class=\"cn-talk-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"talkAppUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChatOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openTalkLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-talk-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"room in rooms\"\n\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\tclass=\"cn-talk-tab__row\"\n\t\t\t\t:class=\"{ 'cn-talk-tab__row--unread': hasUnread(room) }\"\n\t\t\t\t:name=\"roomTitle(room)\"\n\t\t\t\t:bold=\"hasUnread(room)\"\n\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t:size=\"40\"\n\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\thideStatus />\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-talk-tab__preview\">{{ roomSubname(room) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"roomTimestamp(room)\" #details>\n\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\tclass=\"cn-talk-tab__time\"\n\t\t\t\t\t\t:timestamp=\"roomTimestamp(room)\"\n\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"hasUnread(room)\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-talk-tab__badge\"\n\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Talk') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkRoom(room)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink from object (the room stays in Talk)') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<!-- Picker + Create modals (mounted lazily) -->\n\t\t<CnTalkRoomPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onPickerLink\" />\n\n\t\t<CnTalkRoomCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreateSubmit\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcAvatar, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnTalkRoomCreate from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue'\nimport CnTalkRoomPicker from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnTalkTab — bespoke conversation list for the `talk` integration.\n *\n * Renders rows pulled from the OR Tier-2 link table as NcListItem rows\n * styled to mirror NC Talk: conversation avatar, bold name,\n * last-message preview, relative timestamp, and an unread counter\n * bubble. Tier-2: supports link / create / unlink via picker + create\n * dialogs.\n */\nexport default {\n\tname: 'CnTalkTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tChatOutline,\n\t\tClose,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnTalkRoomPicker,\n\t\tCnTalkRoomCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (matches the PHP-side provider id, always `talk`). */\n\t\tintegrationId: { type: String, default: 'talk' },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Empty state shown when no conversation is linked yet. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No conversations linked yet') },\n\t\t/** Label for the link that opens the conversation in Talk. */\n\t\topenTalkLabel: { type: String, default: () => t('nextcloud-vue', 'Open Talk') },\n\t\t/** Message shown when the Talk app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** Base URL of the Talk app, used to build a conversation link. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchRooms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.roomToken ?? room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.roomName ?? room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\n\t\t},\n\n\t\troomUrl(room) {\n\t\t\tif (room.url) {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\t/**\n\t\t * Last-message preview line. Falls back to a participant-count\n\t\t * summary so the subname is never empty in NC-Talk style.\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {string} Preview or participant summary.\n\t\t */\n\t\troomSubname(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\tif (preview) {\n\t\t\t\treturn preview\n\t\t\t}\n\t\t\tconst participants = Number(room.participantCount ?? room.numParticipants ?? 0)\n\t\t\tif (Number.isFinite(participants) && participants > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} participants', { n: participants })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tlet msg = room.lastMessage\n\t\t\t// The provider may hand us the message as a JSON string; parse it\n\t\t\t// so we never render a raw `{\"message\":...}` blob in the preview.\n\t\t\tif (typeof msg === 'string') {\n\t\t\t\tconst trimmed = msg.trim()\n\t\t\t\tif (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmsg = JSON.parse(trimmed)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn this.cleanPreview(trimmed)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn this.cleanPreview(msg)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\t// Talk system messages carry a snake_case key (e.g. `user_added`)\n\t\t\t\t// in `systemMessage`/`message` plus rich `parameters`. Humanise\n\t\t\t\t// them instead of dumping the placeholder/JSON.\n\t\t\t\tconst isSystem = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t\t|| msg.messageType === 'system'\n\t\t\t\tconst raw = msg.message ?? msg.text ?? ''\n\t\t\t\tif (isSystem) {\n\t\t\t\t\treturn this.humaniseSystemMessage(msg)\n\t\t\t\t}\n\t\t\t\treturn this.cleanPreview(typeof raw === 'string' ? raw : '')\n\t\t\t}\n\t\t\treturn this.cleanPreview(room.lastMessageText ?? room.preview ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Turn a Talk system message into readable text. Substitutes rich\n\t\t * `{placeholder}` parameters with their display name; falls back to a\n\t\t * title-cased version of the snake_case key (e.g. `user_added` →\n\t\t * \"User added\").\n\t\t *\n\t\t * @param {object} msg The last-message object.\n\t\t * @return {string} Human-readable line.\n\t\t */\n\t\thumaniseSystemMessage(msg) {\n\t\t\tconst key = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t? msg.systemMessage\n\t\t\t\t: (typeof msg.message === 'string' ? msg.message : '')\n\t\t\tconst template = (typeof msg.message === 'string' && msg.message.indexOf('{') !== -1)\n\t\t\t\t? msg.message\n\t\t\t\t: ''\n\t\t\tconst params = msg.messageParameters || msg.parameters || {}\n\t\t\tif (template !== '' && params && typeof params === 'object') {\n\t\t\t\treturn template.replace(/\\{(\\w+)\\}/g, (whole, name) => {\n\t\t\t\t\tconst p = params[name]\n\t\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\t\treturn p.name || p.displayName || p.id || name\n\t\t\t\t\t}\n\t\t\t\t\treturn (p === undefined || p === null) ? name : String(p)\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Bare key like `user_added` / `conversation_created`.\n\t\t\tif (key !== '') {\n\t\t\t\tconst words = key.replace(/[_-]+/g, ' ').trim()\n\t\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'System message')\n\t\t},\n\n\t\t/**\n\t\t * Guard against a raw JSON blob ever reaching the preview line.\n\t\t *\n\t\t * @param {string} text Candidate preview text.\n\t\t * @return {string} The text, or '' when it still looks like JSON.\n\t\t */\n\t\tcleanPreview(text) {\n\t\t\tconst s = String(text || '').trim()\n\t\t\tif (s.charAt(0) === '{' || s.charAt(0) === '[') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Resolve the room's last-activity moment as an epoch-millisecond\n\t\t * value for NcDateTime. Accepts unix seconds, millisecond numbers,\n\t\t * or an ISO-8601 string; returns 0 when unparseable (no timestamp).\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {number} Epoch milliseconds, or 0 when unknown.\n\t\t */\n\t\troomTimestamp(room) {\n\t\t\tconst raw = room.lastActivity ?? room.lastActivityAt ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\t// Heuristic: treat 10-digit values as unix seconds.\n\t\t\t\treturn raw < 1e12 ? raw * 1000 : raw\n\t\t\t}\n\t\t\tconst parsed = new Date(raw).getTime()\n\t\t\treturn Number.isNaN(parsed) ? 0 : parsed\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ roomToken: payload.roomToken }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This room is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] link room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/new`), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] create room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkRoom(room) {\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id\n\t\t\tif (!token) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(token)}`), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] unlink room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchRooms() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.rooms = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\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-talk-tab {\n\tpadding: 8px 0;\n}\n\n.cn-talk-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-talk-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-talk-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-talk-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-talk-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Last-message preview — single line, muted, like NC Talk. */\n.cn-talk-tab__preview {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnTalkTab.vue3.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkTab.vue?vue&type=template&id=e4b81da4&lang.js"],"sourcesContent":["<!--\n CnTalkTab — bespoke sidebar tab for the `talk` integration.\n\n Replaces the generic CnIntegrationTab for the `talk` leaf: mirrors the\n real NC Talk conversation list — each row is an NcListItem with the\n conversation avatar (NcAvatar keyed on the room name), a bold\n conversation name, a last-message preview as the subname, a relative\n timestamp (NcDateTime) in the details slot, and an unread-count bubble\n (NcCounterBubble) when the room has unread messages. Per-row unlink and\n \"Open in Talk\" live in the row action menu. Tier-2: adds explicit\n \"Link existing room\" + \"Create new room\" actions powered by\n CnTalkRoomPicker / CnTalkRoomCreate; unlink does NOT destroy the\n underlying Talk room.\n\n Talks to the OpenRegister Tier-2 endpoint\n GET /api/objects/{register}/{schema}/{objectId}/talk\n POST /api/objects/{register}/{schema}/{objectId}/talk — link\n POST /api/objects/{register}/{schema}/{objectId}/talk/new — create\n DELETE /api/objects/{register}/{schema}/{objectId}/talk/{roomToken} — unlink\n served by OCA\\OpenRegister\\Controller\\TalkLinksController.\n\n Surface behaviour:\n - Empty state with \"Open Talk\" + Link/Create CTAs when no linked rooms.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-talk-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-talk-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<!-- Tier-2 action bar — always visible when not in error/loading -->\n\t\t<div v-if=\"!loading && !error\" class=\"cn-talk-tab__actions\">\n\t\t\t<NcButton @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing room') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new room') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-talk-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-sidebar-tab__empty cn-talk-tab__empty\">\n\t\t\t<ChatOutline :size=\"32\" class=\"cn-talk-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"talkAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChatOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openTalkLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-talk-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"room in rooms\"\n\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\tclass=\"cn-talk-tab__row\"\n\t\t\t\t:class=\"{ 'cn-talk-tab__row--unread': hasUnread(room) }\"\n\t\t\t\t:name=\"roomTitle(room)\"\n\t\t\t\t:bold=\"hasUnread(room)\"\n\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t:size=\"40\"\n\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\thideStatus />\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-talk-tab__preview\">{{ roomSubname(room) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"roomTimestamp(room)\" #details>\n\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\tclass=\"cn-talk-tab__time\"\n\t\t\t\t\t\t:timestamp=\"roomTimestamp(room)\"\n\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"hasUnread(room)\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-talk-tab__badge\"\n\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Talk') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkRoom(room)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink from object (the room stays in Talk)') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<!-- Picker + Create modals (mounted lazily) -->\n\t\t<CnTalkRoomPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onPickerLink\" />\n\n\t\t<CnTalkRoomCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreateSubmit\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcAvatar, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnTalkRoomCreate from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue'\nimport CnTalkRoomPicker from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnTalkTab — bespoke conversation list for the `talk` integration.\n *\n * Renders rows pulled from the OR Tier-2 link table as NcListItem rows\n * styled to mirror NC Talk: conversation avatar, bold name,\n * last-message preview, relative timestamp, and an unread counter\n * bubble. Tier-2: supports link / create / unlink via picker + create\n * dialogs.\n */\nexport default {\n\tname: 'CnTalkTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tChatOutline,\n\t\tClose,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnTalkRoomPicker,\n\t\tCnTalkRoomCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (matches the PHP-side provider id, always `talk`). */\n\t\tintegrationId: { type: String, default: 'talk' },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Empty state shown when no conversation is linked yet. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No conversations linked yet') },\n\t\t/** Label for the link that opens the conversation in Talk. */\n\t\topenTalkLabel: { type: String, default: () => t('nextcloud-vue', 'Open Talk') },\n\t\t/** Message shown when the Talk app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** Base URL of the Talk app, used to build a conversation link. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchRooms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.roomToken ?? room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.roomName ?? room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\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\troomUrl(room) {\n\t\t\tif (room.url && safeHref(room.url) !== '#') {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\t/**\n\t\t * Last-message preview line. Falls back to a participant-count\n\t\t * summary so the subname is never empty in NC-Talk style.\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {string} Preview or participant summary.\n\t\t */\n\t\troomSubname(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\tif (preview) {\n\t\t\t\treturn preview\n\t\t\t}\n\t\t\tconst participants = Number(room.participantCount ?? room.numParticipants ?? 0)\n\t\t\tif (Number.isFinite(participants) && participants > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} participants', { n: participants })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tlet msg = room.lastMessage\n\t\t\t// The provider may hand us the message as a JSON string; parse it\n\t\t\t// so we never render a raw `{\"message\":...}` blob in the preview.\n\t\t\tif (typeof msg === 'string') {\n\t\t\t\tconst trimmed = msg.trim()\n\t\t\t\tif (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmsg = JSON.parse(trimmed)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn this.cleanPreview(trimmed)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn this.cleanPreview(msg)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\t// Talk system messages carry a snake_case key (e.g. `user_added`)\n\t\t\t\t// in `systemMessage`/`message` plus rich `parameters`. Humanise\n\t\t\t\t// them instead of dumping the placeholder/JSON.\n\t\t\t\tconst isSystem = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t\t|| msg.messageType === 'system'\n\t\t\t\tconst raw = msg.message ?? msg.text ?? ''\n\t\t\t\tif (isSystem) {\n\t\t\t\t\treturn this.humaniseSystemMessage(msg)\n\t\t\t\t}\n\t\t\t\treturn this.cleanPreview(typeof raw === 'string' ? raw : '')\n\t\t\t}\n\t\t\treturn this.cleanPreview(room.lastMessageText ?? room.preview ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Turn a Talk system message into readable text. Substitutes rich\n\t\t * `{placeholder}` parameters with their display name; falls back to a\n\t\t * title-cased version of the snake_case key (e.g. `user_added` →\n\t\t * \"User added\").\n\t\t *\n\t\t * @param {object} msg The last-message object.\n\t\t * @return {string} Human-readable line.\n\t\t */\n\t\thumaniseSystemMessage(msg) {\n\t\t\tconst key = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t? msg.systemMessage\n\t\t\t\t: (typeof msg.message === 'string' ? msg.message : '')\n\t\t\tconst template = (typeof msg.message === 'string' && msg.message.indexOf('{') !== -1)\n\t\t\t\t? msg.message\n\t\t\t\t: ''\n\t\t\tconst params = msg.messageParameters || msg.parameters || {}\n\t\t\tif (template !== '' && params && typeof params === 'object') {\n\t\t\t\treturn template.replace(/\\{(\\w+)\\}/g, (whole, name) => {\n\t\t\t\t\tconst p = params[name]\n\t\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\t\treturn p.name || p.displayName || p.id || name\n\t\t\t\t\t}\n\t\t\t\t\treturn (p === undefined || p === null) ? name : String(p)\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Bare key like `user_added` / `conversation_created`.\n\t\t\tif (key !== '') {\n\t\t\t\tconst words = key.replace(/[_-]+/g, ' ').trim()\n\t\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'System message')\n\t\t},\n\n\t\t/**\n\t\t * Guard against a raw JSON blob ever reaching the preview line.\n\t\t *\n\t\t * @param {string} text Candidate preview text.\n\t\t * @return {string} The text, or '' when it still looks like JSON.\n\t\t */\n\t\tcleanPreview(text) {\n\t\t\tconst s = String(text || '').trim()\n\t\t\tif (s.charAt(0) === '{' || s.charAt(0) === '[') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Resolve the room's last-activity moment as an epoch-millisecond\n\t\t * value for NcDateTime. Accepts unix seconds, millisecond numbers,\n\t\t * or an ISO-8601 string; returns 0 when unparseable (no timestamp).\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {number} Epoch milliseconds, or 0 when unknown.\n\t\t */\n\t\troomTimestamp(room) {\n\t\t\tconst raw = room.lastActivity ?? room.lastActivityAt ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\t// Heuristic: treat 10-digit values as unix seconds.\n\t\t\t\treturn raw < 1e12 ? raw * 1000 : raw\n\t\t\t}\n\t\t\tconst parsed = new Date(raw).getTime()\n\t\t\treturn Number.isNaN(parsed) ? 0 : parsed\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ roomToken: payload.roomToken }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This room is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] link room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/new`), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] create room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkRoom(room) {\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id\n\t\t\tif (!token) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(token)}`), {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] unlink room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchRooms() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.rooms = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\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-talk-tab {\n\tpadding: 8px 0;\n}\n\n.cn-talk-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-talk-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-talk-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-talk-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-talk-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Last-message preview — single line, muted, like NC Talk. */\n.cn-talk-tab__preview {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { NcLoadingIcon } from '@nextcloud/vue';
|
|
|
3
3
|
import Clock from 'vue-material-design-icons/Clock.vue';
|
|
4
4
|
import Timer from 'vue-material-design-icons/Timer.vue';
|
|
5
5
|
import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
|
|
6
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
6
7
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
7
8
|
|
|
8
9
|
const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
|
|
@@ -191,7 +192,7 @@ var script = {
|
|
|
191
192
|
},
|
|
192
193
|
|
|
193
194
|
rowUrl(row) {
|
|
194
|
-
if (row.url) {
|
|
195
|
+
if (row.url && safeHref(row.url) !== '#') {
|
|
195
196
|
return row.url
|
|
196
197
|
}
|
|
197
198
|
const kind = this.rowKind(row);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnTimeTrackerCard.vue2.js","sources":["../../../../../src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue"],"sourcesContent":["<!--\n CnTimeTrackerCard — bespoke surface-aware widget for the `time-tracker`\n integration.\n\n Replaces the generic CnIntegrationCard for the `time-tracker` leaf.\n Branches on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N tracked entries\";\n sub-line with total tracked time (hours + minutes) summed over\n all linked time-entries; plus a most-recent row link.\n - detail-page : bounded row list with kind chip,\n duration, billable indicator — same shape as the tab but\n read-only.\n - single-entity : chip with row title + kind pill\n (referenceType: 'time-tracker').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnTimeTrackerTab; for `single-entity` the optional `value` prop\n addresses a single row by id.\n\n Note: the leaf slug is `time-tracker` (with a hyphen); the underlying\n NC app id is `timemanager` (no hyphen).\n\n See `openregister/openspec/changes/integration-time-tracker/` for the\n spec delta, ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardHeaderTitle\" :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-time-tracker-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<Clock :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"rowUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ rowTitle(entity) }}</a>\n\t\t\t\t<span class=\"cn-time-tracker-card__chip-pill\" :class=\"kindChipClass(entity)\">\n\t\t\t\t\t{{ kindLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-time-tracker-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total tracked time + 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-time-tracker-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-time-tracker-card__headline\">\n\t\t\t\t<div class=\"cn-time-tracker-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"totalDurationLabel\" class=\"cn-time-tracker-card__total\">\n\t\t\t\t\t<Timer :size=\"14\" />\n\t\t\t\t\t{{ totalDurationLabel }}\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"kindDistribution.length > 0\" class=\"cn-time-tracker-card__distribution\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in kindDistribution\"\n\t\t\t\t\t\t:key=\"bucket.key\"\n\t\t\t\t\t\tclass=\"cn-time-tracker-card__distribution-row\">\n\t\t\t\t\t\t<span class=\"cn-time-tracker-card__distribution-label\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"cn-time-tracker-card__distribution-dot\"\n\t\t\t\t\t\t\t\t:class=\"`cn-time-tracker-card__distribution-dot--${bucket.key}`\" />\n\t\t\t\t\t\t\t{{ bucket.label }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-time-tracker-card__distribution-count\">{{ bucket.count }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-time-tracker-card__headline-recent\">\n\t\t\t\t\t<Clock :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ rowTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: bounded row list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-time-tracker-card__rows\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t:key=\"rowKey(row)\"\n\t\t\t\t\tclass=\"cn-time-tracker-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-time-tracker-card__row--highlight': isLinked(row) }\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-time-tracker-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-time-tracker-card__chip-pill\" :class=\"kindChipClass(row)\">\n\t\t\t\t\t\t{{ kindLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"durationLabel(row)\" class=\"cn-time-tracker-card__duration\">\n\t\t\t\t\t\t{{ durationLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport Clock from 'vue-material-design-icons/Clock.vue'\nimport Timer from 'vue-material-design-icons/Timer.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']\n\n/**\n * CnTimeTrackerCard — bespoke surface-aware widget for the\n * `time-tracker` integration. See file-level docblock.\n */\nexport default {\n\tname: 'CnTimeTrackerCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, Clock, Timer },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'time-tracker'`). */\n\t\tintegrationId: { type: String, default: 'time-tracker' },\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 (row id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Time tracker') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Clock },\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 tracked time linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC TimeManager is currently unavailable.') },\n\t\t/** URL of the NC TimeManager app entry. */\n\t\ttimeTrackerAppUrl: { type: String, default: '/index.php/apps/timemanager' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trows: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardHeaderTitle() {\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\t/**\n\t\t * Rows bucketed by kind so the dashboard surface can show the\n\t\t * clients / tasks / entries distribution.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, count: number}>}\n\t\t */\n\t\tkindDistribution() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst kind = this.rowKind(row)\n\t\t\t\tif (!groups.has(kind)) {\n\t\t\t\t\tgroups.set(kind, { key: kind, label: this.kindLabel(row), count: 0 })\n\t\t\t\t}\n\t\t\t\tgroups.get(kind).count += 1\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} entry', '{count} entries', total, { count: total })\n\t\t},\n\n\t\t/**\n\t\t * Total tracked seconds across all rows that carry a `duration`\n\t\t * (clients without a duration don't contribute to this total —\n\t\t * they're parent containers).\n\t\t *\n\t\t * @return {number} Seconds total.\n\t\t */\n\t\ttotalSeconds() {\n\t\t\tlet total = 0\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst d = this.durationSeconds(row)\n\t\t\t\tif (d !== null) {\n\t\t\t\t\ttotal += d\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\n\t\ttotalDurationLabel() {\n\t\t\tif (this.totalSeconds === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst totalMinutes = Math.round(this.totalSeconds / 60)\n\t\t\tconst hours = Math.floor(totalMinutes / 60)\n\t\t\tconst minutes = totalMinutes % 60\n\t\t\tif (hours > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{hours}h {minutes}m tracked', { hours, minutes })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{minutes}m tracked', { minutes })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.rows].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.startedAt ?? a.started_at ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.startedAt ?? b.started_at ?? b.linkedAt ?? b.created_at ?? '') || 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\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? row.data?.uuid ?? ''\n\t\t},\n\n\t\trowTitle(row) {\n\t\t\treturn row.title ?? row.name ?? this.rowKey(row)\n\t\t},\n\n\t\trowKind(row) {\n\t\t\tconst k = String(row.kind ?? row.type ?? '').toLowerCase()\n\t\t\tif (k === 'task') {\n\t\t\t\treturn 'task'\n\t\t\t}\n\t\t\tif (k === 'time' || k === 'entry' || k === 'time-entry') {\n\t\t\t\treturn 'time'\n\t\t\t}\n\t\t\treturn 'client'\n\t\t},\n\n\t\tkindLabel(row) {\n\t\t\tconst kind = this.rowKind(row)\n\t\t\tif (kind === 'task') {\n\t\t\t\treturn t('nextcloud-vue', 'Task')\n\t\t\t}\n\t\t\tif (kind === 'time') {\n\t\t\t\treturn t('nextcloud-vue', 'Entry')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tkindChipClass(row) {\n\t\t\treturn `cn-time-tracker-card__chip-pill--${this.rowKind(row)}`\n\t\t},\n\n\t\trowUrl(row) {\n\t\t\tif (row.url) {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst kind = this.rowKind(row)\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (kind === 'client') {\n\t\t\t\tconst clientId = row.clientId ?? id\n\t\t\t\tif (clientId) {\n\t\t\t\t\treturn `/index.php/apps/timemanager/clients/${clientId}`\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (kind === 'task') {\n\t\t\t\tconst taskId = row.taskId ?? id\n\t\t\t\tif (taskId) {\n\t\t\t\t\treturn `/index.php/apps/timemanager/tasks/${taskId}`\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (kind === 'time' && id) {\n\t\t\t\treturn `/index.php/apps/timemanager/times/${id}`\n\t\t\t}\n\t\t\tif (id) {\n\t\t\t\treturn `/index.php/apps/timemanager/projects/${id}`\n\t\t\t}\n\t\t\treturn this.timeTrackerAppUrl\n\t\t},\n\n\t\tdurationSeconds(row) {\n\t\t\tconst raw = row.duration ?? row.seconds ?? row.data?.duration ?? null\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.isNaN(num) ? null : num\n\t\t},\n\n\t\tdurationLabel(row) {\n\t\t\tconst total = this.durationSeconds(row)\n\t\t\tif (total === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst totalMinutes = Math.round(total / 60)\n\t\t\tconst hours = Math.floor(totalMinutes / 60)\n\t\t\tconst minutes = totalMinutes % 60\n\t\t\tif (hours > 0) {\n\t\t\t\treturn `${hours}h ${minutes}m`\n\t\t\t}\n\t\t\treturn `${minutes}m`\n\t\t},\n\n\t\tisLinked(row) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.rowKey(row)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(row) {\n\t\t\treturn this.durationLabel(row) || this.kindLabel(row)\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.rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rows = []\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('[CnTimeTrackerCard] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.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('[CnTimeTrackerCard] failed to fetch single row', 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-time-tracker-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-time-tracker-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-time-tracker-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-time-tracker-card__total {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 0.95em;\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-time-tracker-card__distribution {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-time-tracker-card__distribution-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-time-tracker-card__distribution-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-time-tracker-card__distribution-dot {\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n}\n\n.cn-time-tracker-card__distribution-dot--client {\n\tbackground: var(--color-primary-element, #21468B);\n}\n\n.cn-time-tracker-card__distribution-dot--task {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-time-tracker-card__distribution-dot--time {\n\tbackground: var(--color-success, #46ba61);\n}\n\n.cn-time-tracker-card__distribution-count {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-time-tracker-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-time-tracker-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-time-tracker-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-time-tracker-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-time-tracker-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__chip-pill {\n\tdisplay: inline-block;\n\tpadding: 1px 6px;\n\tfont-size: 0.7em;\n\tfont-weight: 600;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\twhite-space: nowrap;\n}\n\n.cn-time-tracker-card__chip-pill--client {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__chip-pill--task {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__chip-pill--time {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-time-tracker-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.85em;\n}\n\n.cn-time-tracker-card__row--highlight {\n\tborder-color: var(--color-primary-element, #21468B);\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tfont-weight: 600;\n}\n\n.cn-time-tracker-card__title {\n\tflex: 1;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-time-tracker-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__duration {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnTimeTrackerCard.vue2.js","sources":["../../../../../src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue"],"sourcesContent":["<!--\n CnTimeTrackerCard — bespoke surface-aware widget for the `time-tracker`\n integration.\n\n Replaces the generic CnIntegrationCard for the `time-tracker` leaf.\n Branches on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N tracked entries\";\n sub-line with total tracked time (hours + minutes) summed over\n all linked time-entries; plus a most-recent row link.\n - detail-page : bounded row list with kind chip,\n duration, billable indicator — same shape as the tab but\n read-only.\n - single-entity : chip with row title + kind pill\n (referenceType: 'time-tracker').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnTimeTrackerTab; for `single-entity` the optional `value` prop\n addresses a single row by id.\n\n Note: the leaf slug is `time-tracker` (with a hyphen); the underlying\n NC app id is `timemanager` (no hyphen).\n\n See `openregister/openspec/changes/integration-time-tracker/` for the\n spec delta, ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardHeaderTitle\" :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-time-tracker-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<Clock :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"rowUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ rowTitle(entity) }}</a>\n\t\t\t\t<span class=\"cn-time-tracker-card__chip-pill\" :class=\"kindChipClass(entity)\">\n\t\t\t\t\t{{ kindLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-time-tracker-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total tracked time + 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-time-tracker-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-time-tracker-card__headline\">\n\t\t\t\t<div class=\"cn-time-tracker-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"totalDurationLabel\" class=\"cn-time-tracker-card__total\">\n\t\t\t\t\t<Timer :size=\"14\" />\n\t\t\t\t\t{{ totalDurationLabel }}\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"kindDistribution.length > 0\" class=\"cn-time-tracker-card__distribution\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in kindDistribution\"\n\t\t\t\t\t\t:key=\"bucket.key\"\n\t\t\t\t\t\tclass=\"cn-time-tracker-card__distribution-row\">\n\t\t\t\t\t\t<span class=\"cn-time-tracker-card__distribution-label\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"cn-time-tracker-card__distribution-dot\"\n\t\t\t\t\t\t\t\t:class=\"`cn-time-tracker-card__distribution-dot--${bucket.key}`\" />\n\t\t\t\t\t\t\t{{ bucket.label }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-time-tracker-card__distribution-count\">{{ bucket.count }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-time-tracker-card__headline-recent\">\n\t\t\t\t\t<Clock :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ rowTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: bounded row list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-time-tracker-card__rows\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t:key=\"rowKey(row)\"\n\t\t\t\t\tclass=\"cn-time-tracker-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-time-tracker-card__row--highlight': isLinked(row) }\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-time-tracker-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-time-tracker-card__chip-pill\" :class=\"kindChipClass(row)\">\n\t\t\t\t\t\t{{ kindLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"durationLabel(row)\" class=\"cn-time-tracker-card__duration\">\n\t\t\t\t\t\t{{ durationLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport Clock from 'vue-material-design-icons/Clock.vue'\nimport Timer from 'vue-material-design-icons/Timer.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']\n\n/**\n * CnTimeTrackerCard — bespoke surface-aware widget for the\n * `time-tracker` integration. See file-level docblock.\n */\nexport default {\n\tname: 'CnTimeTrackerCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, Clock, Timer },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'time-tracker'`). */\n\t\tintegrationId: { type: String, default: 'time-tracker' },\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 (row id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Time tracker') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Clock },\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 tracked time linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC TimeManager is currently unavailable.') },\n\t\t/** URL of the NC TimeManager app entry. */\n\t\ttimeTrackerAppUrl: { type: String, default: '/index.php/apps/timemanager' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trows: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardHeaderTitle() {\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\t/**\n\t\t * Rows bucketed by kind so the dashboard surface can show the\n\t\t * clients / tasks / entries distribution.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, count: number}>}\n\t\t */\n\t\tkindDistribution() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst kind = this.rowKind(row)\n\t\t\t\tif (!groups.has(kind)) {\n\t\t\t\t\tgroups.set(kind, { key: kind, label: this.kindLabel(row), count: 0 })\n\t\t\t\t}\n\t\t\t\tgroups.get(kind).count += 1\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} entry', '{count} entries', total, { count: total })\n\t\t},\n\n\t\t/**\n\t\t * Total tracked seconds across all rows that carry a `duration`\n\t\t * (clients without a duration don't contribute to this total —\n\t\t * they're parent containers).\n\t\t *\n\t\t * @return {number} Seconds total.\n\t\t */\n\t\ttotalSeconds() {\n\t\t\tlet total = 0\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst d = this.durationSeconds(row)\n\t\t\t\tif (d !== null) {\n\t\t\t\t\ttotal += d\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\n\t\ttotalDurationLabel() {\n\t\t\tif (this.totalSeconds === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst totalMinutes = Math.round(this.totalSeconds / 60)\n\t\t\tconst hours = Math.floor(totalMinutes / 60)\n\t\t\tconst minutes = totalMinutes % 60\n\t\t\tif (hours > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{hours}h {minutes}m tracked', { hours, minutes })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{minutes}m tracked', { minutes })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.rows].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.startedAt ?? a.started_at ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.startedAt ?? b.started_at ?? b.linkedAt ?? b.created_at ?? '') || 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\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? row.data?.uuid ?? ''\n\t\t},\n\n\t\trowTitle(row) {\n\t\t\treturn row.title ?? row.name ?? this.rowKey(row)\n\t\t},\n\n\t\trowKind(row) {\n\t\t\tconst k = String(row.kind ?? row.type ?? '').toLowerCase()\n\t\t\tif (k === 'task') {\n\t\t\t\treturn 'task'\n\t\t\t}\n\t\t\tif (k === 'time' || k === 'entry' || k === 'time-entry') {\n\t\t\t\treturn 'time'\n\t\t\t}\n\t\t\treturn 'client'\n\t\t},\n\n\t\tkindLabel(row) {\n\t\t\tconst kind = this.rowKind(row)\n\t\t\tif (kind === 'task') {\n\t\t\t\treturn t('nextcloud-vue', 'Task')\n\t\t\t}\n\t\t\tif (kind === 'time') {\n\t\t\t\treturn t('nextcloud-vue', 'Entry')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tkindChipClass(row) {\n\t\t\treturn `cn-time-tracker-card__chip-pill--${this.rowKind(row)}`\n\t\t},\n\n\t\trowUrl(row) {\n\t\t\tif (row.url && safeHref(row.url) !== '#') {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst kind = this.rowKind(row)\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (kind === 'client') {\n\t\t\t\tconst clientId = row.clientId ?? id\n\t\t\t\tif (clientId) {\n\t\t\t\t\treturn `/index.php/apps/timemanager/clients/${clientId}`\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (kind === 'task') {\n\t\t\t\tconst taskId = row.taskId ?? id\n\t\t\t\tif (taskId) {\n\t\t\t\t\treturn `/index.php/apps/timemanager/tasks/${taskId}`\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (kind === 'time' && id) {\n\t\t\t\treturn `/index.php/apps/timemanager/times/${id}`\n\t\t\t}\n\t\t\tif (id) {\n\t\t\t\treturn `/index.php/apps/timemanager/projects/${id}`\n\t\t\t}\n\t\t\treturn this.timeTrackerAppUrl\n\t\t},\n\n\t\tdurationSeconds(row) {\n\t\t\tconst raw = row.duration ?? row.seconds ?? row.data?.duration ?? null\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.isNaN(num) ? null : num\n\t\t},\n\n\t\tdurationLabel(row) {\n\t\t\tconst total = this.durationSeconds(row)\n\t\t\tif (total === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst totalMinutes = Math.round(total / 60)\n\t\t\tconst hours = Math.floor(totalMinutes / 60)\n\t\t\tconst minutes = totalMinutes % 60\n\t\t\tif (hours > 0) {\n\t\t\t\treturn `${hours}h ${minutes}m`\n\t\t\t}\n\t\t\treturn `${minutes}m`\n\t\t},\n\n\t\tisLinked(row) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.rowKey(row)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(row) {\n\t\t\treturn this.durationLabel(row) || this.kindLabel(row)\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.rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rows = []\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('[CnTimeTrackerCard] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.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('[CnTimeTrackerCard] failed to fetch single row', 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-time-tracker-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-time-tracker-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-time-tracker-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-time-tracker-card__total {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 0.95em;\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-time-tracker-card__distribution {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-time-tracker-card__distribution-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-time-tracker-card__distribution-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-time-tracker-card__distribution-dot {\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n}\n\n.cn-time-tracker-card__distribution-dot--client {\n\tbackground: var(--color-primary-element, #21468B);\n}\n\n.cn-time-tracker-card__distribution-dot--task {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-time-tracker-card__distribution-dot--time {\n\tbackground: var(--color-success, #46ba61);\n}\n\n.cn-time-tracker-card__distribution-count {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-time-tracker-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-time-tracker-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-time-tracker-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-time-tracker-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-time-tracker-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__chip-pill {\n\tdisplay: inline-block;\n\tpadding: 1px 6px;\n\tfont-size: 0.7em;\n\tfont-weight: 600;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\twhite-space: nowrap;\n}\n\n.cn-time-tracker-card__chip-pill--client {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__chip-pill--task {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__chip-pill--time {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-time-tracker-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.85em;\n}\n\n.cn-time-tracker-card__row--highlight {\n\tborder-color: var(--color-primary-element, #21468B);\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tfont-weight: 600;\n}\n\n.cn-time-tracker-card__title {\n\tflex: 1;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-time-tracker-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__duration {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnTimeTrackerCard.vue3.js","sources":["../../../../../src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue?vue&type=template&id=4ee344cc&lang.js"],"sourcesContent":["<!--\n CnTimeTrackerCard — bespoke surface-aware widget for the `time-tracker`\n integration.\n\n Replaces the generic CnIntegrationCard for the `time-tracker` leaf.\n Branches on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N tracked entries\";\n sub-line with total tracked time (hours + minutes) summed over\n all linked time-entries; plus a most-recent row link.\n - detail-page : bounded row list with kind chip,\n duration, billable indicator — same shape as the tab but\n read-only.\n - single-entity : chip with row title + kind pill\n (referenceType: 'time-tracker').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnTimeTrackerTab; for `single-entity` the optional `value` prop\n addresses a single row by id.\n\n Note: the leaf slug is `time-tracker` (with a hyphen); the underlying\n NC app id is `timemanager` (no hyphen).\n\n See `openregister/openspec/changes/integration-time-tracker/` for the\n spec delta, ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardHeaderTitle\" :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-time-tracker-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<Clock :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"rowUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ rowTitle(entity) }}</a>\n\t\t\t\t<span class=\"cn-time-tracker-card__chip-pill\" :class=\"kindChipClass(entity)\">\n\t\t\t\t\t{{ kindLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-time-tracker-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total tracked time + 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-time-tracker-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-time-tracker-card__headline\">\n\t\t\t\t<div class=\"cn-time-tracker-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"totalDurationLabel\" class=\"cn-time-tracker-card__total\">\n\t\t\t\t\t<Timer :size=\"14\" />\n\t\t\t\t\t{{ totalDurationLabel }}\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"kindDistribution.length > 0\" class=\"cn-time-tracker-card__distribution\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in kindDistribution\"\n\t\t\t\t\t\t:key=\"bucket.key\"\n\t\t\t\t\t\tclass=\"cn-time-tracker-card__distribution-row\">\n\t\t\t\t\t\t<span class=\"cn-time-tracker-card__distribution-label\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"cn-time-tracker-card__distribution-dot\"\n\t\t\t\t\t\t\t\t:class=\"`cn-time-tracker-card__distribution-dot--${bucket.key}`\" />\n\t\t\t\t\t\t\t{{ bucket.label }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-time-tracker-card__distribution-count\">{{ bucket.count }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-time-tracker-card__headline-recent\">\n\t\t\t\t\t<Clock :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ rowTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: bounded row list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-time-tracker-card__rows\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t:key=\"rowKey(row)\"\n\t\t\t\t\tclass=\"cn-time-tracker-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-time-tracker-card__row--highlight': isLinked(row) }\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-time-tracker-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-time-tracker-card__chip-pill\" :class=\"kindChipClass(row)\">\n\t\t\t\t\t\t{{ kindLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"durationLabel(row)\" class=\"cn-time-tracker-card__duration\">\n\t\t\t\t\t\t{{ durationLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport Clock from 'vue-material-design-icons/Clock.vue'\nimport Timer from 'vue-material-design-icons/Timer.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']\n\n/**\n * CnTimeTrackerCard — bespoke surface-aware widget for the\n * `time-tracker` integration. See file-level docblock.\n */\nexport default {\n\tname: 'CnTimeTrackerCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, Clock, Timer },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'time-tracker'`). */\n\t\tintegrationId: { type: String, default: 'time-tracker' },\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 (row id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Time tracker') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Clock },\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 tracked time linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC TimeManager is currently unavailable.') },\n\t\t/** URL of the NC TimeManager app entry. */\n\t\ttimeTrackerAppUrl: { type: String, default: '/index.php/apps/timemanager' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trows: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardHeaderTitle() {\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\t/**\n\t\t * Rows bucketed by kind so the dashboard surface can show the\n\t\t * clients / tasks / entries distribution.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, count: number}>}\n\t\t */\n\t\tkindDistribution() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst kind = this.rowKind(row)\n\t\t\t\tif (!groups.has(kind)) {\n\t\t\t\t\tgroups.set(kind, { key: kind, label: this.kindLabel(row), count: 0 })\n\t\t\t\t}\n\t\t\t\tgroups.get(kind).count += 1\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} entry', '{count} entries', total, { count: total })\n\t\t},\n\n\t\t/**\n\t\t * Total tracked seconds across all rows that carry a `duration`\n\t\t * (clients without a duration don't contribute to this total —\n\t\t * they're parent containers).\n\t\t *\n\t\t * @return {number} Seconds total.\n\t\t */\n\t\ttotalSeconds() {\n\t\t\tlet total = 0\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst d = this.durationSeconds(row)\n\t\t\t\tif (d !== null) {\n\t\t\t\t\ttotal += d\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\n\t\ttotalDurationLabel() {\n\t\t\tif (this.totalSeconds === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst totalMinutes = Math.round(this.totalSeconds / 60)\n\t\t\tconst hours = Math.floor(totalMinutes / 60)\n\t\t\tconst minutes = totalMinutes % 60\n\t\t\tif (hours > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{hours}h {minutes}m tracked', { hours, minutes })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{minutes}m tracked', { minutes })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.rows].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.startedAt ?? a.started_at ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.startedAt ?? b.started_at ?? b.linkedAt ?? b.created_at ?? '') || 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\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? row.data?.uuid ?? ''\n\t\t},\n\n\t\trowTitle(row) {\n\t\t\treturn row.title ?? row.name ?? this.rowKey(row)\n\t\t},\n\n\t\trowKind(row) {\n\t\t\tconst k = String(row.kind ?? row.type ?? '').toLowerCase()\n\t\t\tif (k === 'task') {\n\t\t\t\treturn 'task'\n\t\t\t}\n\t\t\tif (k === 'time' || k === 'entry' || k === 'time-entry') {\n\t\t\t\treturn 'time'\n\t\t\t}\n\t\t\treturn 'client'\n\t\t},\n\n\t\tkindLabel(row) {\n\t\t\tconst kind = this.rowKind(row)\n\t\t\tif (kind === 'task') {\n\t\t\t\treturn t('nextcloud-vue', 'Task')\n\t\t\t}\n\t\t\tif (kind === 'time') {\n\t\t\t\treturn t('nextcloud-vue', 'Entry')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tkindChipClass(row) {\n\t\t\treturn `cn-time-tracker-card__chip-pill--${this.rowKind(row)}`\n\t\t},\n\n\t\trowUrl(row) {\n\t\t\tif (row.url) {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst kind = this.rowKind(row)\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (kind === 'client') {\n\t\t\t\tconst clientId = row.clientId ?? id\n\t\t\t\tif (clientId) {\n\t\t\t\t\treturn `/index.php/apps/timemanager/clients/${clientId}`\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (kind === 'task') {\n\t\t\t\tconst taskId = row.taskId ?? id\n\t\t\t\tif (taskId) {\n\t\t\t\t\treturn `/index.php/apps/timemanager/tasks/${taskId}`\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (kind === 'time' && id) {\n\t\t\t\treturn `/index.php/apps/timemanager/times/${id}`\n\t\t\t}\n\t\t\tif (id) {\n\t\t\t\treturn `/index.php/apps/timemanager/projects/${id}`\n\t\t\t}\n\t\t\treturn this.timeTrackerAppUrl\n\t\t},\n\n\t\tdurationSeconds(row) {\n\t\t\tconst raw = row.duration ?? row.seconds ?? row.data?.duration ?? null\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.isNaN(num) ? null : num\n\t\t},\n\n\t\tdurationLabel(row) {\n\t\t\tconst total = this.durationSeconds(row)\n\t\t\tif (total === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst totalMinutes = Math.round(total / 60)\n\t\t\tconst hours = Math.floor(totalMinutes / 60)\n\t\t\tconst minutes = totalMinutes % 60\n\t\t\tif (hours > 0) {\n\t\t\t\treturn `${hours}h ${minutes}m`\n\t\t\t}\n\t\t\treturn `${minutes}m`\n\t\t},\n\n\t\tisLinked(row) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.rowKey(row)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(row) {\n\t\t\treturn this.durationLabel(row) || this.kindLabel(row)\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.rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rows = []\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('[CnTimeTrackerCard] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.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('[CnTimeTrackerCard] failed to fetch single row', 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-time-tracker-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-time-tracker-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-time-tracker-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-time-tracker-card__total {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 0.95em;\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-time-tracker-card__distribution {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-time-tracker-card__distribution-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-time-tracker-card__distribution-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-time-tracker-card__distribution-dot {\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n}\n\n.cn-time-tracker-card__distribution-dot--client {\n\tbackground: var(--color-primary-element, #21468B);\n}\n\n.cn-time-tracker-card__distribution-dot--task {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-time-tracker-card__distribution-dot--time {\n\tbackground: var(--color-success, #46ba61);\n}\n\n.cn-time-tracker-card__distribution-count {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-time-tracker-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-time-tracker-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-time-tracker-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-time-tracker-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-time-tracker-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__chip-pill {\n\tdisplay: inline-block;\n\tpadding: 1px 6px;\n\tfont-size: 0.7em;\n\tfont-weight: 600;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\twhite-space: nowrap;\n}\n\n.cn-time-tracker-card__chip-pill--client {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__chip-pill--task {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__chip-pill--time {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-time-tracker-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.85em;\n}\n\n.cn-time-tracker-card__row--highlight {\n\tborder-color: var(--color-primary-element, #21468B);\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tfont-weight: 600;\n}\n\n.cn-time-tracker-card__title {\n\tflex: 1;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-time-tracker-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__duration {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_normalizeClass","_openBlock","_createTextVNode","_renderList"],"mappings":";;;;;;AAkEW,EAAA,KAAA,EAAA,6BAAA;;;;AAKa,EAAA,KAAK,EAAC,6BAA6B;;;;AAGrB,EAAA,KAAK,EAAC,6BAA4B;;;;AAGzD,EAAA,KAAK,EAAC,gCAAU;;AACtB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qCAAK,GAAA;;;AAGZ,EAAA,KAAA,EAAA,6BAAA;;;;AAIU,EAAA,KAAA,EAAA,oCAAA;;AAKN,MAAA,WAAA,GAAA,EAAA,KAAI,EAAE,0CAAC,GAAA;AAMP,MAAA,WAAA,GAAA,EAAA,KAAK,EAAC,0CAAmB,GAAA;;;AAGb,EAAA,KAAA,EAAA,uCAAA;;;;;AAYZ,EAAA,KAAA,EAAA,6BAAA;;;;;;;;;;;;;;;;;;;;;;;AA5DJ,IAAA,KAAA,EAAA,QAAA,CAAA,eAAA;AAAA,IAAA,IAAA,EAAA,QAAA,CAAA,QAAA;AAAA,IAAA,WAAA,EAAA,MAAA,CAAA,WAAA;;2BAC0B;OAAX,KAAO,CAAA,OAAA;mCAAI,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;WAGzB,MAAA,CAAA,OAAA,KAAA,eAAA;4CAYI,CAAAA,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;eAXE,KAAM,CAAA,MAAA;gCAAjBC,kBASM,CAAA,MAAA,EAAA;;AATa,oBAAA,KAAK,EAAC,4BAA4B;oBAAE,KAAK,EAAE,QAAI,CAAA,YAAA,CAAA,KAAA,CAAA,MAAA,CAAA;;oBACjEC,WAAmB,CAAA,gBAAA,EAAA,EAAZ,IAAI,EAAE,EAAE,EAAA,CAAA;oBACfC,kBAGE,CAAA,GAAA,EAAA;sBAFD,IAAG,EAAA,QAAA,CAAA,MAAA,CAAA,KAAA,CAAA,MAAA,CAAA;AACJ,sBAAA,MAAM,EAAC,QAAQ;AACf,sBAAA,GAAE,EAAA,UAAA;AAAA,qBAAA,EAAAC,eAAA,CAAA,QAAA,CAAA,QAAA,CAAA,KAAA,CAAA,MAAA,CAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;oBACDD,kBAEK,CAAA,MAAA,EAAA;AAFD,sBAAA,KAAK,EAACE,cAAA,CAAA,CAAA,iCAAuB,EAAA,QAAA,CAAA,aAAA,CAAA,KAAA,CAAA,MAAA,CAAA,CAAA,CAAA;AAC/B,qBAAA,EAAAD,eAAA,CAAA,QAAA,CAAA,SAAS,CAAC,KAAI,CAAA,MAAA,CAAA,CAAA,EAAA,CAAA,mBAAA;;kDAGX,CAAA,MAAA,YAAA,EAAAA,eAAA,CAAA,MAAA,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA,CAAA;;AAIC,YAAA,CAAA,MAAA,CAAA,OAAA,yBAAA,MAAA,CAAA,OAAA,KAAA,eAAA;4BAANH,kBAqCO,CAAAD,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;iBApCC,KAAQ,CAAA,QAAA;kCAAhBC,kBAEE,CAAA,KAAA,EAFF,UAEE,EAAAG,eAAA,CADD,KAAE,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;AAEU,oBAAA,CAAA,KAAA,CAAA,IAAI,CAAC,MAAM,KAAA,CAAA;oCAA1BH,kBAEE,CAAA,KAAA,EAFF,UAEE,EAAAG,eAAA,CADC,MAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;uDA+BR,iBAAA,EAAA;AA5BH,wBAAAD,kBAAA,CAEG,OAFH,UAEG,EAAA;AADH,0BAAAA,kBAAA,CAAmC,gCAAzB,QAAa,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;yBAErB,QAAA,CAAA,kBAAA;AAAJ,6BAAAG,SAAA,EAAA,EAAAL,kBAAA,CAGM,OAHN,UAGM,EAAA;8BAFLC,WAAO,CAAA,gBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;AAAA,8BAAAK,eAAA,CAAA;;;AAGE,wBAAA,CAAA,QAAA,CAAA,gBAAI,CAAA,MAAA,GAAA,CAAA;AAAd,6BAAAD,SAAA,EAAA,EAAAL,kBAAA,CAaE,MAbF,UAaE,EAAA;gDAZDA,kBAWK,CAAAD,QAAA,EAAA,IAAA,EAAAQ,UAAA,CAVO,QAAA,CAAA,gBAAA,EAAA,CAAJ,MAAI,KAAA;qDADZP,kBAWK,CAAA,IAAA,EAAA;kCATH,GAAC,EAAA,MAAA,CAAA,GAAA;AACF,kCAAA,KAAK,EAAC,wCAAwC;;AAC/C,kCAAAE,kBAAA,CAKQ,QALR,WAKQ,EAAA;oCAJNA,kBAED,CAAA,MAAA,EAAA;AADE,sCAAA,KAAM,EAAAE,cAAA,CAAA,CAAA,wCAAkC,EAC1C,CAAA,wCAAA,EAAA,MAAA,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA;;oDAAA,GACC,GAAAD,eAAA,CAAG,MAAM,CAAC,KAAK,CAAA,EAAA,CAAA,YAAA;;AAEhB,kCAAAD,kBAAA,CAA+B,MAA/B,EAAA,WAA+B,EAAAC,eAAA,CAAA,MAAA,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;;;;yBAGtB,QAAS,CAAA,UAAA;AAAlB,6BAAAE,SAAA,EAAA,EAAAL,kBAAA,CAMI,OANJ,WAMI,EAAA;8BALJC,WAAc,CAAA,gBAAA,EAAA,EAAP,IAAI,EAAE,EAAC,EAAA,CAAA;gDAIK,CAAA,GAAA,EAAA;gCAFlB,IAAI,EAAE,QAAM,CAAA,MAAA,CAAC,QAAU,CAAA,UAAA,CAAA;AACxB,gCAAA,MAAM,EAAC,QAAQ;AACf,gCAAA,GAAG,EAAC,UAAU;AAAI,+BAAA,EAAAE,eAAA,CAAA,QAAA,CAAA,QAAC,CAAA,QAAA,CAAA,UAAA,CAAA,CAAA,EAAA,CAAA,oBAAA,WAAA,CAAA;;;;;;iBAOb,KAAA,CAAA,QAAA;kCAATH,0BAAA,6BACI,KAAQ,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnTimeTrackerCard.vue3.js","sources":["../../../../../src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue?vue&type=template&id=4ee344cc&lang.js"],"sourcesContent":["<!--\n CnTimeTrackerCard — bespoke surface-aware widget for the `time-tracker`\n integration.\n\n Replaces the generic CnIntegrationCard for the `time-tracker` leaf.\n Branches on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N tracked entries\";\n sub-line with total tracked time (hours + minutes) summed over\n all linked time-entries; plus a most-recent row link.\n - detail-page : bounded row list with kind chip,\n duration, billable indicator — same shape as the tab but\n read-only.\n - single-entity : chip with row title + kind pill\n (referenceType: 'time-tracker').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnTimeTrackerTab; for `single-entity` the optional `value` prop\n addresses a single row by id.\n\n Note: the leaf slug is `time-tracker` (with a hyphen); the underlying\n NC app id is `timemanager` (no hyphen).\n\n See `openregister/openspec/changes/integration-time-tracker/` for the\n spec delta, ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardHeaderTitle\" :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-time-tracker-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<Clock :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"rowUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ rowTitle(entity) }}</a>\n\t\t\t\t<span class=\"cn-time-tracker-card__chip-pill\" :class=\"kindChipClass(entity)\">\n\t\t\t\t\t{{ kindLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-time-tracker-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total tracked time + 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-time-tracker-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-time-tracker-card__headline\">\n\t\t\t\t<div class=\"cn-time-tracker-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"totalDurationLabel\" class=\"cn-time-tracker-card__total\">\n\t\t\t\t\t<Timer :size=\"14\" />\n\t\t\t\t\t{{ totalDurationLabel }}\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"kindDistribution.length > 0\" class=\"cn-time-tracker-card__distribution\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in kindDistribution\"\n\t\t\t\t\t\t:key=\"bucket.key\"\n\t\t\t\t\t\tclass=\"cn-time-tracker-card__distribution-row\">\n\t\t\t\t\t\t<span class=\"cn-time-tracker-card__distribution-label\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"cn-time-tracker-card__distribution-dot\"\n\t\t\t\t\t\t\t\t:class=\"`cn-time-tracker-card__distribution-dot--${bucket.key}`\" />\n\t\t\t\t\t\t\t{{ bucket.label }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-time-tracker-card__distribution-count\">{{ bucket.count }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-time-tracker-card__headline-recent\">\n\t\t\t\t\t<Clock :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ rowTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: bounded row list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-time-tracker-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-time-tracker-card__rows\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t:key=\"rowKey(row)\"\n\t\t\t\t\tclass=\"cn-time-tracker-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-time-tracker-card__row--highlight': isLinked(row) }\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-time-tracker-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-time-tracker-card__chip-pill\" :class=\"kindChipClass(row)\">\n\t\t\t\t\t\t{{ kindLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"durationLabel(row)\" class=\"cn-time-tracker-card__duration\">\n\t\t\t\t\t\t{{ durationLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport Clock from 'vue-material-design-icons/Clock.vue'\nimport Timer from 'vue-material-design-icons/Timer.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']\n\n/**\n * CnTimeTrackerCard — bespoke surface-aware widget for the\n * `time-tracker` integration. See file-level docblock.\n */\nexport default {\n\tname: 'CnTimeTrackerCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, Clock, Timer },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'time-tracker'`). */\n\t\tintegrationId: { type: String, default: 'time-tracker' },\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 (row id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Time tracker') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Clock },\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 tracked time linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC TimeManager is currently unavailable.') },\n\t\t/** URL of the NC TimeManager app entry. */\n\t\ttimeTrackerAppUrl: { type: String, default: '/index.php/apps/timemanager' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trows: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardHeaderTitle() {\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\t/**\n\t\t * Rows bucketed by kind so the dashboard surface can show the\n\t\t * clients / tasks / entries distribution.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, count: number}>}\n\t\t */\n\t\tkindDistribution() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst kind = this.rowKind(row)\n\t\t\t\tif (!groups.has(kind)) {\n\t\t\t\t\tgroups.set(kind, { key: kind, label: this.kindLabel(row), count: 0 })\n\t\t\t\t}\n\t\t\t\tgroups.get(kind).count += 1\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} entry', '{count} entries', total, { count: total })\n\t\t},\n\n\t\t/**\n\t\t * Total tracked seconds across all rows that carry a `duration`\n\t\t * (clients without a duration don't contribute to this total —\n\t\t * they're parent containers).\n\t\t *\n\t\t * @return {number} Seconds total.\n\t\t */\n\t\ttotalSeconds() {\n\t\t\tlet total = 0\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst d = this.durationSeconds(row)\n\t\t\t\tif (d !== null) {\n\t\t\t\t\ttotal += d\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn total\n\t\t},\n\n\t\ttotalDurationLabel() {\n\t\t\tif (this.totalSeconds === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst totalMinutes = Math.round(this.totalSeconds / 60)\n\t\t\tconst hours = Math.floor(totalMinutes / 60)\n\t\t\tconst minutes = totalMinutes % 60\n\t\t\tif (hours > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{hours}h {minutes}m tracked', { hours, minutes })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{minutes}m tracked', { minutes })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.rows].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.startedAt ?? a.started_at ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.startedAt ?? b.started_at ?? b.linkedAt ?? b.created_at ?? '') || 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\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? row.data?.uuid ?? ''\n\t\t},\n\n\t\trowTitle(row) {\n\t\t\treturn row.title ?? row.name ?? this.rowKey(row)\n\t\t},\n\n\t\trowKind(row) {\n\t\t\tconst k = String(row.kind ?? row.type ?? '').toLowerCase()\n\t\t\tif (k === 'task') {\n\t\t\t\treturn 'task'\n\t\t\t}\n\t\t\tif (k === 'time' || k === 'entry' || k === 'time-entry') {\n\t\t\t\treturn 'time'\n\t\t\t}\n\t\t\treturn 'client'\n\t\t},\n\n\t\tkindLabel(row) {\n\t\t\tconst kind = this.rowKind(row)\n\t\t\tif (kind === 'task') {\n\t\t\t\treturn t('nextcloud-vue', 'Task')\n\t\t\t}\n\t\t\tif (kind === 'time') {\n\t\t\t\treturn t('nextcloud-vue', 'Entry')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tkindChipClass(row) {\n\t\t\treturn `cn-time-tracker-card__chip-pill--${this.rowKind(row)}`\n\t\t},\n\n\t\trowUrl(row) {\n\t\t\tif (row.url && safeHref(row.url) !== '#') {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst kind = this.rowKind(row)\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (kind === 'client') {\n\t\t\t\tconst clientId = row.clientId ?? id\n\t\t\t\tif (clientId) {\n\t\t\t\t\treturn `/index.php/apps/timemanager/clients/${clientId}`\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (kind === 'task') {\n\t\t\t\tconst taskId = row.taskId ?? id\n\t\t\t\tif (taskId) {\n\t\t\t\t\treturn `/index.php/apps/timemanager/tasks/${taskId}`\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (kind === 'time' && id) {\n\t\t\t\treturn `/index.php/apps/timemanager/times/${id}`\n\t\t\t}\n\t\t\tif (id) {\n\t\t\t\treturn `/index.php/apps/timemanager/projects/${id}`\n\t\t\t}\n\t\t\treturn this.timeTrackerAppUrl\n\t\t},\n\n\t\tdurationSeconds(row) {\n\t\t\tconst raw = row.duration ?? row.seconds ?? row.data?.duration ?? null\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.isNaN(num) ? null : num\n\t\t},\n\n\t\tdurationLabel(row) {\n\t\t\tconst total = this.durationSeconds(row)\n\t\t\tif (total === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst totalMinutes = Math.round(total / 60)\n\t\t\tconst hours = Math.floor(totalMinutes / 60)\n\t\t\tconst minutes = totalMinutes % 60\n\t\t\tif (hours > 0) {\n\t\t\t\treturn `${hours}h ${minutes}m`\n\t\t\t}\n\t\t\treturn `${minutes}m`\n\t\t},\n\n\t\tisLinked(row) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.rowKey(row)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(row) {\n\t\t\treturn this.durationLabel(row) || this.kindLabel(row)\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.rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rows = []\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('[CnTimeTrackerCard] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.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('[CnTimeTrackerCard] failed to fetch single row', 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-time-tracker-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-time-tracker-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-time-tracker-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-time-tracker-card__total {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 0.95em;\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-time-tracker-card__distribution {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-time-tracker-card__distribution-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-time-tracker-card__distribution-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-time-tracker-card__distribution-dot {\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n}\n\n.cn-time-tracker-card__distribution-dot--client {\n\tbackground: var(--color-primary-element, #21468B);\n}\n\n.cn-time-tracker-card__distribution-dot--task {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-time-tracker-card__distribution-dot--time {\n\tbackground: var(--color-success, #46ba61);\n}\n\n.cn-time-tracker-card__distribution-count {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-time-tracker-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-time-tracker-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-time-tracker-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-time-tracker-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-time-tracker-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__chip-pill {\n\tdisplay: inline-block;\n\tpadding: 1px 6px;\n\tfont-size: 0.7em;\n\tfont-weight: 600;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\twhite-space: nowrap;\n}\n\n.cn-time-tracker-card__chip-pill--client {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__chip-pill--task {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__chip-pill--time {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-main-background);\n}\n\n.cn-time-tracker-card__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-time-tracker-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.85em;\n}\n\n.cn-time-tracker-card__row--highlight {\n\tborder-color: var(--color-primary-element, #21468B);\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tfont-weight: 600;\n}\n\n.cn-time-tracker-card__title {\n\tflex: 1;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-time-tracker-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-time-tracker-card__duration {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_normalizeClass","_openBlock","_createTextVNode","_renderList"],"mappings":"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"}
|