@conduction/nextcloud-vue 2.56.0-beta.4 → 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/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/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +1 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +1 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +71 -7
- package/dist/esm/utils/actionLink.js +5 -0
- package/dist/esm/utils/actionLink.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +16 -1
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/linkNavigation.js +6 -3
- package/dist/esm/utils/linkNavigation.js.map +1 -1
- package/dist/esm/utils/rowAuxClick.js +30 -5
- package/dist/esm/utils/rowAuxClick.js.map +1 -1
- package/dist/esm/utils/safeHref.js +18 -3
- package/dist/esm/utils/safeHref.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +809 -383
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +71 -7
- package/l10n/en.json +1 -0
- package/l10n/nl.json +1 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
- package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
- package/src/components/CnBoardView/CnBoardView.vue +13 -5
- package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
- package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +131 -17
- package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
- package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
- package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
- package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
- package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
- package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
- package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
- package/src/components/CnObjectList/CnObjectList.vue +19 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
- package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
- package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
- package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
- package/src/components/CnRowActions/CnRowActions.vue +7 -2
- package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
- package/src/components/CnTabs/CnTabs.vue +7 -3
- package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
- package/src/css/table.css +26 -0
- package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
- package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
- package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
- package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
- package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
- package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
- package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
- package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
- package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
- package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
- package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
- package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
- package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
- package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
- package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
- package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
- package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
- package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
- package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
- package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
- package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
- package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
- package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
- package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
- package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
- package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
- package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
- package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
- package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
- package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
- package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
- package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
- package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
- package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
- package/src/utils/actionLink.js +5 -0
- package/src/utils/actionsDispatcher.js +15 -0
- package/src/utils/linkNavigation.js +6 -3
- package/src/utils/rowAuxClick.js +29 -4
- package/src/utils/safeHref.js +18 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnOpenprojectTab.vue3.js","sources":["../../../../../src/integrations/builtin/openproject/CnOpenprojectTab.vue?vue&type=template&id=4c26c5c4&lang.js"],"sourcesContent":["<!--\n CnOpenprojectTab — bespoke sidebar tab for the `openproject` integration.\n\n Replaces the generic CnIntegrationTab for the `openproject` leaf:\n renders the work packages linked to the parent OR object as a flat\n list, each row showing subject, type badge (Task / Bug / Feature /\n …), status pill (New / In progress / Closed / …), priority indicator,\n assignee avatar, and project name. Clicking a row deep-links to the\n work package in OpenProject.\n\n Talks to the OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/openproject`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\OpenProjectProvider`\n (which delegates through `ExternalIntegrationRouter` to the\n Integriq source named `openproject`).\n\n Surface behaviour (per ADR-017 graceful degradation + ADR-019 external\n storage policy):\n - Unconfigured: 412 / `health=unavailable` from the provider →\n \"Configure OpenProject connection\" CTA pointing at Integriq.\n - Authorisation expired: 401 with `authStatus: 'expired'` →\n reconnect-in-Integriq banner.\n - Other 4xx/5xx: surface the upstream message (not a silent empty).\n - Loading: spinner.\n - Empty: \"No work packages linked yet\" + \"Open OpenProject\" CTA.\n - Degraded (503): \"OpenProject is currently unavailable\" banner.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link\n list which loses OpenProject's primary signals — *type of work*,\n *current status* and *who's responsible*. Surfacing them inline\n reproduces enough of the OpenProject row affordance for case handlers\n to triage at a glance without context-switching to OpenProject itself.\n\n As the first external-storage integration this tab also pioneers the\n auth-status surfacing the umbrella's `IntegrationHealth` contract\n exposes — silently failing on 401 is a debugging black hole.\n\n See `openregister/openspec/changes/integration-openproject/` for the\n spec delta, ADR-019 (registry mechanism), ADR-022 (sidebar tab\n contract).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-openproject-tab\">\n\t\t<div v-if=\"authBanner\" class=\"cn-openproject-tab__banner cn-openproject-tab__banner--auth\" role=\"alert\">\n\t\t\t<LockOutline :size=\"18\" />\n\t\t\t<span>{{ authBanner }}</span>\n\t\t\t<a\n\t\t\t\tv-if=\"openconnectorUrl\"\n\t\t\t\t:href=\"openconnectorUrl\"\n\t\t\t\tclass=\"cn-openproject-tab__banner-cta\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener\">{{ openconnectorLabel }}</a>\n\t\t</div>\n\t\t<div v-else-if=\"degraded\" class=\"cn-openproject-tab__banner cn-openproject-tab__banner--warn\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<!-- Link / Create actions — hidden while unconfigured (the empty\n\t\t state below carries the Configure CTA instead) and while an\n\t\t auth banner is showing. -->\n\t\t<div v-if=\"!unconfigured && !authBanner\" class=\"cn-openproject-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 work package') }}\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 work package') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Unconfigured: provider reports the Integriq `openproject` source is absent. -->\n\t\t<div\n\t\t\tv-else-if=\"unconfigured\"\n\t\t\tclass=\"cn-sidebar-tab__empty cn-openproject-tab__empty\">\n\t\t\t<Briefcase :size=\"32\" class=\"cn-openproject-tab__empty-icon\" />\n\t\t\t<p>{{ unconfiguredLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"openconnectorUrl\"\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<CogOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ configureLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-else-if=\"error\" class=\"cn-openproject-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<div v-else-if=\"workPackages.length === 0\" class=\"cn-sidebar-tab__empty cn-openproject-tab__empty\">\n\t\t\t<Briefcase :size=\"32\" class=\"cn-openproject-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:href=\"openprojectAppUrl\"\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<OpenInNew :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openOpenprojectLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<ul v-else class=\"cn-openproject-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"wp in workPackages\"\n\t\t\t\t:key=\"wpKey(wp)\"\n\t\t\t\tclass=\"cn-openproject-tab__row\"\n\t\t\t\t:class=\"rowClass(wp)\"\n\t\t\t\t:name=\"wpSubject(wp)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"wpUrl(wp)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<!-- Type indicator: a coloured square chip echoing OpenProject's\n\t\t\t\t type colour-coding (Task / Bug / Feature / Milestone). -->\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-openproject-tab__type-icon\"\n\t\t\t\t\t\t:class=\"typeBadgeClass(wp)\"\n\t\t\t\t\t\t:title=\"typeTitle(wp)\">\n\t\t\t\t\t\t<RhombusOutline v-if=\"isMilestoneType(wp)\" :size=\"18\" />\n\t\t\t\t\t\t<Bug v-else-if=\"isBugType(wp)\" :size=\"18\" />\n\t\t\t\t\t\t<StarFourPointsOutline v-else-if=\"isFeatureType(wp)\" :size=\"18\" />\n\t\t\t\t\t\t<CheckboxMarkedOutline v-else :size=\"18\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<!-- Sub-line: #id reference + status chip, the OpenProject row meta. -->\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-openproject-tab__subline\">\n\t\t\t\t\t\t<span v-if=\"wpReference(wp)\" class=\"cn-openproject-tab__ref\">{{ wpReference(wp) }}</span>\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tv-if=\"wpStatus(wp)\"\n\t\t\t\t\t\t\tclass=\"cn-openproject-tab__status-pill\"\n\t\t\t\t\t\t\t:class=\"statusPillClass(wp)\"\n\t\t\t\t\t\t\t:label=\"wpStatus(wp)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(wp)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<span v-if=\"wpProject(wp)\" class=\"cn-openproject-tab__project\">\n\t\t\t\t\t\t\t<Briefcase :size=\"12\" />\n\t\t\t\t\t\t\t{{ wpProject(wp) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<!-- Trailing column: type badge label so the type stays legible. -->\n\t\t\t\t<template v-if=\"wpType(wp)\" #details>\n\t\t\t\t\t<span class=\"cn-openproject-tab__type-label\">{{ wpType(wp) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<!-- Right-edge indicators: priority marker + assignee avatar. -->\n\t\t\t\t<template #indicator>\n\t\t\t\t\t<span class=\"cn-openproject-tab__indicators\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"wpPriority(wp)\"\n\t\t\t\t\t\t\tclass=\"cn-openproject-tab__priority\"\n\t\t\t\t\t\t\t:class=\"priorityClass(wp)\"\n\t\t\t\t\t\t\t:title=\"priorityTitle(wp)\">\n\t\t\t\t\t\t\t<AlertCircleOutline v-if=\"isHighPriority(wp)\" :size=\"14\" />\n\t\t\t\t\t\t\t<ChevronDoubleUp v-else-if=\"isMediumPriority(wp)\" :size=\"14\" />\n\t\t\t\t\t\t\t<ChevronUp v-else :size=\"14\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\tv-if=\"wpAssignee(wp)\"\n\t\t\t\t\t\t\tclass=\"cn-openproject-tab__assignee\"\n\t\t\t\t\t\t\t:size=\"24\"\n\t\t\t\t\t\t\t:displayName=\"wpAssignee(wp)\"\n\t\t\t\t\t\t\t:user=\"assigneeSeed(wp)\"\n\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t\t:disableTooltip=\"false\"\n\t\t\t\t\t\t\thideStatus\n\t\t\t\t\t\t\t:title=\"assigneeTitle(wp)\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-openproject-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlinkWorkPackage(wp)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink work package') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnOpenProjectPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:openconnectorUrl=\"openconnectorUrl\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnOpenProjectCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:openconnectorUrl=\"openconnectorUrl\"\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, NcAvatar, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport Briefcase from 'vue-material-design-icons/Briefcase.vue'\nimport Bug from 'vue-material-design-icons/Bug.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport ChevronDoubleUp from 'vue-material-design-icons/ChevronDoubleUp.vue'\nimport ChevronUp from 'vue-material-design-icons/ChevronUp.vue'\nimport CogOutline from 'vue-material-design-icons/CogOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RhombusOutline from 'vue-material-design-icons/RhombusOutline.vue'\nimport StarFourPointsOutline from 'vue-material-design-icons/StarFourPointsOutline.vue'\nimport CnOpenProjectCreate from '../../../components/CnOpenProjectCreate/CnOpenProjectCreate.vue'\nimport CnOpenProjectPicker from '../../../components/CnOpenProjectPicker/CnOpenProjectPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnOpenprojectTab — bespoke sidebar tab for the `openproject` integration.\n *\n * Renders linked work packages with type / status / priority /\n * assignee, plus auth-status surfacing for Integriq configured /\n * missing / expired states. See the file-level docblock for the full\n * surface contract.\n */\nexport default {\n\tname: 'CnOpenprojectTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tBriefcase,\n\t\tBug,\n\t\tCheckboxMarkedOutline,\n\t\tChevronDoubleUp,\n\t\tChevronUp,\n\t\tCogOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tLockOutline,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tRhombusOutline,\n\t\tStarFourPointsOutline,\n\t\tCnStatusBadge,\n\t\tCnOpenProjectPicker,\n\t\tCnOpenProjectCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'openproject'`). */\n\t\tintegrationId: { type: String, default: 'openproject' },\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: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No work packages linked yet'),\n\t\t},\n\n\t\t/** Pre-translated label for the \"Open OpenProject\" CTA. */\n\t\topenOpenprojectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Open OpenProject'),\n\t\t},\n\n\t\t/** Pre-translated label for the \"Configure OpenProject connection\" CTA. */\n\t\tconfigureLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Configure OpenProject connection'),\n\t\t},\n\n\t\t/** Pre-translated label shown when the integration is unconfigured. */\n\t\tunconfiguredLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'OpenProject is not configured yet. Add an `openproject` source in Integriq to start linking work packages.'),\n\t\t},\n\n\t\t/** Pre-translated banner when OpenProject is unavailable. */\n\t\tunavailableLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'OpenProject is currently unavailable.'),\n\t\t},\n\n\t\t/** Pre-translated banner when the OAuth/API token has expired. */\n\t\tauthExpiredLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Authorisation for OpenProject expired. Reconnect the source in Integriq to restore access.'),\n\t\t},\n\n\t\t/** Pre-translated link label to the Integriq admin. */\n\t\topenconnectorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Open Integriq'),\n\t\t},\n\n\t\t/** URL of the Integriq source admin page (for the `openproject` source). */\n\t\topenconnectorUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '/index.php/apps/openconnector/sources/openproject',\n\t\t},\n\n\t\t/** Fallback URL of the OpenProject app entry. */\n\t\topenprojectAppUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: 'https://www.openproject.org',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tworkPackages: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tauthBanner: '',\n\t\t\tunconfigured: false,\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchWorkPackages()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchWorkPackages()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchWorkPackages()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${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 openproject endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\topenProjectEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/openproject`)\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(this.openProjectEndpoint(), {\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.fetchWorkPackages()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This work package is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link work package.')\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('[CnOpenprojectTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link work package.')\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(`${this.openProjectEndpoint()}/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.fetchWorkPackages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create work package.')\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('[CnOpenprojectTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create work package.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkWorkPackage(wp) {\n\t\t\tconst id = this.wpKey(wp)\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(`${this.openProjectEndpoint()}/${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.fetchWorkPackages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink work package.')\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('[CnOpenprojectTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink work package.')\n\t\t\t}\n\t\t},\n\n\t\twpKey(wp) {\n\t\t\treturn wp.id ?? wp.reference ?? ''\n\t\t},\n\n\t\t/**\n\t\t * The OpenProject `#id` reference shown on the row sub-line.\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {string} A `#123` reference, or '' when no id is known.\n\t\t */\n\t\twpReference(wp) {\n\t\t\tconst id = wp.id ?? wp.reference ?? ''\n\t\t\tif (id === '' || id === null || id === undefined) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst str = String(id)\n\t\t\treturn str.charAt(0) === '#' ? str : '#' + str\n\t\t},\n\n\t\t/**\n\t\t * Stable seed for NcAvatar's deterministic colour + initials.\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {string} A non-empty seed.\n\t\t */\n\t\tassigneeSeed(wp) {\n\t\t\treturn String(this.wpAssignee(wp) || this.wpKey(wp) || 'op')\n\t\t},\n\n\t\t/**\n\t\t * Row modifier classes (high-priority emphasis on the left edge).\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {object} The class map.\n\t\t */\n\t\trowClass(wp) {\n\t\t\treturn { 'cn-openproject-tab__row--high-priority': this.isHighPriority(wp) }\n\t\t},\n\n\t\ttypeTitle(wp) {\n\t\t\treturn t('nextcloud-vue', 'Type: {type}', { type: this.wpType(wp) || t('nextcloud-vue', 'Task') })\n\t\t},\n\n\t\tisBugType(wp) {\n\t\t\treturn String(this.wpType(wp)).toLowerCase().includes('bug')\n\t\t},\n\n\t\tisMilestoneType(wp) {\n\t\t\tconst type = String(this.wpType(wp)).toLowerCase()\n\t\t\treturn type.includes('milestone') || type.includes('phase')\n\t\t},\n\n\t\tisFeatureType(wp) {\n\t\t\tconst type = String(this.wpType(wp)).toLowerCase()\n\t\t\treturn type.includes('feature') || type.includes('user story') || type.includes('epic')\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge variant matching the OpenProject status family.\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {string} A CnStatusBadge variant.\n\t\t */\n\t\tstatusVariant(wp) {\n\t\t\tconst status = String(this.wpStatus(wp)).toLowerCase()\n\t\t\tif (status.includes('closed') || status.includes('done') || status.includes('resolved')) {\n\t\t\t\treturn 'success'\n\t\t\t}\n\t\t\tif (status.includes('progress') || status.includes('developed') || status.includes('review')) {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\tif (status.includes('reject') || status.includes('block')) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn 'info'\n\t\t},\n\n\t\t/**\n\t\t * Priority-marker emphasis class.\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {object} The class map.\n\t\t */\n\t\tpriorityClass(wp) {\n\t\t\treturn {\n\t\t\t\t'cn-openproject-tab__priority--high': this.isHighPriority(wp),\n\t\t\t\t'cn-openproject-tab__priority--medium': this.isMediumPriority(wp),\n\t\t\t}\n\t\t},\n\n\t\twpSubject(wp) {\n\t\t\treturn wp.title ?? wp.subject ?? wp.name ?? this.wpKey(wp)\n\t\t},\n\n\t\twpUrl(wp) {\n\t\t\treturn wp.url ?? wp._links?.self?.href ?? this.openprojectAppUrl\n\t\t},\n\n\t\twpStatus(wp) {\n\t\t\treturn wp.status ?? wp._links?.status?.title ?? ''\n\t\t},\n\n\t\twpType(wp) {\n\t\t\treturn wp.type ?? wp._links?.type?.title ?? wp._embedded?.type?.name ?? ''\n\t\t},\n\n\t\twpPriority(wp) {\n\t\t\treturn wp.priority ?? wp._links?.priority?.title ?? wp._embedded?.priority?.name ?? ''\n\t\t},\n\n\t\twpAssignee(wp) {\n\t\t\treturn wp.assignee\n\t\t\t\t?? wp._links?.assignee?.title\n\t\t\t\t?? wp._embedded?.assignee?.name\n\t\t\t\t?? ''\n\t\t},\n\n\t\twpProject(wp) {\n\t\t\treturn wp.project\n\t\t\t\t?? wp._links?.project?.title\n\t\t\t\t?? wp._embedded?.project?.name\n\t\t\t\t?? ''\n\t\t},\n\n\t\tassigneeTitle(wp) {\n\t\t\treturn t('nextcloud-vue', 'Assigned to {name}', { name: this.wpAssignee(wp) })\n\t\t},\n\n\t\tpriorityTitle(wp) {\n\t\t\treturn t('nextcloud-vue', 'Priority: {priority}', { priority: this.wpPriority(wp) })\n\t\t},\n\n\t\tisHighPriority(wp) {\n\t\t\tconst priority = String(this.wpPriority(wp)).toLowerCase()\n\t\t\treturn priority === 'high' || priority === 'immediate' || priority === 'urgent'\n\t\t},\n\n\t\tisMediumPriority(wp) {\n\t\t\treturn String(this.wpPriority(wp)).toLowerCase() === 'normal'\n\t\t},\n\n\t\tstatusPillClass(wp) {\n\t\t\tconst status = String(this.wpStatus(wp)).toLowerCase()\n\t\t\tif (status.includes('closed') || status.includes('done') || status.includes('resolved')) {\n\t\t\t\treturn 'cn-openproject-tab__status-pill--done'\n\t\t\t}\n\t\t\tif (status.includes('progress') || status.includes('developed') || status.includes('review')) {\n\t\t\t\treturn 'cn-openproject-tab__status-pill--progress'\n\t\t\t}\n\t\t\tif (status.includes('reject') || status.includes('block')) {\n\t\t\t\treturn 'cn-openproject-tab__status-pill--blocked'\n\t\t\t}\n\t\t\treturn 'cn-openproject-tab__status-pill--new'\n\t\t},\n\n\t\ttypeBadgeClass(wp) {\n\t\t\tconst type = String(this.wpType(wp)).toLowerCase()\n\t\t\tif (type.includes('bug')) {\n\t\t\t\treturn 'cn-openproject-tab__type-badge--bug'\n\t\t\t}\n\t\t\tif (type.includes('feature') || type.includes('user story') || type.includes('epic')) {\n\t\t\t\treturn 'cn-openproject-tab__type-badge--feature'\n\t\t\t}\n\t\t\treturn 'cn-openproject-tab__type-badge--task'\n\t\t},\n\n\t\tasync fetchWorkPackages() {\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\tthis.authBanner = ''\n\t\t\tthis.unconfigured = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results\n\t\t\t\t\t\t|| data.items\n\t\t\t\t\t\t|| data._embedded?.elements\n\t\t\t\t\t\t|| (Array.isArray(data) ? data : [])\n\t\t\t\t\t\t|| []\n\t\t\t\t\tthis.workPackages = rows\n\t\t\t\t} else if (response.status === 401 || response.status === 403) {\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.authBanner = this.authExpiredLabel\n\t\t\t\t} else if (response.status === 412) {\n\t\t\t\t\t// Provider returned \"preconditions failed\" — convention for \"unconfigured\".\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.unconfigured = true\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else if (response.status === 404) {\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.unconfigured = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.error = await this.extractError(response)\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('[CnOpenprojectTab] failed to fetch work packages', err)\n\t\t\t\tthis.workPackages = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load work packages.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync extractError(response) {\n\t\t\ttry {\n\t\t\t\tconst body = await response.json()\n\t\t\t\tif (body && typeof body.message === 'string' && body.message !== '') {\n\t\t\t\t\treturn body.message\n\t\t\t\t}\n\t\t\t\tif (body && typeof body.error === 'string' && body.error !== '') {\n\t\t\t\t\treturn body.error\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Fall through.\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Could not load work packages.')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-openproject-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n}\n\n.cn-openproject-tab__unlink {\n\tflex-shrink: 0;\n}\n\n.cn-openproject-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\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-openproject-tab__banner--warn {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-openproject-tab__banner--auth {\n\tbackground: var(--color-error, #e9322d);\n}\n\n.cn-openproject-tab__banner-cta {\n\tmargin-left: auto;\n\tcolor: var(--color-main-background);\n\ttext-decoration: underline;\n}\n\n.cn-openproject-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-openproject-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-openproject-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-openproject-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* High-priority work packages get a coloured left rail so case handlers\n spot them while scanning the list. */\n.cn-openproject-tab__row--high-priority {\n\tborder-left: 3px solid var(--color-error);\n}\n\n/* Type indicator: a rounded square chip echoing OpenProject's\n colour-coded work-package types. */\n.cn-openproject-tab__type-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 30px;\n\theight: 30px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-primary-element, #1A67A3);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-openproject-tab__type-icon.cn-openproject-tab__type-badge--bug {\n\tbackground: var(--color-error);\n}\n\n.cn-openproject-tab__type-icon.cn-openproject-tab__type-badge--feature {\n\tbackground: var(--color-success, #46ba61);\n}\n\n.cn-openproject-tab__type-icon.cn-openproject-tab__type-badge--task {\n\tbackground: #1A67A3;\n}\n\n.cn-openproject-tab__subline {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tflex-wrap: wrap;\n\tgap: 6px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* OpenProject-style `#id` reference, monospaced for a ticket feel. */\n.cn-openproject-tab__ref {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-openproject-tab__project {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-openproject-tab__type-label {\n\tfont-size: 0.78em;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n.cn-openproject-tab__indicators {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-openproject-tab__priority {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-openproject-tab__priority--high {\n\tcolor: var(--color-error);\n}\n\n.cn-openproject-tab__priority--medium {\n\tcolor: var(--color-warning, #e9a40f);\n}\n\n.cn-openproject-tab__assignee {\n\tflex-shrink: 0;\n}\n\n/* CnStatusBadge keeps its own colours; the legacy status-pill modifier\n hooks are retained so existing tests and consumers keep matching. */\n.cn-openproject-tab__status-pill {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_toDisplayString","_createBlock","_normalizeClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnOpenprojectTab.vue3.js","sources":["../../../../../src/integrations/builtin/openproject/CnOpenprojectTab.vue?vue&type=template&id=4c26c5c4&lang.js"],"sourcesContent":["<!--\n CnOpenprojectTab — bespoke sidebar tab for the `openproject` integration.\n\n Replaces the generic CnIntegrationTab for the `openproject` leaf:\n renders the work packages linked to the parent OR object as a flat\n list, each row showing subject, type badge (Task / Bug / Feature /\n …), status pill (New / In progress / Closed / …), priority indicator,\n assignee avatar, and project name. Clicking a row deep-links to the\n work package in OpenProject.\n\n Talks to the OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/openproject`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\OpenProjectProvider`\n (which delegates through `ExternalIntegrationRouter` to the\n Integriq source named `openproject`).\n\n Surface behaviour (per ADR-017 graceful degradation + ADR-019 external\n storage policy):\n - Unconfigured: 412 / `health=unavailable` from the provider →\n \"Configure OpenProject connection\" CTA pointing at Integriq.\n - Authorisation expired: 401 with `authStatus: 'expired'` →\n reconnect-in-Integriq banner.\n - Other 4xx/5xx: surface the upstream message (not a silent empty).\n - Loading: spinner.\n - Empty: \"No work packages linked yet\" + \"Open OpenProject\" CTA.\n - Degraded (503): \"OpenProject is currently unavailable\" banner.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link\n list which loses OpenProject's primary signals — *type of work*,\n *current status* and *who's responsible*. Surfacing them inline\n reproduces enough of the OpenProject row affordance for case handlers\n to triage at a glance without context-switching to OpenProject itself.\n\n As the first external-storage integration this tab also pioneers the\n auth-status surfacing the umbrella's `IntegrationHealth` contract\n exposes — silently failing on 401 is a debugging black hole.\n\n See `openregister/openspec/changes/integration-openproject/` for the\n spec delta, ADR-019 (registry mechanism), ADR-022 (sidebar tab\n contract).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-openproject-tab\">\n\t\t<div v-if=\"authBanner\" class=\"cn-openproject-tab__banner cn-openproject-tab__banner--auth\" role=\"alert\">\n\t\t\t<LockOutline :size=\"18\" />\n\t\t\t<span>{{ authBanner }}</span>\n\t\t\t<a\n\t\t\t\tv-if=\"openconnectorUrl\"\n\t\t\t\t:href=\"openconnectorUrl\"\n\t\t\t\tclass=\"cn-openproject-tab__banner-cta\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener\">{{ openconnectorLabel }}</a>\n\t\t</div>\n\t\t<div v-else-if=\"degraded\" class=\"cn-openproject-tab__banner cn-openproject-tab__banner--warn\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<!-- Link / Create actions — hidden while unconfigured (the empty\n\t\t state below carries the Configure CTA instead) and while an\n\t\t auth banner is showing. -->\n\t\t<div v-if=\"!unconfigured && !authBanner\" class=\"cn-openproject-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 work package') }}\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 work package') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Unconfigured: provider reports the Integriq `openproject` source is absent. -->\n\t\t<div\n\t\t\tv-else-if=\"unconfigured\"\n\t\t\tclass=\"cn-sidebar-tab__empty cn-openproject-tab__empty\">\n\t\t\t<Briefcase :size=\"32\" class=\"cn-openproject-tab__empty-icon\" />\n\t\t\t<p>{{ unconfiguredLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"openconnectorUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<CogOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ configureLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-else-if=\"error\" class=\"cn-openproject-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<div v-else-if=\"workPackages.length === 0\" class=\"cn-sidebar-tab__empty cn-openproject-tab__empty\">\n\t\t\t<Briefcase :size=\"32\" class=\"cn-openproject-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t:href=\"openprojectAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openOpenprojectLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<ul v-else class=\"cn-openproject-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"wp in workPackages\"\n\t\t\t\t:key=\"wpKey(wp)\"\n\t\t\t\tclass=\"cn-openproject-tab__row\"\n\t\t\t\t:class=\"rowClass(wp)\"\n\t\t\t\t:name=\"wpSubject(wp)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"wpUrl(wp)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<!-- Type indicator: a coloured square chip echoing OpenProject's\n\t\t\t\t type colour-coding (Task / Bug / Feature / Milestone). -->\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-openproject-tab__type-icon\"\n\t\t\t\t\t\t:class=\"typeBadgeClass(wp)\"\n\t\t\t\t\t\t:title=\"typeTitle(wp)\">\n\t\t\t\t\t\t<RhombusOutline v-if=\"isMilestoneType(wp)\" :size=\"18\" />\n\t\t\t\t\t\t<Bug v-else-if=\"isBugType(wp)\" :size=\"18\" />\n\t\t\t\t\t\t<StarFourPointsOutline v-else-if=\"isFeatureType(wp)\" :size=\"18\" />\n\t\t\t\t\t\t<CheckboxMarkedOutline v-else :size=\"18\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<!-- Sub-line: #id reference + status chip, the OpenProject row meta. -->\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-openproject-tab__subline\">\n\t\t\t\t\t\t<span v-if=\"wpReference(wp)\" class=\"cn-openproject-tab__ref\">{{ wpReference(wp) }}</span>\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tv-if=\"wpStatus(wp)\"\n\t\t\t\t\t\t\tclass=\"cn-openproject-tab__status-pill\"\n\t\t\t\t\t\t\t:class=\"statusPillClass(wp)\"\n\t\t\t\t\t\t\t:label=\"wpStatus(wp)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(wp)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<span v-if=\"wpProject(wp)\" class=\"cn-openproject-tab__project\">\n\t\t\t\t\t\t\t<Briefcase :size=\"12\" />\n\t\t\t\t\t\t\t{{ wpProject(wp) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<!-- Trailing column: type badge label so the type stays legible. -->\n\t\t\t\t<template v-if=\"wpType(wp)\" #details>\n\t\t\t\t\t<span class=\"cn-openproject-tab__type-label\">{{ wpType(wp) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<!-- Right-edge indicators: priority marker + assignee avatar. -->\n\t\t\t\t<template #indicator>\n\t\t\t\t\t<span class=\"cn-openproject-tab__indicators\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"wpPriority(wp)\"\n\t\t\t\t\t\t\tclass=\"cn-openproject-tab__priority\"\n\t\t\t\t\t\t\t:class=\"priorityClass(wp)\"\n\t\t\t\t\t\t\t:title=\"priorityTitle(wp)\">\n\t\t\t\t\t\t\t<AlertCircleOutline v-if=\"isHighPriority(wp)\" :size=\"14\" />\n\t\t\t\t\t\t\t<ChevronDoubleUp v-else-if=\"isMediumPriority(wp)\" :size=\"14\" />\n\t\t\t\t\t\t\t<ChevronUp v-else :size=\"14\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\tv-if=\"wpAssignee(wp)\"\n\t\t\t\t\t\t\tclass=\"cn-openproject-tab__assignee\"\n\t\t\t\t\t\t\t:size=\"24\"\n\t\t\t\t\t\t\t:displayName=\"wpAssignee(wp)\"\n\t\t\t\t\t\t\t:user=\"assigneeSeed(wp)\"\n\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t\t:disableTooltip=\"false\"\n\t\t\t\t\t\t\thideStatus\n\t\t\t\t\t\t\t:title=\"assigneeTitle(wp)\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-openproject-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlinkWorkPackage(wp)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink work package') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnOpenProjectPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:openconnectorUrl=\"openconnectorUrl\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnOpenProjectCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:openconnectorUrl=\"openconnectorUrl\"\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, NcAvatar, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport Briefcase from 'vue-material-design-icons/Briefcase.vue'\nimport Bug from 'vue-material-design-icons/Bug.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport ChevronDoubleUp from 'vue-material-design-icons/ChevronDoubleUp.vue'\nimport ChevronUp from 'vue-material-design-icons/ChevronUp.vue'\nimport CogOutline from 'vue-material-design-icons/CogOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport LockOutline from 'vue-material-design-icons/LockOutline.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport RhombusOutline from 'vue-material-design-icons/RhombusOutline.vue'\nimport StarFourPointsOutline from 'vue-material-design-icons/StarFourPointsOutline.vue'\nimport CnOpenProjectCreate from '../../../components/CnOpenProjectCreate/CnOpenProjectCreate.vue'\nimport CnOpenProjectPicker from '../../../components/CnOpenProjectPicker/CnOpenProjectPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnOpenprojectTab — bespoke sidebar tab for the `openproject` integration.\n *\n * Renders linked work packages with type / status / priority /\n * assignee, plus auth-status surfacing for Integriq configured /\n * missing / expired states. See the file-level docblock for the full\n * surface contract.\n */\nexport default {\n\tname: 'CnOpenprojectTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tBriefcase,\n\t\tBug,\n\t\tCheckboxMarkedOutline,\n\t\tChevronDoubleUp,\n\t\tChevronUp,\n\t\tCogOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tLockOutline,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tRhombusOutline,\n\t\tStarFourPointsOutline,\n\t\tCnStatusBadge,\n\t\tCnOpenProjectPicker,\n\t\tCnOpenProjectCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'openproject'`). */\n\t\tintegrationId: { type: String, default: 'openproject' },\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: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No work packages linked yet'),\n\t\t},\n\n\t\t/** Pre-translated label for the \"Open OpenProject\" CTA. */\n\t\topenOpenprojectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Open OpenProject'),\n\t\t},\n\n\t\t/** Pre-translated label for the \"Configure OpenProject connection\" CTA. */\n\t\tconfigureLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Configure OpenProject connection'),\n\t\t},\n\n\t\t/** Pre-translated label shown when the integration is unconfigured. */\n\t\tunconfiguredLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'OpenProject is not configured yet. Add an `openproject` source in Integriq to start linking work packages.'),\n\t\t},\n\n\t\t/** Pre-translated banner when OpenProject is unavailable. */\n\t\tunavailableLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'OpenProject is currently unavailable.'),\n\t\t},\n\n\t\t/** Pre-translated banner when the OAuth/API token has expired. */\n\t\tauthExpiredLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Authorisation for OpenProject expired. Reconnect the source in Integriq to restore access.'),\n\t\t},\n\n\t\t/** Pre-translated link label to the Integriq admin. */\n\t\topenconnectorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Open Integriq'),\n\t\t},\n\n\t\t/** URL of the Integriq source admin page (for the `openproject` source). */\n\t\topenconnectorUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '/index.php/apps/openconnector/sources/openproject',\n\t\t},\n\n\t\t/** Fallback URL of the OpenProject app entry. */\n\t\topenprojectAppUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: 'https://www.openproject.org',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tworkPackages: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tauthBanner: '',\n\t\t\tunconfigured: false,\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchWorkPackages()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchWorkPackages()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchWorkPackages()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${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 openproject endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\topenProjectEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/openproject`)\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(this.openProjectEndpoint(), {\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.fetchWorkPackages()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This work package is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link work package.')\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('[CnOpenprojectTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link work package.')\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(`${this.openProjectEndpoint()}/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.fetchWorkPackages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create work package.')\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('[CnOpenprojectTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create work package.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkWorkPackage(wp) {\n\t\t\tconst id = this.wpKey(wp)\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(`${this.openProjectEndpoint()}/${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.fetchWorkPackages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink work package.')\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('[CnOpenprojectTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink work package.')\n\t\t\t}\n\t\t},\n\n\t\twpKey(wp) {\n\t\t\treturn wp.id ?? wp.reference ?? ''\n\t\t},\n\n\t\t/**\n\t\t * The OpenProject `#id` reference shown on the row sub-line.\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {string} A `#123` reference, or '' when no id is known.\n\t\t */\n\t\twpReference(wp) {\n\t\t\tconst id = wp.id ?? wp.reference ?? ''\n\t\t\tif (id === '' || id === null || id === undefined) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst str = String(id)\n\t\t\treturn str.charAt(0) === '#' ? str : '#' + str\n\t\t},\n\n\t\t/**\n\t\t * Stable seed for NcAvatar's deterministic colour + initials.\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {string} A non-empty seed.\n\t\t */\n\t\tassigneeSeed(wp) {\n\t\t\treturn String(this.wpAssignee(wp) || this.wpKey(wp) || 'op')\n\t\t},\n\n\t\t/**\n\t\t * Row modifier classes (high-priority emphasis on the left edge).\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {object} The class map.\n\t\t */\n\t\trowClass(wp) {\n\t\t\treturn { 'cn-openproject-tab__row--high-priority': this.isHighPriority(wp) }\n\t\t},\n\n\t\ttypeTitle(wp) {\n\t\t\treturn t('nextcloud-vue', 'Type: {type}', { type: this.wpType(wp) || t('nextcloud-vue', 'Task') })\n\t\t},\n\n\t\tisBugType(wp) {\n\t\t\treturn String(this.wpType(wp)).toLowerCase().includes('bug')\n\t\t},\n\n\t\tisMilestoneType(wp) {\n\t\t\tconst type = String(this.wpType(wp)).toLowerCase()\n\t\t\treturn type.includes('milestone') || type.includes('phase')\n\t\t},\n\n\t\tisFeatureType(wp) {\n\t\t\tconst type = String(this.wpType(wp)).toLowerCase()\n\t\t\treturn type.includes('feature') || type.includes('user story') || type.includes('epic')\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge variant matching the OpenProject status family.\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {string} A CnStatusBadge variant.\n\t\t */\n\t\tstatusVariant(wp) {\n\t\t\tconst status = String(this.wpStatus(wp)).toLowerCase()\n\t\t\tif (status.includes('closed') || status.includes('done') || status.includes('resolved')) {\n\t\t\t\treturn 'success'\n\t\t\t}\n\t\t\tif (status.includes('progress') || status.includes('developed') || status.includes('review')) {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\tif (status.includes('reject') || status.includes('block')) {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn 'info'\n\t\t},\n\n\t\t/**\n\t\t * Priority-marker emphasis class.\n\t\t *\n\t\t * @param {object} wp - The work-package row.\n\t\t * @return {object} The class map.\n\t\t */\n\t\tpriorityClass(wp) {\n\t\t\treturn {\n\t\t\t\t'cn-openproject-tab__priority--high': this.isHighPriority(wp),\n\t\t\t\t'cn-openproject-tab__priority--medium': this.isMediumPriority(wp),\n\t\t\t}\n\t\t},\n\n\t\twpSubject(wp) {\n\t\t\treturn wp.title ?? wp.subject ?? wp.name ?? this.wpKey(wp)\n\t\t},\n\n\t\twpUrl(wp) {\n\t\t\tconst url = wp.url ?? wp._links?.self?.href\n\t\t\treturn url && safeHref(url) !== '#' ? url : this.openprojectAppUrl\n\t\t},\n\n\t\twpStatus(wp) {\n\t\t\treturn wp.status ?? wp._links?.status?.title ?? ''\n\t\t},\n\n\t\twpType(wp) {\n\t\t\treturn wp.type ?? wp._links?.type?.title ?? wp._embedded?.type?.name ?? ''\n\t\t},\n\n\t\twpPriority(wp) {\n\t\t\treturn wp.priority ?? wp._links?.priority?.title ?? wp._embedded?.priority?.name ?? ''\n\t\t},\n\n\t\twpAssignee(wp) {\n\t\t\treturn wp.assignee\n\t\t\t\t?? wp._links?.assignee?.title\n\t\t\t\t?? wp._embedded?.assignee?.name\n\t\t\t\t?? ''\n\t\t},\n\n\t\twpProject(wp) {\n\t\t\treturn wp.project\n\t\t\t\t?? wp._links?.project?.title\n\t\t\t\t?? wp._embedded?.project?.name\n\t\t\t\t?? ''\n\t\t},\n\n\t\tassigneeTitle(wp) {\n\t\t\treturn t('nextcloud-vue', 'Assigned to {name}', { name: this.wpAssignee(wp) })\n\t\t},\n\n\t\tpriorityTitle(wp) {\n\t\t\treturn t('nextcloud-vue', 'Priority: {priority}', { priority: this.wpPriority(wp) })\n\t\t},\n\n\t\tisHighPriority(wp) {\n\t\t\tconst priority = String(this.wpPriority(wp)).toLowerCase()\n\t\t\treturn priority === 'high' || priority === 'immediate' || priority === 'urgent'\n\t\t},\n\n\t\tisMediumPriority(wp) {\n\t\t\treturn String(this.wpPriority(wp)).toLowerCase() === 'normal'\n\t\t},\n\n\t\tstatusPillClass(wp) {\n\t\t\tconst status = String(this.wpStatus(wp)).toLowerCase()\n\t\t\tif (status.includes('closed') || status.includes('done') || status.includes('resolved')) {\n\t\t\t\treturn 'cn-openproject-tab__status-pill--done'\n\t\t\t}\n\t\t\tif (status.includes('progress') || status.includes('developed') || status.includes('review')) {\n\t\t\t\treturn 'cn-openproject-tab__status-pill--progress'\n\t\t\t}\n\t\t\tif (status.includes('reject') || status.includes('block')) {\n\t\t\t\treturn 'cn-openproject-tab__status-pill--blocked'\n\t\t\t}\n\t\t\treturn 'cn-openproject-tab__status-pill--new'\n\t\t},\n\n\t\ttypeBadgeClass(wp) {\n\t\t\tconst type = String(this.wpType(wp)).toLowerCase()\n\t\t\tif (type.includes('bug')) {\n\t\t\t\treturn 'cn-openproject-tab__type-badge--bug'\n\t\t\t}\n\t\t\tif (type.includes('feature') || type.includes('user story') || type.includes('epic')) {\n\t\t\t\treturn 'cn-openproject-tab__type-badge--feature'\n\t\t\t}\n\t\t\treturn 'cn-openproject-tab__type-badge--task'\n\t\t},\n\n\t\tasync fetchWorkPackages() {\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\tthis.authBanner = ''\n\t\t\tthis.unconfigured = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results\n\t\t\t\t\t\t|| data.items\n\t\t\t\t\t\t|| data._embedded?.elements\n\t\t\t\t\t\t|| (Array.isArray(data) ? data : [])\n\t\t\t\t\t\t|| []\n\t\t\t\t\tthis.workPackages = rows\n\t\t\t\t} else if (response.status === 401 || response.status === 403) {\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.authBanner = this.authExpiredLabel\n\t\t\t\t} else if (response.status === 412) {\n\t\t\t\t\t// Provider returned \"preconditions failed\" — convention for \"unconfigured\".\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.unconfigured = true\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else if (response.status === 404) {\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.unconfigured = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.workPackages = []\n\t\t\t\t\tthis.error = await this.extractError(response)\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('[CnOpenprojectTab] failed to fetch work packages', err)\n\t\t\t\tthis.workPackages = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load work packages.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync extractError(response) {\n\t\t\ttry {\n\t\t\t\tconst body = await response.json()\n\t\t\t\tif (body && typeof body.message === 'string' && body.message !== '') {\n\t\t\t\t\treturn body.message\n\t\t\t\t}\n\t\t\t\tif (body && typeof body.error === 'string' && body.error !== '') {\n\t\t\t\t\treturn body.error\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Fall through.\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Could not load work packages.')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-openproject-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n}\n\n.cn-openproject-tab__unlink {\n\tflex-shrink: 0;\n}\n\n.cn-openproject-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\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-openproject-tab__banner--warn {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-openproject-tab__banner--auth {\n\tbackground: var(--color-error, #e9322d);\n}\n\n.cn-openproject-tab__banner-cta {\n\tmargin-left: auto;\n\tcolor: var(--color-main-background);\n\ttext-decoration: underline;\n}\n\n.cn-openproject-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-openproject-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-openproject-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-openproject-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* High-priority work packages get a coloured left rail so case handlers\n spot them while scanning the list. */\n.cn-openproject-tab__row--high-priority {\n\tborder-left: 3px solid var(--color-error);\n}\n\n/* Type indicator: a rounded square chip echoing OpenProject's\n colour-coded work-package types. */\n.cn-openproject-tab__type-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 30px;\n\theight: 30px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-primary-element, #1A67A3);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-openproject-tab__type-icon.cn-openproject-tab__type-badge--bug {\n\tbackground: var(--color-error);\n}\n\n.cn-openproject-tab__type-icon.cn-openproject-tab__type-badge--feature {\n\tbackground: var(--color-success, #46ba61);\n}\n\n.cn-openproject-tab__type-icon.cn-openproject-tab__type-badge--task {\n\tbackground: #1A67A3;\n}\n\n.cn-openproject-tab__subline {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tflex-wrap: wrap;\n\tgap: 6px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* OpenProject-style `#id` reference, monospaced for a ticket feel. */\n.cn-openproject-tab__ref {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-openproject-tab__project {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-openproject-tab__type-label {\n\tfont-size: 0.78em;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n.cn-openproject-tab__indicators {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-openproject-tab__priority {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-openproject-tab__priority--high {\n\tcolor: var(--color-error);\n}\n\n.cn-openproject-tab__priority--medium {\n\tcolor: var(--color-warning, #e9a40f);\n}\n\n.cn-openproject-tab__assignee {\n\tflex-shrink: 0;\n}\n\n/* CnStatusBadge keeps its own colours; the legacy status-pill modifier\n hooks are retained so existing tests and consumers keep matching. */\n.cn-openproject-tab__status-pill {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { NcLoadingIcon } from '@nextcloud/vue';
|
|
|
3
3
|
import ImageIcon from 'vue-material-design-icons/Image.vue';
|
|
4
4
|
import ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue';
|
|
5
5
|
import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
|
|
6
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
6
7
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
7
8
|
|
|
8
9
|
const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
|
|
@@ -140,7 +141,7 @@ var script = {
|
|
|
140
141
|
},
|
|
141
142
|
|
|
142
143
|
albumUrl(album) {
|
|
143
|
-
if (album && album.url) {
|
|
144
|
+
if (album && album.url && safeHref(album.url) !== '#') {
|
|
144
145
|
return album.url
|
|
145
146
|
}
|
|
146
147
|
const name = this.albumName(album);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnPhotosCard.vue2.js","sources":["../../../../../src/integrations/builtin/photos/CnPhotosCard.vue"],"sourcesContent":["<!--\n CnPhotosCard — bespoke surface-aware widget for the `photos`\n integration.\n\n Replaces the generic CnIntegrationCard for the `photos` leaf. Branches\n on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N albums\" + the\n first album's cover thumbnail + a \"view all\" trail-off.\n - detail-page : a compact 2-column thumbnail grid\n of the linked albums (COMPACT_LIMIT cap) with \"view all\".\n - single-entity : a chip with the cover thumbnail +\n album name for reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnPhotosTab; for `single-entity` the optional `value` prop addresses a\n single album by id.\n\n See `openregister/openspec/changes/integration-photos/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-photos-card__chip\" :title=\"albumName(entity)\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"coverUrl(entity)\"\n\t\t\t\t\t:src=\"coverUrl(entity)\"\n\t\t\t\t\talt=\"\"\n\t\t\t\t\tclass=\"cn-photos-card__chip-cover\"\n\t\t\t\t\t@error=\"onCoverError(entity)\">\n\t\t\t\t<ImageIcon v-else :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"albumUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ albumName(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-photos-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + first cover -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-photos-card__headline\">\n\t\t\t\t<div class=\"cn-photos-card__headline-line\">\n\t\t\t\t\t<strong>{{ albumCountLabel }}</strong>\n\t\t\t\t\t<span v-if=\"totalPhotoCount !== null\" class=\"cn-photos-card__photo-total\">\n\t\t\t\t\t\t· {{ totalPhotosLabel }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"firstAlbum\" class=\"cn-photos-card__headline-album\">\n\t\t\t\t\t<div class=\"cn-photos-card__headline-cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(firstAlbum)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(firstAlbum)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-photos-card__headline-cover-img\"\n\t\t\t\t\t\t\t@error=\"onCoverError(firstAlbum)\">\n\t\t\t\t\t\t<ImageMultiple v-else :size=\"18\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"albumUrl(firstAlbum)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-photos-card__headline-name\">\n\t\t\t\t\t\t{{ albumName(firstAlbum) }}\n\t\t\t\t\t</a>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"albums.length > 1\" class=\"cn-photos-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"photosAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact grid -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-photos-card__grid\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"album in displayedAlbums\"\n\t\t\t\t\t:key=\"albumKey(album)\"\n\t\t\t\t\t:href=\"albumUrl(album)\"\n\t\t\t\t\tclass=\"cn-photos-card__tile\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t:title=\"albumName(album)\">\n\t\t\t\t\t<div class=\"cn-photos-card__tile-cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(album)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(album)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-photos-card__tile-cover-img\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onCoverError(album)\">\n\t\t\t\t\t\t<ImageMultiple v-else :size=\"20\" class=\"cn-photos-card__tile-fallback\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<span class=\"cn-photos-card__tile-name\">{{ albumName(album) }}</span>\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t\t<div v-if=\"albums.length > COMPACT_LIMIT\" class=\"cn-photos-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"photosAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ImageIcon from 'vue-material-design-icons/Image.vue'\nimport ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 4\n\n/**\n * CnPhotosCard — bespoke surface-aware widget for the `photos`\n * integration.\n *\n * Renders Photos-aware metadata (album count, cover thumbnail, total\n * photo count) across all four AD-19 surfaces. See the file-level\n * docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnPhotosCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ImageIcon, ImageMultiple },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'photos'`). */\n\t\tintegrationId: { type: String, default: 'photos' },\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 (album id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Photos') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ImageIcon },\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 albums linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Photos is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Photos') },\n\t\t/** URL of the NC Photos app entry. */\n\t\tphotosAppUrl: { type: String, default: '/index.php/apps/photos' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\talbums: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t\tbrokenCovers: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedAlbums() {\n\t\t\treturn this.albums.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\talbumCountLabel() {\n\t\t\tconst total = this.albums.length\n\t\t\treturn n('nextcloud-vue', '{count} album', '{count} albums', total, { count: total })\n\t\t},\n\n\t\ttotalPhotoCount() {\n\t\t\tlet total = 0\n\t\t\tlet any = false\n\t\t\tfor (const a of this.albums) {\n\t\t\t\tconst c = this.photoCount(a)\n\t\t\t\tif (c !== null) {\n\t\t\t\t\ttotal += c\n\t\t\t\t\tany = true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn any ? total : null\n\t\t},\n\n\t\ttotalPhotosLabel() {\n\t\t\tconst count = this.totalPhotoCount ?? 0\n\t\t\treturn n('nextcloud-vue', '{count} photo', '{count} photos', count, { count })\n\t\t},\n\n\t\tfirstAlbum() {\n\t\t\treturn this.albums.length > 0 ? this.albums[0] : null\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\talbumKey(album) {\n\t\t\treturn (album && (album.id ?? album.data?.album_id)) || ''\n\t\t},\n\n\t\talbumName(album) {\n\t\t\tconst raw = (album && (album.title || album.name || album.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled album')\n\t\t},\n\n\t\talbumUrl(album) {\n\t\t\tif (album && album.url) {\n\t\t\t\treturn album.url\n\t\t\t}\n\t\t\tconst name = this.albumName(album)\n\t\t\treturn `/index.php/apps/photos/albums/${encodeURIComponent(name)}`\n\t\t},\n\n\t\tcoverUrl(album) {\n\t\t\tif (!album || this.brokenCovers[this.albumKey(album)]) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn album.coverPhotoUrl || album.data?.coverPhotoUrl || ''\n\t\t},\n\n\t\tonCoverError(album) {\n\t\t\tthis.brokenCovers[this.albumKey(album)] = true\n\t\t},\n\n\t\tphotoCount(album) {\n\t\t\tconst raw = album?.photoCount ?? album?.data?.photoCount ?? album?.data?.count\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\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.albums = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.albums = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.albums = []\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('[CnPhotosCard] failed to fetch albums', err)\n\t\t\t\tthis.albums = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnPhotosCard] failed to fetch single album', 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-photos-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-photos-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-photos-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-photos-card__photo-total {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tfont-weight: 400;\n}\n\n.cn-photos-card__headline-album {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\toverflow: hidden;\n}\n\n.cn-photos-card__headline-cover {\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tflex-shrink: 0;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-photos-card__headline-cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.cn-photos-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tfont-size: 0.95em;\n}\n\n.cn-photos-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-photos-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px 4px 4px;\n\tborder-radius: 14px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n}\n\n.cn-photos-card__chip-cover {\n\twidth: 20px;\n\theight: 20px;\n\tborder-radius: 50%;\n\tobject-fit: cover;\n\tflex-shrink: 0;\n}\n\n.cn-photos-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-photos-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-photos-card__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(2, 1fr);\n\tgap: 8px;\n}\n\n.cn-photos-card__tile {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\ttext-decoration: none;\n\tcolor: inherit;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-photos-card__tile:hover,\n.cn-photos-card__tile:focus-within {\n\tbackground: var(--color-background-dark, var(--color-background-hover));\n}\n\n.cn-photos-card__tile-cover {\n\twidth: 100%;\n\taspect-ratio: 1 / 1;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\toverflow: hidden;\n}\n\n.cn-photos-card__tile-cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.cn-photos-card__tile-fallback {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-photos-card__tile-name {\n\tpadding: 4px 6px 6px;\n\tfont-size: 12px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-photos-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-photos-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-photos-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnPhotosCard.vue2.js","sources":["../../../../../src/integrations/builtin/photos/CnPhotosCard.vue"],"sourcesContent":["<!--\n CnPhotosCard — bespoke surface-aware widget for the `photos`\n integration.\n\n Replaces the generic CnIntegrationCard for the `photos` leaf. Branches\n on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N albums\" + the\n first album's cover thumbnail + a \"view all\" trail-off.\n - detail-page : a compact 2-column thumbnail grid\n of the linked albums (COMPACT_LIMIT cap) with \"view all\".\n - single-entity : a chip with the cover thumbnail +\n album name for reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnPhotosTab; for `single-entity` the optional `value` prop addresses a\n single album by id.\n\n See `openregister/openspec/changes/integration-photos/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-photos-card__chip\" :title=\"albumName(entity)\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"coverUrl(entity)\"\n\t\t\t\t\t:src=\"coverUrl(entity)\"\n\t\t\t\t\talt=\"\"\n\t\t\t\t\tclass=\"cn-photos-card__chip-cover\"\n\t\t\t\t\t@error=\"onCoverError(entity)\">\n\t\t\t\t<ImageIcon v-else :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"albumUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ albumName(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-photos-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + first cover -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-photos-card__headline\">\n\t\t\t\t<div class=\"cn-photos-card__headline-line\">\n\t\t\t\t\t<strong>{{ albumCountLabel }}</strong>\n\t\t\t\t\t<span v-if=\"totalPhotoCount !== null\" class=\"cn-photos-card__photo-total\">\n\t\t\t\t\t\t· {{ totalPhotosLabel }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"firstAlbum\" class=\"cn-photos-card__headline-album\">\n\t\t\t\t\t<div class=\"cn-photos-card__headline-cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(firstAlbum)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(firstAlbum)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-photos-card__headline-cover-img\"\n\t\t\t\t\t\t\t@error=\"onCoverError(firstAlbum)\">\n\t\t\t\t\t\t<ImageMultiple v-else :size=\"18\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"albumUrl(firstAlbum)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-photos-card__headline-name\">\n\t\t\t\t\t\t{{ albumName(firstAlbum) }}\n\t\t\t\t\t</a>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"albums.length > 1\" class=\"cn-photos-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"photosAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact grid -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-photos-card__grid\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"album in displayedAlbums\"\n\t\t\t\t\t:key=\"albumKey(album)\"\n\t\t\t\t\t:href=\"albumUrl(album)\"\n\t\t\t\t\tclass=\"cn-photos-card__tile\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t:title=\"albumName(album)\">\n\t\t\t\t\t<div class=\"cn-photos-card__tile-cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(album)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(album)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-photos-card__tile-cover-img\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onCoverError(album)\">\n\t\t\t\t\t\t<ImageMultiple v-else :size=\"20\" class=\"cn-photos-card__tile-fallback\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<span class=\"cn-photos-card__tile-name\">{{ albumName(album) }}</span>\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t\t<div v-if=\"albums.length > COMPACT_LIMIT\" class=\"cn-photos-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"photosAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ImageIcon from 'vue-material-design-icons/Image.vue'\nimport ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 4\n\n/**\n * CnPhotosCard — bespoke surface-aware widget for the `photos`\n * integration.\n *\n * Renders Photos-aware metadata (album count, cover thumbnail, total\n * photo count) across all four AD-19 surfaces. See the file-level\n * docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnPhotosCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ImageIcon, ImageMultiple },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'photos'`). */\n\t\tintegrationId: { type: String, default: 'photos' },\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 (album id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Photos') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ImageIcon },\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 albums linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Photos is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Photos') },\n\t\t/** URL of the NC Photos app entry. */\n\t\tphotosAppUrl: { type: String, default: '/index.php/apps/photos' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\talbums: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t\tbrokenCovers: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedAlbums() {\n\t\t\treturn this.albums.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\talbumCountLabel() {\n\t\t\tconst total = this.albums.length\n\t\t\treturn n('nextcloud-vue', '{count} album', '{count} albums', total, { count: total })\n\t\t},\n\n\t\ttotalPhotoCount() {\n\t\t\tlet total = 0\n\t\t\tlet any = false\n\t\t\tfor (const a of this.albums) {\n\t\t\t\tconst c = this.photoCount(a)\n\t\t\t\tif (c !== null) {\n\t\t\t\t\ttotal += c\n\t\t\t\t\tany = true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn any ? total : null\n\t\t},\n\n\t\ttotalPhotosLabel() {\n\t\t\tconst count = this.totalPhotoCount ?? 0\n\t\t\treturn n('nextcloud-vue', '{count} photo', '{count} photos', count, { count })\n\t\t},\n\n\t\tfirstAlbum() {\n\t\t\treturn this.albums.length > 0 ? this.albums[0] : null\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\talbumKey(album) {\n\t\t\treturn (album && (album.id ?? album.data?.album_id)) || ''\n\t\t},\n\n\t\talbumName(album) {\n\t\t\tconst raw = (album && (album.title || album.name || album.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled album')\n\t\t},\n\n\t\talbumUrl(album) {\n\t\t\tif (album && album.url && safeHref(album.url) !== '#') {\n\t\t\t\treturn album.url\n\t\t\t}\n\t\t\tconst name = this.albumName(album)\n\t\t\treturn `/index.php/apps/photos/albums/${encodeURIComponent(name)}`\n\t\t},\n\n\t\tcoverUrl(album) {\n\t\t\tif (!album || this.brokenCovers[this.albumKey(album)]) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn album.coverPhotoUrl || album.data?.coverPhotoUrl || ''\n\t\t},\n\n\t\tonCoverError(album) {\n\t\t\tthis.brokenCovers[this.albumKey(album)] = true\n\t\t},\n\n\t\tphotoCount(album) {\n\t\t\tconst raw = album?.photoCount ?? album?.data?.photoCount ?? album?.data?.count\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\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.albums = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.albums = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.albums = []\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('[CnPhotosCard] failed to fetch albums', err)\n\t\t\t\tthis.albums = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnPhotosCard] failed to fetch single album', 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-photos-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-photos-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-photos-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-photos-card__photo-total {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tfont-weight: 400;\n}\n\n.cn-photos-card__headline-album {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\toverflow: hidden;\n}\n\n.cn-photos-card__headline-cover {\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tflex-shrink: 0;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-photos-card__headline-cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.cn-photos-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tfont-size: 0.95em;\n}\n\n.cn-photos-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-photos-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px 4px 4px;\n\tborder-radius: 14px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n}\n\n.cn-photos-card__chip-cover {\n\twidth: 20px;\n\theight: 20px;\n\tborder-radius: 50%;\n\tobject-fit: cover;\n\tflex-shrink: 0;\n}\n\n.cn-photos-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-photos-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-photos-card__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(2, 1fr);\n\tgap: 8px;\n}\n\n.cn-photos-card__tile {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\ttext-decoration: none;\n\tcolor: inherit;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-photos-card__tile:hover,\n.cn-photos-card__tile:focus-within {\n\tbackground: var(--color-background-dark, var(--color-background-hover));\n}\n\n.cn-photos-card__tile-cover {\n\twidth: 100%;\n\taspect-ratio: 1 / 1;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\toverflow: hidden;\n}\n\n.cn-photos-card__tile-cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.cn-photos-card__tile-fallback {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-photos-card__tile-name {\n\tpadding: 4px 6px 6px;\n\tfont-size: 12px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-photos-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-photos-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-photos-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnPhotosCard.vue3.js","sources":["../../../../../src/integrations/builtin/photos/CnPhotosCard.vue?vue&type=template&id=3ab3b432&lang.js"],"sourcesContent":["<!--\n CnPhotosCard — bespoke surface-aware widget for the `photos`\n integration.\n\n Replaces the generic CnIntegrationCard for the `photos` leaf. Branches\n on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N albums\" + the\n first album's cover thumbnail + a \"view all\" trail-off.\n - detail-page : a compact 2-column thumbnail grid\n of the linked albums (COMPACT_LIMIT cap) with \"view all\".\n - single-entity : a chip with the cover thumbnail +\n album name for reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnPhotosTab; for `single-entity` the optional `value` prop addresses a\n single album by id.\n\n See `openregister/openspec/changes/integration-photos/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-photos-card__chip\" :title=\"albumName(entity)\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"coverUrl(entity)\"\n\t\t\t\t\t:src=\"coverUrl(entity)\"\n\t\t\t\t\talt=\"\"\n\t\t\t\t\tclass=\"cn-photos-card__chip-cover\"\n\t\t\t\t\t@error=\"onCoverError(entity)\">\n\t\t\t\t<ImageIcon v-else :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"albumUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ albumName(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-photos-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + first cover -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-photos-card__headline\">\n\t\t\t\t<div class=\"cn-photos-card__headline-line\">\n\t\t\t\t\t<strong>{{ albumCountLabel }}</strong>\n\t\t\t\t\t<span v-if=\"totalPhotoCount !== null\" class=\"cn-photos-card__photo-total\">\n\t\t\t\t\t\t· {{ totalPhotosLabel }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"firstAlbum\" class=\"cn-photos-card__headline-album\">\n\t\t\t\t\t<div class=\"cn-photos-card__headline-cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(firstAlbum)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(firstAlbum)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-photos-card__headline-cover-img\"\n\t\t\t\t\t\t\t@error=\"onCoverError(firstAlbum)\">\n\t\t\t\t\t\t<ImageMultiple v-else :size=\"18\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"albumUrl(firstAlbum)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-photos-card__headline-name\">\n\t\t\t\t\t\t{{ albumName(firstAlbum) }}\n\t\t\t\t\t</a>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"albums.length > 1\" class=\"cn-photos-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"photosAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact grid -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-photos-card__grid\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"album in displayedAlbums\"\n\t\t\t\t\t:key=\"albumKey(album)\"\n\t\t\t\t\t:href=\"albumUrl(album)\"\n\t\t\t\t\tclass=\"cn-photos-card__tile\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t:title=\"albumName(album)\">\n\t\t\t\t\t<div class=\"cn-photos-card__tile-cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(album)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(album)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-photos-card__tile-cover-img\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onCoverError(album)\">\n\t\t\t\t\t\t<ImageMultiple v-else :size=\"20\" class=\"cn-photos-card__tile-fallback\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<span class=\"cn-photos-card__tile-name\">{{ albumName(album) }}</span>\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t\t<div v-if=\"albums.length > COMPACT_LIMIT\" class=\"cn-photos-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"photosAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ImageIcon from 'vue-material-design-icons/Image.vue'\nimport ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 4\n\n/**\n * CnPhotosCard — bespoke surface-aware widget for the `photos`\n * integration.\n *\n * Renders Photos-aware metadata (album count, cover thumbnail, total\n * photo count) across all four AD-19 surfaces. See the file-level\n * docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnPhotosCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ImageIcon, ImageMultiple },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'photos'`). */\n\t\tintegrationId: { type: String, default: 'photos' },\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 (album id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Photos') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ImageIcon },\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 albums linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Photos is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Photos') },\n\t\t/** URL of the NC Photos app entry. */\n\t\tphotosAppUrl: { type: String, default: '/index.php/apps/photos' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\talbums: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t\tbrokenCovers: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedAlbums() {\n\t\t\treturn this.albums.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\talbumCountLabel() {\n\t\t\tconst total = this.albums.length\n\t\t\treturn n('nextcloud-vue', '{count} album', '{count} albums', total, { count: total })\n\t\t},\n\n\t\ttotalPhotoCount() {\n\t\t\tlet total = 0\n\t\t\tlet any = false\n\t\t\tfor (const a of this.albums) {\n\t\t\t\tconst c = this.photoCount(a)\n\t\t\t\tif (c !== null) {\n\t\t\t\t\ttotal += c\n\t\t\t\t\tany = true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn any ? total : null\n\t\t},\n\n\t\ttotalPhotosLabel() {\n\t\t\tconst count = this.totalPhotoCount ?? 0\n\t\t\treturn n('nextcloud-vue', '{count} photo', '{count} photos', count, { count })\n\t\t},\n\n\t\tfirstAlbum() {\n\t\t\treturn this.albums.length > 0 ? this.albums[0] : null\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\talbumKey(album) {\n\t\t\treturn (album && (album.id ?? album.data?.album_id)) || ''\n\t\t},\n\n\t\talbumName(album) {\n\t\t\tconst raw = (album && (album.title || album.name || album.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled album')\n\t\t},\n\n\t\talbumUrl(album) {\n\t\t\tif (album && album.url) {\n\t\t\t\treturn album.url\n\t\t\t}\n\t\t\tconst name = this.albumName(album)\n\t\t\treturn `/index.php/apps/photos/albums/${encodeURIComponent(name)}`\n\t\t},\n\n\t\tcoverUrl(album) {\n\t\t\tif (!album || this.brokenCovers[this.albumKey(album)]) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn album.coverPhotoUrl || album.data?.coverPhotoUrl || ''\n\t\t},\n\n\t\tonCoverError(album) {\n\t\t\tthis.brokenCovers[this.albumKey(album)] = true\n\t\t},\n\n\t\tphotoCount(album) {\n\t\t\tconst raw = album?.photoCount ?? album?.data?.photoCount ?? album?.data?.count\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\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.albums = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.albums = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.albums = []\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('[CnPhotosCard] failed to fetch albums', err)\n\t\t\t\tthis.albums = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnPhotosCard] failed to fetch single album', 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-photos-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-photos-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-photos-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-photos-card__photo-total {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tfont-weight: 400;\n}\n\n.cn-photos-card__headline-album {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\toverflow: hidden;\n}\n\n.cn-photos-card__headline-cover {\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tflex-shrink: 0;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-photos-card__headline-cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.cn-photos-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tfont-size: 0.95em;\n}\n\n.cn-photos-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-photos-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px 4px 4px;\n\tborder-radius: 14px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n}\n\n.cn-photos-card__chip-cover {\n\twidth: 20px;\n\theight: 20px;\n\tborder-radius: 50%;\n\tobject-fit: cover;\n\tflex-shrink: 0;\n}\n\n.cn-photos-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-photos-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-photos-card__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(2, 1fr);\n\tgap: 8px;\n}\n\n.cn-photos-card__tile {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\ttext-decoration: none;\n\tcolor: inherit;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-photos-card__tile:hover,\n.cn-photos-card__tile:focus-within {\n\tbackground: var(--color-background-dark, var(--color-background-hover));\n}\n\n.cn-photos-card__tile-cover {\n\twidth: 100%;\n\taspect-ratio: 1 / 1;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\toverflow: hidden;\n}\n\n.cn-photos-card__tile-cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.cn-photos-card__tile-fallback {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-photos-card__tile-name {\n\tpadding: 4px 6px 6px;\n\tfont-size: 12px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-photos-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-photos-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-photos-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_Fragment","_createElementBlock","_createElementVNode","_toDisplayString","_openBlock"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnPhotosCard.vue3.js","sources":["../../../../../src/integrations/builtin/photos/CnPhotosCard.vue?vue&type=template&id=3ab3b432&lang.js"],"sourcesContent":["<!--\n CnPhotosCard — bespoke surface-aware widget for the `photos`\n integration.\n\n Replaces the generic CnIntegrationCard for the `photos` leaf. Branches\n on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N albums\" + the\n first album's cover thumbnail + a \"view all\" trail-off.\n - detail-page : a compact 2-column thumbnail grid\n of the linked albums (COMPACT_LIMIT cap) with \"view all\".\n - single-entity : a chip with the cover thumbnail +\n album name for reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnPhotosTab; for `single-entity` the optional `value` prop addresses a\n single album by id.\n\n See `openregister/openspec/changes/integration-photos/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-photos-card__chip\" :title=\"albumName(entity)\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"coverUrl(entity)\"\n\t\t\t\t\t:src=\"coverUrl(entity)\"\n\t\t\t\t\talt=\"\"\n\t\t\t\t\tclass=\"cn-photos-card__chip-cover\"\n\t\t\t\t\t@error=\"onCoverError(entity)\">\n\t\t\t\t<ImageIcon v-else :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"albumUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ albumName(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-photos-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + first cover -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-photos-card__headline\">\n\t\t\t\t<div class=\"cn-photos-card__headline-line\">\n\t\t\t\t\t<strong>{{ albumCountLabel }}</strong>\n\t\t\t\t\t<span v-if=\"totalPhotoCount !== null\" class=\"cn-photos-card__photo-total\">\n\t\t\t\t\t\t· {{ totalPhotosLabel }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"firstAlbum\" class=\"cn-photos-card__headline-album\">\n\t\t\t\t\t<div class=\"cn-photos-card__headline-cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(firstAlbum)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(firstAlbum)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-photos-card__headline-cover-img\"\n\t\t\t\t\t\t\t@error=\"onCoverError(firstAlbum)\">\n\t\t\t\t\t\t<ImageMultiple v-else :size=\"18\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"albumUrl(firstAlbum)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-photos-card__headline-name\">\n\t\t\t\t\t\t{{ albumName(firstAlbum) }}\n\t\t\t\t\t</a>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"albums.length > 1\" class=\"cn-photos-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"photosAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact grid -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-photos-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-photos-card__grid\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"album in displayedAlbums\"\n\t\t\t\t\t:key=\"albumKey(album)\"\n\t\t\t\t\t:href=\"albumUrl(album)\"\n\t\t\t\t\tclass=\"cn-photos-card__tile\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t:title=\"albumName(album)\">\n\t\t\t\t\t<div class=\"cn-photos-card__tile-cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(album)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(album)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-photos-card__tile-cover-img\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onCoverError(album)\">\n\t\t\t\t\t\t<ImageMultiple v-else :size=\"20\" class=\"cn-photos-card__tile-fallback\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<span class=\"cn-photos-card__tile-name\">{{ albumName(album) }}</span>\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t\t<div v-if=\"albums.length > COMPACT_LIMIT\" class=\"cn-photos-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"photosAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ImageIcon from 'vue-material-design-icons/Image.vue'\nimport ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 4\n\n/**\n * CnPhotosCard — bespoke surface-aware widget for the `photos`\n * integration.\n *\n * Renders Photos-aware metadata (album count, cover thumbnail, total\n * photo count) across all four AD-19 surfaces. See the file-level\n * docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnPhotosCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ImageIcon, ImageMultiple },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'photos'`). */\n\t\tintegrationId: { type: String, default: 'photos' },\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 (album id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Photos') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ImageIcon },\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 albums linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Photos is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Photos') },\n\t\t/** URL of the NC Photos app entry. */\n\t\tphotosAppUrl: { type: String, default: '/index.php/apps/photos' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\talbums: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t\tbrokenCovers: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedAlbums() {\n\t\t\treturn this.albums.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\talbumCountLabel() {\n\t\t\tconst total = this.albums.length\n\t\t\treturn n('nextcloud-vue', '{count} album', '{count} albums', total, { count: total })\n\t\t},\n\n\t\ttotalPhotoCount() {\n\t\t\tlet total = 0\n\t\t\tlet any = false\n\t\t\tfor (const a of this.albums) {\n\t\t\t\tconst c = this.photoCount(a)\n\t\t\t\tif (c !== null) {\n\t\t\t\t\ttotal += c\n\t\t\t\t\tany = true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn any ? total : null\n\t\t},\n\n\t\ttotalPhotosLabel() {\n\t\t\tconst count = this.totalPhotoCount ?? 0\n\t\t\treturn n('nextcloud-vue', '{count} photo', '{count} photos', count, { count })\n\t\t},\n\n\t\tfirstAlbum() {\n\t\t\treturn this.albums.length > 0 ? this.albums[0] : null\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\talbumKey(album) {\n\t\t\treturn (album && (album.id ?? album.data?.album_id)) || ''\n\t\t},\n\n\t\talbumName(album) {\n\t\t\tconst raw = (album && (album.title || album.name || album.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled album')\n\t\t},\n\n\t\talbumUrl(album) {\n\t\t\tif (album && album.url && safeHref(album.url) !== '#') {\n\t\t\t\treturn album.url\n\t\t\t}\n\t\t\tconst name = this.albumName(album)\n\t\t\treturn `/index.php/apps/photos/albums/${encodeURIComponent(name)}`\n\t\t},\n\n\t\tcoverUrl(album) {\n\t\t\tif (!album || this.brokenCovers[this.albumKey(album)]) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn album.coverPhotoUrl || album.data?.coverPhotoUrl || ''\n\t\t},\n\n\t\tonCoverError(album) {\n\t\t\tthis.brokenCovers[this.albumKey(album)] = true\n\t\t},\n\n\t\tphotoCount(album) {\n\t\t\tconst raw = album?.photoCount ?? album?.data?.photoCount ?? album?.data?.count\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\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.albums = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.albums = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.albums = []\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('[CnPhotosCard] failed to fetch albums', err)\n\t\t\t\tthis.albums = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnPhotosCard] failed to fetch single album', 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-photos-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-photos-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-photos-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-photos-card__photo-total {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tfont-weight: 400;\n}\n\n.cn-photos-card__headline-album {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\toverflow: hidden;\n}\n\n.cn-photos-card__headline-cover {\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tflex-shrink: 0;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-photos-card__headline-cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.cn-photos-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tfont-size: 0.95em;\n}\n\n.cn-photos-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-photos-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px 4px 4px;\n\tborder-radius: 14px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n}\n\n.cn-photos-card__chip-cover {\n\twidth: 20px;\n\theight: 20px;\n\tborder-radius: 50%;\n\tobject-fit: cover;\n\tflex-shrink: 0;\n}\n\n.cn-photos-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-photos-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-photos-card__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(2, 1fr);\n\tgap: 8px;\n}\n\n.cn-photos-card__tile {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\ttext-decoration: none;\n\tcolor: inherit;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-photos-card__tile:hover,\n.cn-photos-card__tile:focus-within {\n\tbackground: var(--color-background-dark, var(--color-background-hover));\n}\n\n.cn-photos-card__tile-cover {\n\twidth: 100%;\n\taspect-ratio: 1 / 1;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\toverflow: hidden;\n}\n\n.cn-photos-card__tile-cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.cn-photos-card__tile-fallback {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-photos-card__tile-name {\n\tpadding: 4px 6px 6px;\n\tfont-size: 12px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-photos-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-photos-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-photos-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_Fragment","_createElementBlock","_createElementVNode","_toDisplayString","_openBlock"],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ import LinkVariant from 'vue-material-design-icons/LinkVariant.vue';
|
|
|
8
8
|
import Plus from 'vue-material-design-icons/Plus.vue';
|
|
9
9
|
import __sfc_main$1 from '../../../components/CnPhotoAlbumCreate/CnPhotoAlbumCreate.vue.js';
|
|
10
10
|
import __sfc_main from '../../../components/CnPhotoAlbumPicker/CnPhotoAlbumPicker.vue.js';
|
|
11
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
11
12
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -116,7 +117,7 @@ var script = {
|
|
|
116
117
|
},
|
|
117
118
|
|
|
118
119
|
albumUrl(album) {
|
|
119
|
-
if (album && album.url) {
|
|
120
|
+
if (album && album.url && safeHref(album.url) !== '#') {
|
|
120
121
|
return album.url
|
|
121
122
|
}
|
|
122
123
|
const id = this.albumKey(album);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnPhotosTab.vue2.js","sources":["../../../../../src/integrations/builtin/photos/CnPhotosTab.vue"],"sourcesContent":["<!--\n CnPhotosTab — bespoke sidebar tab for the `photos` integration leaf.\n\n Replaces the generic `CnIntegrationTab` for the `photos` leaf: renders\n the linked NC Photos albums as a card grid (cover thumbnail, album\n name, photo count, last-edited date). Clicking a card deep-links into\n the NC Photos app at `/index.php/apps/photos/albums/{albumId}`.\n\n Tier-2 surface (this commit):\n - \"Link existing album\" → opens CnPhotoAlbumPicker (modal)\n - \"Create new album\" → opens CnPhotoAlbumCreate (modal)\n - Per-tile unlink → DELETE …/photos/{albumId}\n\n Talks to the OpenRegister Tier-2 photo-link endpoints\n GET /api/objects/{r}/{s}/{id}/photos — list\n POST /api/objects/{r}/{s}/{id}/photos — link existing\n POST /api/objects/{r}/{s}/{id}/photos/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/photos/{albumId} — unlink\n GET /api/integrations/photos/available — picker source\n served by `OCA\\OpenRegister\\Controller\\PhotoLinksController`.\n\n The endpoint returns rows shaped:\n { id, title, url, albumId, albumName, coverPhotoUrl, photoCount,\n lastEdited, data: { … } }\n\n Empty + loading + error + 503 unavailable states follow ADR-017 and\n AD-23 graceful degradation. All UI strings are passed through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link list\n which loses the cover thumbnail + photo count + recency — the three\n signals users rely on when triaging which album is relevant for a\n case. The bespoke tab surfaces them per card.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-photos-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-photos-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 class=\"cn-photos-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 album') }}\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 album') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-photos-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-sidebar-tab__empty cn-photos-tab__empty\">\n\t\t\t<ImageIcon :size=\"32\" class=\"cn-photos-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=\"photosAppUrl\"\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<ImageIcon :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openPhotosLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Photo mosaic grid (mirrors the NC Photos timeline) -->\n\t\t<div v-else class=\"cn-photos-tab__grid\">\n\t\t\t<div\n\t\t\t\tv-for=\"album in albums\"\n\t\t\t\t:key=\"albumKey(album)\"\n\t\t\t\tclass=\"cn-photos-tab__tile\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"albumUrl(album)\"\n\t\t\t\t\tclass=\"cn-photos-tab__tile-link\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t:title=\"albumName(album)\">\n\t\t\t\t\t<div class=\"cn-photos-tab__cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(album)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(album)\"\n\t\t\t\t\t\t\t:alt=\"albumName(album)\"\n\t\t\t\t\t\t\tclass=\"cn-photos-tab__cover-img\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onCoverError(album)\">\n\t\t\t\t\t\t<span v-else class=\"cn-photos-tab__cover-placeholder\">\n\t\t\t\t\t\t\t<ImageMultiple :size=\"36\" class=\"cn-photos-tab__cover-fallback\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<!-- Photo-count badge, top-left (mirrors album stack overlay) -->\n\t\t\t\t\t\t<span v-if=\"photoCount(album) !== null\" class=\"cn-photos-tab__badge\">\n\t\t\t\t\t\t\t<ImageMultiple :size=\"12\" />\n\t\t\t\t\t\t\t<span class=\"cn-photos-tab__badge-count\">{{ photoCountLabel(album) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<!-- Hover scrim with name + date -->\n\t\t\t\t\t\t<div class=\"cn-photos-tab__meta\">\n\t\t\t\t\t\t\t<span class=\"cn-photos-tab__name\">{{ albumName(album) }}</span>\n\t\t\t\t\t\t\t<span v-if=\"lastEdited(album)\" class=\"cn-photos-tab__when\">\n\t\t\t\t\t\t\t\t{{ formatWhen(lastEdited(album)) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</a>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary-no-background\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Unlink album')\"\n\t\t\t\t\tclass=\"cn-photos-tab__unlink\"\n\t\t\t\t\t@click=\"unlinkAlbum(album)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<LinkOff :size=\"16\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<CnPhotoAlbumPicker\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<CnPhotoAlbumCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ImageIcon from 'vue-material-design-icons/Image.vue'\nimport ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnPhotoAlbumCreate from '../../../components/CnPhotoAlbumCreate/CnPhotoAlbumCreate.vue'\nimport CnPhotoAlbumPicker from '../../../components/CnPhotoAlbumPicker/CnPhotoAlbumPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnPhotosTab — bespoke album grid for the `photos` integration.\n *\n * Renders one tile per linked NC Photos album with cover thumbnail,\n * album name, photo count, and last-edited date. Clicking a tile opens\n * the album in the NC Photos app in a new tab. Tier-2: includes\n * link/create modals and per-tile unlink.\n */\nexport default {\n\tname: 'CnPhotosTab',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tImageIcon,\n\t\tImageMultiple,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tCnPhotoAlbumPicker,\n\t\tCnPhotoAlbumCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'photos'`). */\n\t\tintegrationId: { type: String, default: 'photos' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No albums linked yet') },\n\t\t/** Pre-translated \"Open Photos\" CTA label. */\n\t\topenPhotosLabel: { type: String, default: () => t('nextcloud-vue', 'Open Photos') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Photos is currently unavailable.') },\n\t\t/** URL of the NC Photos app entry. */\n\t\tphotosAppUrl: { type: String, default: '/index.php/apps/photos' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\talbums: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tbrokenCovers: {},\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchAlbums()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchAlbums()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchAlbums()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 photos endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tphotosEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/photos`\n\t\t},\n\n\t\talbumKey(album) {\n\t\t\treturn (album && (album.albumId ?? album.id ?? album.data?.album_id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable album name from the row, stripping any\n\t\t * legacy `[or:{uuid}]` marker (pre-Tier-2 albums embedded the\n\t\t * marker inside `name`).\n\t\t *\n\t\t * @param {object} album Provider row.\n\t\t *\n\t\t * @return {string} The display name.\n\t\t */\n\t\talbumName(album) {\n\t\t\tconst raw = (album && (album.albumName || album.title || album.name || album.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled album')\n\t\t},\n\n\t\talbumUrl(album) {\n\t\t\tif (album && album.url) {\n\t\t\t\treturn album.url\n\t\t\t}\n\t\t\tconst id = this.albumKey(album)\n\t\t\treturn `/index.php/apps/photos/albums/${encodeURIComponent(id)}`\n\t\t},\n\n\t\tcoverUrl(album) {\n\t\t\tif (!album || this.brokenCovers[this.albumKey(album)]) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn album.coverPhotoUrl || album.data?.coverPhotoUrl || ''\n\t\t},\n\n\t\tonCoverError(album) {\n\t\t\tthis.brokenCovers[this.albumKey(album)] = true\n\t\t},\n\n\t\tphotoCount(album) {\n\t\t\tconst raw = album?.photoCount ?? album?.data?.photoCount ?? album?.data?.count\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\tphotoCountLabel(album) {\n\t\t\tconst count = this.photoCount(album)\n\t\t\tif (count === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn n('nextcloud-vue', '{count} photo', '{count} photos', count, { count })\n\t\t},\n\n\t\tlastEdited(album) {\n\t\t\treturn album?.lastEdited\n\t\t\t\t|| album?.data?.lastEdited\n\t\t\t\t|| album?.data?.created\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// NC Photos stores `created` as a unix timestamp in\n\t\t\t\t// seconds; accept ISO strings transparently too.\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\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.photosEndpoint()), {\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.fetchAlbums()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This album is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link album.')\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('[CnPhotosTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link album.')\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.photosEndpoint()}/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.fetchAlbums()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create album.')\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('[CnPhotosTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create album.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkAlbum(album) {\n\t\t\tconst albumId = this.albumKey(album)\n\t\t\tif (!albumId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.photosEndpoint()}/${albumId}`), {\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.fetchAlbums()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink album.')\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('[CnPhotosTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink album.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchAlbums() {\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.photosEndpoint()), { 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.albums = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.albums = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.albums = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load albums.')\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('[CnPhotosTab] failed to fetch albums', err)\n\t\t\t\tthis.albums = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load albums.')\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-photos-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-photos-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-photos-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-photos-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-photos-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-photos-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/*\n * Dense edge-to-edge mosaic that mirrors the NC Photos timeline: square\n * thumbnails packed tight with a thin main-background gutter (the same\n * 2px border NC Photos draws between tiles), name + date revealed in a\n * bottom scrim on hover/focus so the imagery dominates at rest.\n */\n.cn-photos-tab__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(104px, 1fr));\n\tgap: 2px;\n\tborder-radius: var(--border-radius-large, var(--border-radius));\n\toverflow: hidden;\n}\n\n.cn-photos-tab__tile {\n\tposition: relative;\n}\n\n.cn-photos-tab__unlink {\n\tposition: absolute;\n\ttop: 4px;\n\tright: 4px;\n\tz-index: 3;\n\topacity: 0;\n\tbackground: var(--color-main-background);\n\tborder-radius: 50%;\n\ttransition: opacity 0.15s ease;\n}\n\n.cn-photos-tab__tile:hover .cn-photos-tab__unlink,\n.cn-photos-tab__tile:focus-within .cn-photos-tab__unlink {\n\topacity: 1;\n}\n\n.cn-photos-tab__tile-link {\n\tdisplay: block;\n\ttext-decoration: none;\n\tcolor: inherit;\n}\n\n.cn-photos-tab__cover {\n\tposition: relative;\n\taspect-ratio: 1 / 1;\n\twidth: 100%;\n\t/* Match the NC Photos selection outline on focus. */\n\tborder: 2px solid var(--color-main-background);\n\tbox-sizing: border-box;\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\toverflow: hidden;\n}\n\n.cn-photos-tab__tile-link:focus-visible .cn-photos-tab__cover {\n\toutline: var(--color-primary-element) solid 2px;\n\toutline-offset: -2px;\n}\n\n.cn-photos-tab__cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tdisplay: block;\n\ttransition: transform 0.2s ease;\n}\n\n.cn-photos-tab__tile:hover .cn-photos-tab__cover-img,\n.cn-photos-tab__tile:focus-within .cn-photos-tab__cover-img {\n\ttransform: scale(1.04);\n}\n\n.cn-photos-tab__cover-placeholder {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 100%;\n\theight: 100%;\n}\n\n.cn-photos-tab__cover-fallback {\n\tcolor: var(--color-primary-element);\n}\n\n/* Photo-count badge (mirrors the album stack count overlay). */\n.cn-photos-tab__badge {\n\tposition: absolute;\n\ttop: 4px;\n\tleft: 4px;\n\tz-index: 2;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n\tpadding: 1px 6px 1px 4px;\n\tborder-radius: var(--border-radius-pill, 12px);\n\tbackground: rgba(0, 0, 0, 0.55);\n\tcolor: #fff;\n\tfont-size: 11px;\n\tfont-weight: 500;\n\tline-height: 1.6;\n}\n\n.cn-photos-tab__badge-count {\n\twhite-space: nowrap;\n}\n\n/* Bottom scrim: hidden at rest, revealed on hover/focus. */\n.cn-photos-tab__meta {\n\tposition: absolute;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 1px;\n\tpadding: 16px 8px 6px;\n\tbackground: linear-gradient(to top, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0));\n\topacity: 0;\n\ttransition: opacity 0.15s ease;\n\tpointer-events: none;\n}\n\n.cn-photos-tab__tile:hover .cn-photos-tab__meta,\n.cn-photos-tab__tile:focus-within .cn-photos-tab__meta {\n\topacity: 1;\n}\n\n.cn-photos-tab__name {\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: #fff;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-photos-tab__when {\n\tfont-size: 10px;\n\tcolor: rgba(255, 255, 255, 0.85);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n</style>\n"],"names":["CnPhotoAlbumPicker","CnPhotoAlbumCreate","t","n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnPhotosTab.vue2.js","sources":["../../../../../src/integrations/builtin/photos/CnPhotosTab.vue"],"sourcesContent":["<!--\n CnPhotosTab — bespoke sidebar tab for the `photos` integration leaf.\n\n Replaces the generic `CnIntegrationTab` for the `photos` leaf: renders\n the linked NC Photos albums as a card grid (cover thumbnail, album\n name, photo count, last-edited date). Clicking a card deep-links into\n the NC Photos app at `/index.php/apps/photos/albums/{albumId}`.\n\n Tier-2 surface (this commit):\n - \"Link existing album\" → opens CnPhotoAlbumPicker (modal)\n - \"Create new album\" → opens CnPhotoAlbumCreate (modal)\n - Per-tile unlink → DELETE …/photos/{albumId}\n\n Talks to the OpenRegister Tier-2 photo-link endpoints\n GET /api/objects/{r}/{s}/{id}/photos — list\n POST /api/objects/{r}/{s}/{id}/photos — link existing\n POST /api/objects/{r}/{s}/{id}/photos/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/photos/{albumId} — unlink\n GET /api/integrations/photos/available — picker source\n served by `OCA\\OpenRegister\\Controller\\PhotoLinksController`.\n\n The endpoint returns rows shaped:\n { id, title, url, albumId, albumName, coverPhotoUrl, photoCount,\n lastEdited, data: { … } }\n\n Empty + loading + error + 503 unavailable states follow ADR-017 and\n AD-23 graceful degradation. All UI strings are passed through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link list\n which loses the cover thumbnail + photo count + recency — the three\n signals users rely on when triaging which album is relevant for a\n case. The bespoke tab surfaces them per card.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-photos-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-photos-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 class=\"cn-photos-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 album') }}\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 album') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-photos-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"albums.length === 0\" class=\"cn-sidebar-tab__empty cn-photos-tab__empty\">\n\t\t\t<ImageIcon :size=\"32\" class=\"cn-photos-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=\"photosAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ImageIcon :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openPhotosLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Photo mosaic grid (mirrors the NC Photos timeline) -->\n\t\t<div v-else class=\"cn-photos-tab__grid\">\n\t\t\t<div\n\t\t\t\tv-for=\"album in albums\"\n\t\t\t\t:key=\"albumKey(album)\"\n\t\t\t\tclass=\"cn-photos-tab__tile\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"albumUrl(album)\"\n\t\t\t\t\tclass=\"cn-photos-tab__tile-link\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t:title=\"albumName(album)\">\n\t\t\t\t\t<div class=\"cn-photos-tab__cover\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"coverUrl(album)\"\n\t\t\t\t\t\t\t:src=\"coverUrl(album)\"\n\t\t\t\t\t\t\t:alt=\"albumName(album)\"\n\t\t\t\t\t\t\tclass=\"cn-photos-tab__cover-img\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onCoverError(album)\">\n\t\t\t\t\t\t<span v-else class=\"cn-photos-tab__cover-placeholder\">\n\t\t\t\t\t\t\t<ImageMultiple :size=\"36\" class=\"cn-photos-tab__cover-fallback\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<!-- Photo-count badge, top-left (mirrors album stack overlay) -->\n\t\t\t\t\t\t<span v-if=\"photoCount(album) !== null\" class=\"cn-photos-tab__badge\">\n\t\t\t\t\t\t\t<ImageMultiple :size=\"12\" />\n\t\t\t\t\t\t\t<span class=\"cn-photos-tab__badge-count\">{{ photoCountLabel(album) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<!-- Hover scrim with name + date -->\n\t\t\t\t\t\t<div class=\"cn-photos-tab__meta\">\n\t\t\t\t\t\t\t<span class=\"cn-photos-tab__name\">{{ albumName(album) }}</span>\n\t\t\t\t\t\t\t<span v-if=\"lastEdited(album)\" class=\"cn-photos-tab__when\">\n\t\t\t\t\t\t\t\t{{ formatWhen(lastEdited(album)) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</a>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary-no-background\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Unlink album')\"\n\t\t\t\t\tclass=\"cn-photos-tab__unlink\"\n\t\t\t\t\t@click=\"unlinkAlbum(album)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<LinkOff :size=\"16\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<CnPhotoAlbumPicker\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<CnPhotoAlbumCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ImageIcon from 'vue-material-design-icons/Image.vue'\nimport ImageMultiple from 'vue-material-design-icons/ImageMultiple.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnPhotoAlbumCreate from '../../../components/CnPhotoAlbumCreate/CnPhotoAlbumCreate.vue'\nimport CnPhotoAlbumPicker from '../../../components/CnPhotoAlbumPicker/CnPhotoAlbumPicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnPhotosTab — bespoke album grid for the `photos` integration.\n *\n * Renders one tile per linked NC Photos album with cover thumbnail,\n * album name, photo count, and last-edited date. Clicking a tile opens\n * the album in the NC Photos app in a new tab. Tier-2: includes\n * link/create modals and per-tile unlink.\n */\nexport default {\n\tname: 'CnPhotosTab',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tImageIcon,\n\t\tImageMultiple,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tCnPhotoAlbumPicker,\n\t\tCnPhotoAlbumCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'photos'`). */\n\t\tintegrationId: { type: String, default: 'photos' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No albums linked yet') },\n\t\t/** Pre-translated \"Open Photos\" CTA label. */\n\t\topenPhotosLabel: { type: String, default: () => t('nextcloud-vue', 'Open Photos') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Photos is currently unavailable.') },\n\t\t/** URL of the NC Photos app entry. */\n\t\tphotosAppUrl: { type: String, default: '/index.php/apps/photos' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\talbums: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tbrokenCovers: {},\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchAlbums()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchAlbums()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchAlbums()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 photos endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tphotosEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/photos`\n\t\t},\n\n\t\talbumKey(album) {\n\t\t\treturn (album && (album.albumId ?? album.id ?? album.data?.album_id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable album name from the row, stripping any\n\t\t * legacy `[or:{uuid}]` marker (pre-Tier-2 albums embedded the\n\t\t * marker inside `name`).\n\t\t *\n\t\t * @param {object} album Provider row.\n\t\t *\n\t\t * @return {string} The display name.\n\t\t */\n\t\talbumName(album) {\n\t\t\tconst raw = (album && (album.albumName || album.title || album.name || album.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled album')\n\t\t},\n\n\t\talbumUrl(album) {\n\t\t\tif (album && album.url && safeHref(album.url) !== '#') {\n\t\t\t\treturn album.url\n\t\t\t}\n\t\t\tconst id = this.albumKey(album)\n\t\t\treturn `/index.php/apps/photos/albums/${encodeURIComponent(id)}`\n\t\t},\n\n\t\tcoverUrl(album) {\n\t\t\tif (!album || this.brokenCovers[this.albumKey(album)]) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn album.coverPhotoUrl || album.data?.coverPhotoUrl || ''\n\t\t},\n\n\t\tonCoverError(album) {\n\t\t\tthis.brokenCovers[this.albumKey(album)] = true\n\t\t},\n\n\t\tphotoCount(album) {\n\t\t\tconst raw = album?.photoCount ?? album?.data?.photoCount ?? album?.data?.count\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\tphotoCountLabel(album) {\n\t\t\tconst count = this.photoCount(album)\n\t\t\tif (count === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn n('nextcloud-vue', '{count} photo', '{count} photos', count, { count })\n\t\t},\n\n\t\tlastEdited(album) {\n\t\t\treturn album?.lastEdited\n\t\t\t\t|| album?.data?.lastEdited\n\t\t\t\t|| album?.data?.created\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// NC Photos stores `created` as a unix timestamp in\n\t\t\t\t// seconds; accept ISO strings transparently too.\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\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.photosEndpoint()), {\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.fetchAlbums()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This album is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link album.')\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('[CnPhotosTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link album.')\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.photosEndpoint()}/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.fetchAlbums()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create album.')\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('[CnPhotosTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create album.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkAlbum(album) {\n\t\t\tconst albumId = this.albumKey(album)\n\t\t\tif (!albumId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.photosEndpoint()}/${albumId}`), {\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.fetchAlbums()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink album.')\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('[CnPhotosTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink album.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchAlbums() {\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.photosEndpoint()), { 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.albums = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.albums = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.albums = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load albums.')\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('[CnPhotosTab] failed to fetch albums', err)\n\t\t\t\tthis.albums = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load albums.')\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-photos-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-photos-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-photos-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-photos-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-photos-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-photos-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/*\n * Dense edge-to-edge mosaic that mirrors the NC Photos timeline: square\n * thumbnails packed tight with a thin main-background gutter (the same\n * 2px border NC Photos draws between tiles), name + date revealed in a\n * bottom scrim on hover/focus so the imagery dominates at rest.\n */\n.cn-photos-tab__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(104px, 1fr));\n\tgap: 2px;\n\tborder-radius: var(--border-radius-large, var(--border-radius));\n\toverflow: hidden;\n}\n\n.cn-photos-tab__tile {\n\tposition: relative;\n}\n\n.cn-photos-tab__unlink {\n\tposition: absolute;\n\ttop: 4px;\n\tright: 4px;\n\tz-index: 3;\n\topacity: 0;\n\tbackground: var(--color-main-background);\n\tborder-radius: 50%;\n\ttransition: opacity 0.15s ease;\n}\n\n.cn-photos-tab__tile:hover .cn-photos-tab__unlink,\n.cn-photos-tab__tile:focus-within .cn-photos-tab__unlink {\n\topacity: 1;\n}\n\n.cn-photos-tab__tile-link {\n\tdisplay: block;\n\ttext-decoration: none;\n\tcolor: inherit;\n}\n\n.cn-photos-tab__cover {\n\tposition: relative;\n\taspect-ratio: 1 / 1;\n\twidth: 100%;\n\t/* Match the NC Photos selection outline on focus. */\n\tborder: 2px solid var(--color-main-background);\n\tbox-sizing: border-box;\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\toverflow: hidden;\n}\n\n.cn-photos-tab__tile-link:focus-visible .cn-photos-tab__cover {\n\toutline: var(--color-primary-element) solid 2px;\n\toutline-offset: -2px;\n}\n\n.cn-photos-tab__cover-img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tdisplay: block;\n\ttransition: transform 0.2s ease;\n}\n\n.cn-photos-tab__tile:hover .cn-photos-tab__cover-img,\n.cn-photos-tab__tile:focus-within .cn-photos-tab__cover-img {\n\ttransform: scale(1.04);\n}\n\n.cn-photos-tab__cover-placeholder {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 100%;\n\theight: 100%;\n}\n\n.cn-photos-tab__cover-fallback {\n\tcolor: var(--color-primary-element);\n}\n\n/* Photo-count badge (mirrors the album stack count overlay). */\n.cn-photos-tab__badge {\n\tposition: absolute;\n\ttop: 4px;\n\tleft: 4px;\n\tz-index: 2;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n\tpadding: 1px 6px 1px 4px;\n\tborder-radius: var(--border-radius-pill, 12px);\n\tbackground: rgba(0, 0, 0, 0.55);\n\tcolor: #fff;\n\tfont-size: 11px;\n\tfont-weight: 500;\n\tline-height: 1.6;\n}\n\n.cn-photos-tab__badge-count {\n\twhite-space: nowrap;\n}\n\n/* Bottom scrim: hidden at rest, revealed on hover/focus. */\n.cn-photos-tab__meta {\n\tposition: absolute;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 1px;\n\tpadding: 16px 8px 6px;\n\tbackground: linear-gradient(to top, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0));\n\topacity: 0;\n\ttransition: opacity 0.15s ease;\n\tpointer-events: none;\n}\n\n.cn-photos-tab__tile:hover .cn-photos-tab__meta,\n.cn-photos-tab__tile:focus-within .cn-photos-tab__meta {\n\topacity: 1;\n}\n\n.cn-photos-tab__name {\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: #fff;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-photos-tab__when {\n\tfont-size: 10px;\n\tcolor: rgba(255, 255, 255, 0.85);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n</style>\n"],"names":["CnPhotoAlbumPicker","CnPhotoAlbumCreate","t","n"],"mappings":"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"}
|
|
@@ -98,8 +98,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
98
98
|
createVNode(_component_NcButton, {
|
|
99
99
|
variant: "primary",
|
|
100
100
|
href: $props.photosAppUrl,
|
|
101
|
-
target: "_blank"
|
|
102
|
-
rel: "noopener noreferrer"
|
|
101
|
+
target: "_blank"
|
|
103
102
|
}, {
|
|
104
103
|
icon: withCtx(() => [
|
|
105
104
|
createVNode(_component_ImageIcon, { size: 20 })
|