@conduction/nextcloud-vue 2.56.0-beta.3 → 2.56.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +21 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +49 -5
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +2 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +2 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +78 -7
- package/dist/esm/utils/actionLink.js +5 -0
- package/dist/esm/utils/actionLink.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +16 -1
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/linkNavigation.js +6 -3
- package/dist/esm/utils/linkNavigation.js.map +1 -1
- package/dist/esm/utils/rowAuxClick.js +30 -5
- package/dist/esm/utils/rowAuxClick.js.map +1 -1
- package/dist/esm/utils/safeHref.js +18 -3
- package/dist/esm/utils/safeHref.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +885 -393
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +78 -7
- package/l10n/en.json +2 -0
- package/l10n/nl.json +2 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
- package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
- package/src/components/CnAppRoot/CnAppRoot.vue +31 -2
- package/src/components/CnBoardView/CnBoardView.vue +13 -5
- package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
- package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +131 -17
- package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
- package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
- package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
- package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
- package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
- package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
- package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
- package/src/components/CnObjectList/CnObjectList.vue +19 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
- package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
- package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
- package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
- package/src/components/CnRowActions/CnRowActions.vue +7 -2
- package/src/components/CnSetupWizard/CnSetupWizard.vue +57 -6
- package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
- package/src/components/CnTabs/CnTabs.vue +7 -3
- package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
- package/src/css/table.css +26 -0
- package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
- package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
- package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
- package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
- package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
- package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
- package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
- package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
- package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
- package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
- package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
- package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
- package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
- package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
- package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
- package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
- package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
- package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
- package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
- package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
- package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
- package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
- package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
- package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
- package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
- package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
- package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
- package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
- package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
- package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
- package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
- package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
- package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
- package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
- package/src/schemas/app-manifest-v2.schema.json +2 -1
- package/src/utils/actionLink.js +5 -0
- package/src/utils/actionsDispatcher.js +15 -0
- package/src/utils/linkNavigation.js +6 -3
- package/src/utils/rowAuxClick.js +29 -4
- package/src/utils/safeHref.js +18 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnCollectivesTab.vue2.js","sources":["../../../../../src/integrations/builtin/collectives/CnCollectivesTab.vue"],"sourcesContent":["<!--\n CnCollectivesTab — bespoke sidebar tab for the `collectives` integration.\n\n Replaces the generic CnIntegrationTab for the `collectives` leaf:\n renders linked Knowledge pages grouped by collective (emoji + title +\n optional content snippet + last-modified hint). Each row deep-links to\n the page inside the NC Collectives app via\n /index.php/apps/collectives/{collectiveSlug}/{pageSlug}\n (with a sane fallback to /index.php/apps/collectives/{pageId} when the\n provider only carries an id).\n\n Talks to the same OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/collectives`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CollectivesProvider`\n (storage strategy `link-table` — the marker `[or:{uuid}]` lives in the\n Collectives `collectives_pages.slug` column). Provider payload shape\n (verified against `CollectivesProvider::list`):\n { id, title, url, data: { id, slug, emoji, last_user_id, ... } }\n The richer \"spec\" shape (title, slug, emoji, content, fileId,\n lastModified, collectiveId, collectiveName, url) is accepted as well —\n the UI reads both top-level and `data.*` fields defensively so a future\n provider expansion stays drop-in.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat title\n list which loses the collective grouping, the emoji glyph, and the\n per-page content snippet — three signals that make Knowledge linkage\n legible at-a-glance. The bespoke tab surfaces them per row.\n\n Surface behaviour:\n - Empty state (\"No Knowledge pages linked yet\") + \"Open Knowledge\"\n CTA when zero rows.\n - Loading state via NcLoadingIcon for the full-tab spin.\n - 503 unavailable banner (matches CnIntegrationTab's AD-23 graceful\n degradation), generic error path on non-OK / fetch throw.\n\n See `openregister/openspec/changes/integration-collectives/` for the\n spec delta and ADR-019 (registry mechanism), ADR-022 (consumption).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-collectives-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-collectives-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div v-if=\"!degraded\" class=\"cn-collectives-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @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 page') }}\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 page') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<div v-else-if=\"error\" class=\"cn-collectives-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"pages.length === 0 && !degraded\"\n\t\t\tclass=\"cn-sidebar-tab__empty cn-collectives-tab__empty\">\n\t\t\t<BookOpenPageVariant :size=\"32\" class=\"cn-collectives-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=\"collectivesAppUrl\"\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<BookOpenPageVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCollectivesLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-else-if=\"pages.length > 0\" class=\"cn-collectives-tab__groups\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groupedPages\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-collectives-tab__group\">\n\t\t\t\t<header v-if=\"group.label\" class=\"cn-collectives-tab__group-header\">\n\t\t\t\t\t<FolderTextOutline :size=\"14\" />\n\t\t\t\t\t<span>{{ group.label }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-collectives-tab__list\">\n\t\t\t\t\t<NcListItem\n\t\t\t\t\t\tv-for=\"page in group.pages\"\n\t\t\t\t\t\t:key=\"pageKey(page)\"\n\t\t\t\t\t\tclass=\"cn-collectives-tab__row\"\n\t\t\t\t\t\t:name=\"pageTitle(page)\"\n\t\t\t\t\t\t:bold=\"true\"\n\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__emoji\" :aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<template v-if=\"pageEmoji(page)\">{{ pageEmoji(page) }}</template>\n\t\t\t\t\t\t\t\t<FileDocumentOutline v-else :size=\"22\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__subname\">\n\t\t\t\t\t\t\t\t<span v-if=\"sublineLabel(page)\" class=\"cn-collectives-tab__subname-line\">{{ sublineLabel(page) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"pageSnippet(page)\" class=\"cn-collectives-tab__snippet\">{{ pageSnippet(page) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"modifiedMs(page)\" #details>\n\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\tclass=\"cn-collectives-tab__time\"\n\t\t\t\t\t\t\t\t:timestamp=\"modifiedMs(page)\"\n\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Knowledge') }}\n\t\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkPage(page)\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink page') }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcListItem>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<CnCollectivePagePicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnCollectivePageCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport BookOpenPageVariant from 'vue-material-design-icons/BookOpenPageVariant.vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport FolderTextOutline from 'vue-material-design-icons/FolderTextOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.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 CnCollectivePageCreate from '../../../components/CnCollectivePageCreate/CnCollectivePageCreate.vue'\nimport CnCollectivePagePicker from '../../../components/CnCollectivePagePicker/CnCollectivePagePicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\nconst MS_PER_HOUR = 60 * 60 * 1000\nconst SNIPPET_MAX_CHARS = 160\n\n/**\n * CnCollectivesTab — bespoke sidebar list for the `collectives`\n * integration.\n *\n * Renders Knowledge pages grouped by parent collective; each row has\n * emoji + title + content snippet + last-modified hint and deep-links\n * to the page inside NC Collectives.\n */\nexport default {\n\tname: 'CnCollectivesTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tBookOpenPageVariant,\n\t\tFileDocumentOutline,\n\t\tFolderTextOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnCollectivePagePicker,\n\t\tCnCollectivePageCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'collectives'`). */\n\t\tintegrationId: { type: String, default: 'collectives' },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No Knowledge pages linked yet') },\n\t\t/** Pre-translated \"Open Knowledge\" CTA label. */\n\t\topenCollectivesLabel: { type: String, default: () => t('nextcloud-vue', 'Open Knowledge') },\n\t\t/** Pre-translated banner when Collectives is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Knowledge is currently unavailable.') },\n\t\t/** URL of the NC Collectives app entry. */\n\t\tcollectivesAppUrl: { type: String, default: '/index.php/apps/collectives' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpages: [],\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\tcomputed: {\n\t\tgroupedPages() {\n\t\t\tconst buckets = new Map()\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst key = String(this.collectiveKey(page) || '__default__')\n\t\t\t\tif (!buckets.has(key)) {\n\t\t\t\t\tbuckets.set(key, { key, label: this.collectiveLabel(page), pages: [] })\n\t\t\t\t}\n\t\t\t\tbuckets.get(key).pages.push(page)\n\t\t\t}\n\t\t\treturn Array.from(buckets.values())\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.fetchPages()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchPages()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchPages()\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\t/**\n\t\t * Base for the Tier-2 collectives endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcollectivesEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/collectives`\n\t\t},\n\n\t\t/**\n\t\t * Resolve the numeric page id from a row (top-level or `data.*`).\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The page id, or '' when absent.\n\t\t */\n\t\tpageId(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? ''\n\t\t},\n\n\t\tdataOf(page) {\n\t\t\treturn (page && typeof page.data === 'object' && page.data !== null) ? page.data : {}\n\t\t},\n\n\t\tpageKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? page.slug ?? d.slug ?? ''\n\t\t},\n\n\t\tpageTitle(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst candidate = page.pageTitle ?? d.pageTitle ?? page.title ?? d.title ?? d.slug ?? page.slug ?? ''\n\t\t\t// Provider returns the slug (which carries the `[or:{uuid}]`\n\t\t\t// marker) when no real title is available. Strip the marker\n\t\t\t// AND any dangling slug separator (`-`, `_`, `.`) left\n\t\t\t// behind — `phase-d1-page-[or:UUID]` would otherwise render\n\t\t\t// as `phase-d1-page-` with a trailing hyphen (D-1 bug).\n\t\t\tconst stripped = stripMarker(candidate).replace(/[-_.]+$/, '').replace(/^[-_.]+/, '').trim()\n\t\t\treturn stripped || String(this.pageKey(page))\n\t\t},\n\n\t\tpageEmoji(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst emoji = page.emoji ?? d.emoji ?? ''\n\t\t\treturn typeof emoji === 'string' ? emoji.trim() : ''\n\t\t},\n\n\t\tpageSnippet(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst raw = page.content ?? d.content ?? ''\n\t\t\tconst text = String(raw).replace(/\\s+/g, ' ').trim()\n\t\t\tif (text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (text.length > SNIPPET_MAX_CHARS) {\n\t\t\t\treturn text.slice(0, SNIPPET_MAX_CHARS).trimEnd() + '…'\n\t\t\t}\n\t\t\treturn text\n\t\t},\n\n\t\tpageUrl(page) {\n\t\t\tif (typeof page.url === 'string' && page.url !== '') {\n\t\t\t\treturn page.url\n\t\t\t}\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst collectiveSlug = page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\tconst pageSlug = page.slug ?? d.slug ?? this.pageKey(page)\n\t\t\tif (collectiveSlug !== '' && pageSlug !== '') {\n\t\t\t\treturn `/index.php/apps/collectives/${collectiveSlug}/${pageSlug}`\n\t\t\t}\n\t\t\treturn `/index.php/apps/collectives/${this.pageKey(page)}`\n\t\t},\n\n\t\tcollectiveKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.collectiveId ?? d.collectiveId ?? d.collective_id ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t},\n\n\t\tcollectiveLabel(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst name = page.collectiveName ?? d.collectiveName ?? d.collective_name ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\treturn String(name).trim()\n\t\t},\n\n\t\tmodifiedMs(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst v = page.lastModified ?? page.modified ?? page.updated ?? d.lastModified ?? d.modified ?? d.last_modified ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tmetaLabel(page) {\n\t\t\tconst ms = this.modifiedMs(page)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = Date.now() - ms\n\t\t\tif (diff < 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (diff < MS_PER_HOUR) {\n\t\t\t\tconst minutes = Math.max(1, Math.floor(diff / (60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} minutes ago', { n: minutes })\n\t\t\t}\n\t\t\tif (diff < MS_PER_DAY) {\n\t\t\t\tconst hours = Math.max(1, Math.floor(diff / MS_PER_HOUR))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} hours ago', { n: hours })\n\t\t\t}\n\t\t\tconst days = Math.max(1, Math.floor(diff / MS_PER_DAY))\n\t\t\treturn t('nextcloud-vue', 'Updated {n} days ago', { n: days })\n\t\t},\n\n\t\t/**\n\t\t * Subname line for a row: the parent collective name. NcDateTime\n\t\t * renders the \"last edited\" moment separately in the row's\n\t\t * #details slot, so the subname carries the collective only.\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t * @return {string} The collective name, or '' when unknown.\n\t\t */\n\t\tsublineLabel(page) {\n\t\t\treturn this.collectiveLabel(page)\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 onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.collectivesEndpoint()), {\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.fetchPages()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This page is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\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('[CnCollectivesTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.collectivesEndpoint()}/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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\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('[CnCollectivesTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkPage(page) {\n\t\t\tconst id = this.pageId(page)\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.collectivesEndpoint()}/${id}`), {\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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\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('[CnCollectivesTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchPages() {\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.collectivesEndpoint()), { 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.pages = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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('[CnCollectivesTab] failed to fetch Knowledge pages', err)\n\t\t\t\tthis.pages = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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-collectives-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-collectives-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-collectives-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-collectives-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-collectives-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__groups {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-collectives-tab__group-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.8em;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-bottom: 2px;\n\tpadding: 0 4px;\n}\n\n.cn-collectives-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Emoji glyph stands in for the NcListItem avatar — circular tile\n tinted with the collective accent, mirroring NC Collectives pages. */\n.cn-collectives-tab__emoji {\n\tflex-shrink: 0;\n\twidth: 40px;\n\theight: 40px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 1.25em;\n\tline-height: 1;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-primary-element);\n}\n\n.cn-collectives-tab__subname {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-collectives-tab__subname-line {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__snippet {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n}\n\n.cn-collectives-tab__time {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnCollectivePagePicker","CnCollectivePageCreate","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnCollectivesTab.vue2.js","sources":["../../../../../src/integrations/builtin/collectives/CnCollectivesTab.vue"],"sourcesContent":["<!--\n CnCollectivesTab — bespoke sidebar tab for the `collectives` integration.\n\n Replaces the generic CnIntegrationTab for the `collectives` leaf:\n renders linked Knowledge pages grouped by collective (emoji + title +\n optional content snippet + last-modified hint). Each row deep-links to\n the page inside the NC Collectives app via\n /index.php/apps/collectives/{collectiveSlug}/{pageSlug}\n (with a sane fallback to /index.php/apps/collectives/{pageId} when the\n provider only carries an id).\n\n Talks to the same OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/collectives`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CollectivesProvider`\n (storage strategy `link-table` — the marker `[or:{uuid}]` lives in the\n Collectives `collectives_pages.slug` column). Provider payload shape\n (verified against `CollectivesProvider::list`):\n { id, title, url, data: { id, slug, emoji, last_user_id, ... } }\n The richer \"spec\" shape (title, slug, emoji, content, fileId,\n lastModified, collectiveId, collectiveName, url) is accepted as well —\n the UI reads both top-level and `data.*` fields defensively so a future\n provider expansion stays drop-in.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat title\n list which loses the collective grouping, the emoji glyph, and the\n per-page content snippet — three signals that make Knowledge linkage\n legible at-a-glance. The bespoke tab surfaces them per row.\n\n Surface behaviour:\n - Empty state (\"No Knowledge pages linked yet\") + \"Open Knowledge\"\n CTA when zero rows.\n - Loading state via NcLoadingIcon for the full-tab spin.\n - 503 unavailable banner (matches CnIntegrationTab's AD-23 graceful\n degradation), generic error path on non-OK / fetch throw.\n\n See `openregister/openspec/changes/integration-collectives/` for the\n spec delta and ADR-019 (registry mechanism), ADR-022 (consumption).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-collectives-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-collectives-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div v-if=\"!degraded\" class=\"cn-collectives-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @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 page') }}\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 page') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<div v-else-if=\"error\" class=\"cn-collectives-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"pages.length === 0 && !degraded\"\n\t\t\tclass=\"cn-sidebar-tab__empty cn-collectives-tab__empty\">\n\t\t\t<BookOpenPageVariant :size=\"32\" class=\"cn-collectives-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=\"collectivesAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<BookOpenPageVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCollectivesLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-else-if=\"pages.length > 0\" class=\"cn-collectives-tab__groups\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groupedPages\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-collectives-tab__group\">\n\t\t\t\t<header v-if=\"group.label\" class=\"cn-collectives-tab__group-header\">\n\t\t\t\t\t<FolderTextOutline :size=\"14\" />\n\t\t\t\t\t<span>{{ group.label }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-collectives-tab__list\">\n\t\t\t\t\t<NcListItem\n\t\t\t\t\t\tv-for=\"page in group.pages\"\n\t\t\t\t\t\t:key=\"pageKey(page)\"\n\t\t\t\t\t\tclass=\"cn-collectives-tab__row\"\n\t\t\t\t\t\t:name=\"pageTitle(page)\"\n\t\t\t\t\t\t:bold=\"true\"\n\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__emoji\" :aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<template v-if=\"pageEmoji(page)\">{{ pageEmoji(page) }}</template>\n\t\t\t\t\t\t\t\t<FileDocumentOutline v-else :size=\"22\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__subname\">\n\t\t\t\t\t\t\t\t<span v-if=\"sublineLabel(page)\" class=\"cn-collectives-tab__subname-line\">{{ sublineLabel(page) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"pageSnippet(page)\" class=\"cn-collectives-tab__snippet\">{{ pageSnippet(page) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"modifiedMs(page)\" #details>\n\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\tclass=\"cn-collectives-tab__time\"\n\t\t\t\t\t\t\t\t:timestamp=\"modifiedMs(page)\"\n\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Knowledge') }}\n\t\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkPage(page)\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink page') }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcListItem>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<CnCollectivePagePicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnCollectivePageCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport BookOpenPageVariant from 'vue-material-design-icons/BookOpenPageVariant.vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport FolderTextOutline from 'vue-material-design-icons/FolderTextOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.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 CnCollectivePageCreate from '../../../components/CnCollectivePageCreate/CnCollectivePageCreate.vue'\nimport CnCollectivePagePicker from '../../../components/CnCollectivePagePicker/CnCollectivePagePicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\nconst MS_PER_HOUR = 60 * 60 * 1000\nconst SNIPPET_MAX_CHARS = 160\n\n/**\n * CnCollectivesTab — bespoke sidebar list for the `collectives`\n * integration.\n *\n * Renders Knowledge pages grouped by parent collective; each row has\n * emoji + title + content snippet + last-modified hint and deep-links\n * to the page inside NC Collectives.\n */\nexport default {\n\tname: 'CnCollectivesTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tBookOpenPageVariant,\n\t\tFileDocumentOutline,\n\t\tFolderTextOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnCollectivePagePicker,\n\t\tCnCollectivePageCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'collectives'`). */\n\t\tintegrationId: { type: String, default: 'collectives' },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No Knowledge pages linked yet') },\n\t\t/** Pre-translated \"Open Knowledge\" CTA label. */\n\t\topenCollectivesLabel: { type: String, default: () => t('nextcloud-vue', 'Open Knowledge') },\n\t\t/** Pre-translated banner when Collectives is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Knowledge is currently unavailable.') },\n\t\t/** URL of the NC Collectives app entry. */\n\t\tcollectivesAppUrl: { type: String, default: '/index.php/apps/collectives' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpages: [],\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\tcomputed: {\n\t\tgroupedPages() {\n\t\t\tconst buckets = new Map()\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst key = String(this.collectiveKey(page) || '__default__')\n\t\t\t\tif (!buckets.has(key)) {\n\t\t\t\t\tbuckets.set(key, { key, label: this.collectiveLabel(page), pages: [] })\n\t\t\t\t}\n\t\t\t\tbuckets.get(key).pages.push(page)\n\t\t\t}\n\t\t\treturn Array.from(buckets.values())\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.fetchPages()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchPages()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchPages()\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\t/**\n\t\t * Base for the Tier-2 collectives endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcollectivesEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/collectives`\n\t\t},\n\n\t\t/**\n\t\t * Resolve the numeric page id from a row (top-level or `data.*`).\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The page id, or '' when absent.\n\t\t */\n\t\tpageId(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? ''\n\t\t},\n\n\t\tdataOf(page) {\n\t\t\treturn (page && typeof page.data === 'object' && page.data !== null) ? page.data : {}\n\t\t},\n\n\t\tpageKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? page.slug ?? d.slug ?? ''\n\t\t},\n\n\t\tpageTitle(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst candidate = page.pageTitle ?? d.pageTitle ?? page.title ?? d.title ?? d.slug ?? page.slug ?? ''\n\t\t\t// Provider returns the slug (which carries the `[or:{uuid}]`\n\t\t\t// marker) when no real title is available. Strip the marker\n\t\t\t// AND any dangling slug separator (`-`, `_`, `.`) left\n\t\t\t// behind — `phase-d1-page-[or:UUID]` would otherwise render\n\t\t\t// as `phase-d1-page-` with a trailing hyphen (D-1 bug).\n\t\t\tconst stripped = stripMarker(candidate).replace(/[-_.]+$/, '').replace(/^[-_.]+/, '').trim()\n\t\t\treturn stripped || String(this.pageKey(page))\n\t\t},\n\n\t\tpageEmoji(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst emoji = page.emoji ?? d.emoji ?? ''\n\t\t\treturn typeof emoji === 'string' ? emoji.trim() : ''\n\t\t},\n\n\t\tpageSnippet(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst raw = page.content ?? d.content ?? ''\n\t\t\tconst text = String(raw).replace(/\\s+/g, ' ').trim()\n\t\t\tif (text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (text.length > SNIPPET_MAX_CHARS) {\n\t\t\t\treturn text.slice(0, SNIPPET_MAX_CHARS).trimEnd() + '…'\n\t\t\t}\n\t\t\treturn text\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\tpageUrl(page) {\n\t\t\tif (typeof page.url === 'string' && page.url !== '' && safeHref(page.url) !== '#') {\n\t\t\t\treturn page.url\n\t\t\t}\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst collectiveSlug = page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\tconst pageSlug = page.slug ?? d.slug ?? this.pageKey(page)\n\t\t\tif (collectiveSlug !== '' && pageSlug !== '') {\n\t\t\t\treturn `/index.php/apps/collectives/${collectiveSlug}/${pageSlug}`\n\t\t\t}\n\t\t\treturn `/index.php/apps/collectives/${this.pageKey(page)}`\n\t\t},\n\n\t\tcollectiveKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.collectiveId ?? d.collectiveId ?? d.collective_id ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t},\n\n\t\tcollectiveLabel(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst name = page.collectiveName ?? d.collectiveName ?? d.collective_name ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\treturn String(name).trim()\n\t\t},\n\n\t\tmodifiedMs(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst v = page.lastModified ?? page.modified ?? page.updated ?? d.lastModified ?? d.modified ?? d.last_modified ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tmetaLabel(page) {\n\t\t\tconst ms = this.modifiedMs(page)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = Date.now() - ms\n\t\t\tif (diff < 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (diff < MS_PER_HOUR) {\n\t\t\t\tconst minutes = Math.max(1, Math.floor(diff / (60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} minutes ago', { n: minutes })\n\t\t\t}\n\t\t\tif (diff < MS_PER_DAY) {\n\t\t\t\tconst hours = Math.max(1, Math.floor(diff / MS_PER_HOUR))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} hours ago', { n: hours })\n\t\t\t}\n\t\t\tconst days = Math.max(1, Math.floor(diff / MS_PER_DAY))\n\t\t\treturn t('nextcloud-vue', 'Updated {n} days ago', { n: days })\n\t\t},\n\n\t\t/**\n\t\t * Subname line for a row: the parent collective name. NcDateTime\n\t\t * renders the \"last edited\" moment separately in the row's\n\t\t * #details slot, so the subname carries the collective only.\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t * @return {string} The collective name, or '' when unknown.\n\t\t */\n\t\tsublineLabel(page) {\n\t\t\treturn this.collectiveLabel(page)\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 onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.collectivesEndpoint()), {\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.fetchPages()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This page is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\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('[CnCollectivesTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.collectivesEndpoint()}/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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\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('[CnCollectivesTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkPage(page) {\n\t\t\tconst id = this.pageId(page)\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.collectivesEndpoint()}/${id}`), {\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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\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('[CnCollectivesTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchPages() {\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.collectivesEndpoint()), { 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.pages = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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('[CnCollectivesTab] failed to fetch Knowledge pages', err)\n\t\t\t\tthis.pages = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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-collectives-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-collectives-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-collectives-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-collectives-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-collectives-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__groups {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-collectives-tab__group-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.8em;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-bottom: 2px;\n\tpadding: 0 4px;\n}\n\n.cn-collectives-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Emoji glyph stands in for the NcListItem avatar — circular tile\n tinted with the collective accent, mirroring NC Collectives pages. */\n.cn-collectives-tab__emoji {\n\tflex-shrink: 0;\n\twidth: 40px;\n\theight: 40px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 1.25em;\n\tline-height: 1;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-primary-element);\n}\n\n.cn-collectives-tab__subname {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-collectives-tab__subname-line {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__snippet {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n}\n\n.cn-collectives-tab__time {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnCollectivePagePicker","CnCollectivePageCreate","t"],"mappings":"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"}
|
|
@@ -109,8 +109,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
109
109
|
createVNode(_component_NcButton, {
|
|
110
110
|
variant: "primary",
|
|
111
111
|
href: $props.collectivesAppUrl,
|
|
112
|
-
target: "_blank"
|
|
113
|
-
rel: "noopener noreferrer"
|
|
112
|
+
target: "_blank"
|
|
114
113
|
}, {
|
|
115
114
|
icon: withCtx(() => [
|
|
116
115
|
createVNode(_component_BookOpenPageVariant, { size: 20 })
|
|
@@ -171,8 +170,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
171
170
|
createVNode(_component_NcActionLink, {
|
|
172
171
|
closeAfterClick: true,
|
|
173
172
|
href: $options.pageUrl(page),
|
|
174
|
-
target: "_blank"
|
|
175
|
-
rel: "noopener noreferrer"
|
|
173
|
+
target: "_blank"
|
|
176
174
|
}, {
|
|
177
175
|
icon: withCtx(() => [
|
|
178
176
|
createVNode(_component_OpenInNew, { size: 20 })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnCollectivesTab.vue3.js","sources":["../../../../../src/integrations/builtin/collectives/CnCollectivesTab.vue?vue&type=template&id=23e94f50&lang.js"],"sourcesContent":["<!--\n CnCollectivesTab — bespoke sidebar tab for the `collectives` integration.\n\n Replaces the generic CnIntegrationTab for the `collectives` leaf:\n renders linked Knowledge pages grouped by collective (emoji + title +\n optional content snippet + last-modified hint). Each row deep-links to\n the page inside the NC Collectives app via\n /index.php/apps/collectives/{collectiveSlug}/{pageSlug}\n (with a sane fallback to /index.php/apps/collectives/{pageId} when the\n provider only carries an id).\n\n Talks to the same OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/collectives`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CollectivesProvider`\n (storage strategy `link-table` — the marker `[or:{uuid}]` lives in the\n Collectives `collectives_pages.slug` column). Provider payload shape\n (verified against `CollectivesProvider::list`):\n { id, title, url, data: { id, slug, emoji, last_user_id, ... } }\n The richer \"spec\" shape (title, slug, emoji, content, fileId,\n lastModified, collectiveId, collectiveName, url) is accepted as well —\n the UI reads both top-level and `data.*` fields defensively so a future\n provider expansion stays drop-in.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat title\n list which loses the collective grouping, the emoji glyph, and the\n per-page content snippet — three signals that make Knowledge linkage\n legible at-a-glance. The bespoke tab surfaces them per row.\n\n Surface behaviour:\n - Empty state (\"No Knowledge pages linked yet\") + \"Open Knowledge\"\n CTA when zero rows.\n - Loading state via NcLoadingIcon for the full-tab spin.\n - 503 unavailable banner (matches CnIntegrationTab's AD-23 graceful\n degradation), generic error path on non-OK / fetch throw.\n\n See `openregister/openspec/changes/integration-collectives/` for the\n spec delta and ADR-019 (registry mechanism), ADR-022 (consumption).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-collectives-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-collectives-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div v-if=\"!degraded\" class=\"cn-collectives-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @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 page') }}\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 page') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<div v-else-if=\"error\" class=\"cn-collectives-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"pages.length === 0 && !degraded\"\n\t\t\tclass=\"cn-sidebar-tab__empty cn-collectives-tab__empty\">\n\t\t\t<BookOpenPageVariant :size=\"32\" class=\"cn-collectives-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=\"collectivesAppUrl\"\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<BookOpenPageVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCollectivesLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-else-if=\"pages.length > 0\" class=\"cn-collectives-tab__groups\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groupedPages\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-collectives-tab__group\">\n\t\t\t\t<header v-if=\"group.label\" class=\"cn-collectives-tab__group-header\">\n\t\t\t\t\t<FolderTextOutline :size=\"14\" />\n\t\t\t\t\t<span>{{ group.label }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-collectives-tab__list\">\n\t\t\t\t\t<NcListItem\n\t\t\t\t\t\tv-for=\"page in group.pages\"\n\t\t\t\t\t\t:key=\"pageKey(page)\"\n\t\t\t\t\t\tclass=\"cn-collectives-tab__row\"\n\t\t\t\t\t\t:name=\"pageTitle(page)\"\n\t\t\t\t\t\t:bold=\"true\"\n\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__emoji\" :aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<template v-if=\"pageEmoji(page)\">{{ pageEmoji(page) }}</template>\n\t\t\t\t\t\t\t\t<FileDocumentOutline v-else :size=\"22\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__subname\">\n\t\t\t\t\t\t\t\t<span v-if=\"sublineLabel(page)\" class=\"cn-collectives-tab__subname-line\">{{ sublineLabel(page) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"pageSnippet(page)\" class=\"cn-collectives-tab__snippet\">{{ pageSnippet(page) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"modifiedMs(page)\" #details>\n\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\tclass=\"cn-collectives-tab__time\"\n\t\t\t\t\t\t\t\t:timestamp=\"modifiedMs(page)\"\n\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Knowledge') }}\n\t\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkPage(page)\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink page') }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcListItem>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<CnCollectivePagePicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnCollectivePageCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport BookOpenPageVariant from 'vue-material-design-icons/BookOpenPageVariant.vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport FolderTextOutline from 'vue-material-design-icons/FolderTextOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.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 CnCollectivePageCreate from '../../../components/CnCollectivePageCreate/CnCollectivePageCreate.vue'\nimport CnCollectivePagePicker from '../../../components/CnCollectivePagePicker/CnCollectivePagePicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\nconst MS_PER_HOUR = 60 * 60 * 1000\nconst SNIPPET_MAX_CHARS = 160\n\n/**\n * CnCollectivesTab — bespoke sidebar list for the `collectives`\n * integration.\n *\n * Renders Knowledge pages grouped by parent collective; each row has\n * emoji + title + content snippet + last-modified hint and deep-links\n * to the page inside NC Collectives.\n */\nexport default {\n\tname: 'CnCollectivesTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tBookOpenPageVariant,\n\t\tFileDocumentOutline,\n\t\tFolderTextOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnCollectivePagePicker,\n\t\tCnCollectivePageCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'collectives'`). */\n\t\tintegrationId: { type: String, default: 'collectives' },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No Knowledge pages linked yet') },\n\t\t/** Pre-translated \"Open Knowledge\" CTA label. */\n\t\topenCollectivesLabel: { type: String, default: () => t('nextcloud-vue', 'Open Knowledge') },\n\t\t/** Pre-translated banner when Collectives is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Knowledge is currently unavailable.') },\n\t\t/** URL of the NC Collectives app entry. */\n\t\tcollectivesAppUrl: { type: String, default: '/index.php/apps/collectives' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpages: [],\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\tcomputed: {\n\t\tgroupedPages() {\n\t\t\tconst buckets = new Map()\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst key = String(this.collectiveKey(page) || '__default__')\n\t\t\t\tif (!buckets.has(key)) {\n\t\t\t\t\tbuckets.set(key, { key, label: this.collectiveLabel(page), pages: [] })\n\t\t\t\t}\n\t\t\t\tbuckets.get(key).pages.push(page)\n\t\t\t}\n\t\t\treturn Array.from(buckets.values())\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.fetchPages()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchPages()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchPages()\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\t/**\n\t\t * Base for the Tier-2 collectives endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcollectivesEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/collectives`\n\t\t},\n\n\t\t/**\n\t\t * Resolve the numeric page id from a row (top-level or `data.*`).\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The page id, or '' when absent.\n\t\t */\n\t\tpageId(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? ''\n\t\t},\n\n\t\tdataOf(page) {\n\t\t\treturn (page && typeof page.data === 'object' && page.data !== null) ? page.data : {}\n\t\t},\n\n\t\tpageKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? page.slug ?? d.slug ?? ''\n\t\t},\n\n\t\tpageTitle(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst candidate = page.pageTitle ?? d.pageTitle ?? page.title ?? d.title ?? d.slug ?? page.slug ?? ''\n\t\t\t// Provider returns the slug (which carries the `[or:{uuid}]`\n\t\t\t// marker) when no real title is available. Strip the marker\n\t\t\t// AND any dangling slug separator (`-`, `_`, `.`) left\n\t\t\t// behind — `phase-d1-page-[or:UUID]` would otherwise render\n\t\t\t// as `phase-d1-page-` with a trailing hyphen (D-1 bug).\n\t\t\tconst stripped = stripMarker(candidate).replace(/[-_.]+$/, '').replace(/^[-_.]+/, '').trim()\n\t\t\treturn stripped || String(this.pageKey(page))\n\t\t},\n\n\t\tpageEmoji(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst emoji = page.emoji ?? d.emoji ?? ''\n\t\t\treturn typeof emoji === 'string' ? emoji.trim() : ''\n\t\t},\n\n\t\tpageSnippet(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst raw = page.content ?? d.content ?? ''\n\t\t\tconst text = String(raw).replace(/\\s+/g, ' ').trim()\n\t\t\tif (text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (text.length > SNIPPET_MAX_CHARS) {\n\t\t\t\treturn text.slice(0, SNIPPET_MAX_CHARS).trimEnd() + '…'\n\t\t\t}\n\t\t\treturn text\n\t\t},\n\n\t\tpageUrl(page) {\n\t\t\tif (typeof page.url === 'string' && page.url !== '') {\n\t\t\t\treturn page.url\n\t\t\t}\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst collectiveSlug = page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\tconst pageSlug = page.slug ?? d.slug ?? this.pageKey(page)\n\t\t\tif (collectiveSlug !== '' && pageSlug !== '') {\n\t\t\t\treturn `/index.php/apps/collectives/${collectiveSlug}/${pageSlug}`\n\t\t\t}\n\t\t\treturn `/index.php/apps/collectives/${this.pageKey(page)}`\n\t\t},\n\n\t\tcollectiveKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.collectiveId ?? d.collectiveId ?? d.collective_id ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t},\n\n\t\tcollectiveLabel(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst name = page.collectiveName ?? d.collectiveName ?? d.collective_name ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\treturn String(name).trim()\n\t\t},\n\n\t\tmodifiedMs(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst v = page.lastModified ?? page.modified ?? page.updated ?? d.lastModified ?? d.modified ?? d.last_modified ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tmetaLabel(page) {\n\t\t\tconst ms = this.modifiedMs(page)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = Date.now() - ms\n\t\t\tif (diff < 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (diff < MS_PER_HOUR) {\n\t\t\t\tconst minutes = Math.max(1, Math.floor(diff / (60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} minutes ago', { n: minutes })\n\t\t\t}\n\t\t\tif (diff < MS_PER_DAY) {\n\t\t\t\tconst hours = Math.max(1, Math.floor(diff / MS_PER_HOUR))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} hours ago', { n: hours })\n\t\t\t}\n\t\t\tconst days = Math.max(1, Math.floor(diff / MS_PER_DAY))\n\t\t\treturn t('nextcloud-vue', 'Updated {n} days ago', { n: days })\n\t\t},\n\n\t\t/**\n\t\t * Subname line for a row: the parent collective name. NcDateTime\n\t\t * renders the \"last edited\" moment separately in the row's\n\t\t * #details slot, so the subname carries the collective only.\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t * @return {string} The collective name, or '' when unknown.\n\t\t */\n\t\tsublineLabel(page) {\n\t\t\treturn this.collectiveLabel(page)\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 onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.collectivesEndpoint()), {\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.fetchPages()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This page is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\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('[CnCollectivesTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.collectivesEndpoint()}/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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\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('[CnCollectivesTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkPage(page) {\n\t\t\tconst id = this.pageId(page)\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.collectivesEndpoint()}/${id}`), {\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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\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('[CnCollectivesTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchPages() {\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.collectivesEndpoint()), { 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.pages = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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('[CnCollectivesTab] failed to fetch Knowledge pages', err)\n\t\t\t\tthis.pages = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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-collectives-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-collectives-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-collectives-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-collectives-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-collectives-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__groups {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-collectives-tab__group-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.8em;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-bottom: 2px;\n\tpadding: 0 4px;\n}\n\n.cn-collectives-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Emoji glyph stands in for the NcListItem avatar — circular tile\n tinted with the collective accent, mirroring NC Collectives pages. */\n.cn-collectives-tab__emoji {\n\tflex-shrink: 0;\n\twidth: 40px;\n\theight: 40px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 1.25em;\n\tline-height: 1;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-primary-element);\n}\n\n.cn-collectives-tab__subname {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-collectives-tab__subname-line {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__snippet {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n}\n\n.cn-collectives-tab__time {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","_createTextVNode","_toDisplayString","_renderList","_createBlock"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnCollectivesTab.vue3.js","sources":["../../../../../src/integrations/builtin/collectives/CnCollectivesTab.vue?vue&type=template&id=23e94f50&lang.js"],"sourcesContent":["<!--\n CnCollectivesTab — bespoke sidebar tab for the `collectives` integration.\n\n Replaces the generic CnIntegrationTab for the `collectives` leaf:\n renders linked Knowledge pages grouped by collective (emoji + title +\n optional content snippet + last-modified hint). Each row deep-links to\n the page inside the NC Collectives app via\n /index.php/apps/collectives/{collectiveSlug}/{pageSlug}\n (with a sane fallback to /index.php/apps/collectives/{pageId} when the\n provider only carries an id).\n\n Talks to the same OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/collectives`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CollectivesProvider`\n (storage strategy `link-table` — the marker `[or:{uuid}]` lives in the\n Collectives `collectives_pages.slug` column). Provider payload shape\n (verified against `CollectivesProvider::list`):\n { id, title, url, data: { id, slug, emoji, last_user_id, ... } }\n The richer \"spec\" shape (title, slug, emoji, content, fileId,\n lastModified, collectiveId, collectiveName, url) is accepted as well —\n the UI reads both top-level and `data.*` fields defensively so a future\n provider expansion stays drop-in.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat title\n list which loses the collective grouping, the emoji glyph, and the\n per-page content snippet — three signals that make Knowledge linkage\n legible at-a-glance. The bespoke tab surfaces them per row.\n\n Surface behaviour:\n - Empty state (\"No Knowledge pages linked yet\") + \"Open Knowledge\"\n CTA when zero rows.\n - Loading state via NcLoadingIcon for the full-tab spin.\n - 503 unavailable banner (matches CnIntegrationTab's AD-23 graceful\n degradation), generic error path on non-OK / fetch throw.\n\n See `openregister/openspec/changes/integration-collectives/` for the\n spec delta and ADR-019 (registry mechanism), ADR-022 (consumption).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-collectives-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-collectives-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div v-if=\"!degraded\" class=\"cn-collectives-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @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 page') }}\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 page') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<div v-else-if=\"error\" class=\"cn-collectives-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"pages.length === 0 && !degraded\"\n\t\t\tclass=\"cn-sidebar-tab__empty cn-collectives-tab__empty\">\n\t\t\t<BookOpenPageVariant :size=\"32\" class=\"cn-collectives-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=\"collectivesAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<BookOpenPageVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCollectivesLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-else-if=\"pages.length > 0\" class=\"cn-collectives-tab__groups\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groupedPages\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-collectives-tab__group\">\n\t\t\t\t<header v-if=\"group.label\" class=\"cn-collectives-tab__group-header\">\n\t\t\t\t\t<FolderTextOutline :size=\"14\" />\n\t\t\t\t\t<span>{{ group.label }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-collectives-tab__list\">\n\t\t\t\t\t<NcListItem\n\t\t\t\t\t\tv-for=\"page in group.pages\"\n\t\t\t\t\t\t:key=\"pageKey(page)\"\n\t\t\t\t\t\tclass=\"cn-collectives-tab__row\"\n\t\t\t\t\t\t:name=\"pageTitle(page)\"\n\t\t\t\t\t\t:bold=\"true\"\n\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__emoji\" :aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<template v-if=\"pageEmoji(page)\">{{ pageEmoji(page) }}</template>\n\t\t\t\t\t\t\t\t<FileDocumentOutline v-else :size=\"22\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__subname\">\n\t\t\t\t\t\t\t\t<span v-if=\"sublineLabel(page)\" class=\"cn-collectives-tab__subname-line\">{{ sublineLabel(page) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"pageSnippet(page)\" class=\"cn-collectives-tab__snippet\">{{ pageSnippet(page) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"modifiedMs(page)\" #details>\n\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\tclass=\"cn-collectives-tab__time\"\n\t\t\t\t\t\t\t\t:timestamp=\"modifiedMs(page)\"\n\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Knowledge') }}\n\t\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkPage(page)\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink page') }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcListItem>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<CnCollectivePagePicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnCollectivePageCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport BookOpenPageVariant from 'vue-material-design-icons/BookOpenPageVariant.vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport FolderTextOutline from 'vue-material-design-icons/FolderTextOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.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 CnCollectivePageCreate from '../../../components/CnCollectivePageCreate/CnCollectivePageCreate.vue'\nimport CnCollectivePagePicker from '../../../components/CnCollectivePagePicker/CnCollectivePagePicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\nconst MS_PER_HOUR = 60 * 60 * 1000\nconst SNIPPET_MAX_CHARS = 160\n\n/**\n * CnCollectivesTab — bespoke sidebar list for the `collectives`\n * integration.\n *\n * Renders Knowledge pages grouped by parent collective; each row has\n * emoji + title + content snippet + last-modified hint and deep-links\n * to the page inside NC Collectives.\n */\nexport default {\n\tname: 'CnCollectivesTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tBookOpenPageVariant,\n\t\tFileDocumentOutline,\n\t\tFolderTextOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnCollectivePagePicker,\n\t\tCnCollectivePageCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'collectives'`). */\n\t\tintegrationId: { type: String, default: 'collectives' },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No Knowledge pages linked yet') },\n\t\t/** Pre-translated \"Open Knowledge\" CTA label. */\n\t\topenCollectivesLabel: { type: String, default: () => t('nextcloud-vue', 'Open Knowledge') },\n\t\t/** Pre-translated banner when Collectives is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Knowledge is currently unavailable.') },\n\t\t/** URL of the NC Collectives app entry. */\n\t\tcollectivesAppUrl: { type: String, default: '/index.php/apps/collectives' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpages: [],\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\tcomputed: {\n\t\tgroupedPages() {\n\t\t\tconst buckets = new Map()\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst key = String(this.collectiveKey(page) || '__default__')\n\t\t\t\tif (!buckets.has(key)) {\n\t\t\t\t\tbuckets.set(key, { key, label: this.collectiveLabel(page), pages: [] })\n\t\t\t\t}\n\t\t\t\tbuckets.get(key).pages.push(page)\n\t\t\t}\n\t\t\treturn Array.from(buckets.values())\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.fetchPages()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchPages()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchPages()\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\t/**\n\t\t * Base for the Tier-2 collectives endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcollectivesEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/collectives`\n\t\t},\n\n\t\t/**\n\t\t * Resolve the numeric page id from a row (top-level or `data.*`).\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The page id, or '' when absent.\n\t\t */\n\t\tpageId(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? ''\n\t\t},\n\n\t\tdataOf(page) {\n\t\t\treturn (page && typeof page.data === 'object' && page.data !== null) ? page.data : {}\n\t\t},\n\n\t\tpageKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? page.slug ?? d.slug ?? ''\n\t\t},\n\n\t\tpageTitle(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst candidate = page.pageTitle ?? d.pageTitle ?? page.title ?? d.title ?? d.slug ?? page.slug ?? ''\n\t\t\t// Provider returns the slug (which carries the `[or:{uuid}]`\n\t\t\t// marker) when no real title is available. Strip the marker\n\t\t\t// AND any dangling slug separator (`-`, `_`, `.`) left\n\t\t\t// behind — `phase-d1-page-[or:UUID]` would otherwise render\n\t\t\t// as `phase-d1-page-` with a trailing hyphen (D-1 bug).\n\t\t\tconst stripped = stripMarker(candidate).replace(/[-_.]+$/, '').replace(/^[-_.]+/, '').trim()\n\t\t\treturn stripped || String(this.pageKey(page))\n\t\t},\n\n\t\tpageEmoji(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst emoji = page.emoji ?? d.emoji ?? ''\n\t\t\treturn typeof emoji === 'string' ? emoji.trim() : ''\n\t\t},\n\n\t\tpageSnippet(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst raw = page.content ?? d.content ?? ''\n\t\t\tconst text = String(raw).replace(/\\s+/g, ' ').trim()\n\t\t\tif (text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (text.length > SNIPPET_MAX_CHARS) {\n\t\t\t\treturn text.slice(0, SNIPPET_MAX_CHARS).trimEnd() + '…'\n\t\t\t}\n\t\t\treturn text\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\tpageUrl(page) {\n\t\t\tif (typeof page.url === 'string' && page.url !== '' && safeHref(page.url) !== '#') {\n\t\t\t\treturn page.url\n\t\t\t}\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst collectiveSlug = page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\tconst pageSlug = page.slug ?? d.slug ?? this.pageKey(page)\n\t\t\tif (collectiveSlug !== '' && pageSlug !== '') {\n\t\t\t\treturn `/index.php/apps/collectives/${collectiveSlug}/${pageSlug}`\n\t\t\t}\n\t\t\treturn `/index.php/apps/collectives/${this.pageKey(page)}`\n\t\t},\n\n\t\tcollectiveKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.collectiveId ?? d.collectiveId ?? d.collective_id ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t},\n\n\t\tcollectiveLabel(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst name = page.collectiveName ?? d.collectiveName ?? d.collective_name ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\treturn String(name).trim()\n\t\t},\n\n\t\tmodifiedMs(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst v = page.lastModified ?? page.modified ?? page.updated ?? d.lastModified ?? d.modified ?? d.last_modified ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tmetaLabel(page) {\n\t\t\tconst ms = this.modifiedMs(page)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = Date.now() - ms\n\t\t\tif (diff < 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (diff < MS_PER_HOUR) {\n\t\t\t\tconst minutes = Math.max(1, Math.floor(diff / (60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} minutes ago', { n: minutes })\n\t\t\t}\n\t\t\tif (diff < MS_PER_DAY) {\n\t\t\t\tconst hours = Math.max(1, Math.floor(diff / MS_PER_HOUR))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} hours ago', { n: hours })\n\t\t\t}\n\t\t\tconst days = Math.max(1, Math.floor(diff / MS_PER_DAY))\n\t\t\treturn t('nextcloud-vue', 'Updated {n} days ago', { n: days })\n\t\t},\n\n\t\t/**\n\t\t * Subname line for a row: the parent collective name. NcDateTime\n\t\t * renders the \"last edited\" moment separately in the row's\n\t\t * #details slot, so the subname carries the collective only.\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t * @return {string} The collective name, or '' when unknown.\n\t\t */\n\t\tsublineLabel(page) {\n\t\t\treturn this.collectiveLabel(page)\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 onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.collectivesEndpoint()), {\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.fetchPages()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This page is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\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('[CnCollectivesTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.collectivesEndpoint()}/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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\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('[CnCollectivesTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkPage(page) {\n\t\t\tconst id = this.pageId(page)\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.collectivesEndpoint()}/${id}`), {\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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\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('[CnCollectivesTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchPages() {\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.collectivesEndpoint()), { 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.pages = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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('[CnCollectivesTab] failed to fetch Knowledge pages', err)\n\t\t\t\tthis.pages = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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-collectives-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-collectives-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-collectives-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-collectives-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-collectives-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__groups {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-collectives-tab__group-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.8em;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-bottom: 2px;\n\tpadding: 0 4px;\n}\n\n.cn-collectives-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Emoji glyph stands in for the NcListItem avatar — circular tile\n tinted with the collective accent, mirroring NC Collectives pages. */\n.cn-collectives-tab__emoji {\n\tflex-shrink: 0;\n\twidth: 40px;\n\theight: 40px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 1.25em;\n\tline-height: 1;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-primary-element);\n}\n\n.cn-collectives-tab__subname {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-collectives-tab__subname-line {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__snippet {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n}\n\n.cn-collectives-tab__time {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createVNode","_createElementVNode","_openBlock","_createElementBlock","_createTextVNode","_createBlock","_toDisplayString","_renderList","_Fragment"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { translate, translatePlural } from '@nextcloud/l10n';
|
|
|
2
2
|
import { NcLoadingIcon } from '@nextcloud/vue';
|
|
3
3
|
import CurrencyEur from 'vue-material-design-icons/CurrencyEur.vue';
|
|
4
4
|
import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
|
|
5
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
5
6
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
6
7
|
|
|
7
8
|
const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
|
|
@@ -144,7 +145,7 @@ var script = {
|
|
|
144
145
|
},
|
|
145
146
|
|
|
146
147
|
rowUrl(row) {
|
|
147
|
-
if (row.url) {
|
|
148
|
+
if (row.url && safeHref(row.url) !== '#') {
|
|
148
149
|
return row.url
|
|
149
150
|
}
|
|
150
151
|
const projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnCospendCard.vue2.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendCard.vue"],"sourcesContent":["<!--\n CnCospendCard — bespoke surface-aware widget for the `cospend`\n integration.\n\n Replaces the generic CnIntegrationCard for the `cospend` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N costs linked\";\n sub-line with total amount across all linked bills (per\n currency); plus a most-recent row link.\n - detail-page : bounded row list with type chip,\n amount, currency, payer — same shape as the tab but read-only.\n - single-entity : chip with row title + type chip\n (referenceType: 'cospend').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnCospendTab; for `single-entity` the optional `value` prop addresses\n a single row by id.\n\n See `openregister/openspec/changes/integration-cospend/` for the spec\n 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-cospend-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<CurrencyEur :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-cospend-card__chip-pill\" :class=\"typeChipClass(entity)\">\n\t\t\t\t\t{{ typeLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cospend-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total amount + 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-cospend-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-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-cospend-card__headline\">\n\t\t\t\t<div class=\"cn-cospend-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"totals.length > 0\" class=\"cn-cospend-card__totals\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in totals\"\n\t\t\t\t\t\t:key=\"bucket.currency\"\n\t\t\t\t\t\tclass=\"cn-cospend-card__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-label\">\n\t\t\t\t\t\t\t<CurrencyEur :size=\"12\" />\n\t\t\t\t\t\t\t{{ bucket.currency }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-amount\">{{ bucket.amountLabel }}</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-cospend-card__headline-recent\">\n\t\t\t\t\t<CurrencyEur :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-cospend-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-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-cospend-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-cospend-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-cospend-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-cospend-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(row)\">\n\t\t\t\t\t\t{{ typeLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-card__amount\">\n\t\t\t\t\t\t{{ amountLabel(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 CurrencyEur from 'vue-material-design-icons/CurrencyEur.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 * CnCospendCard — bespoke surface-aware widget for the `cospend`\n * integration. See the file-level docblock for surface-by-surface\n * behaviour.\n */\nexport default {\n\tname: 'CnCospendCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CurrencyEur },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'cospend'`). */\n\t\tintegrationId: { type: String, default: 'cospend' },\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', 'Costs') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => CurrencyEur },\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 costs linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Costs is currently unavailable.') },\n\t\t/** URL of the NC Cospend app entry. */\n\t\tcospendAppUrl: { type: String, default: '/index.php/apps/cospend' },\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 * Totals grouped by currency code so dashboards can show a\n\t\t * per-currency sum (Cospend supports multi-currency projects).\n\t\t *\n\t\t * @return {Array<{currency: string, total: number, amountLabel: string}>}\n\t\t */\n\t\ttotals() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tif (this.rowType(row) !== 'bill' && this.amountRaw(row) === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst amount = this.amountRaw(row)\n\t\t\t\tif (amount === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst currency = this.currencyOf(row) || ''\n\t\t\t\tconst existing = groups.get(currency) || { currency, total: 0 }\n\t\t\t\texisting.total += amount\n\t\t\t\tgroups.set(currency, existing)\n\t\t\t}\n\t\t\treturn Array.from(groups.values()).map((b) => ({\n\t\t\t\tcurrency: b.currency || t('nextcloud-vue', 'Unspecified'),\n\t\t\t\ttotal: b.total,\n\t\t\t\tamountLabel: b.total.toFixed(2),\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} cost', '{count} costs', total, { count: total })\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.date ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.date ?? 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 ?? ''\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\trowUrl(row) {\n\t\t\tif (row.url) {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null\n\t\t\tif (projectId !== null && projectId !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${projectId}`\n\t\t\t}\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (id !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${id}`\n\t\t\t}\n\t\t\treturn this.cospendAppUrl\n\t\t},\n\n\t\trowType(row) {\n\t\t\tconst t = String(row.type ?? row.kind ?? '').toLowerCase()\n\t\t\tif (t === 'bill' || t === 'expense') {\n\t\t\t\treturn 'bill'\n\t\t\t}\n\t\t\treturn 'project'\n\t\t},\n\n\t\ttypeLabel(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? t('nextcloud-vue', 'Bill')\n\t\t\t\t: t('nextcloud-vue', 'Project')\n\t\t},\n\n\t\ttypeChipClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-card__chip-pill--bill'\n\t\t\t\t: 'cn-cospend-card__chip-pill--project'\n\t\t},\n\n\t\tamountRaw(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? 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\tcurrencyOf(row) {\n\t\t\treturn row.currency ?? row.currency_name ?? row.data?.currency_name ?? row.data?.currency ?? ''\n\t\t},\n\n\t\tamountLabel(row) {\n\t\t\tconst num = this.amountRaw(row)\n\t\t\tif (num === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\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.amountLabel(row) || this.typeLabel(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('[CnCospendCard] 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('[CnCospendCard] 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-cospend-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-cospend-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-cospend-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-card__totals {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cospend-card__totals-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-cospend-card__totals-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-cospend-card__totals-amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-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-cospend-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip-pill--bill {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__chip-pill--project {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-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-cospend-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-cospend-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-cospend-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-cospend-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__amount {\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":"CnCospendCard.vue2.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendCard.vue"],"sourcesContent":["<!--\n CnCospendCard — bespoke surface-aware widget for the `cospend`\n integration.\n\n Replaces the generic CnIntegrationCard for the `cospend` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N costs linked\";\n sub-line with total amount across all linked bills (per\n currency); plus a most-recent row link.\n - detail-page : bounded row list with type chip,\n amount, currency, payer — same shape as the tab but read-only.\n - single-entity : chip with row title + type chip\n (referenceType: 'cospend').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnCospendTab; for `single-entity` the optional `value` prop addresses\n a single row by id.\n\n See `openregister/openspec/changes/integration-cospend/` for the spec\n 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-cospend-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<CurrencyEur :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-cospend-card__chip-pill\" :class=\"typeChipClass(entity)\">\n\t\t\t\t\t{{ typeLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cospend-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total amount + 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-cospend-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-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-cospend-card__headline\">\n\t\t\t\t<div class=\"cn-cospend-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"totals.length > 0\" class=\"cn-cospend-card__totals\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in totals\"\n\t\t\t\t\t\t:key=\"bucket.currency\"\n\t\t\t\t\t\tclass=\"cn-cospend-card__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-label\">\n\t\t\t\t\t\t\t<CurrencyEur :size=\"12\" />\n\t\t\t\t\t\t\t{{ bucket.currency }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-amount\">{{ bucket.amountLabel }}</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-cospend-card__headline-recent\">\n\t\t\t\t\t<CurrencyEur :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-cospend-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-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-cospend-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-cospend-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-cospend-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-cospend-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(row)\">\n\t\t\t\t\t\t{{ typeLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-card__amount\">\n\t\t\t\t\t\t{{ amountLabel(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 CurrencyEur from 'vue-material-design-icons/CurrencyEur.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 * CnCospendCard — bespoke surface-aware widget for the `cospend`\n * integration. See the file-level docblock for surface-by-surface\n * behaviour.\n */\nexport default {\n\tname: 'CnCospendCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CurrencyEur },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'cospend'`). */\n\t\tintegrationId: { type: String, default: 'cospend' },\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', 'Costs') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => CurrencyEur },\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 costs linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Costs is currently unavailable.') },\n\t\t/** URL of the NC Cospend app entry. */\n\t\tcospendAppUrl: { type: String, default: '/index.php/apps/cospend' },\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 * Totals grouped by currency code so dashboards can show a\n\t\t * per-currency sum (Cospend supports multi-currency projects).\n\t\t *\n\t\t * @return {Array<{currency: string, total: number, amountLabel: string}>}\n\t\t */\n\t\ttotals() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tif (this.rowType(row) !== 'bill' && this.amountRaw(row) === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst amount = this.amountRaw(row)\n\t\t\t\tif (amount === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst currency = this.currencyOf(row) || ''\n\t\t\t\tconst existing = groups.get(currency) || { currency, total: 0 }\n\t\t\t\texisting.total += amount\n\t\t\t\tgroups.set(currency, existing)\n\t\t\t}\n\t\t\treturn Array.from(groups.values()).map((b) => ({\n\t\t\t\tcurrency: b.currency || t('nextcloud-vue', 'Unspecified'),\n\t\t\t\ttotal: b.total,\n\t\t\t\tamountLabel: b.total.toFixed(2),\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} cost', '{count} costs', total, { count: total })\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.date ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.date ?? 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 ?? ''\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\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 projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null\n\t\t\tif (projectId !== null && projectId !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${projectId}`\n\t\t\t}\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (id !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${id}`\n\t\t\t}\n\t\t\treturn this.cospendAppUrl\n\t\t},\n\n\t\trowType(row) {\n\t\t\tconst t = String(row.type ?? row.kind ?? '').toLowerCase()\n\t\t\tif (t === 'bill' || t === 'expense') {\n\t\t\t\treturn 'bill'\n\t\t\t}\n\t\t\treturn 'project'\n\t\t},\n\n\t\ttypeLabel(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? t('nextcloud-vue', 'Bill')\n\t\t\t\t: t('nextcloud-vue', 'Project')\n\t\t},\n\n\t\ttypeChipClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-card__chip-pill--bill'\n\t\t\t\t: 'cn-cospend-card__chip-pill--project'\n\t\t},\n\n\t\tamountRaw(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? 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\tcurrencyOf(row) {\n\t\t\treturn row.currency ?? row.currency_name ?? row.data?.currency_name ?? row.data?.currency ?? ''\n\t\t},\n\n\t\tamountLabel(row) {\n\t\t\tconst num = this.amountRaw(row)\n\t\t\tif (num === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\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.amountLabel(row) || this.typeLabel(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('[CnCospendCard] 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('[CnCospendCard] 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-cospend-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-cospend-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-cospend-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-card__totals {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cospend-card__totals-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-cospend-card__totals-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-cospend-card__totals-amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-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-cospend-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip-pill--bill {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__chip-pill--project {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-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-cospend-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-cospend-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-cospend-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-cospend-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__amount {\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":"CnCospendCard.vue3.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendCard.vue?vue&type=template&id=b777ec12&lang.js"],"sourcesContent":["<!--\n CnCospendCard — bespoke surface-aware widget for the `cospend`\n integration.\n\n Replaces the generic CnIntegrationCard for the `cospend` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N costs linked\";\n sub-line with total amount across all linked bills (per\n currency); plus a most-recent row link.\n - detail-page : bounded row list with type chip,\n amount, currency, payer — same shape as the tab but read-only.\n - single-entity : chip with row title + type chip\n (referenceType: 'cospend').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnCospendTab; for `single-entity` the optional `value` prop addresses\n a single row by id.\n\n See `openregister/openspec/changes/integration-cospend/` for the spec\n 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-cospend-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<CurrencyEur :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-cospend-card__chip-pill\" :class=\"typeChipClass(entity)\">\n\t\t\t\t\t{{ typeLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cospend-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total amount + 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-cospend-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-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-cospend-card__headline\">\n\t\t\t\t<div class=\"cn-cospend-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"totals.length > 0\" class=\"cn-cospend-card__totals\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in totals\"\n\t\t\t\t\t\t:key=\"bucket.currency\"\n\t\t\t\t\t\tclass=\"cn-cospend-card__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-label\">\n\t\t\t\t\t\t\t<CurrencyEur :size=\"12\" />\n\t\t\t\t\t\t\t{{ bucket.currency }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-amount\">{{ bucket.amountLabel }}</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-cospend-card__headline-recent\">\n\t\t\t\t\t<CurrencyEur :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-cospend-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-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-cospend-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-cospend-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-cospend-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-cospend-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(row)\">\n\t\t\t\t\t\t{{ typeLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-card__amount\">\n\t\t\t\t\t\t{{ amountLabel(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 CurrencyEur from 'vue-material-design-icons/CurrencyEur.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 * CnCospendCard — bespoke surface-aware widget for the `cospend`\n * integration. See the file-level docblock for surface-by-surface\n * behaviour.\n */\nexport default {\n\tname: 'CnCospendCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CurrencyEur },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'cospend'`). */\n\t\tintegrationId: { type: String, default: 'cospend' },\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', 'Costs') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => CurrencyEur },\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 costs linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Costs is currently unavailable.') },\n\t\t/** URL of the NC Cospend app entry. */\n\t\tcospendAppUrl: { type: String, default: '/index.php/apps/cospend' },\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 * Totals grouped by currency code so dashboards can show a\n\t\t * per-currency sum (Cospend supports multi-currency projects).\n\t\t *\n\t\t * @return {Array<{currency: string, total: number, amountLabel: string}>}\n\t\t */\n\t\ttotals() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tif (this.rowType(row) !== 'bill' && this.amountRaw(row) === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst amount = this.amountRaw(row)\n\t\t\t\tif (amount === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst currency = this.currencyOf(row) || ''\n\t\t\t\tconst existing = groups.get(currency) || { currency, total: 0 }\n\t\t\t\texisting.total += amount\n\t\t\t\tgroups.set(currency, existing)\n\t\t\t}\n\t\t\treturn Array.from(groups.values()).map((b) => ({\n\t\t\t\tcurrency: b.currency || t('nextcloud-vue', 'Unspecified'),\n\t\t\t\ttotal: b.total,\n\t\t\t\tamountLabel: b.total.toFixed(2),\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} cost', '{count} costs', total, { count: total })\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.date ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.date ?? 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 ?? ''\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\trowUrl(row) {\n\t\t\tif (row.url) {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null\n\t\t\tif (projectId !== null && projectId !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${projectId}`\n\t\t\t}\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (id !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${id}`\n\t\t\t}\n\t\t\treturn this.cospendAppUrl\n\t\t},\n\n\t\trowType(row) {\n\t\t\tconst t = String(row.type ?? row.kind ?? '').toLowerCase()\n\t\t\tif (t === 'bill' || t === 'expense') {\n\t\t\t\treturn 'bill'\n\t\t\t}\n\t\t\treturn 'project'\n\t\t},\n\n\t\ttypeLabel(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? t('nextcloud-vue', 'Bill')\n\t\t\t\t: t('nextcloud-vue', 'Project')\n\t\t},\n\n\t\ttypeChipClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-card__chip-pill--bill'\n\t\t\t\t: 'cn-cospend-card__chip-pill--project'\n\t\t},\n\n\t\tamountRaw(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? 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\tcurrencyOf(row) {\n\t\t\treturn row.currency ?? row.currency_name ?? row.data?.currency_name ?? row.data?.currency ?? ''\n\t\t},\n\n\t\tamountLabel(row) {\n\t\t\tconst num = this.amountRaw(row)\n\t\t\tif (num === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\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.amountLabel(row) || this.typeLabel(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('[CnCospendCard] 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('[CnCospendCard] 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-cospend-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-cospend-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-cospend-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-card__totals {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cospend-card__totals-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-cospend-card__totals-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-cospend-card__totals-amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-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-cospend-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip-pill--bill {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__chip-pill--project {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-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-cospend-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-cospend-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-cospend-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-cospend-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__amount {\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","_renderList"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnCospendCard.vue3.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendCard.vue?vue&type=template&id=b777ec12&lang.js"],"sourcesContent":["<!--\n CnCospendCard — bespoke surface-aware widget for the `cospend`\n integration.\n\n Replaces the generic CnIntegrationCard for the `cospend` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N costs linked\";\n sub-line with total amount across all linked bills (per\n currency); plus a most-recent row link.\n - detail-page : bounded row list with type chip,\n amount, currency, payer — same shape as the tab but read-only.\n - single-entity : chip with row title + type chip\n (referenceType: 'cospend').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnCospendTab; for `single-entity` the optional `value` prop addresses\n a single row by id.\n\n See `openregister/openspec/changes/integration-cospend/` for the spec\n 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-cospend-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<CurrencyEur :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-cospend-card__chip-pill\" :class=\"typeChipClass(entity)\">\n\t\t\t\t\t{{ typeLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cospend-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total amount + 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-cospend-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-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-cospend-card__headline\">\n\t\t\t\t<div class=\"cn-cospend-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"totals.length > 0\" class=\"cn-cospend-card__totals\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in totals\"\n\t\t\t\t\t\t:key=\"bucket.currency\"\n\t\t\t\t\t\tclass=\"cn-cospend-card__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-label\">\n\t\t\t\t\t\t\t<CurrencyEur :size=\"12\" />\n\t\t\t\t\t\t\t{{ bucket.currency }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-amount\">{{ bucket.amountLabel }}</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-cospend-card__headline-recent\">\n\t\t\t\t\t<CurrencyEur :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-cospend-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-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-cospend-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-cospend-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-cospend-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-cospend-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(row)\">\n\t\t\t\t\t\t{{ typeLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-card__amount\">\n\t\t\t\t\t\t{{ amountLabel(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 CurrencyEur from 'vue-material-design-icons/CurrencyEur.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 * CnCospendCard — bespoke surface-aware widget for the `cospend`\n * integration. See the file-level docblock for surface-by-surface\n * behaviour.\n */\nexport default {\n\tname: 'CnCospendCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CurrencyEur },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'cospend'`). */\n\t\tintegrationId: { type: String, default: 'cospend' },\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', 'Costs') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => CurrencyEur },\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 costs linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Costs is currently unavailable.') },\n\t\t/** URL of the NC Cospend app entry. */\n\t\tcospendAppUrl: { type: String, default: '/index.php/apps/cospend' },\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 * Totals grouped by currency code so dashboards can show a\n\t\t * per-currency sum (Cospend supports multi-currency projects).\n\t\t *\n\t\t * @return {Array<{currency: string, total: number, amountLabel: string}>}\n\t\t */\n\t\ttotals() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tif (this.rowType(row) !== 'bill' && this.amountRaw(row) === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst amount = this.amountRaw(row)\n\t\t\t\tif (amount === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst currency = this.currencyOf(row) || ''\n\t\t\t\tconst existing = groups.get(currency) || { currency, total: 0 }\n\t\t\t\texisting.total += amount\n\t\t\t\tgroups.set(currency, existing)\n\t\t\t}\n\t\t\treturn Array.from(groups.values()).map((b) => ({\n\t\t\t\tcurrency: b.currency || t('nextcloud-vue', 'Unspecified'),\n\t\t\t\ttotal: b.total,\n\t\t\t\tamountLabel: b.total.toFixed(2),\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} cost', '{count} costs', total, { count: total })\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.date ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.date ?? 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 ?? ''\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\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 projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null\n\t\t\tif (projectId !== null && projectId !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${projectId}`\n\t\t\t}\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (id !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${id}`\n\t\t\t}\n\t\t\treturn this.cospendAppUrl\n\t\t},\n\n\t\trowType(row) {\n\t\t\tconst t = String(row.type ?? row.kind ?? '').toLowerCase()\n\t\t\tif (t === 'bill' || t === 'expense') {\n\t\t\t\treturn 'bill'\n\t\t\t}\n\t\t\treturn 'project'\n\t\t},\n\n\t\ttypeLabel(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? t('nextcloud-vue', 'Bill')\n\t\t\t\t: t('nextcloud-vue', 'Project')\n\t\t},\n\n\t\ttypeChipClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-card__chip-pill--bill'\n\t\t\t\t: 'cn-cospend-card__chip-pill--project'\n\t\t},\n\n\t\tamountRaw(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? 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\tcurrencyOf(row) {\n\t\t\treturn row.currency ?? row.currency_name ?? row.data?.currency_name ?? row.data?.currency ?? ''\n\t\t},\n\n\t\tamountLabel(row) {\n\t\t\tconst num = this.amountRaw(row)\n\t\t\tif (num === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\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.amountLabel(row) || this.typeLabel(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('[CnCospendCard] 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('[CnCospendCard] 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-cospend-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-cospend-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-cospend-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-card__totals {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cospend-card__totals-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-cospend-card__totals-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-cospend-card__totals-amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-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-cospend-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip-pill--bill {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__chip-pill--project {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-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-cospend-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-cospend-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-cospend-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-cospend-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__amount {\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","_renderList"],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ import __sfc_main$2 from '../../../components/CnCospendCreate/CnCospendCreate.vu
|
|
|
11
11
|
import __sfc_main$1 from '../../../components/CnCospendPicker/CnCospendPicker.vue.js';
|
|
12
12
|
import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
|
|
13
13
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
14
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* CnCospendTab — bespoke sidebar tab for the `cospend` integration.
|
|
@@ -224,7 +225,7 @@ var script = {
|
|
|
224
225
|
},
|
|
225
226
|
|
|
226
227
|
rowUrl(row) {
|
|
227
|
-
if (row.url) {
|
|
228
|
+
if (row.url && safeHref(row.url) !== '#') {
|
|
228
229
|
return row.url
|
|
229
230
|
}
|
|
230
231
|
const projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null;
|