@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":"CnPhotosTab.vue3.js","sources":["../../../../../src/integrations/builtin/photos/CnPhotosTab.vue?vue&type=template&id=7921c8e4&lang.js"],"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":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_createBlock","_toDisplayString"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnPhotosTab.vue3.js","sources":["../../../../../src/integrations/builtin/photos/CnPhotosTab.vue?vue&type=template&id=7921c8e4&lang.js"],"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":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_createBlock","_toDisplayString"],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@ import Poll from 'vue-material-design-icons/Poll.vue';
|
|
|
6
6
|
import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
|
|
7
7
|
import __sfc_main$1 from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
|
|
8
8
|
import { stripMarker } from '../../utils/marker.js';
|
|
9
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
9
10
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
10
11
|
|
|
11
12
|
const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
|
|
@@ -142,7 +143,7 @@ var script = {
|
|
|
142
143
|
},
|
|
143
144
|
|
|
144
145
|
pollUrl(poll) {
|
|
145
|
-
if (poll.url) {
|
|
146
|
+
if (poll.url && safeHref(poll.url) !== '#') {
|
|
146
147
|
return poll.url
|
|
147
148
|
}
|
|
148
149
|
const id = poll.id ?? '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnPollsCard.vue2.js","sources":["../../../../../src/integrations/builtin/polls/CnPollsCard.vue"],"sourcesContent":["<!--\n CnPollsCard — bespoke surface-aware widget for the `polls` integration.\n\n Replaces the generic CnIntegrationCard for the `polls` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N polls\" + \"M open\"\n secondary line names the most-recent poll with its leading\n option (per integration-polls spec).\n - detail-page : compact list of linked polls\n with deadline countdown and a mini per-option bar set per row.\n - single-entity : chip with poll title + leading\n option indicator (referenceType: 'polls').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnPollsTab; for `single-entity` the optional `value` prop addresses a\n single poll by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-polls/` 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-polls-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<Poll :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"pollUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ pollTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"leadingOption(entity)\" class=\"cn-polls-card__chip-leader\">\n\t\t\t\t\t· {{ leadingOption(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-polls-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-polls-card__headline\">\n\t\t\t\t<div class=\"cn-polls-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-polls-card__headline-recent\">\n\t\t\t\t\t<Poll :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pollUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ pollTitle(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"leadingOption(mostRecent)\" class=\"cn-polls-card__headline-leader\">\n\t\t\t\t\t\t· {{ leadingOption(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-polls-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"poll in displayedPolls\"\n\t\t\t\t\t:key=\"pollKey(poll)\"\n\t\t\t\t\tclass=\"cn-polls-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-polls-card__row--closed': isClosed(poll) }\">\n\t\t\t\t\t<div class=\"cn-polls-card__row-header\">\n\t\t\t\t\t\t<span class=\"cn-polls-card__row-icon\" :title=\"typeLabel(poll)\" :aria-label=\"typeLabel(poll)\">\n\t\t\t\t\t\t\t<CalendarRange v-if=\"isDatePoll(poll)\" :size=\"16\" />\n\t\t\t\t\t\t\t<FormatListChecks v-else :size=\"16\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"pollUrl(poll)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__title\">{{ pollTitle(poll) }}</a>\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t:label=\"statusLabel(poll)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(poll)\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__status\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(poll)\" class=\"cn-polls-card__subtitle\">{{ rowMeta(poll) }}</span>\n\t\t\t\t\t<ul v-if=\"pollOptions(poll).length > 0\" class=\"cn-polls-card__options\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"opt in pollOptions(poll)\"\n\t\t\t\t\t\t\t:key=\"optionKey(opt)\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__option\">\n\t\t\t\t\t\t\t<div class=\"cn-polls-card__option-label\">\n\t\t\t\t\t\t\t\t<span class=\"cn-polls-card__option-text\">{{ optionLabel(opt) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-polls-card__option-count\">{{ optionPercent(poll, opt) }}%</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclass=\"cn-polls-card__option-bar\"\n\t\t\t\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\t\t\t\t:aria-valuenow=\"optionPercent(poll, opt)\"\n\t\t\t\t\t\t\t\taria-valuemin=\"0\"\n\t\t\t\t\t\t\t\taria-valuemax=\"100\">\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclass=\"cn-polls-card__option-bar-fill\"\n\t\t\t\t\t\t\t\t\t:style=\"{ width: optionPercent(poll, opt) + '%' }\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CalendarRange from 'vue-material-design-icons/CalendarRange.vue'\nimport FormatListChecks from 'vue-material-design-icons/FormatListChecks.vue'\nimport Poll from 'vue-material-design-icons/Poll.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnPollsCard — bespoke surface-aware widget for the `polls` integration.\n *\n * Renders Polls-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnPollsCard',\n\n\tcomponents: { CnDetailCard, CnStatusBadge, NcLoadingIcon, CalendarRange, FormatListChecks, Poll },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'polls'`). */\n\t\tintegrationId: { type: String, default: 'polls' },\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 (poll id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Polls') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Poll },\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 polls linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Polls is currently unavailable.') },\n\t\t/** URL of the NC Polls app entry. */\n\t\tpollsAppUrl: { type: String, default: '/index.php/apps/polls' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpolls: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedPolls() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.polls.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.polls\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.polls.reduce((sum, p) => sum + (this.isClosed(p) ? 0 : 1), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.polls.length\n\t\t\tconst open = this.openCount\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} poll (all closed)', '{count} polls (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} poll', '{count} polls', total, { count: total })\n\t\t\tconst openFragment = n('nextcloud-vue', '{count} open', '{count} open', open, { count: open })\n\t\t\treturn `${totalFragment} · ${openFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.polls.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Sort defensive copy by deadline / created desc.\n\t\t\tconst sorted = [...this.polls].sort((a, b) => {\n\t\t\t\tconst ta = this.sortTimestamp(a)\n\t\t\t\tconst tb = this.sortTimestamp(b)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tpollKey(poll) {\n\t\t\treturn poll.id ?? poll.reference ?? ''\n\t\t},\n\n\t\tpollTitle(poll) {\n\t\t\tconst raw = poll.title ?? poll.name ?? this.pollKey(poll)\n\t\t\treturn stripMarker(raw) || String(raw)\n\t\t},\n\n\t\tpollUrl(poll) {\n\t\t\tif (poll.url) {\n\t\t\t\treturn poll.url\n\t\t\t}\n\t\t\tconst id = poll.id ?? ''\n\t\t\treturn id ? `/index.php/apps/polls/vote/${id}` : this.pollsAppUrl\n\t\t},\n\n\t\tpollType(poll) {\n\t\t\treturn poll.type ?? poll.pollType ?? ''\n\t\t},\n\n\t\tisDatePoll(poll) {\n\t\t\tconst type = String(this.pollType(poll)).toLowerCase()\n\t\t\treturn type.indexOf('date') !== -1\n\t\t},\n\n\t\ttypeLabel(poll) {\n\t\t\treturn this.isDatePoll(poll)\n\t\t\t\t? t('nextcloud-vue', 'Date poll')\n\t\t\t\t: t('nextcloud-vue', 'Text poll')\n\t\t},\n\n\t\tstatusLabel(poll) {\n\t\t\treturn this.isClosed(poll)\n\t\t\t\t? t('nextcloud-vue', 'Closed')\n\t\t\t\t: t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusVariant(poll) {\n\t\t\treturn this.isClosed(poll) ? 'default' : 'success'\n\t\t},\n\n\t\tpollOptions(poll) {\n\t\t\tconst opts = poll.options ?? poll.results ?? poll.optionResults ?? []\n\t\t\treturn Array.isArray(opts) ? opts : []\n\t\t},\n\n\t\toptionKey(opt) {\n\t\t\treturn opt.id ?? opt.key ?? opt.text ?? ''\n\t\t},\n\n\t\toptionLabel(opt) {\n\t\t\treturn opt.text ?? opt.label ?? opt.name ?? String(this.optionKey(opt))\n\t\t},\n\n\t\toptionCount(opt) {\n\t\t\tconst c = Number(opt.votes ?? opt.count ?? opt.voteCount ?? 0)\n\t\t\treturn Number.isFinite(c) ? c : 0\n\t\t},\n\n\t\ttotalVotes(poll) {\n\t\t\treturn this.pollOptions(poll).reduce((sum, o) => sum + this.optionCount(o), 0)\n\t\t},\n\n\t\toptionPercent(poll, opt) {\n\t\t\tconst total = this.totalVotes(poll)\n\t\t\tif (total <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.round((this.optionCount(opt) / total) * 100)\n\t\t},\n\n\t\tleadingOption(poll) {\n\t\t\tconst opts = this.pollOptions(poll)\n\t\t\tif (opts.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tlet best = opts[0]\n\t\t\tfor (const o of opts) {\n\t\t\t\tif (this.optionCount(o) > this.optionCount(best)) {\n\t\t\t\t\tbest = o\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.optionCount(best) === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.optionLabel(best)} (${this.optionPercent(poll, best)}%)`\n\t\t},\n\n\t\tdeadline(poll) {\n\t\t\treturn poll.deadline ?? poll.expire ?? poll.expiresAt ?? null\n\t\t},\n\n\t\tdeadlineMs(poll) {\n\t\t\tconst v = this.deadline(poll)\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tsortTimestamp(poll) {\n\t\t\tconst d = this.deadlineMs(poll)\n\t\t\tif (d !== null) {\n\t\t\t\treturn d\n\t\t\t}\n\t\t\tconst c = Number(poll.created ?? 0)\n\t\t\treturn Number.isFinite(c) ? (c < 1e12 ? c * 1000 : c) : 0\n\t\t},\n\n\t\tisClosed(poll) {\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\treturn poll.closed === true || poll.status === 'closed'\n\t\t\t}\n\t\t\treturn ms <= Date.now()\n\t\t},\n\n\t\trowMeta(poll) {\n\t\t\tif (this.isClosed(poll)) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = ms - Date.now()\n\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\tif (days <= 1) {\n\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Closes in {n} hours', { n: hours })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Closes in {n} days', { n: days })\n\t\t},\n\n\t\tchipSubtitle(poll) {\n\t\t\treturn this.leadingOption(poll) || this.pollTitle(poll)\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.polls = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.polls = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.polls = []\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('[CnPollsCard] failed to fetch polls', err)\n\t\t\t\tthis.polls = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnPollsCard] failed to fetch single poll', 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-polls-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-polls-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-polls-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-polls-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-polls-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__headline-leader {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-polls-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-polls-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-polls-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__chip-leader {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-polls-card__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-polls-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-polls-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-polls-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-polls-card__row-icon {\n\tdisplay: inline-flex;\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-polls-card__row--closed .cn-polls-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__status {\n\tflex-shrink: 0;\n}\n\n.cn-polls-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-polls-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n\n.cn-polls-card__options {\n\tlist-style: none;\n\tmargin: 2px 0 0;\n\tpadding: 0 0 0 24px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-polls-card__option {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 1px;\n}\n\n.cn-polls-card__option-label {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 6px;\n\tfont-size: 0.8em;\n}\n\n.cn-polls-card__option-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-card__option-count {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__option-bar {\n\theight: 4px;\n\tborder-radius: 2px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n}\n\n.cn-polls-card__option-bar-fill {\n\theight: 100%;\n\tbackground: var(--color-primary-element, #21468B);\n\ttransition: width 0.2s ease;\n}\n</style>\n"],"names":["CnDetailCard","CnStatusBadge","t","n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnPollsCard.vue2.js","sources":["../../../../../src/integrations/builtin/polls/CnPollsCard.vue"],"sourcesContent":["<!--\n CnPollsCard — bespoke surface-aware widget for the `polls` integration.\n\n Replaces the generic CnIntegrationCard for the `polls` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N polls\" + \"M open\"\n secondary line names the most-recent poll with its leading\n option (per integration-polls spec).\n - detail-page : compact list of linked polls\n with deadline countdown and a mini per-option bar set per row.\n - single-entity : chip with poll title + leading\n option indicator (referenceType: 'polls').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnPollsTab; for `single-entity` the optional `value` prop addresses a\n single poll by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-polls/` 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-polls-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<Poll :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"pollUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ pollTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"leadingOption(entity)\" class=\"cn-polls-card__chip-leader\">\n\t\t\t\t\t· {{ leadingOption(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-polls-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-polls-card__headline\">\n\t\t\t\t<div class=\"cn-polls-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-polls-card__headline-recent\">\n\t\t\t\t\t<Poll :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pollUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ pollTitle(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"leadingOption(mostRecent)\" class=\"cn-polls-card__headline-leader\">\n\t\t\t\t\t\t· {{ leadingOption(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-polls-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"poll in displayedPolls\"\n\t\t\t\t\t:key=\"pollKey(poll)\"\n\t\t\t\t\tclass=\"cn-polls-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-polls-card__row--closed': isClosed(poll) }\">\n\t\t\t\t\t<div class=\"cn-polls-card__row-header\">\n\t\t\t\t\t\t<span class=\"cn-polls-card__row-icon\" :title=\"typeLabel(poll)\" :aria-label=\"typeLabel(poll)\">\n\t\t\t\t\t\t\t<CalendarRange v-if=\"isDatePoll(poll)\" :size=\"16\" />\n\t\t\t\t\t\t\t<FormatListChecks v-else :size=\"16\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"pollUrl(poll)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__title\">{{ pollTitle(poll) }}</a>\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t:label=\"statusLabel(poll)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(poll)\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__status\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(poll)\" class=\"cn-polls-card__subtitle\">{{ rowMeta(poll) }}</span>\n\t\t\t\t\t<ul v-if=\"pollOptions(poll).length > 0\" class=\"cn-polls-card__options\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"opt in pollOptions(poll)\"\n\t\t\t\t\t\t\t:key=\"optionKey(opt)\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__option\">\n\t\t\t\t\t\t\t<div class=\"cn-polls-card__option-label\">\n\t\t\t\t\t\t\t\t<span class=\"cn-polls-card__option-text\">{{ optionLabel(opt) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-polls-card__option-count\">{{ optionPercent(poll, opt) }}%</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclass=\"cn-polls-card__option-bar\"\n\t\t\t\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\t\t\t\t:aria-valuenow=\"optionPercent(poll, opt)\"\n\t\t\t\t\t\t\t\taria-valuemin=\"0\"\n\t\t\t\t\t\t\t\taria-valuemax=\"100\">\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclass=\"cn-polls-card__option-bar-fill\"\n\t\t\t\t\t\t\t\t\t:style=\"{ width: optionPercent(poll, opt) + '%' }\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CalendarRange from 'vue-material-design-icons/CalendarRange.vue'\nimport FormatListChecks from 'vue-material-design-icons/FormatListChecks.vue'\nimport Poll from 'vue-material-design-icons/Poll.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnPollsCard — bespoke surface-aware widget for the `polls` integration.\n *\n * Renders Polls-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnPollsCard',\n\n\tcomponents: { CnDetailCard, CnStatusBadge, NcLoadingIcon, CalendarRange, FormatListChecks, Poll },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'polls'`). */\n\t\tintegrationId: { type: String, default: 'polls' },\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 (poll id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Polls') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Poll },\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 polls linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Polls is currently unavailable.') },\n\t\t/** URL of the NC Polls app entry. */\n\t\tpollsAppUrl: { type: String, default: '/index.php/apps/polls' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpolls: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedPolls() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.polls.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.polls\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.polls.reduce((sum, p) => sum + (this.isClosed(p) ? 0 : 1), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.polls.length\n\t\t\tconst open = this.openCount\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} poll (all closed)', '{count} polls (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} poll', '{count} polls', total, { count: total })\n\t\t\tconst openFragment = n('nextcloud-vue', '{count} open', '{count} open', open, { count: open })\n\t\t\treturn `${totalFragment} · ${openFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.polls.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Sort defensive copy by deadline / created desc.\n\t\t\tconst sorted = [...this.polls].sort((a, b) => {\n\t\t\t\tconst ta = this.sortTimestamp(a)\n\t\t\t\tconst tb = this.sortTimestamp(b)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tpollKey(poll) {\n\t\t\treturn poll.id ?? poll.reference ?? ''\n\t\t},\n\n\t\tpollTitle(poll) {\n\t\t\tconst raw = poll.title ?? poll.name ?? this.pollKey(poll)\n\t\t\treturn stripMarker(raw) || String(raw)\n\t\t},\n\n\t\tpollUrl(poll) {\n\t\t\tif (poll.url && safeHref(poll.url) !== '#') {\n\t\t\t\treturn poll.url\n\t\t\t}\n\t\t\tconst id = poll.id ?? ''\n\t\t\treturn id ? `/index.php/apps/polls/vote/${id}` : this.pollsAppUrl\n\t\t},\n\n\t\tpollType(poll) {\n\t\t\treturn poll.type ?? poll.pollType ?? ''\n\t\t},\n\n\t\tisDatePoll(poll) {\n\t\t\tconst type = String(this.pollType(poll)).toLowerCase()\n\t\t\treturn type.indexOf('date') !== -1\n\t\t},\n\n\t\ttypeLabel(poll) {\n\t\t\treturn this.isDatePoll(poll)\n\t\t\t\t? t('nextcloud-vue', 'Date poll')\n\t\t\t\t: t('nextcloud-vue', 'Text poll')\n\t\t},\n\n\t\tstatusLabel(poll) {\n\t\t\treturn this.isClosed(poll)\n\t\t\t\t? t('nextcloud-vue', 'Closed')\n\t\t\t\t: t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusVariant(poll) {\n\t\t\treturn this.isClosed(poll) ? 'default' : 'success'\n\t\t},\n\n\t\tpollOptions(poll) {\n\t\t\tconst opts = poll.options ?? poll.results ?? poll.optionResults ?? []\n\t\t\treturn Array.isArray(opts) ? opts : []\n\t\t},\n\n\t\toptionKey(opt) {\n\t\t\treturn opt.id ?? opt.key ?? opt.text ?? ''\n\t\t},\n\n\t\toptionLabel(opt) {\n\t\t\treturn opt.text ?? opt.label ?? opt.name ?? String(this.optionKey(opt))\n\t\t},\n\n\t\toptionCount(opt) {\n\t\t\tconst c = Number(opt.votes ?? opt.count ?? opt.voteCount ?? 0)\n\t\t\treturn Number.isFinite(c) ? c : 0\n\t\t},\n\n\t\ttotalVotes(poll) {\n\t\t\treturn this.pollOptions(poll).reduce((sum, o) => sum + this.optionCount(o), 0)\n\t\t},\n\n\t\toptionPercent(poll, opt) {\n\t\t\tconst total = this.totalVotes(poll)\n\t\t\tif (total <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.round((this.optionCount(opt) / total) * 100)\n\t\t},\n\n\t\tleadingOption(poll) {\n\t\t\tconst opts = this.pollOptions(poll)\n\t\t\tif (opts.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tlet best = opts[0]\n\t\t\tfor (const o of opts) {\n\t\t\t\tif (this.optionCount(o) > this.optionCount(best)) {\n\t\t\t\t\tbest = o\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.optionCount(best) === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.optionLabel(best)} (${this.optionPercent(poll, best)}%)`\n\t\t},\n\n\t\tdeadline(poll) {\n\t\t\treturn poll.deadline ?? poll.expire ?? poll.expiresAt ?? null\n\t\t},\n\n\t\tdeadlineMs(poll) {\n\t\t\tconst v = this.deadline(poll)\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tsortTimestamp(poll) {\n\t\t\tconst d = this.deadlineMs(poll)\n\t\t\tif (d !== null) {\n\t\t\t\treturn d\n\t\t\t}\n\t\t\tconst c = Number(poll.created ?? 0)\n\t\t\treturn Number.isFinite(c) ? (c < 1e12 ? c * 1000 : c) : 0\n\t\t},\n\n\t\tisClosed(poll) {\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\treturn poll.closed === true || poll.status === 'closed'\n\t\t\t}\n\t\t\treturn ms <= Date.now()\n\t\t},\n\n\t\trowMeta(poll) {\n\t\t\tif (this.isClosed(poll)) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = ms - Date.now()\n\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\tif (days <= 1) {\n\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Closes in {n} hours', { n: hours })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Closes in {n} days', { n: days })\n\t\t},\n\n\t\tchipSubtitle(poll) {\n\t\t\treturn this.leadingOption(poll) || this.pollTitle(poll)\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.polls = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.polls = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.polls = []\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('[CnPollsCard] failed to fetch polls', err)\n\t\t\t\tthis.polls = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnPollsCard] failed to fetch single poll', 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-polls-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-polls-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-polls-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-polls-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-polls-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__headline-leader {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-polls-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-polls-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-polls-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__chip-leader {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-polls-card__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-polls-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-polls-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-polls-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-polls-card__row-icon {\n\tdisplay: inline-flex;\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-polls-card__row--closed .cn-polls-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__status {\n\tflex-shrink: 0;\n}\n\n.cn-polls-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-polls-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n\n.cn-polls-card__options {\n\tlist-style: none;\n\tmargin: 2px 0 0;\n\tpadding: 0 0 0 24px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-polls-card__option {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 1px;\n}\n\n.cn-polls-card__option-label {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 6px;\n\tfont-size: 0.8em;\n}\n\n.cn-polls-card__option-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-card__option-count {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__option-bar {\n\theight: 4px;\n\tborder-radius: 2px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n}\n\n.cn-polls-card__option-bar-fill {\n\theight: 100%;\n\tbackground: var(--color-primary-element, #21468B);\n\ttransition: width 0.2s ease;\n}\n</style>\n"],"names":["CnDetailCard","CnStatusBadge","t","n"],"mappings":";;;;;;;;;;;AAsIA,MAAM,cAAe,GAAE,CAAC,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAA;AACzF,MAAM,gBAAgB,EAAA;AACtB,MAAM,aAAa,EAAG,GAAE,EAAC,GAAI,EAAG,GAAE,KAAG;;AAErC;AACC;AACA;;;;AAID,aAAe;AACd,CAAA,IAAI,EAAE,aAAa;;AAEnB,CAAA,UAAU,EAAE,gBAAEA,UAAY,iBAAEC,YAAa,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM;;AAEjG,CAAA,KAAK,EAAE;AACN;AACA,EAAA,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS;AACjD;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;AAC1C;AACA,EAAA,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;AACxC;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;AAE1C,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,aAAa;AACtB,GAAA,SAAS,EAAE,CAAC,CAAC,KAAK,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;GAC5C;;AAED;AACA,EAAA,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAC,EAAG;;EAEpC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA,EAAG;AACnE;AACA,EAAA,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,MAAM;AAC3C;EACA,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAuB,EAAG;AAC5D;AACA,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM;AAC7C;AACA,EAAA,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,qBAAqB,GAAG;AACtF;EACA,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,oCAAoC,GAAG;;EAE3G,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAsB,EAAG;EAC/D;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN,GAAA,KAAK,EAAE,EAAE;GACT,MAAM,EAAE,IAAI;GACZ,OAAO,EAAE,KAAK;GACd,QAAQ,EAAE,EAAE;AACb,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;EACT,SAAS,GAAG;AACX,GAAA,OAAO,IAAI,CAAC,KAAM,IAAG,IAAI,CAAC,aAAY;GACtC;;EAED,QAAQ,GAAG;AACV,GAAA,OAAO,IAAI,CAAC,IAAG;GACf;;AAED,EAAA,cAAc,GAAG;AAChB,GAAA,IAAI,IAAI,CAAC,OAAQ,KAAI,gBAAiB,IAAG,IAAI,CAAC,YAAY,eAAe,EAAE;AAC1E,IAAA,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAA;AACzC,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,KAAI;GAChB;;EAED,SAAS,GAAG;GACX,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,GAAE,IAAK,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA,GAAI,CAAA,GAAI,CAAC,CAAC,EAAE,CAAC,CAAA;GACxE;;AAED,EAAA,aAAa,GAAG;GACf,MAAM,KAAM,GAAE,IAAI,CAAC,KAAK,CAAC,OAAK;GAC9B,MAAM,OAAO,IAAI,CAAC,UAAQ;GAC1B,IAAI,SAAS,CAAC,EAAE;IACf,OAAOC,eAAC,CAAC,eAAe,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,CAAA;AAC7G,IAAA;AACA,GAAA,MAAM,gBAAgBA,eAAC,CAAC,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAA;AACjG,GAAA,MAAM,eAAeA,eAAC,CAAC,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAA;AAC7F,GAAA,OAAO,CAAC,EAAE,aAAa,CAAE,GAAE,EAAE,YAAY,CAAC,CAAA;GAC1C;;EAED,UAAU,GAAG;GACZ,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE;AAC5B,IAAA,OAAO,IAAG;AACX,IAAA;AACA;AACA,GAAA,MAAM,MAAK,GAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK;IAC7C,MAAM,EAAG,GAAE,IAAI,CAAC,aAAa,CAAC,CAAC,EAAA;IAC/B,MAAM,EAAG,GAAE,IAAI,CAAC,aAAa,CAAC,CAAC,EAAA;IAC/B,OAAO,EAAC,GAAI,EAAC;IACb,EAAA;AACD,GAAA,OAAO,MAAM,CAAC,CAAC,CAAA;GACf;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG;GACtC,IAAI,CAAC,KAAK,GAAC;AACZ,GAAA,EAAG;;AAEH,EAAA,OAAO,GAAG;GACT,IAAI,CAAC,KAAK,GAAC;GACX;;AAED,EAAA,KAAK,GAAG;AACP,GAAA,IAAI,IAAI,CAAC,YAAY,eAAe,EAAE;AACrC,IAAA,IAAI,CAAC,WAAW,GAAC;AAClB,IAAA;GACA;EACD;;AAED,CAAA,OAAO,EAAE;AACR,EAAA,OAAO,GAAG;GACT,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,CAAA;GACnH;;EAED,OAAO,CAAC,IAAI,EAAE;AACb,GAAA,OAAO,IAAI,CAAC,EAAC,IAAK,IAAI,CAAC,SAAU,IAAG,EAAC;GACrC;;AAED,EAAA,SAAS,CAAC,IAAI,EAAE;GACf,MAAM,GAAE,GAAI,IAAI,CAAC,KAAM,IAAG,IAAI,CAAC,IAAG,IAAK,IAAI,CAAC,OAAO,CAAC,IAAI,EAAA;AACxD,GAAA,OAAO,WAAW,CAAC,GAAG,CAAE,IAAG,MAAM,CAAC,GAAG,CAAA;GACrC;;EAED,OAAO,CAAC,IAAI,EAAE;AACb,GAAA,IAAI,IAAI,CAAC,GAAE,IAAK,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAA,KAAM,GAAG,EAAE;IAC3C,OAAO,IAAI,CAAC,GAAE;AACf,IAAA;AACA,GAAA,MAAM,KAAK,IAAI,CAAC,EAAG,IAAG,GAAC;GACvB,OAAO,EAAC,GAAI,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAE,GAAE,IAAI,CAAC,WAAU;GAChE;;AAED,EAAA,QAAQ,CAAC,IAAI,EAAE;AACd,GAAA,OAAO,IAAI,CAAC,IAAK,IAAG,IAAI,CAAC,QAAS,IAAG,EAAC;GACtC;;AAED,EAAA,UAAU,CAAC,IAAI,EAAE;GAChB,MAAM,IAAK,GAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,GAAC;AACrD,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAE,KAAI,CAAC,CAAA;GACjC;;AAED,EAAA,SAAS,CAAC,IAAI,EAAE;GACf,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAA;MACxBD,SAAC,CAAC,eAAe,EAAE,WAAW,CAAA;MAC9BA,SAAC,CAAC,eAAe,EAAE,WAAW,CAAA;GACjC;;AAED,EAAA,WAAW,CAAC,IAAI,EAAE;GACjB,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAA;MACtBA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAA;MAC3BA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA;GAC5B;;AAED,EAAA,aAAa,CAAC,IAAI,EAAE;AACnB,GAAA,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,SAAU,GAAE,SAAQ;GACjD;;AAED,EAAA,WAAW,CAAC,IAAI,EAAE;GACjB,MAAM,IAAK,GAAE,IAAI,CAAC,OAAQ,IAAG,IAAI,CAAC,OAAM,IAAK,IAAI,CAAC,aAAY,IAAK,GAAC;AACpE,GAAA,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAE,GAAE,IAAG,GAAI,EAAC;GACrC;;AAED,EAAA,SAAS,CAAC,GAAG,EAAE;AACd,GAAA,OAAO,GAAG,CAAC,EAAC,IAAK,GAAG,CAAC,GAAE,IAAK,GAAG,CAAC,IAAK,IAAG,EAAC;GACzC;;AAED,EAAA,WAAW,CAAC,GAAG,EAAE;AAChB,GAAA,OAAO,GAAG,CAAC,IAAK,IAAG,GAAG,CAAC,KAAI,IAAK,GAAG,CAAC,IAAG,IAAK,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;GACtE;;AAED,EAAA,WAAW,CAAC,GAAG,EAAE;GAChB,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC,KAAM,IAAG,GAAG,CAAC,KAAM,IAAG,GAAG,CAAC,SAAQ,IAAK,CAAC,EAAA;GAC7D,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAA,GAAI,CAAA,GAAI,CAAA;GAChC;;AAED,EAAA,UAAU,CAAC,IAAI,EAAE;AAChB,GAAA,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,GAAE,GAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;GAC7E;;EAED,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE;AACxB,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAA;GAClC,IAAI,KAAM,IAAG,CAAC,EAAE;AACf,IAAA,OAAO,CAAA;AACR,IAAA;GACA,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,IAAI,KAAK,IAAI,GAAG,CAAA;GACvD;;AAED,EAAA,aAAa,CAAC,IAAI,EAAE;AACnB,GAAA,MAAM,IAAG,GAAI,IAAI,CAAC,WAAW,CAAC,IAAI,EAAA;AAClC,GAAA,IAAI,IAAI,CAAC,MAAK,KAAM,CAAC,EAAE;AACtB,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,IAAI,IAAG,GAAI,IAAI,CAAC,CAAC,EAAA;AACjB,GAAA,KAAK,MAAM,CAAA,IAAK,IAAI,EAAE;IACrB,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAE,GAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE;KACjD,IAAG,GAAI,EAAA;AACR,KAAA;AACD,IAAA;AACA,GAAA,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,CAAA,KAAM,CAAC,EAAE;AACjC,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAE,EAAC,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAA;GACtE;;AAED,EAAA,QAAQ,CAAC,IAAI,EAAE;GACd,OAAO,IAAI,CAAC,QAAO,IAAK,IAAI,CAAC,MAAO,IAAG,IAAI,CAAC,aAAa,IAAG;GAC5D;;AAED,EAAA,UAAU,CAAC,IAAI,EAAE;GAChB,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAA;AAC5B,GAAA,IAAI,CAAE,KAAI,IAAK,IAAG,CAAA,KAAM,SAAQ,IAAK,MAAM,EAAE,EAAE;AAC9C,IAAA,OAAO,IAAG;AACX,IAAA;GACA,IAAI,OAAO,CAAE,KAAI,QAAQ,EAAE;AAC1B,IAAA,OAAO,CAAA,GAAI,IAAG,GAAI,CAAE,GAAE,OAAO,CAAA;AAC9B,IAAA;AACA,GAAA,MAAM,MAAK,GAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAA;AACnC,GAAA,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAE,KAAI,IAAG,GAAI,IAAK,GAAE,MAAK;GACnD;;AAED,EAAA,aAAa,CAAC,IAAI,EAAE;GACnB,MAAM,CAAA,GAAI,IAAI,CAAC,UAAU,CAAC,IAAI,EAAA;GAC9B,IAAI,CAAA,KAAM,IAAI,EAAE;AACf,IAAA,OAAO,CAAA;AACR,IAAA;GACA,MAAM,CAAA,GAAI,MAAM,CAAC,IAAI,CAAC,OAAM,IAAK,CAAC,EAAA;AAClC,GAAA,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI,IAAG,GAAI,CAAE,GAAE,OAAO,CAAC,IAAI,CAAA;GACxD;;AAED,EAAA,QAAQ,CAAC,IAAI,EAAE;GACd,MAAM,EAAG,GAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAA;GAC/B,IAAI,EAAG,KAAI,IAAI,EAAE;IAChB,OAAO,IAAI,CAAC,MAAK,KAAM,QAAQ,IAAI,CAAC,MAAO,KAAI,QAAO;AACvD,IAAA;AACA,GAAA,OAAO,EAAG,IAAG,IAAI,CAAC,GAAG,EAAC;GACtB;;EAED,OAAO,CAAC,IAAI,EAAE;GACb,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;AACxB,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAA;AACnC,IAAA;GACA,MAAM,EAAG,GAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAA;GAC/B,IAAI,EAAG,KAAI,IAAI,EAAE;AAChB,IAAA,OAAO,EAAC;AACT,IAAA;GACA,MAAM,OAAO,KAAK,IAAI,CAAC,GAAG,GAAC;AAC3B,GAAA,MAAM,IAAK,GAAE,IAAI,CAAC,IAAI,CAAC,OAAO,UAAU,EAAA;GACxC,IAAI,IAAK,IAAG,CAAC,EAAE;IACd,MAAM,KAAI,GAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAG,IAAK,EAAG,GAAE,EAAG,GAAE,IAAI,CAAC,CAAC,EAAA;IAC5D,OAAOA,SAAC,CAAC,eAAe,EAAE,qBAAqB,EAAE,EAAE,CAAC,EAAE,KAAI,EAAG,CAAA;AAC9D,IAAA;GACA,OAAOA,SAAC,CAAC,eAAe,EAAE,oBAAoB,EAAE,EAAE,CAAC,EAAE,IAAG,EAAG,CAAA;GAC3D;;AAED,EAAA,YAAY,CAAC,IAAI,EAAE;GAClB,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAE,IAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAA;GACtD;;AAED,EAAA,KAAK,GAAG;AACP,GAAA,IAAI,IAAI,CAAC,YAAY,eAAe,EAAE;AACrC,IAAA,IAAI,CAAC,WAAW,GAAC;IACjB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,SAAS,GAAC;GACf;;AAED,EAAA,MAAM,SAAS,GAAG;GACjB,IAAI,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE;IACrD,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,QAAO,GAAI,GAAC;AACjB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AACnF,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;AACjC,KAAA,IAAI,CAAC,KAAM,GAAE,IAAI,CAAC,WAAW,IAAI,CAAC,KAAM,KAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAE,GAAE,IAAK,GAAE,EAAE,CAAE,IAAG,GAAC;AAClF,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;KACnC,IAAI,CAAC,KAAM,GAAE,GAAC;AACd,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;KACN,IAAI,CAAC,KAAM,GAAE,GAAC;AACf,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,GAAG,EAAA;IACxD,IAAI,CAAC,KAAM,GAAE,GAAC;AACf,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED,EAAA,MAAM,WAAW,GAAG;GACnB,IAAI,CAAC,IAAI,CAAC,KAAI,IAAK,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,MAAK,IAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;AACpE,IAAA,IAAI,CAAC,MAAK,GAAI,KAAG;IACjB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,QAAO,GAAI,GAAC;AACjB,GAAA,IAAI;IACH,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,EAAC,EAAG,EAAA;AAC1H,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;AAChB,KAAA,IAAI,CAAC,MAAK,GAAI,MAAM,QAAQ,CAAC,IAAI,GAAC;AACnC,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AACjB,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;AACN,KAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AAClB,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,GAAG,EAAA;AAC9D,IAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AAClB,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;EACD;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnPollsCard.vue3.js","sources":["../../../../../src/integrations/builtin/polls/CnPollsCard.vue?vue&type=template&id=5977968a&lang.js"],"sourcesContent":["<!--\n CnPollsCard — bespoke surface-aware widget for the `polls` integration.\n\n Replaces the generic CnIntegrationCard for the `polls` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N polls\" + \"M open\"\n secondary line names the most-recent poll with its leading\n option (per integration-polls spec).\n - detail-page : compact list of linked polls\n with deadline countdown and a mini per-option bar set per row.\n - single-entity : chip with poll title + leading\n option indicator (referenceType: 'polls').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnPollsTab; for `single-entity` the optional `value` prop addresses a\n single poll by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-polls/` 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-polls-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<Poll :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"pollUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ pollTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"leadingOption(entity)\" class=\"cn-polls-card__chip-leader\">\n\t\t\t\t\t· {{ leadingOption(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-polls-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-polls-card__headline\">\n\t\t\t\t<div class=\"cn-polls-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-polls-card__headline-recent\">\n\t\t\t\t\t<Poll :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pollUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ pollTitle(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"leadingOption(mostRecent)\" class=\"cn-polls-card__headline-leader\">\n\t\t\t\t\t\t· {{ leadingOption(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-polls-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"poll in displayedPolls\"\n\t\t\t\t\t:key=\"pollKey(poll)\"\n\t\t\t\t\tclass=\"cn-polls-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-polls-card__row--closed': isClosed(poll) }\">\n\t\t\t\t\t<div class=\"cn-polls-card__row-header\">\n\t\t\t\t\t\t<span class=\"cn-polls-card__row-icon\" :title=\"typeLabel(poll)\" :aria-label=\"typeLabel(poll)\">\n\t\t\t\t\t\t\t<CalendarRange v-if=\"isDatePoll(poll)\" :size=\"16\" />\n\t\t\t\t\t\t\t<FormatListChecks v-else :size=\"16\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"pollUrl(poll)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__title\">{{ pollTitle(poll) }}</a>\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t:label=\"statusLabel(poll)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(poll)\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__status\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(poll)\" class=\"cn-polls-card__subtitle\">{{ rowMeta(poll) }}</span>\n\t\t\t\t\t<ul v-if=\"pollOptions(poll).length > 0\" class=\"cn-polls-card__options\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"opt in pollOptions(poll)\"\n\t\t\t\t\t\t\t:key=\"optionKey(opt)\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__option\">\n\t\t\t\t\t\t\t<div class=\"cn-polls-card__option-label\">\n\t\t\t\t\t\t\t\t<span class=\"cn-polls-card__option-text\">{{ optionLabel(opt) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-polls-card__option-count\">{{ optionPercent(poll, opt) }}%</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclass=\"cn-polls-card__option-bar\"\n\t\t\t\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\t\t\t\t:aria-valuenow=\"optionPercent(poll, opt)\"\n\t\t\t\t\t\t\t\taria-valuemin=\"0\"\n\t\t\t\t\t\t\t\taria-valuemax=\"100\">\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclass=\"cn-polls-card__option-bar-fill\"\n\t\t\t\t\t\t\t\t\t:style=\"{ width: optionPercent(poll, opt) + '%' }\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CalendarRange from 'vue-material-design-icons/CalendarRange.vue'\nimport FormatListChecks from 'vue-material-design-icons/FormatListChecks.vue'\nimport Poll from 'vue-material-design-icons/Poll.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnPollsCard — bespoke surface-aware widget for the `polls` integration.\n *\n * Renders Polls-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnPollsCard',\n\n\tcomponents: { CnDetailCard, CnStatusBadge, NcLoadingIcon, CalendarRange, FormatListChecks, Poll },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'polls'`). */\n\t\tintegrationId: { type: String, default: 'polls' },\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 (poll id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Polls') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Poll },\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 polls linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Polls is currently unavailable.') },\n\t\t/** URL of the NC Polls app entry. */\n\t\tpollsAppUrl: { type: String, default: '/index.php/apps/polls' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpolls: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedPolls() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.polls.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.polls\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.polls.reduce((sum, p) => sum + (this.isClosed(p) ? 0 : 1), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.polls.length\n\t\t\tconst open = this.openCount\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} poll (all closed)', '{count} polls (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} poll', '{count} polls', total, { count: total })\n\t\t\tconst openFragment = n('nextcloud-vue', '{count} open', '{count} open', open, { count: open })\n\t\t\treturn `${totalFragment} · ${openFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.polls.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Sort defensive copy by deadline / created desc.\n\t\t\tconst sorted = [...this.polls].sort((a, b) => {\n\t\t\t\tconst ta = this.sortTimestamp(a)\n\t\t\t\tconst tb = this.sortTimestamp(b)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tpollKey(poll) {\n\t\t\treturn poll.id ?? poll.reference ?? ''\n\t\t},\n\n\t\tpollTitle(poll) {\n\t\t\tconst raw = poll.title ?? poll.name ?? this.pollKey(poll)\n\t\t\treturn stripMarker(raw) || String(raw)\n\t\t},\n\n\t\tpollUrl(poll) {\n\t\t\tif (poll.url) {\n\t\t\t\treturn poll.url\n\t\t\t}\n\t\t\tconst id = poll.id ?? ''\n\t\t\treturn id ? `/index.php/apps/polls/vote/${id}` : this.pollsAppUrl\n\t\t},\n\n\t\tpollType(poll) {\n\t\t\treturn poll.type ?? poll.pollType ?? ''\n\t\t},\n\n\t\tisDatePoll(poll) {\n\t\t\tconst type = String(this.pollType(poll)).toLowerCase()\n\t\t\treturn type.indexOf('date') !== -1\n\t\t},\n\n\t\ttypeLabel(poll) {\n\t\t\treturn this.isDatePoll(poll)\n\t\t\t\t? t('nextcloud-vue', 'Date poll')\n\t\t\t\t: t('nextcloud-vue', 'Text poll')\n\t\t},\n\n\t\tstatusLabel(poll) {\n\t\t\treturn this.isClosed(poll)\n\t\t\t\t? t('nextcloud-vue', 'Closed')\n\t\t\t\t: t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusVariant(poll) {\n\t\t\treturn this.isClosed(poll) ? 'default' : 'success'\n\t\t},\n\n\t\tpollOptions(poll) {\n\t\t\tconst opts = poll.options ?? poll.results ?? poll.optionResults ?? []\n\t\t\treturn Array.isArray(opts) ? opts : []\n\t\t},\n\n\t\toptionKey(opt) {\n\t\t\treturn opt.id ?? opt.key ?? opt.text ?? ''\n\t\t},\n\n\t\toptionLabel(opt) {\n\t\t\treturn opt.text ?? opt.label ?? opt.name ?? String(this.optionKey(opt))\n\t\t},\n\n\t\toptionCount(opt) {\n\t\t\tconst c = Number(opt.votes ?? opt.count ?? opt.voteCount ?? 0)\n\t\t\treturn Number.isFinite(c) ? c : 0\n\t\t},\n\n\t\ttotalVotes(poll) {\n\t\t\treturn this.pollOptions(poll).reduce((sum, o) => sum + this.optionCount(o), 0)\n\t\t},\n\n\t\toptionPercent(poll, opt) {\n\t\t\tconst total = this.totalVotes(poll)\n\t\t\tif (total <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.round((this.optionCount(opt) / total) * 100)\n\t\t},\n\n\t\tleadingOption(poll) {\n\t\t\tconst opts = this.pollOptions(poll)\n\t\t\tif (opts.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tlet best = opts[0]\n\t\t\tfor (const o of opts) {\n\t\t\t\tif (this.optionCount(o) > this.optionCount(best)) {\n\t\t\t\t\tbest = o\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.optionCount(best) === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.optionLabel(best)} (${this.optionPercent(poll, best)}%)`\n\t\t},\n\n\t\tdeadline(poll) {\n\t\t\treturn poll.deadline ?? poll.expire ?? poll.expiresAt ?? null\n\t\t},\n\n\t\tdeadlineMs(poll) {\n\t\t\tconst v = this.deadline(poll)\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tsortTimestamp(poll) {\n\t\t\tconst d = this.deadlineMs(poll)\n\t\t\tif (d !== null) {\n\t\t\t\treturn d\n\t\t\t}\n\t\t\tconst c = Number(poll.created ?? 0)\n\t\t\treturn Number.isFinite(c) ? (c < 1e12 ? c * 1000 : c) : 0\n\t\t},\n\n\t\tisClosed(poll) {\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\treturn poll.closed === true || poll.status === 'closed'\n\t\t\t}\n\t\t\treturn ms <= Date.now()\n\t\t},\n\n\t\trowMeta(poll) {\n\t\t\tif (this.isClosed(poll)) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = ms - Date.now()\n\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\tif (days <= 1) {\n\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Closes in {n} hours', { n: hours })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Closes in {n} days', { n: days })\n\t\t},\n\n\t\tchipSubtitle(poll) {\n\t\t\treturn this.leadingOption(poll) || this.pollTitle(poll)\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.polls = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.polls = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.polls = []\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('[CnPollsCard] failed to fetch polls', err)\n\t\t\t\tthis.polls = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnPollsCard] failed to fetch single poll', 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-polls-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-polls-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-polls-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-polls-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-polls-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__headline-leader {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-polls-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-polls-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-polls-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__chip-leader {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-polls-card__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-polls-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-polls-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-polls-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-polls-card__row-icon {\n\tdisplay: inline-flex;\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-polls-card__row--closed .cn-polls-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__status {\n\tflex-shrink: 0;\n}\n\n.cn-polls-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-polls-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n\n.cn-polls-card__options {\n\tlist-style: none;\n\tmargin: 2px 0 0;\n\tpadding: 0 0 0 24px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-polls-card__option {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 1px;\n}\n\n.cn-polls-card__option-label {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 6px;\n\tfont-size: 0.8em;\n}\n\n.cn-polls-card__option-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-card__option-count {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__option-bar {\n\theight: 4px;\n\tborder-radius: 2px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n}\n\n.cn-polls-card__option-bar-fill {\n\theight: 100%;\n\tbackground: var(--color-primary-element, #21468B);\n\ttransition: width 0.2s ease;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_openBlock","_normalizeClass","_createBlock","_renderList"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnPollsCard.vue3.js","sources":["../../../../../src/integrations/builtin/polls/CnPollsCard.vue?vue&type=template&id=5977968a&lang.js"],"sourcesContent":["<!--\n CnPollsCard — bespoke surface-aware widget for the `polls` integration.\n\n Replaces the generic CnIntegrationCard for the `polls` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N polls\" + \"M open\"\n secondary line names the most-recent poll with its leading\n option (per integration-polls spec).\n - detail-page : compact list of linked polls\n with deadline countdown and a mini per-option bar set per row.\n - single-entity : chip with poll title + leading\n option indicator (referenceType: 'polls').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnPollsTab; for `single-entity` the optional `value` prop addresses a\n single poll by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-polls/` 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-polls-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<Poll :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"pollUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ pollTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"leadingOption(entity)\" class=\"cn-polls-card__chip-leader\">\n\t\t\t\t\t· {{ leadingOption(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-polls-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-polls-card__headline\">\n\t\t\t\t<div class=\"cn-polls-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-polls-card__headline-recent\">\n\t\t\t\t\t<Poll :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pollUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ pollTitle(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"leadingOption(mostRecent)\" class=\"cn-polls-card__headline-leader\">\n\t\t\t\t\t\t· {{ leadingOption(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-polls-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-polls-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"poll in displayedPolls\"\n\t\t\t\t\t:key=\"pollKey(poll)\"\n\t\t\t\t\tclass=\"cn-polls-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-polls-card__row--closed': isClosed(poll) }\">\n\t\t\t\t\t<div class=\"cn-polls-card__row-header\">\n\t\t\t\t\t\t<span class=\"cn-polls-card__row-icon\" :title=\"typeLabel(poll)\" :aria-label=\"typeLabel(poll)\">\n\t\t\t\t\t\t\t<CalendarRange v-if=\"isDatePoll(poll)\" :size=\"16\" />\n\t\t\t\t\t\t\t<FormatListChecks v-else :size=\"16\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"pollUrl(poll)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__title\">{{ pollTitle(poll) }}</a>\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t:label=\"statusLabel(poll)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(poll)\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__status\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(poll)\" class=\"cn-polls-card__subtitle\">{{ rowMeta(poll) }}</span>\n\t\t\t\t\t<ul v-if=\"pollOptions(poll).length > 0\" class=\"cn-polls-card__options\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"opt in pollOptions(poll)\"\n\t\t\t\t\t\t\t:key=\"optionKey(opt)\"\n\t\t\t\t\t\t\tclass=\"cn-polls-card__option\">\n\t\t\t\t\t\t\t<div class=\"cn-polls-card__option-label\">\n\t\t\t\t\t\t\t\t<span class=\"cn-polls-card__option-text\">{{ optionLabel(opt) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-polls-card__option-count\">{{ optionPercent(poll, opt) }}%</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclass=\"cn-polls-card__option-bar\"\n\t\t\t\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\t\t\t\t:aria-valuenow=\"optionPercent(poll, opt)\"\n\t\t\t\t\t\t\t\taria-valuemin=\"0\"\n\t\t\t\t\t\t\t\taria-valuemax=\"100\">\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclass=\"cn-polls-card__option-bar-fill\"\n\t\t\t\t\t\t\t\t\t:style=\"{ width: optionPercent(poll, opt) + '%' }\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CalendarRange from 'vue-material-design-icons/CalendarRange.vue'\nimport FormatListChecks from 'vue-material-design-icons/FormatListChecks.vue'\nimport Poll from 'vue-material-design-icons/Poll.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnPollsCard — bespoke surface-aware widget for the `polls` integration.\n *\n * Renders Polls-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnPollsCard',\n\n\tcomponents: { CnDetailCard, CnStatusBadge, NcLoadingIcon, CalendarRange, FormatListChecks, Poll },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'polls'`). */\n\t\tintegrationId: { type: String, default: 'polls' },\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 (poll id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Polls') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Poll },\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 polls linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Polls is currently unavailable.') },\n\t\t/** URL of the NC Polls app entry. */\n\t\tpollsAppUrl: { type: String, default: '/index.php/apps/polls' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpolls: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedPolls() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.polls.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.polls\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.polls.reduce((sum, p) => sum + (this.isClosed(p) ? 0 : 1), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.polls.length\n\t\t\tconst open = this.openCount\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} poll (all closed)', '{count} polls (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} poll', '{count} polls', total, { count: total })\n\t\t\tconst openFragment = n('nextcloud-vue', '{count} open', '{count} open', open, { count: open })\n\t\t\treturn `${totalFragment} · ${openFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.polls.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Sort defensive copy by deadline / created desc.\n\t\t\tconst sorted = [...this.polls].sort((a, b) => {\n\t\t\t\tconst ta = this.sortTimestamp(a)\n\t\t\t\tconst tb = this.sortTimestamp(b)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tpollKey(poll) {\n\t\t\treturn poll.id ?? poll.reference ?? ''\n\t\t},\n\n\t\tpollTitle(poll) {\n\t\t\tconst raw = poll.title ?? poll.name ?? this.pollKey(poll)\n\t\t\treturn stripMarker(raw) || String(raw)\n\t\t},\n\n\t\tpollUrl(poll) {\n\t\t\tif (poll.url && safeHref(poll.url) !== '#') {\n\t\t\t\treturn poll.url\n\t\t\t}\n\t\t\tconst id = poll.id ?? ''\n\t\t\treturn id ? `/index.php/apps/polls/vote/${id}` : this.pollsAppUrl\n\t\t},\n\n\t\tpollType(poll) {\n\t\t\treturn poll.type ?? poll.pollType ?? ''\n\t\t},\n\n\t\tisDatePoll(poll) {\n\t\t\tconst type = String(this.pollType(poll)).toLowerCase()\n\t\t\treturn type.indexOf('date') !== -1\n\t\t},\n\n\t\ttypeLabel(poll) {\n\t\t\treturn this.isDatePoll(poll)\n\t\t\t\t? t('nextcloud-vue', 'Date poll')\n\t\t\t\t: t('nextcloud-vue', 'Text poll')\n\t\t},\n\n\t\tstatusLabel(poll) {\n\t\t\treturn this.isClosed(poll)\n\t\t\t\t? t('nextcloud-vue', 'Closed')\n\t\t\t\t: t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusVariant(poll) {\n\t\t\treturn this.isClosed(poll) ? 'default' : 'success'\n\t\t},\n\n\t\tpollOptions(poll) {\n\t\t\tconst opts = poll.options ?? poll.results ?? poll.optionResults ?? []\n\t\t\treturn Array.isArray(opts) ? opts : []\n\t\t},\n\n\t\toptionKey(opt) {\n\t\t\treturn opt.id ?? opt.key ?? opt.text ?? ''\n\t\t},\n\n\t\toptionLabel(opt) {\n\t\t\treturn opt.text ?? opt.label ?? opt.name ?? String(this.optionKey(opt))\n\t\t},\n\n\t\toptionCount(opt) {\n\t\t\tconst c = Number(opt.votes ?? opt.count ?? opt.voteCount ?? 0)\n\t\t\treturn Number.isFinite(c) ? c : 0\n\t\t},\n\n\t\ttotalVotes(poll) {\n\t\t\treturn this.pollOptions(poll).reduce((sum, o) => sum + this.optionCount(o), 0)\n\t\t},\n\n\t\toptionPercent(poll, opt) {\n\t\t\tconst total = this.totalVotes(poll)\n\t\t\tif (total <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.round((this.optionCount(opt) / total) * 100)\n\t\t},\n\n\t\tleadingOption(poll) {\n\t\t\tconst opts = this.pollOptions(poll)\n\t\t\tif (opts.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tlet best = opts[0]\n\t\t\tfor (const o of opts) {\n\t\t\t\tif (this.optionCount(o) > this.optionCount(best)) {\n\t\t\t\t\tbest = o\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.optionCount(best) === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.optionLabel(best)} (${this.optionPercent(poll, best)}%)`\n\t\t},\n\n\t\tdeadline(poll) {\n\t\t\treturn poll.deadline ?? poll.expire ?? poll.expiresAt ?? null\n\t\t},\n\n\t\tdeadlineMs(poll) {\n\t\t\tconst v = this.deadline(poll)\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tsortTimestamp(poll) {\n\t\t\tconst d = this.deadlineMs(poll)\n\t\t\tif (d !== null) {\n\t\t\t\treturn d\n\t\t\t}\n\t\t\tconst c = Number(poll.created ?? 0)\n\t\t\treturn Number.isFinite(c) ? (c < 1e12 ? c * 1000 : c) : 0\n\t\t},\n\n\t\tisClosed(poll) {\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\treturn poll.closed === true || poll.status === 'closed'\n\t\t\t}\n\t\t\treturn ms <= Date.now()\n\t\t},\n\n\t\trowMeta(poll) {\n\t\t\tif (this.isClosed(poll)) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = ms - Date.now()\n\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\tif (days <= 1) {\n\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Closes in {n} hours', { n: hours })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Closes in {n} days', { n: days })\n\t\t},\n\n\t\tchipSubtitle(poll) {\n\t\t\treturn this.leadingOption(poll) || this.pollTitle(poll)\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.polls = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.polls = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.polls = []\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('[CnPollsCard] failed to fetch polls', err)\n\t\t\t\tthis.polls = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (!this.value || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnPollsCard] failed to fetch single poll', 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-polls-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-polls-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-polls-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-polls-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-polls-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__headline-leader {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-polls-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-polls-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-polls-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__chip-leader {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-polls-card__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-polls-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-polls-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-polls-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-polls-card__row-icon {\n\tdisplay: inline-flex;\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-polls-card__row--closed .cn-polls-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__status {\n\tflex-shrink: 0;\n}\n\n.cn-polls-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-polls-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n\n.cn-polls-card__options {\n\tlist-style: none;\n\tmargin: 2px 0 0;\n\tpadding: 0 0 0 24px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-polls-card__option {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 1px;\n}\n\n.cn-polls-card__option-label {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 6px;\n\tfont-size: 0.8em;\n}\n\n.cn-polls-card__option-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-card__option-count {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-card__option-bar {\n\theight: 4px;\n\tborder-radius: 2px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n}\n\n.cn-polls-card__option-bar-fill {\n\theight: 100%;\n\tbackground: var(--color-primary-element, #21468B);\n\ttransition: width 0.2s ease;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_openBlock","_normalizeClass","_createBlock","_renderList"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ import __sfc_main$1 from '../../../components/CnPollPicker/CnPollPicker.vue.js';
|
|
|
15
15
|
import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
|
|
16
16
|
import { stripMarker } from '../../utils/marker.js';
|
|
17
17
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
18
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* CnPollsTab — bespoke live-tally list for the `polls` integration.
|
|
@@ -199,7 +200,7 @@ var script = {
|
|
|
199
200
|
},
|
|
200
201
|
|
|
201
202
|
pollUrl(poll) {
|
|
202
|
-
if (poll.url) {
|
|
203
|
+
if (poll.url && safeHref(poll.url) !== '#') {
|
|
203
204
|
return poll.url
|
|
204
205
|
}
|
|
205
206
|
const id = this.pollKey(poll);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnPollsTab.vue2.js","sources":["../../../../../src/integrations/builtin/polls/CnPollsTab.vue"],"sourcesContent":["<!--\n CnPollsTab — bespoke sidebar tab for the `polls` integration.\n\n Replaces the generic CnIntegrationTab for the `polls` leaf: renders a\n live vote tally for each linked NC Polls poll, mirroring the real NC\n Polls list look. Each row foregrounds a poll-type icon (text vs date\n poll), the bold poll question/title, an open / closed status chip\n (CnStatusBadge), a meta line with participant + option counts, a\n relative expiry (NcDateTime) when a deadline is present, and a\n per-option vote-tally progress bar. A trailing \"Open in Polls\" action\n per row deep-links to `/index.php/apps/polls/vote/{id}`.\n\n Tier-2 surface (this commit):\n - \"Link existing poll\" → opens CnPollPicker (modal)\n - \"Create new poll\" → opens CnPollCreate (modal)\n - Per-row unlink → DELETE …/polls/{pollId}\n\n Talks to the OpenRegister Tier-2 poll-link endpoints\n GET /api/objects/{r}/{s}/{id}/polls — list\n POST /api/objects/{r}/{s}/{id}/polls — link existing\n POST /api/objects/{r}/{s}/{id}/polls/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/polls/{pollId} — unlink\n GET /api/integrations/polls/available — picker source\n served by `OCA\\OpenRegister\\Controller\\PollLinksController`.\n\n Surface behaviour:\n - Empty state with \"Open Polls\" CTA when no linked polls.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-polls/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-polls-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-polls-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-polls-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 poll') }}\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 poll') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-polls-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-sidebar-tab__empty cn-polls-tab__empty\">\n\t\t\t<Poll :size=\"32\" class=\"cn-polls-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=\"pollsAppUrl\"\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<Poll :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openPollsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-polls-tab__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"poll in polls\"\n\t\t\t\t:key=\"pollKey(poll)\"\n\t\t\t\tclass=\"cn-polls-tab__row\"\n\t\t\t\t:class=\"{ 'cn-polls-tab__row--closed': isClosed(poll) }\">\n\t\t\t\t<div class=\"cn-polls-tab__row-header\">\n\t\t\t\t\t<span class=\"cn-polls-tab__row-icon\" :title=\"typeLabel(poll)\" :aria-label=\"typeLabel(poll)\">\n\t\t\t\t\t\t<CalendarRange v-if=\"isDatePoll(poll)\" :size=\"20\" />\n\t\t\t\t\t\t<FormatListChecks v-else :size=\"20\" />\n\t\t\t\t\t</span>\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pollUrl(poll)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-polls-tab__title\">{{ pollTitle(poll) }}</a>\n\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t:label=\"statusLabel(poll)\"\n\t\t\t\t\t\t:variant=\"statusVariant(poll)\"\n\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\tclass=\"cn-polls-tab__status\" />\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tvariant=\"tertiary-no-background\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Unlink poll')\"\n\t\t\t\t\t\tclass=\"cn-polls-tab__unlink\"\n\t\t\t\t\t\t@click=\"unlinkPoll(poll)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"18\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\n\t\t\t\t<div class=\"cn-polls-tab__meta\">\n\t\t\t\t\t<span class=\"cn-polls-tab__meta-item\">\n\t\t\t\t\t\t<AccountMultiple :size=\"14\" class=\"cn-polls-tab__meta-icon\" />\n\t\t\t\t\t\t<span>{{ voterMetaLabel(poll) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"pollOptions(poll).length > 0\" class=\"cn-polls-tab__meta-item\">\n\t\t\t\t\t\t<FormatListBulleted :size=\"14\" class=\"cn-polls-tab__meta-icon\" />\n\t\t\t\t\t\t<span>{{ optionMetaLabel(poll) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"deadlineMs(poll) !== null\" class=\"cn-polls-tab__meta-item\">\n\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-polls-tab__meta-icon\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tclass=\"cn-polls-tab__time\"\n\t\t\t\t\t\t\t:timestamp=\"deadlineMs(poll)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\n\t\t\t\t<ul v-if=\"pollOptions(poll).length > 0\" class=\"cn-polls-tab__options\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"opt in pollOptions(poll)\"\n\t\t\t\t\t\t:key=\"optionKey(opt)\"\n\t\t\t\t\t\tclass=\"cn-polls-tab__option\">\n\t\t\t\t\t\t<div class=\"cn-polls-tab__option-label\">\n\t\t\t\t\t\t\t<span class=\"cn-polls-tab__option-text\">{{ optionLabel(opt) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-polls-tab__option-count\">\n\t\t\t\t\t\t\t\t{{ optionCount(opt) }} ({{ optionPercent(poll, opt) }}%)\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"cn-polls-tab__option-bar\"\n\t\t\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\t\t\t:aria-valuenow=\"optionPercent(poll, opt)\"\n\t\t\t\t\t\t\taria-valuemin=\"0\"\n\t\t\t\t\t\t\taria-valuemax=\"100\">\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclass=\"cn-polls-tab__option-bar-fill\"\n\t\t\t\t\t\t\t\t:style=\"{ width: optionPercent(poll, opt) + '%' }\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"pollDescription(poll)\" class=\"cn-polls-tab__description\">\n\t\t\t\t\t{{ pollDescription(poll) }}\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<CnPollPicker\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<CnPollCreate\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 { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDateTime, NcLoadingIcon } from '@nextcloud/vue'\nimport AccountMultiple from 'vue-material-design-icons/AccountMultiple.vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CalendarRange from 'vue-material-design-icons/CalendarRange.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport FormatListChecks from 'vue-material-design-icons/FormatListChecks.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 Poll from 'vue-material-design-icons/Poll.vue'\nimport CnPollCreate from '../../../components/CnPollCreate/CnPollCreate.vue'\nimport CnPollPicker from '../../../components/CnPollPicker/CnPollPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnPollsTab — bespoke live-tally list for the `polls` integration.\n *\n * Renders rows pulled from the OR pluggable-integration endpoint that\n * mirror the real NC Polls list: type icon, bold question, open/closed\n * status chip, participant + option counts, a relative expiry, and\n * per-option vote-tally progress bars. Tier-2: includes link/create\n * modals and per-row unlink.\n */\nexport default {\n\tname: 'CnPollsTab',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAccountMultiple,\n\t\tAlertCircleOutline,\n\t\tCalendarRange,\n\t\tClockOutline,\n\t\tFormatListBulleted,\n\t\tFormatListChecks,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tPoll,\n\t\tCnPollPicker,\n\t\tCnPollCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'polls'`). */\n\t\tintegrationId: { type: String, default: 'polls' },\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 polls linked yet') },\n\t\t/** Pre-translated label for the \"Open in Polls\" CTA. */\n\t\topenPollsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Polls') },\n\t\t/** Pre-translated banner when Polls is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Polls is currently unavailable.') },\n\t\t/** URL of the NC Polls app entry. */\n\t\tpollsAppUrl: { type: String, default: '/index.php/apps/polls' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpolls: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchPolls()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchPolls()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchPolls()\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 polls endpoints (link/new/destroy).\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tpollsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/polls`)\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.pollsEndpoint(), {\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.fetchPolls()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This poll is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link poll.')\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('[CnPollsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link poll.')\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.pollsEndpoint()}/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.fetchPolls()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create poll.')\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('[CnPollsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create poll.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkPoll(poll) {\n\t\t\tconst pollId = this.pollKey(poll)\n\t\t\tif (!pollId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.pollsEndpoint()}/${pollId}`, {\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.fetchPolls()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink poll.')\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('[CnPollsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink poll.')\n\t\t\t}\n\t\t},\n\n\t\tpollKey(poll) {\n\t\t\treturn poll.pollId ?? poll.id ?? poll.reference ?? ''\n\t\t},\n\n\t\tpollTitle(poll) {\n\t\t\tconst raw = poll.title ?? poll.pollTitle ?? poll.name ?? this.pollKey(poll)\n\t\t\t// Strip any legacy `[or:{uuid}]` marker from displayed title\n\t\t\t// via the shared helper (ADR-019).\n\t\t\treturn stripMarker(raw) || String(raw)\n\t\t},\n\n\t\tpollDescription(poll) {\n\t\t\treturn stripMarker(poll.description)\n\t\t},\n\n\t\tpollUrl(poll) {\n\t\t\tif (poll.url) {\n\t\t\t\treturn poll.url\n\t\t\t}\n\t\t\tconst id = this.pollKey(poll)\n\t\t\treturn id ? `/index.php/apps/polls/vote/${id}` : this.pollsAppUrl\n\t\t},\n\n\t\tpollType(poll) {\n\t\t\treturn poll.type ?? poll.pollType ?? ''\n\t\t},\n\n\t\tisDatePoll(poll) {\n\t\t\tconst type = String(this.pollType(poll)).toLowerCase()\n\t\t\treturn type.indexOf('date') !== -1\n\t\t},\n\n\t\ttypeLabel(poll) {\n\t\t\treturn this.isDatePoll(poll)\n\t\t\t\t? t('nextcloud-vue', 'Date poll')\n\t\t\t\t: t('nextcloud-vue', 'Text poll')\n\t\t},\n\n\t\tpollOptions(poll) {\n\t\t\tconst opts = poll.options ?? poll.results ?? poll.optionResults ?? []\n\t\t\treturn Array.isArray(opts) ? opts : []\n\t\t},\n\n\t\toptionKey(opt) {\n\t\t\treturn opt.id ?? opt.key ?? opt.text ?? ''\n\t\t},\n\n\t\toptionLabel(opt) {\n\t\t\treturn opt.text ?? opt.label ?? opt.name ?? String(this.optionKey(opt))\n\t\t},\n\n\t\toptionCount(opt) {\n\t\t\tconst n = Number(opt.votes ?? opt.count ?? opt.voteCount ?? 0)\n\t\t\treturn Number.isFinite(n) ? n : 0\n\t\t},\n\n\t\toptionPercent(poll, opt) {\n\t\t\tconst total = this.totalVotes(poll)\n\t\t\tif (total <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.round((this.optionCount(opt) / total) * 100)\n\t\t},\n\n\t\ttotalVotes(poll) {\n\t\t\treturn this.pollOptions(poll).reduce((sum, o) => sum + this.optionCount(o), 0)\n\t\t},\n\n\t\tvoterCount(poll) {\n\t\t\tconst n = Number(poll.voterCount ?? poll.voters ?? poll.participants ?? 0)\n\t\t\treturn Number.isFinite(n) && n > 0 ? n : this.totalVotes(poll)\n\t\t},\n\n\t\tvoterMetaLabel(poll) {\n\t\t\tconst voters = this.voterCount(poll)\n\t\t\treturn t('nextcloud-vue', '{n} participants', { n: voters })\n\t\t},\n\n\t\toptionMetaLabel(poll) {\n\t\t\tconst count = this.pollOptions(poll).length\n\t\t\treturn t('nextcloud-vue', '{n} options', { n: count })\n\t\t},\n\n\t\tdeadline(poll) {\n\t\t\treturn poll.deadline ?? poll.expire ?? poll.expiresAt ?? null\n\t\t},\n\n\t\tdeadlineMs(poll) {\n\t\t\tconst v = this.deadline(poll)\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\t// Treat as unix seconds when it's small enough to be a 10-digit epoch.\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(poll) {\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\t// Without a deadline, fall back to a `closed` flag if present.\n\t\t\t\treturn poll.closed === true || poll.status === 'closed'\n\t\t\t}\n\t\t\treturn ms <= Date.now()\n\t\t},\n\n\t\tstatusLabel(poll) {\n\t\t\treturn this.isClosed(poll)\n\t\t\t\t? t('nextcloud-vue', 'Closed')\n\t\t\t\t: t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusVariant(poll) {\n\t\t\treturn this.isClosed(poll) ? 'default' : 'success'\n\t\t},\n\n\t\tasync fetchPolls() {\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(this.baseUrl(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.polls = rows\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.polls = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.polls = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load polls.')\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('[CnPollsTab] failed to fetch polls', err)\n\t\t\t\tthis.polls = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load polls.')\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-polls-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-polls-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-polls-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-polls-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-polls-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-polls-tab__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tpadding: 10px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-polls-tab__row--closed {\n\tborder-left-color: var(--color-border-dark, var(--color-border));\n\tbackground: transparent;\n\topacity: 0.78;\n}\n\n.cn-polls-tab__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-polls-tab__row-icon {\n\tdisplay: inline-flex;\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-polls-tab__row--closed .cn-polls-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\tfont-weight: 600;\n}\n\na.cn-polls-tab__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-tab__status {\n\tflex-shrink: 0;\n}\n\n.cn-polls-tab__unlink {\n\tflex-shrink: 0;\n}\n\n.cn-polls-tab__meta {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 12px;\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 28px;\n}\n\n.cn-polls-tab__meta-item {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n}\n\n.cn-polls-tab__meta-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__time {\n\tfont-size: 1em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__options {\n\tlist-style: none;\n\tmargin: 4px 0 0;\n\tpadding: 0 0 0 28px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-polls-tab__option {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-polls-tab__option-label {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n}\n\n.cn-polls-tab__option-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-tab__option-count {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__option-bar {\n\theight: 6px;\n\tborder-radius: 3px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n}\n\n.cn-polls-tab__option-bar-fill {\n\theight: 100%;\n\tbackground: var(--color-primary-element, #21468B);\n\ttransition: width 0.2s ease;\n}\n\n.cn-polls-tab__description {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 28px;\n}\n</style>\n"],"names":["CnStatusBadge","CnPollPicker","CnPollCreate","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnPollsTab.vue2.js","sources":["../../../../../src/integrations/builtin/polls/CnPollsTab.vue"],"sourcesContent":["<!--\n CnPollsTab — bespoke sidebar tab for the `polls` integration.\n\n Replaces the generic CnIntegrationTab for the `polls` leaf: renders a\n live vote tally for each linked NC Polls poll, mirroring the real NC\n Polls list look. Each row foregrounds a poll-type icon (text vs date\n poll), the bold poll question/title, an open / closed status chip\n (CnStatusBadge), a meta line with participant + option counts, a\n relative expiry (NcDateTime) when a deadline is present, and a\n per-option vote-tally progress bar. A trailing \"Open in Polls\" action\n per row deep-links to `/index.php/apps/polls/vote/{id}`.\n\n Tier-2 surface (this commit):\n - \"Link existing poll\" → opens CnPollPicker (modal)\n - \"Create new poll\" → opens CnPollCreate (modal)\n - Per-row unlink → DELETE …/polls/{pollId}\n\n Talks to the OpenRegister Tier-2 poll-link endpoints\n GET /api/objects/{r}/{s}/{id}/polls — list\n POST /api/objects/{r}/{s}/{id}/polls — link existing\n POST /api/objects/{r}/{s}/{id}/polls/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/polls/{pollId} — unlink\n GET /api/integrations/polls/available — picker source\n served by `OCA\\OpenRegister\\Controller\\PollLinksController`.\n\n Surface behaviour:\n - Empty state with \"Open Polls\" CTA when no linked polls.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-polls/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-polls-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-polls-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-polls-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 poll') }}\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 poll') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-polls-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"polls.length === 0\" class=\"cn-sidebar-tab__empty cn-polls-tab__empty\">\n\t\t\t<Poll :size=\"32\" class=\"cn-polls-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=\"pollsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Poll :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openPollsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-polls-tab__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"poll in polls\"\n\t\t\t\t:key=\"pollKey(poll)\"\n\t\t\t\tclass=\"cn-polls-tab__row\"\n\t\t\t\t:class=\"{ 'cn-polls-tab__row--closed': isClosed(poll) }\">\n\t\t\t\t<div class=\"cn-polls-tab__row-header\">\n\t\t\t\t\t<span class=\"cn-polls-tab__row-icon\" :title=\"typeLabel(poll)\" :aria-label=\"typeLabel(poll)\">\n\t\t\t\t\t\t<CalendarRange v-if=\"isDatePoll(poll)\" :size=\"20\" />\n\t\t\t\t\t\t<FormatListChecks v-else :size=\"20\" />\n\t\t\t\t\t</span>\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pollUrl(poll)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-polls-tab__title\">{{ pollTitle(poll) }}</a>\n\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t:label=\"statusLabel(poll)\"\n\t\t\t\t\t\t:variant=\"statusVariant(poll)\"\n\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\tclass=\"cn-polls-tab__status\" />\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tvariant=\"tertiary-no-background\"\n\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Unlink poll')\"\n\t\t\t\t\t\tclass=\"cn-polls-tab__unlink\"\n\t\t\t\t\t\t@click=\"unlinkPoll(poll)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"18\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\n\t\t\t\t<div class=\"cn-polls-tab__meta\">\n\t\t\t\t\t<span class=\"cn-polls-tab__meta-item\">\n\t\t\t\t\t\t<AccountMultiple :size=\"14\" class=\"cn-polls-tab__meta-icon\" />\n\t\t\t\t\t\t<span>{{ voterMetaLabel(poll) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"pollOptions(poll).length > 0\" class=\"cn-polls-tab__meta-item\">\n\t\t\t\t\t\t<FormatListBulleted :size=\"14\" class=\"cn-polls-tab__meta-icon\" />\n\t\t\t\t\t\t<span>{{ optionMetaLabel(poll) }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"deadlineMs(poll) !== null\" class=\"cn-polls-tab__meta-item\">\n\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-polls-tab__meta-icon\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tclass=\"cn-polls-tab__time\"\n\t\t\t\t\t\t\t:timestamp=\"deadlineMs(poll)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\n\t\t\t\t<ul v-if=\"pollOptions(poll).length > 0\" class=\"cn-polls-tab__options\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"opt in pollOptions(poll)\"\n\t\t\t\t\t\t:key=\"optionKey(opt)\"\n\t\t\t\t\t\tclass=\"cn-polls-tab__option\">\n\t\t\t\t\t\t<div class=\"cn-polls-tab__option-label\">\n\t\t\t\t\t\t\t<span class=\"cn-polls-tab__option-text\">{{ optionLabel(opt) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-polls-tab__option-count\">\n\t\t\t\t\t\t\t\t{{ optionCount(opt) }} ({{ optionPercent(poll, opt) }}%)\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"cn-polls-tab__option-bar\"\n\t\t\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\t\t\t:aria-valuenow=\"optionPercent(poll, opt)\"\n\t\t\t\t\t\t\taria-valuemin=\"0\"\n\t\t\t\t\t\t\taria-valuemax=\"100\">\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclass=\"cn-polls-tab__option-bar-fill\"\n\t\t\t\t\t\t\t\t:style=\"{ width: optionPercent(poll, opt) + '%' }\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"pollDescription(poll)\" class=\"cn-polls-tab__description\">\n\t\t\t\t\t{{ pollDescription(poll) }}\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<CnPollPicker\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<CnPollCreate\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 { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDateTime, NcLoadingIcon } from '@nextcloud/vue'\nimport AccountMultiple from 'vue-material-design-icons/AccountMultiple.vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CalendarRange from 'vue-material-design-icons/CalendarRange.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport FormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'\nimport FormatListChecks from 'vue-material-design-icons/FormatListChecks.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 Poll from 'vue-material-design-icons/Poll.vue'\nimport CnPollCreate from '../../../components/CnPollCreate/CnPollCreate.vue'\nimport CnPollPicker from '../../../components/CnPollPicker/CnPollPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnPollsTab — bespoke live-tally list for the `polls` integration.\n *\n * Renders rows pulled from the OR pluggable-integration endpoint that\n * mirror the real NC Polls list: type icon, bold question, open/closed\n * status chip, participant + option counts, a relative expiry, and\n * per-option vote-tally progress bars. Tier-2: includes link/create\n * modals and per-row unlink.\n */\nexport default {\n\tname: 'CnPollsTab',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAccountMultiple,\n\t\tAlertCircleOutline,\n\t\tCalendarRange,\n\t\tClockOutline,\n\t\tFormatListBulleted,\n\t\tFormatListChecks,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tPoll,\n\t\tCnPollPicker,\n\t\tCnPollCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'polls'`). */\n\t\tintegrationId: { type: String, default: 'polls' },\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 polls linked yet') },\n\t\t/** Pre-translated label for the \"Open in Polls\" CTA. */\n\t\topenPollsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Polls') },\n\t\t/** Pre-translated banner when Polls is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Polls is currently unavailable.') },\n\t\t/** URL of the NC Polls app entry. */\n\t\tpollsAppUrl: { type: String, default: '/index.php/apps/polls' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpolls: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchPolls()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchPolls()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchPolls()\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 polls endpoints (link/new/destroy).\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tpollsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/polls`)\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.pollsEndpoint(), {\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.fetchPolls()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This poll is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link poll.')\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('[CnPollsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link poll.')\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.pollsEndpoint()}/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.fetchPolls()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create poll.')\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('[CnPollsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create poll.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkPoll(poll) {\n\t\t\tconst pollId = this.pollKey(poll)\n\t\t\tif (!pollId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.pollsEndpoint()}/${pollId}`, {\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.fetchPolls()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink poll.')\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('[CnPollsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink poll.')\n\t\t\t}\n\t\t},\n\n\t\tpollKey(poll) {\n\t\t\treturn poll.pollId ?? poll.id ?? poll.reference ?? ''\n\t\t},\n\n\t\tpollTitle(poll) {\n\t\t\tconst raw = poll.title ?? poll.pollTitle ?? poll.name ?? this.pollKey(poll)\n\t\t\t// Strip any legacy `[or:{uuid}]` marker from displayed title\n\t\t\t// via the shared helper (ADR-019).\n\t\t\treturn stripMarker(raw) || String(raw)\n\t\t},\n\n\t\tpollDescription(poll) {\n\t\t\treturn stripMarker(poll.description)\n\t\t},\n\n\t\tpollUrl(poll) {\n\t\t\tif (poll.url && safeHref(poll.url) !== '#') {\n\t\t\t\treturn poll.url\n\t\t\t}\n\t\t\tconst id = this.pollKey(poll)\n\t\t\treturn id ? `/index.php/apps/polls/vote/${id}` : this.pollsAppUrl\n\t\t},\n\n\t\tpollType(poll) {\n\t\t\treturn poll.type ?? poll.pollType ?? ''\n\t\t},\n\n\t\tisDatePoll(poll) {\n\t\t\tconst type = String(this.pollType(poll)).toLowerCase()\n\t\t\treturn type.indexOf('date') !== -1\n\t\t},\n\n\t\ttypeLabel(poll) {\n\t\t\treturn this.isDatePoll(poll)\n\t\t\t\t? t('nextcloud-vue', 'Date poll')\n\t\t\t\t: t('nextcloud-vue', 'Text poll')\n\t\t},\n\n\t\tpollOptions(poll) {\n\t\t\tconst opts = poll.options ?? poll.results ?? poll.optionResults ?? []\n\t\t\treturn Array.isArray(opts) ? opts : []\n\t\t},\n\n\t\toptionKey(opt) {\n\t\t\treturn opt.id ?? opt.key ?? opt.text ?? ''\n\t\t},\n\n\t\toptionLabel(opt) {\n\t\t\treturn opt.text ?? opt.label ?? opt.name ?? String(this.optionKey(opt))\n\t\t},\n\n\t\toptionCount(opt) {\n\t\t\tconst n = Number(opt.votes ?? opt.count ?? opt.voteCount ?? 0)\n\t\t\treturn Number.isFinite(n) ? n : 0\n\t\t},\n\n\t\toptionPercent(poll, opt) {\n\t\t\tconst total = this.totalVotes(poll)\n\t\t\tif (total <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.round((this.optionCount(opt) / total) * 100)\n\t\t},\n\n\t\ttotalVotes(poll) {\n\t\t\treturn this.pollOptions(poll).reduce((sum, o) => sum + this.optionCount(o), 0)\n\t\t},\n\n\t\tvoterCount(poll) {\n\t\t\tconst n = Number(poll.voterCount ?? poll.voters ?? poll.participants ?? 0)\n\t\t\treturn Number.isFinite(n) && n > 0 ? n : this.totalVotes(poll)\n\t\t},\n\n\t\tvoterMetaLabel(poll) {\n\t\t\tconst voters = this.voterCount(poll)\n\t\t\treturn t('nextcloud-vue', '{n} participants', { n: voters })\n\t\t},\n\n\t\toptionMetaLabel(poll) {\n\t\t\tconst count = this.pollOptions(poll).length\n\t\t\treturn t('nextcloud-vue', '{n} options', { n: count })\n\t\t},\n\n\t\tdeadline(poll) {\n\t\t\treturn poll.deadline ?? poll.expire ?? poll.expiresAt ?? null\n\t\t},\n\n\t\tdeadlineMs(poll) {\n\t\t\tconst v = this.deadline(poll)\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\t// Treat as unix seconds when it's small enough to be a 10-digit epoch.\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(poll) {\n\t\t\tconst ms = this.deadlineMs(poll)\n\t\t\tif (ms === null) {\n\t\t\t\t// Without a deadline, fall back to a `closed` flag if present.\n\t\t\t\treturn poll.closed === true || poll.status === 'closed'\n\t\t\t}\n\t\t\treturn ms <= Date.now()\n\t\t},\n\n\t\tstatusLabel(poll) {\n\t\t\treturn this.isClosed(poll)\n\t\t\t\t? t('nextcloud-vue', 'Closed')\n\t\t\t\t: t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusVariant(poll) {\n\t\t\treturn this.isClosed(poll) ? 'default' : 'success'\n\t\t},\n\n\t\tasync fetchPolls() {\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(this.baseUrl(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.polls = rows\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.polls = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.polls = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load polls.')\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('[CnPollsTab] failed to fetch polls', err)\n\t\t\t\tthis.polls = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load polls.')\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-polls-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-polls-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-polls-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-polls-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-polls-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-polls-tab__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tpadding: 10px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-polls-tab__row--closed {\n\tborder-left-color: var(--color-border-dark, var(--color-border));\n\tbackground: transparent;\n\topacity: 0.78;\n}\n\n.cn-polls-tab__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-polls-tab__row-icon {\n\tdisplay: inline-flex;\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-polls-tab__row--closed .cn-polls-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\tfont-weight: 600;\n}\n\na.cn-polls-tab__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-polls-tab__status {\n\tflex-shrink: 0;\n}\n\n.cn-polls-tab__unlink {\n\tflex-shrink: 0;\n}\n\n.cn-polls-tab__meta {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 12px;\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 28px;\n}\n\n.cn-polls-tab__meta-item {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n}\n\n.cn-polls-tab__meta-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__time {\n\tfont-size: 1em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__options {\n\tlist-style: none;\n\tmargin: 4px 0 0;\n\tpadding: 0 0 0 28px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-polls-tab__option {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-polls-tab__option-label {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n}\n\n.cn-polls-tab__option-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-polls-tab__option-count {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-polls-tab__option-bar {\n\theight: 6px;\n\tborder-radius: 3px;\n\tbackground: var(--color-background-dark);\n\toverflow: hidden;\n}\n\n.cn-polls-tab__option-bar-fill {\n\theight: 100%;\n\tbackground: var(--color-primary-element, #21468B);\n\ttransition: width 0.2s ease;\n}\n\n.cn-polls-tab__description {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 28px;\n}\n</style>\n"],"names":["CnStatusBadge","CnPollPicker","CnPollCreate","t"],"mappings":"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"}
|
|
@@ -111,8 +111,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
111
111
|
createVNode(_component_NcButton, {
|
|
112
112
|
variant: "primary",
|
|
113
113
|
href: $props.pollsAppUrl,
|
|
114
|
-
target: "_blank"
|
|
115
|
-
rel: "noopener noreferrer"
|
|
114
|
+
target: "_blank"
|
|
116
115
|
}, {
|
|
117
116
|
icon: withCtx(() => [
|
|
118
117
|
createVNode(_component_Poll, { size: 20 })
|