@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":"CnFormsCard.vue2.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsCard.vue"],"sourcesContent":["<!--\n CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n\n Replaces the generic CnIntegrationCard for the `forms` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N forms · M open\"\n secondary line names the most-recent form with its submission\n count.\n - detail-page : compact list of linked forms\n with status pill + submission count per row.\n - single-entity : chip with form title + status\n indicator (referenceType: 'forms').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnFormsTab; for `single-entity` the optional `value` prop addresses a\n single form by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-forms/` 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-forms-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"formUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ formTitle(entity) }}</a>\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-forms-card__chip-status\"\n\t\t\t\t\t:class=\"statusClass(entity)\">{{ statusLabel(entity) }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-forms-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-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-forms-card__headline\">\n\t\t\t\t<div class=\"cn-forms-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-forms-card__headline-total\">\n\t\t\t\t\t{{ submissionsHeadline }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-forms-card__headline-recent\">\n\t\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"formUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ formTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-forms-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"form in displayedForms\"\n\t\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\t\tclass=\"cn-forms-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-forms-card__row--closed': isClosed(form) }\">\n\t\t\t\t\t<div class=\"cn-forms-card__row-header\">\n\t\t\t\t\t\t<ClipboardText :size=\"16\" class=\"cn-forms-card__row-icon\" />\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"formUrl(form)\"\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-forms-card__title\">{{ formTitle(form) }}</a>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-forms-card__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\">{{ statusLabel(form) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(form)\" class=\"cn-forms-card__subtitle\">{{ rowMeta(form) }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n *\n * Renders Forms-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnFormsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ClipboardText },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\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 (form id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Forms') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ClipboardText },\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 forms linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tentityRow: 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\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tentity() {\n\t\t\tif (this.entityRow === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normaliseFormRow(this.entityRow)\n\t\t},\n\n\t\tdisplayedForms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.forms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.forms\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + (this.isClosed(f) ? 0 : 1), 0)\n\t\t},\n\n\t\ttotalSubmissions() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + this.submissionCount(f), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.forms.length\n\t\t\tconst open = this.openCount\n\t\t\tif (total === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} form (all closed)', '{count} forms (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} form', '{count} forms', 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\tsubmissionsHeadline() {\n\t\t\tconst total = this.totalSubmissions\n\t\t\treturn n('nextcloud-vue', '{count} submission', '{count} submissions', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.forms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.forms].sort((a, b) => {\n\t\t\t\treturn this.sortTimestamp(b) - this.sortTimestamp(a)\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\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformUrl(form) {\n\t\t\tif (form.url) {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\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\tisClosed(form) {\n\t\t\tif (form.status === 'closed') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-card__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-card__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-card__status--open'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tsortTimestamp(form) {\n\t\t\tconst lu = Number(form.lastUpdated ?? 0)\n\t\t\tif (Number.isFinite(lu) && lu > 0) {\n\t\t\t\treturn lu < 1e12 ? lu * 1000 : lu\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\trowMeta(form) {\n\t\t\tconst parts = []\n\t\t\tconst count = this.submissionCount(form)\n\t\t\tif (count > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} submissions', { n: count }))\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\tconst diff = ms - Date.now()\n\t\t\t\tif (diff > 0) {\n\t\t\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\t\t\tif (days <= 1) {\n\t\t\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} hours', { n: hours }))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} days', { n: days }))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tchipSubtitle(form) {\n\t\t\treturn this.statusLabel(form) || this.formTitle(form)\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.rawRows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\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('[CnFormsCard] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\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.entityRow = 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.entityRow = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entityRow = 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('[CnFormsCard] failed to fetch single form', err)\n\t\t\t\tthis.entityRow = 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-forms-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-forms-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-forms-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-forms-card__headline-total {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-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-forms-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-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-forms-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip-status {\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.75em;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-forms-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-forms-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-forms-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-forms-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-forms-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-forms-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__status,\n.cn-forms-card__chip-status {\n\tflex-shrink: 0;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n}\n\n.cn-forms-card__status--open {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-primary-element-text, #ffffff);\n}\n\n.cn-forms-card__status--closed {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__status--draft {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-forms-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":";;;;;;AAwGA,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;;CAEnB,UAAU,EAAE,gBAAEA,UAAY,EAAE,aAAa,EAAE,aAAY,EAAG;;AAE1D,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,eAAe;AACpD;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;GACN,OAAO,EAAE,EAAE;AACX,GAAA,SAAS,EAAE,IAAI;GACf,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,KAAK,GAAG;GACP,MAAM,GAAE,GAAI,GAAC;AACb,GAAA,MAAM,IAAK,GAAE,IAAI,GAAG,GAAC;GACrB,KAAK,MAAM,GAAI,IAAG,IAAI,CAAC,OAAO,EAAE;AAC/B,IAAA,IAAI,CAAC,GAAE,IAAK,OAAO,GAAI,KAAI,QAAQ,EAAE;AACpC,KAAA,QAAO;AACR,KAAA;AACA,IAAA,IAAI,CAAC,GAAG,CAAC,IAAK,IAAG,MAAM,MAAM,YAAY,EAAE;AAC1C,KAAA,MAAM,MAAK,GAAI,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,EAAA;KAC5C,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAE,KAAI,IAAI,EAAE;MAC9B,MAAM,MAAK,GAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAA;AAC9B,MAAA,MAAM,CAAC,WAAU,GAAI,CAAC,MAAM,CAAC,WAAU,IAAK,CAAC,IAAI,EAAA;AAClD,MAAA;AACA,KAAA,QAAO;AACR,KAAA;AACA,IAAA,MAAM,aAAa,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAA;AAC5C,IAAA,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,UAAU,EAAA;AAC1C,IAAA,GAAG,CAAC,IAAI,CAAC,UAAU,EAAA;AACpB,IAAA;AACA,GAAA,OAAO,GAAE;GACT;;AAED,EAAA,MAAM,GAAG;GACR,IAAI,IAAI,CAAC,SAAQ,KAAM,IAAI,EAAE;AAC5B,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAA;GAC3C;;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,gBAAgB,GAAG;AAClB,GAAA,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,GAAI,GAAE,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;GACrE;;AAED,EAAA,aAAa,GAAG;GACf,MAAM,KAAM,GAAE,IAAI,CAAC,KAAK,CAAC,OAAK;GAC9B,MAAM,OAAO,IAAI,CAAC,UAAQ;GAC1B,IAAI,KAAI,KAAM,CAAC,EAAE;AAChB,IAAA,OAAO,EAAC;AACT,IAAA;GACA,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;;AAED,EAAA,mBAAmB,GAAG;AACrB,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,iBAAe;AAClC,GAAA,OAAOA,eAAC,CAAC,eAAe,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,CAAA;GAC9F;;EAED,UAAU,GAAG;GACZ,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE;AAC5B,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,MAAK,GAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK;IAC7C,OAAO,IAAI,CAAC,aAAa,CAAC,CAAC,CAAA,GAAI,IAAI,CAAC,aAAa,CAAC,CAAC,CAAA;IACnD,EAAA;AACD,GAAA,OAAO,MAAM,CAAC,CAAC,CAAA;GACf;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG;GACtC,IAAI,CAAC,KAAK,GAAC;AACZ,GAAA,EAAG;;AAEH,EAAA,OAAO,GAAG;GACT,IAAI,CAAC,KAAK,GAAC;GACX;;AAED,EAAA,KAAK,GAAG;AACP,GAAA,IAAI,IAAI,CAAC,YAAY,eAAe,EAAE;AACrC,IAAA,IAAI,CAAC,WAAW,GAAC;AAClB,IAAA;GACA;EACD;;AAED,CAAA,OAAO,EAAE;AACR,EAAA,OAAO,GAAG;GACT,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,CAAA;GACnH;;AAED,EAAA,gBAAgB,CAAC,GAAG,EAAE;AACrB,GAAA,MAAM,IAAK,GAAE,GAAG,CAAC,IAAK,IAAG,GAAC;AAC1B,GAAA,OAAO;IACN,EAAE,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE;IAC3B,IAAI,EAAE,IAAI,CAAC,IAAG,IAAK,GAAG,CAAC,IAAK,IAAG,EAAE;AACjC,IAAA,KAAK,EAAE,GAAG,CAAC,KAAI,IAAK,IAAI,CAAC,KAAI,IAAK,EAAE;IACpC,WAAW,EAAE,GAAG,CAAC,WAAU,IAAK,IAAI,CAAC,WAAY,IAAG,EAAE;AACtD,IAAA,GAAG,EAAE,GAAG,CAAC,GAAE,IAAK,EAAE;IAClB,WAAW,EAAE,GAAG,CAAC,WAAY,IAAG,IAAI,CAAC,WAAU,IAAK,IAAI;AACxD,IAAA,MAAM,EAAE,GAAG,CAAC,MAAO,IAAG,IAAI,CAAC,UAAU,IAAI;AACzC,IAAA,SAAS,EAAE,GAAG,CAAC,aAAa,IAAI,CAAC,SAAU,IAAG,IAAI;IAClD,UAAU,EAAE,GAAG,CAAC,cAAc,IAAI,CAAC,UAAS,IAAK,IAAI;AACrD,IAAA,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,mBAAmB,IAAI,CAAC,eAAc,IAAK,CAAC,CAAE,IAAG,CAAC;IAC9E,WAAW,EAAE,CAAC;AACf,IAAA;GACA;;EAED,OAAO,CAAC,IAAI,EAAE;GACb,OAAO,IAAI,CAAC,EAAC,IAAK,IAAI,CAAC,IAAG,IAAK,EAAC;GAChC;;AAED,EAAA,SAAS,CAAC,IAAI,EAAE;GACf,OAAO,IAAI,CAAC,KAAM,IAAG,IAAI,CAAC,EAAC,IAAKD,SAAC,CAAC,eAAe,EAAE,eAAe,CAAA;GAClE;;EAED,OAAO,CAAC,IAAI,EAAE;GACb,IAAI,IAAI,CAAC,GAAG,EAAE;IACb,OAAO,IAAI,CAAC,GAAE;AACf,IAAA;GACA,IAAI,IAAI,CAAC,IAAI,EAAE;AACd,IAAA,OAAO,CAAC,sBAAsB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;AAC3C,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,WAAU;GACtB;;AAED,EAAA,WAAW,CAAC,IAAI,EAAE;AACjB,GAAA,MAAM,CAAE,GAAE,IAAI,CAAC,UAAQ;AACvB,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,QAAQ,CAAC,IAAI,EAAE;GACd,IAAI,IAAI,CAAC,WAAW,QAAQ,EAAE;AAC7B,IAAA,OAAO,IAAG;AACX,IAAA;GACA,MAAM,EAAG,GAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAA;GAChC,IAAI,EAAG,KAAI,IAAI,EAAE;AAChB,IAAA,OAAO,EAAG,IAAG,IAAI,CAAC,GAAG,EAAC;AACvB,IAAA;GACA,OAAO,KAAI;GACX;;AAED,EAAA,WAAW,CAAC,IAAI,EAAE;AACjB,GAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAE,KAAI,IAAI,EAAE;AACjC,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAA;AACnC,IAAA;GACA,IAAI,IAAI,CAAC,MAAO,KAAI,OAAO,EAAE;AAC5B,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA;AAClC,IAAA;AACA,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA;GAChC;;AAED,EAAA,WAAW,CAAC,IAAI,EAAE;AACjB,GAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAE,KAAI,IAAI,EAAE;AACjC,IAAA,OAAO,+BAA8B;AACtC,IAAA;GACA,IAAI,IAAI,CAAC,MAAO,KAAI,OAAO,EAAE;AAC5B,IAAA,OAAO,8BAA6B;AACrC,IAAA;AACA,GAAA,OAAO,6BAA4B;GACnC;;AAED,EAAA,eAAe,CAAC,IAAI,EAAE;GACrB,IAAI,IAAI,CAAC,eAAc,GAAI,CAAC,EAAE;IAC7B,OAAO,IAAI,CAAC,eAAc;AAC3B,IAAA;GACA,OAAO,IAAI,CAAC,WAAY,IAAG,CAAA;GAC3B;;AAED,EAAA,aAAa,CAAC,IAAI,EAAE;AACnB,GAAA,MAAM,EAAG,GAAE,MAAM,CAAC,IAAI,CAAC,WAAY,IAAG,CAAC,EAAA;GACvC,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAA,IAAK,EAAC,GAAI,CAAC,EAAE;IAClC,OAAO,EAAC,GAAI,IAAK,GAAE,EAAG,GAAE,IAAK,GAAE,EAAC;AACjC,IAAA;AACA,GAAA,OAAO,CAAA;GACP;;EAED,OAAO,CAAC,IAAI,EAAE;GACb,MAAM,KAAM,GAAE,GAAC;AACf,GAAA,MAAM,KAAI,GAAI,IAAI,CAAC,eAAe,CAAC,IAAI,EAAA;GACvC,IAAI,KAAI,GAAI,CAAC,EAAE;IACd,KAAK,CAAC,IAAI,CAACA,SAAC,CAAC,eAAe,EAAE,iBAAiB,EAAE,EAAE,CAAC,EAAE,KAAM,EAAC,CAAC,EAAA;AAC/D,IAAA;GACA,MAAM,EAAG,GAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAA;GAChC,IAAI,EAAG,KAAI,IAAI,EAAE;IAChB,MAAM,OAAO,KAAK,IAAI,CAAC,GAAG,GAAC;IAC3B,IAAI,IAAK,GAAE,CAAC,EAAE;AACb,KAAA,MAAM,IAAK,GAAE,IAAI,CAAC,IAAI,CAAC,OAAO,UAAU,EAAA;KACxC,IAAI,IAAK,IAAG,CAAC,EAAE;MACd,MAAM,KAAI,GAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAG,IAAK,EAAG,GAAE,EAAG,GAAE,IAAI,CAAC,CAAC,EAAA;MAC5D,KAAK,CAAC,IAAI,CAACA,SAAC,CAAC,eAAe,EAAE,qBAAqB,EAAE,EAAE,CAAC,EAAE,KAAM,EAAC,CAAC,EAAA;MACjE,MAAK;MACN,KAAK,CAAC,IAAI,CAACA,SAAC,CAAC,eAAe,EAAE,oBAAoB,EAAE,EAAE,CAAC,EAAE,IAAK,EAAC,CAAC,EAAA;AACjE,MAAA;AACD,KAAA;AACD,IAAA;AACA,GAAA,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAA;GACvB;;AAED,EAAA,YAAY,CAAC,IAAI,EAAE;GAClB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAA;GACpD;;AAED,EAAA,KAAK,GAAG;AACP,GAAA,IAAI,IAAI,CAAC,YAAY,eAAe,EAAE;AACrC,IAAA,IAAI,CAAC,WAAW,GAAC;IACjB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,SAAS,GAAC;GACf;;AAED,EAAA,MAAM,SAAS,GAAG;GACjB,IAAI,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE;IACrD,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,QAAO,GAAI,GAAC;AACjB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AACnF,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;AACjC,KAAA,IAAI,CAAC,OAAM,GAAI,IAAI,CAAC,OAAQ,IAAG,IAAI,CAAC,KAAI,KAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAA,GAAI,IAAG,GAAI,EAAE,CAAE,IAAG,GAAC;AACpF,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,OAAQ,GAAE,GAAC;AAChB,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;AACN,KAAA,IAAI,CAAC,OAAQ,GAAE,GAAC;AACjB,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,GAAG,EAAA;AACxD,IAAA,IAAI,CAAC,OAAQ,GAAE,GAAC;AACjB,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED,EAAA,MAAM,WAAW,GAAG;GACnB,IAAI,CAAC,IAAI,CAAC,KAAI,IAAK,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,MAAK,IAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;AACpE,IAAA,IAAI,CAAC,SAAQ,GAAI,KAAG;IACpB,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,SAAQ,GAAI,MAAM,QAAQ,CAAC,IAAI,GAAC;AACtC,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,SAAQ,GAAI,KAAG;AACpB,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;AACN,KAAA,IAAI,CAAC,SAAQ,GAAI,KAAG;AACrB,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,GAAG,EAAA;AAC9D,IAAA,IAAI,CAAC,SAAQ,GAAI,KAAG;AACrB,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;EACD;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"CnFormsCard.vue2.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsCard.vue"],"sourcesContent":["<!--\n CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n\n Replaces the generic CnIntegrationCard for the `forms` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N forms · M open\"\n secondary line names the most-recent form with its submission\n count.\n - detail-page : compact list of linked forms\n with status pill + submission count per row.\n - single-entity : chip with form title + status\n indicator (referenceType: 'forms').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnFormsTab; for `single-entity` the optional `value` prop addresses a\n single form by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-forms/` 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-forms-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"formUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ formTitle(entity) }}</a>\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-forms-card__chip-status\"\n\t\t\t\t\t:class=\"statusClass(entity)\">{{ statusLabel(entity) }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-forms-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-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-forms-card__headline\">\n\t\t\t\t<div class=\"cn-forms-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-forms-card__headline-total\">\n\t\t\t\t\t{{ submissionsHeadline }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-forms-card__headline-recent\">\n\t\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"formUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ formTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-forms-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"form in displayedForms\"\n\t\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\t\tclass=\"cn-forms-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-forms-card__row--closed': isClosed(form) }\">\n\t\t\t\t\t<div class=\"cn-forms-card__row-header\">\n\t\t\t\t\t\t<ClipboardText :size=\"16\" class=\"cn-forms-card__row-icon\" />\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"formUrl(form)\"\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-forms-card__title\">{{ formTitle(form) }}</a>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-forms-card__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\">{{ statusLabel(form) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(form)\" class=\"cn-forms-card__subtitle\">{{ rowMeta(form) }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n *\n * Renders Forms-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnFormsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ClipboardText },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\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 (form id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Forms') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ClipboardText },\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 forms linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tentityRow: 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\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tentity() {\n\t\t\tif (this.entityRow === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normaliseFormRow(this.entityRow)\n\t\t},\n\n\t\tdisplayedForms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.forms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.forms\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + (this.isClosed(f) ? 0 : 1), 0)\n\t\t},\n\n\t\ttotalSubmissions() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + this.submissionCount(f), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.forms.length\n\t\t\tconst open = this.openCount\n\t\t\tif (total === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} form (all closed)', '{count} forms (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} form', '{count} forms', 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\tsubmissionsHeadline() {\n\t\t\tconst total = this.totalSubmissions\n\t\t\treturn n('nextcloud-vue', '{count} submission', '{count} submissions', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.forms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.forms].sort((a, b) => {\n\t\t\t\treturn this.sortTimestamp(b) - this.sortTimestamp(a)\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\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\tformUrl(form) {\n\t\t\tif (form.url && safeHref(form.url) !== '#') {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\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\tisClosed(form) {\n\t\t\tif (form.status === 'closed') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-card__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-card__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-card__status--open'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tsortTimestamp(form) {\n\t\t\tconst lu = Number(form.lastUpdated ?? 0)\n\t\t\tif (Number.isFinite(lu) && lu > 0) {\n\t\t\t\treturn lu < 1e12 ? lu * 1000 : lu\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\trowMeta(form) {\n\t\t\tconst parts = []\n\t\t\tconst count = this.submissionCount(form)\n\t\t\tif (count > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} submissions', { n: count }))\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\tconst diff = ms - Date.now()\n\t\t\t\tif (diff > 0) {\n\t\t\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\t\t\tif (days <= 1) {\n\t\t\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} hours', { n: hours }))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} days', { n: days }))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tchipSubtitle(form) {\n\t\t\treturn this.statusLabel(form) || this.formTitle(form)\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.rawRows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\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('[CnFormsCard] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\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.entityRow = 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.entityRow = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entityRow = 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('[CnFormsCard] failed to fetch single form', err)\n\t\t\t\tthis.entityRow = 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-forms-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-forms-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-forms-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-forms-card__headline-total {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-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-forms-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-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-forms-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip-status {\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.75em;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-forms-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-forms-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-forms-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-forms-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-forms-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-forms-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__status,\n.cn-forms-card__chip-status {\n\tflex-shrink: 0;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n}\n\n.cn-forms-card__status--open {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-primary-element-text, #ffffff);\n}\n\n.cn-forms-card__status--closed {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__status--draft {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-forms-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFormsCard.vue3.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsCard.vue?vue&type=template&id=0c622af1&lang.js"],"sourcesContent":["<!--\n CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n\n Replaces the generic CnIntegrationCard for the `forms` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N forms · M open\"\n secondary line names the most-recent form with its submission\n count.\n - detail-page : compact list of linked forms\n with status pill + submission count per row.\n - single-entity : chip with form title + status\n indicator (referenceType: 'forms').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnFormsTab; for `single-entity` the optional `value` prop addresses a\n single form by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-forms/` 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-forms-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"formUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ formTitle(entity) }}</a>\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-forms-card__chip-status\"\n\t\t\t\t\t:class=\"statusClass(entity)\">{{ statusLabel(entity) }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-forms-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-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-forms-card__headline\">\n\t\t\t\t<div class=\"cn-forms-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-forms-card__headline-total\">\n\t\t\t\t\t{{ submissionsHeadline }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-forms-card__headline-recent\">\n\t\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"formUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ formTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-forms-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"form in displayedForms\"\n\t\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\t\tclass=\"cn-forms-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-forms-card__row--closed': isClosed(form) }\">\n\t\t\t\t\t<div class=\"cn-forms-card__row-header\">\n\t\t\t\t\t\t<ClipboardText :size=\"16\" class=\"cn-forms-card__row-icon\" />\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"formUrl(form)\"\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-forms-card__title\">{{ formTitle(form) }}</a>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-forms-card__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\">{{ statusLabel(form) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(form)\" class=\"cn-forms-card__subtitle\">{{ rowMeta(form) }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n *\n * Renders Forms-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnFormsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ClipboardText },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\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 (form id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Forms') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ClipboardText },\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 forms linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tentityRow: 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\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tentity() {\n\t\t\tif (this.entityRow === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normaliseFormRow(this.entityRow)\n\t\t},\n\n\t\tdisplayedForms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.forms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.forms\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + (this.isClosed(f) ? 0 : 1), 0)\n\t\t},\n\n\t\ttotalSubmissions() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + this.submissionCount(f), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.forms.length\n\t\t\tconst open = this.openCount\n\t\t\tif (total === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} form (all closed)', '{count} forms (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} form', '{count} forms', 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\tsubmissionsHeadline() {\n\t\t\tconst total = this.totalSubmissions\n\t\t\treturn n('nextcloud-vue', '{count} submission', '{count} submissions', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.forms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.forms].sort((a, b) => {\n\t\t\t\treturn this.sortTimestamp(b) - this.sortTimestamp(a)\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\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformUrl(form) {\n\t\t\tif (form.url) {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\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\tisClosed(form) {\n\t\t\tif (form.status === 'closed') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-card__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-card__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-card__status--open'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tsortTimestamp(form) {\n\t\t\tconst lu = Number(form.lastUpdated ?? 0)\n\t\t\tif (Number.isFinite(lu) && lu > 0) {\n\t\t\t\treturn lu < 1e12 ? lu * 1000 : lu\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\trowMeta(form) {\n\t\t\tconst parts = []\n\t\t\tconst count = this.submissionCount(form)\n\t\t\tif (count > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} submissions', { n: count }))\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\tconst diff = ms - Date.now()\n\t\t\t\tif (diff > 0) {\n\t\t\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\t\t\tif (days <= 1) {\n\t\t\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} hours', { n: hours }))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} days', { n: days }))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tchipSubtitle(form) {\n\t\t\treturn this.statusLabel(form) || this.formTitle(form)\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.rawRows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\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('[CnFormsCard] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\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.entityRow = 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.entityRow = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entityRow = 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('[CnFormsCard] failed to fetch single form', err)\n\t\t\t\tthis.entityRow = 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-forms-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-forms-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-forms-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-forms-card__headline-total {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-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-forms-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-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-forms-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip-status {\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.75em;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-forms-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-forms-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-forms-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-forms-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-forms-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-forms-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__status,\n.cn-forms-card__chip-status {\n\tflex-shrink: 0;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n}\n\n.cn-forms-card__status--open {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-primary-element-text, #ffffff);\n}\n\n.cn-forms-card__status--closed {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__status--draft {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-forms-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_normalizeClass","_openBlock"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFormsCard.vue3.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsCard.vue?vue&type=template&id=0c622af1&lang.js"],"sourcesContent":["<!--\n CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n\n Replaces the generic CnIntegrationCard for the `forms` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N forms · M open\"\n secondary line names the most-recent form with its submission\n count.\n - detail-page : compact list of linked forms\n with status pill + submission count per row.\n - single-entity : chip with form title + status\n indicator (referenceType: 'forms').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnFormsTab; for `single-entity` the optional `value` prop addresses a\n single form by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-forms/` 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-forms-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"formUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ formTitle(entity) }}</a>\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-forms-card__chip-status\"\n\t\t\t\t\t:class=\"statusClass(entity)\">{{ statusLabel(entity) }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-forms-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-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-forms-card__headline\">\n\t\t\t\t<div class=\"cn-forms-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-forms-card__headline-total\">\n\t\t\t\t\t{{ submissionsHeadline }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-forms-card__headline-recent\">\n\t\t\t\t\t<ClipboardText :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"formUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ formTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-forms-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-forms-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"form in displayedForms\"\n\t\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\t\tclass=\"cn-forms-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-forms-card__row--closed': isClosed(form) }\">\n\t\t\t\t\t<div class=\"cn-forms-card__row-header\">\n\t\t\t\t\t\t<ClipboardText :size=\"16\" class=\"cn-forms-card__row-icon\" />\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"formUrl(form)\"\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-forms-card__title\">{{ formTitle(form) }}</a>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-forms-card__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\">{{ statusLabel(form) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span v-if=\"rowMeta(form)\" class=\"cn-forms-card__subtitle\">{{ rowMeta(form) }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * CnFormsCard — bespoke surface-aware widget for the `forms` integration.\n *\n * Renders Forms-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnFormsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ClipboardText },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\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 (form id). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Forms') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ClipboardText },\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 forms linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tentityRow: 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\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tentity() {\n\t\t\tif (this.entityRow === null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normaliseFormRow(this.entityRow)\n\t\t},\n\n\t\tdisplayedForms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.forms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.forms\n\t\t},\n\n\t\topenCount() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + (this.isClosed(f) ? 0 : 1), 0)\n\t\t},\n\n\t\ttotalSubmissions() {\n\t\t\treturn this.forms.reduce((sum, f) => sum + this.submissionCount(f), 0)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.forms.length\n\t\t\tconst open = this.openCount\n\t\t\tif (total === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (open === 0) {\n\t\t\t\treturn n('nextcloud-vue', '{count} form (all closed)', '{count} forms (all closed)', total, { count: total })\n\t\t\t}\n\t\t\tconst totalFragment = n('nextcloud-vue', '{count} form', '{count} forms', 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\tsubmissionsHeadline() {\n\t\t\tconst total = this.totalSubmissions\n\t\t\treturn n('nextcloud-vue', '{count} submission', '{count} submissions', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.forms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.forms].sort((a, b) => {\n\t\t\t\treturn this.sortTimestamp(b) - this.sortTimestamp(a)\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\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\tformUrl(form) {\n\t\t\tif (form.url && safeHref(form.url) !== '#') {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\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\tisClosed(form) {\n\t\t\tif (form.status === 'closed') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-card__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-card__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-card__status--open'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tsortTimestamp(form) {\n\t\t\tconst lu = Number(form.lastUpdated ?? 0)\n\t\t\tif (Number.isFinite(lu) && lu > 0) {\n\t\t\t\treturn lu < 1e12 ? lu * 1000 : lu\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\trowMeta(form) {\n\t\t\tconst parts = []\n\t\t\tconst count = this.submissionCount(form)\n\t\t\tif (count > 0) {\n\t\t\t\tparts.push(t('nextcloud-vue', '{n} submissions', { n: count }))\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\tconst diff = ms - Date.now()\n\t\t\t\tif (diff > 0) {\n\t\t\t\t\tconst days = Math.ceil(diff / MS_PER_DAY)\n\t\t\t\t\tif (days <= 1) {\n\t\t\t\t\t\tconst hours = Math.max(1, Math.ceil(diff / (60 * 60 * 1000)))\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} hours', { n: hours }))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparts.push(t('nextcloud-vue', 'Closes in {n} days', { n: days }))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tchipSubtitle(form) {\n\t\t\treturn this.statusLabel(form) || this.formTitle(form)\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.rawRows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\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('[CnFormsCard] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\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.entityRow = 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.entityRow = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entityRow = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entityRow = 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('[CnFormsCard] failed to fetch single form', err)\n\t\t\t\tthis.entityRow = 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-forms-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-forms-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-forms-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-forms-card__headline-total {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-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-forms-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-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-forms-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-forms-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__chip-status {\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.75em;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-forms-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-forms-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-forms-card__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-forms-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n}\n\n.cn-forms-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-forms-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-forms-card__status,\n.cn-forms-card__chip-status {\n\tflex-shrink: 0;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n}\n\n.cn-forms-card__status--open {\n\tbackground: var(--color-success, #46ba61);\n\tcolor: var(--color-primary-element-text, #ffffff);\n}\n\n.cn-forms-card__status--closed {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-card__status--draft {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-forms-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\tpadding-left: 24px;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_normalizeClass","_openBlock"],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
|
|
|
10
10
|
import __sfc_main$1 from './CnFormCreate.vue.js';
|
|
11
11
|
import __sfc_main$2 from './CnFormPicker.vue.js';
|
|
12
12
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
13
|
+
import { safeHref } from '../../../utils/safeHref.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.
|
|
@@ -173,8 +174,9 @@ var script = {
|
|
|
173
174
|
return form.description || ''
|
|
174
175
|
},
|
|
175
176
|
|
|
177
|
+
// A provider URL is used only when it is a safe scheme; else the app link.
|
|
176
178
|
formUrl(form) {
|
|
177
|
-
if (form.url) {
|
|
179
|
+
if (form.url && safeHref(form.url) !== '#') {
|
|
178
180
|
return form.url
|
|
179
181
|
}
|
|
180
182
|
if (form.hash) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFormsTab.vue2.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsTab.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnFormsTab — bespoke sidebar tab for the Tier-2 `forms` integration.\n -\n - Replaces the generic CnIntegrationTab for the `forms` leaf: renders a\n - list of linked NC Forms forms styled to mirror the real Forms app —\n - each row is an NcListItem with a clipboard/form icon, the bold form\n - title, an optional description, a response-count bubble\n - (NcCounterBubble), an open/closed/draft state chip (CnStatusBadge),\n - and an expiry date (NcDateTime) when the form has one. Per-row \"Open\n - in Forms\" + unlink live in the row actions menu, with Tier-2\n - link/unlink + create-new actions on the header.\n -\n - Talks to the OpenRegister Tier-2 forms link surface:\n - GET /api/objects/{r}/{s}/{id}/forms — list linked\n - POST /api/objects/{r}/{s}/{id}/forms — link existing\n - POST /api/objects/{r}/{s}/{id}/forms/new — create + link\n - DELETE /api/objects/{r}/{s}/{id}/forms/{id} — unlink form\n -\n - The Tier-2 endpoint returns canonical form rows enriched with\n - submission roll-ups; pre-Tier-2 forms (still on the marker scan)\n - keep working through the same endpoint via the provider's\n - backwards-compat fallback.\n -\n - Per ADR-004 (modal isolation) the picker + create modals live in\n - their own .vue files (CnFormPicker, CnFormCreate) and are wired\n - in here rather than inlined.\n -\n - @spec openspec/changes/integration-forms/specs/integrations/forms/spec.md\n -->\n<template>\n\t<div class=\"cn-sidebar-tab cn-forms-tab\">\n\t\t<div class=\"cn-forms-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:aria-label=\"linkExistingLabel\"\n\t\t\t\t@click=\"showPicker = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariantPlus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\t:aria-label=\"createNewLabel\"\n\t\t\t\t@click=\"showCreate = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ createNewLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"degraded\" class=\"cn-forms-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<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-forms-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-sidebar-tab__empty cn-forms-tab__empty\">\n\t\t\t<ClipboardText :size=\"32\" class=\"cn-forms-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=\"formsAppUrl\"\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<ClipboardText :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openFormsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-forms-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"form in forms\"\n\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\tclass=\"cn-forms-tab__row\"\n\t\t\t\t:class=\"{ 'cn-forms-tab__row--closed': isClosed(form) }\"\n\t\t\t\t:name=\"formTitle(form)\"\n\t\t\t\t:bold=\"false\"\n\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClipboardText :size=\"40\" class=\"cn-forms-tab__row-icon\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"formDescription(form)\" #subname>\n\t\t\t\t\t<span class=\"cn-forms-tab__description\">{{ formDescription(form) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #details>\n\t\t\t\t\t<div class=\"cn-forms-tab__details\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\"\n\t\t\t\t\t\t\t:label=\"statusLabel(form)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(form)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tv-if=\"expiresAtMs(form) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__expiry\"\n\t\t\t\t\t\t\t:timestamp=\"expiresAtMs(form)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"submissionCount(form) > 0\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-forms-tab__count\"\n\t\t\t\t\t\t:aria-label=\"responsesAriaLabel(form)\">\n\t\t\t\t\t\t{{ submissionCount(form) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ openInFormsLabel }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-forms-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlink(form)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnFormPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onLinkSelected\"\n\t\t\t@close=\"showPicker = false\" />\n\n\t\t<CnFormCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:submitting=\"creating\"\n\t\t\t:submitError=\"createError\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"onCreateClose\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariantPlus from 'vue-material-design-icons/LinkVariantPlus.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnFormCreate from './CnFormCreate.vue'\nimport CnFormPicker from './CnFormPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.\n *\n * Renders rows pulled from the OR Tier-2 forms link endpoint, styled to\n * mirror NC Forms (form icon, bold title, description, response-count\n * bubble, state chip, expiry date), with inline \"Link existing form\" +\n * \"Create new form\" actions on the header and per-row open/unlink in the\n * row actions menu.\n */\nexport default {\n\tname: 'CnFormsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClipboardText,\n\t\tClose,\n\t\tLinkVariantPlus,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnFormCreate,\n\t\tCnFormPicker,\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 `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\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 forms linked yet') },\n\t\t/** Pre-translated label for the \"Open in Forms\" CTA. */\n\t\topenFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Forms') },\n\t\t/** Pre-translated label for the per-row \"Open in Forms\" action. */\n\t\topenInFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Forms') },\n\t\t/** Pre-translated banner when Forms is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** Pre-translated label for the link-existing action. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing form') },\n\t\t/** Pre-translated label for the create-new action. */\n\t\tcreateNewLabel: { type: String, default: () => t('nextcloud-vue', 'Create new form') },\n\t\t/** Pre-translated label for the per-row unlink action. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink form') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t\tcreating: false,\n\t\t\tcreateError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Group provider rows into a per-form list with attached\n\t\t * submissions. The Tier-2 endpoint already emits per-form rows\n\t\t * with `submissionCount` rolled up; this method also handles\n\t\t * the legacy flat shape (rows with `type: 'submission'`) for\n\t\t * forms that pre-date the link table.\n\t\t *\n\t\t * @return {object[]} Per-form display rows.\n\t\t */\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t\tif (row.lastUpdated && (!target.lastSubmissionAt || row.lastUpdated > target.lastSubmissionAt)) {\n\t\t\t\t\t\t\ttarget.lastSubmissionAt = row.lastUpdated\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\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.fetchForms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchForms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchForms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/forms`)\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? row.formId ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.formHash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t\tlastSubmissionAt: null,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformDescription(form) {\n\t\t\treturn form.description || ''\n\t\t},\n\n\t\tformUrl(form) {\n\t\t\tif (form.url) {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Resolve the form's expiry as a millisecond epoch, or null when\n\t\t * the form never expires. Accepts both second- and millisecond-\n\t\t * granularity numbers and ISO strings.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {?number}\n\t\t */\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\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\tisClosed(form) {\n\t\t\tif (form.status === 'closed' || form.status === 'archived') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-tab__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-tab__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-tab__status--open'\n\t\t},\n\n\t\t/**\n\t\t * Map the form's state to a CnStatusBadge colour variant —\n\t\t * open = success (green), draft = warning, closed = default.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tstatusVariant(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'success'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tresponsesAriaLabel(form) {\n\t\t\treturn t('nextcloud-vue', '{n} submissions', { n: this.submissionCount(form) })\n\t\t},\n\n\t\tasync fetchForms() {\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\tthis.rawRows = this.unwrapList(data)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Canonical wrapper-key cascade — mirrors CnContactsTab.unwrapList.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tasync onLinkSelected(payload) {\n\t\t\tif (!payload || !payload.formId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('[CnFormsTab] link failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showPicker = false\n\t\t\t\tawait this.fetchForms()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] link error', err)\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tif (!payload || !payload.title) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.creating = true\n\t\t\tthis.createError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.createError = data?.error || `${response.status} ${response.statusText}`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreate = false\n\t\t\t\tawait this.fetchForms()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] create error', err)\n\t\t\t\tthis.createError = String(err?.message || err)\n\t\t\t} finally {\n\t\t\t\tthis.creating = false\n\t\t\t}\n\t\t},\n\n\t\tonCreateClose() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.createError = ''\n\t\t},\n\n\t\tasync unlink(form) {\n\t\t\tif (!form?.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/${encodeURIComponent(form.id)}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('[CnFormsTab] unlink failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.rawRows = this.rawRows.filter((r) => String(r.id ?? r.formId ?? '') !== String(form.id))\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] unlink error', err)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-tab {\n\tpadding: 12px;\n}\n\n.cn-forms-tab__header {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: flex-end;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-forms-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-forms-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-forms-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-forms-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Forms brand accent on the row icon, mirroring the real Forms app. */\n.cn-forms-tab__row-icon {\n\tcolor: var(--cn-forms-accent, #0d7000);\n\tflex-shrink: 0;\n}\n\n.cn-forms-tab__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-tab__row--closed .cn-forms-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__description {\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__details {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n}\n\n.cn-forms-tab__expiry {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n/* Status chip colours layered on top of CnStatusBadge variants so the\n open/closed/draft state reads at a glance, like the real Forms app. */\n.cn-forms-tab__status--open {\n\t--cn-forms-status: var(--color-success, #46ba61);\n}\n\n.cn-forms-tab__status--closed {\n\t--cn-forms-status: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__status--draft {\n\t--cn-forms-status: var(--color-warning, #e9a40f);\n}\n</style>\n"],"names":["CnStatusBadge","CnFormCreate","CnFormPicker","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFormsTab.vue2.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsTab.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnFormsTab — bespoke sidebar tab for the Tier-2 `forms` integration.\n -\n - Replaces the generic CnIntegrationTab for the `forms` leaf: renders a\n - list of linked NC Forms forms styled to mirror the real Forms app —\n - each row is an NcListItem with a clipboard/form icon, the bold form\n - title, an optional description, a response-count bubble\n - (NcCounterBubble), an open/closed/draft state chip (CnStatusBadge),\n - and an expiry date (NcDateTime) when the form has one. Per-row \"Open\n - in Forms\" + unlink live in the row actions menu, with Tier-2\n - link/unlink + create-new actions on the header.\n -\n - Talks to the OpenRegister Tier-2 forms link surface:\n - GET /api/objects/{r}/{s}/{id}/forms — list linked\n - POST /api/objects/{r}/{s}/{id}/forms — link existing\n - POST /api/objects/{r}/{s}/{id}/forms/new — create + link\n - DELETE /api/objects/{r}/{s}/{id}/forms/{id} — unlink form\n -\n - The Tier-2 endpoint returns canonical form rows enriched with\n - submission roll-ups; pre-Tier-2 forms (still on the marker scan)\n - keep working through the same endpoint via the provider's\n - backwards-compat fallback.\n -\n - Per ADR-004 (modal isolation) the picker + create modals live in\n - their own .vue files (CnFormPicker, CnFormCreate) and are wired\n - in here rather than inlined.\n -\n - @spec openspec/changes/integration-forms/specs/integrations/forms/spec.md\n -->\n<template>\n\t<div class=\"cn-sidebar-tab cn-forms-tab\">\n\t\t<div class=\"cn-forms-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:aria-label=\"linkExistingLabel\"\n\t\t\t\t@click=\"showPicker = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariantPlus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\t:aria-label=\"createNewLabel\"\n\t\t\t\t@click=\"showCreate = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ createNewLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"degraded\" class=\"cn-forms-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<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-forms-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-sidebar-tab__empty cn-forms-tab__empty\">\n\t\t\t<ClipboardText :size=\"32\" class=\"cn-forms-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=\"formsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClipboardText :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openFormsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-forms-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"form in forms\"\n\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\tclass=\"cn-forms-tab__row\"\n\t\t\t\t:class=\"{ 'cn-forms-tab__row--closed': isClosed(form) }\"\n\t\t\t\t:name=\"formTitle(form)\"\n\t\t\t\t:bold=\"false\"\n\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClipboardText :size=\"40\" class=\"cn-forms-tab__row-icon\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"formDescription(form)\" #subname>\n\t\t\t\t\t<span class=\"cn-forms-tab__description\">{{ formDescription(form) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #details>\n\t\t\t\t\t<div class=\"cn-forms-tab__details\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\"\n\t\t\t\t\t\t\t:label=\"statusLabel(form)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(form)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tv-if=\"expiresAtMs(form) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__expiry\"\n\t\t\t\t\t\t\t:timestamp=\"expiresAtMs(form)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"submissionCount(form) > 0\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-forms-tab__count\"\n\t\t\t\t\t\t:aria-label=\"responsesAriaLabel(form)\">\n\t\t\t\t\t\t{{ submissionCount(form) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ openInFormsLabel }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-forms-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlink(form)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnFormPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onLinkSelected\"\n\t\t\t@close=\"showPicker = false\" />\n\n\t\t<CnFormCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:submitting=\"creating\"\n\t\t\t:submitError=\"createError\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"onCreateClose\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariantPlus from 'vue-material-design-icons/LinkVariantPlus.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnFormCreate from './CnFormCreate.vue'\nimport CnFormPicker from './CnFormPicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.\n *\n * Renders rows pulled from the OR Tier-2 forms link endpoint, styled to\n * mirror NC Forms (form icon, bold title, description, response-count\n * bubble, state chip, expiry date), with inline \"Link existing form\" +\n * \"Create new form\" actions on the header and per-row open/unlink in the\n * row actions menu.\n */\nexport default {\n\tname: 'CnFormsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClipboardText,\n\t\tClose,\n\t\tLinkVariantPlus,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnFormCreate,\n\t\tCnFormPicker,\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 `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\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 forms linked yet') },\n\t\t/** Pre-translated label for the \"Open in Forms\" CTA. */\n\t\topenFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Forms') },\n\t\t/** Pre-translated label for the per-row \"Open in Forms\" action. */\n\t\topenInFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Forms') },\n\t\t/** Pre-translated banner when Forms is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** Pre-translated label for the link-existing action. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing form') },\n\t\t/** Pre-translated label for the create-new action. */\n\t\tcreateNewLabel: { type: String, default: () => t('nextcloud-vue', 'Create new form') },\n\t\t/** Pre-translated label for the per-row unlink action. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink form') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t\tcreating: false,\n\t\t\tcreateError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Group provider rows into a per-form list with attached\n\t\t * submissions. The Tier-2 endpoint already emits per-form rows\n\t\t * with `submissionCount` rolled up; this method also handles\n\t\t * the legacy flat shape (rows with `type: 'submission'`) for\n\t\t * forms that pre-date the link table.\n\t\t *\n\t\t * @return {object[]} Per-form display rows.\n\t\t */\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t\tif (row.lastUpdated && (!target.lastSubmissionAt || row.lastUpdated > target.lastSubmissionAt)) {\n\t\t\t\t\t\t\ttarget.lastSubmissionAt = row.lastUpdated\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\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.fetchForms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchForms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchForms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/forms`)\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? row.formId ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.formHash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t\tlastSubmissionAt: null,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformDescription(form) {\n\t\t\treturn form.description || ''\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\tformUrl(form) {\n\t\t\tif (form.url && safeHref(form.url) !== '#') {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Resolve the form's expiry as a millisecond epoch, or null when\n\t\t * the form never expires. Accepts both second- and millisecond-\n\t\t * granularity numbers and ISO strings.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {?number}\n\t\t */\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\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\tisClosed(form) {\n\t\t\tif (form.status === 'closed' || form.status === 'archived') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-tab__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-tab__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-tab__status--open'\n\t\t},\n\n\t\t/**\n\t\t * Map the form's state to a CnStatusBadge colour variant —\n\t\t * open = success (green), draft = warning, closed = default.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tstatusVariant(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'success'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tresponsesAriaLabel(form) {\n\t\t\treturn t('nextcloud-vue', '{n} submissions', { n: this.submissionCount(form) })\n\t\t},\n\n\t\tasync fetchForms() {\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\tthis.rawRows = this.unwrapList(data)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Canonical wrapper-key cascade — mirrors CnContactsTab.unwrapList.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tasync onLinkSelected(payload) {\n\t\t\tif (!payload || !payload.formId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('[CnFormsTab] link failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showPicker = false\n\t\t\t\tawait this.fetchForms()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] link error', err)\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tif (!payload || !payload.title) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.creating = true\n\t\t\tthis.createError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.createError = data?.error || `${response.status} ${response.statusText}`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreate = false\n\t\t\t\tawait this.fetchForms()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] create error', err)\n\t\t\t\tthis.createError = String(err?.message || err)\n\t\t\t} finally {\n\t\t\t\tthis.creating = false\n\t\t\t}\n\t\t},\n\n\t\tonCreateClose() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.createError = ''\n\t\t},\n\n\t\tasync unlink(form) {\n\t\t\tif (!form?.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/${encodeURIComponent(form.id)}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('[CnFormsTab] unlink failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.rawRows = this.rawRows.filter((r) => String(r.id ?? r.formId ?? '') !== String(form.id))\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] unlink error', err)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-tab {\n\tpadding: 12px;\n}\n\n.cn-forms-tab__header {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: flex-end;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-forms-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-forms-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-forms-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-forms-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Forms brand accent on the row icon, mirroring the real Forms app. */\n.cn-forms-tab__row-icon {\n\tcolor: var(--cn-forms-accent, #0d7000);\n\tflex-shrink: 0;\n}\n\n.cn-forms-tab__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-tab__row--closed .cn-forms-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__description {\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__details {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n}\n\n.cn-forms-tab__expiry {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n/* Status chip colours layered on top of CnStatusBadge variants so the\n open/closed/draft state reads at a glance, like the real Forms app. */\n.cn-forms-tab__status--open {\n\t--cn-forms-status: var(--color-success, #46ba61);\n}\n\n.cn-forms-tab__status--closed {\n\t--cn-forms-status: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__status--draft {\n\t--cn-forms-status: var(--color-warning, #e9a40f);\n}\n</style>\n"],"names":["CnStatusBadge","CnFormCreate","CnFormPicker","t"],"mappings":"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"}
|
|
@@ -89,8 +89,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
89
89
|
createVNode(_component_NcButton, {
|
|
90
90
|
variant: "primary",
|
|
91
91
|
href: $props.formsAppUrl,
|
|
92
|
-
target: "_blank"
|
|
93
|
-
rel: "noopener noreferrer"
|
|
92
|
+
target: "_blank"
|
|
94
93
|
}, {
|
|
95
94
|
icon: withCtx(() => [
|
|
96
95
|
createVNode(_component_ClipboardText, { size: 20 })
|
|
@@ -140,8 +139,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
140
139
|
createVNode(_component_NcActionLink, {
|
|
141
140
|
closeAfterClick: true,
|
|
142
141
|
href: $options.formUrl(form),
|
|
143
|
-
target: "_blank"
|
|
144
|
-
rel: "noopener noreferrer"
|
|
142
|
+
target: "_blank"
|
|
145
143
|
}, {
|
|
146
144
|
icon: withCtx(() => [
|
|
147
145
|
createVNode(_component_OpenInNew, { size: 20 })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFormsTab.vue3.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsTab.vue?vue&type=template&id=ea0d6578&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnFormsTab — bespoke sidebar tab for the Tier-2 `forms` integration.\n -\n - Replaces the generic CnIntegrationTab for the `forms` leaf: renders a\n - list of linked NC Forms forms styled to mirror the real Forms app —\n - each row is an NcListItem with a clipboard/form icon, the bold form\n - title, an optional description, a response-count bubble\n - (NcCounterBubble), an open/closed/draft state chip (CnStatusBadge),\n - and an expiry date (NcDateTime) when the form has one. Per-row \"Open\n - in Forms\" + unlink live in the row actions menu, with Tier-2\n - link/unlink + create-new actions on the header.\n -\n - Talks to the OpenRegister Tier-2 forms link surface:\n - GET /api/objects/{r}/{s}/{id}/forms — list linked\n - POST /api/objects/{r}/{s}/{id}/forms — link existing\n - POST /api/objects/{r}/{s}/{id}/forms/new — create + link\n - DELETE /api/objects/{r}/{s}/{id}/forms/{id} — unlink form\n -\n - The Tier-2 endpoint returns canonical form rows enriched with\n - submission roll-ups; pre-Tier-2 forms (still on the marker scan)\n - keep working through the same endpoint via the provider's\n - backwards-compat fallback.\n -\n - Per ADR-004 (modal isolation) the picker + create modals live in\n - their own .vue files (CnFormPicker, CnFormCreate) and are wired\n - in here rather than inlined.\n -\n - @spec openspec/changes/integration-forms/specs/integrations/forms/spec.md\n -->\n<template>\n\t<div class=\"cn-sidebar-tab cn-forms-tab\">\n\t\t<div class=\"cn-forms-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:aria-label=\"linkExistingLabel\"\n\t\t\t\t@click=\"showPicker = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariantPlus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\t:aria-label=\"createNewLabel\"\n\t\t\t\t@click=\"showCreate = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ createNewLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"degraded\" class=\"cn-forms-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<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-forms-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-sidebar-tab__empty cn-forms-tab__empty\">\n\t\t\t<ClipboardText :size=\"32\" class=\"cn-forms-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=\"formsAppUrl\"\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<ClipboardText :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openFormsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-forms-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"form in forms\"\n\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\tclass=\"cn-forms-tab__row\"\n\t\t\t\t:class=\"{ 'cn-forms-tab__row--closed': isClosed(form) }\"\n\t\t\t\t:name=\"formTitle(form)\"\n\t\t\t\t:bold=\"false\"\n\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClipboardText :size=\"40\" class=\"cn-forms-tab__row-icon\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"formDescription(form)\" #subname>\n\t\t\t\t\t<span class=\"cn-forms-tab__description\">{{ formDescription(form) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #details>\n\t\t\t\t\t<div class=\"cn-forms-tab__details\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\"\n\t\t\t\t\t\t\t:label=\"statusLabel(form)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(form)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tv-if=\"expiresAtMs(form) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__expiry\"\n\t\t\t\t\t\t\t:timestamp=\"expiresAtMs(form)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"submissionCount(form) > 0\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-forms-tab__count\"\n\t\t\t\t\t\t:aria-label=\"responsesAriaLabel(form)\">\n\t\t\t\t\t\t{{ submissionCount(form) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ openInFormsLabel }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-forms-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlink(form)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnFormPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onLinkSelected\"\n\t\t\t@close=\"showPicker = false\" />\n\n\t\t<CnFormCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:submitting=\"creating\"\n\t\t\t:submitError=\"createError\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"onCreateClose\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariantPlus from 'vue-material-design-icons/LinkVariantPlus.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnFormCreate from './CnFormCreate.vue'\nimport CnFormPicker from './CnFormPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.\n *\n * Renders rows pulled from the OR Tier-2 forms link endpoint, styled to\n * mirror NC Forms (form icon, bold title, description, response-count\n * bubble, state chip, expiry date), with inline \"Link existing form\" +\n * \"Create new form\" actions on the header and per-row open/unlink in the\n * row actions menu.\n */\nexport default {\n\tname: 'CnFormsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClipboardText,\n\t\tClose,\n\t\tLinkVariantPlus,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnFormCreate,\n\t\tCnFormPicker,\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 `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\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 forms linked yet') },\n\t\t/** Pre-translated label for the \"Open in Forms\" CTA. */\n\t\topenFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Forms') },\n\t\t/** Pre-translated label for the per-row \"Open in Forms\" action. */\n\t\topenInFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Forms') },\n\t\t/** Pre-translated banner when Forms is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** Pre-translated label for the link-existing action. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing form') },\n\t\t/** Pre-translated label for the create-new action. */\n\t\tcreateNewLabel: { type: String, default: () => t('nextcloud-vue', 'Create new form') },\n\t\t/** Pre-translated label for the per-row unlink action. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink form') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t\tcreating: false,\n\t\t\tcreateError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Group provider rows into a per-form list with attached\n\t\t * submissions. The Tier-2 endpoint already emits per-form rows\n\t\t * with `submissionCount` rolled up; this method also handles\n\t\t * the legacy flat shape (rows with `type: 'submission'`) for\n\t\t * forms that pre-date the link table.\n\t\t *\n\t\t * @return {object[]} Per-form display rows.\n\t\t */\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t\tif (row.lastUpdated && (!target.lastSubmissionAt || row.lastUpdated > target.lastSubmissionAt)) {\n\t\t\t\t\t\t\ttarget.lastSubmissionAt = row.lastUpdated\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\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.fetchForms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchForms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchForms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/forms`)\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? row.formId ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.formHash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t\tlastSubmissionAt: null,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformDescription(form) {\n\t\t\treturn form.description || ''\n\t\t},\n\n\t\tformUrl(form) {\n\t\t\tif (form.url) {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Resolve the form's expiry as a millisecond epoch, or null when\n\t\t * the form never expires. Accepts both second- and millisecond-\n\t\t * granularity numbers and ISO strings.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {?number}\n\t\t */\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\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\tisClosed(form) {\n\t\t\tif (form.status === 'closed' || form.status === 'archived') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-tab__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-tab__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-tab__status--open'\n\t\t},\n\n\t\t/**\n\t\t * Map the form's state to a CnStatusBadge colour variant —\n\t\t * open = success (green), draft = warning, closed = default.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tstatusVariant(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'success'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tresponsesAriaLabel(form) {\n\t\t\treturn t('nextcloud-vue', '{n} submissions', { n: this.submissionCount(form) })\n\t\t},\n\n\t\tasync fetchForms() {\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\tthis.rawRows = this.unwrapList(data)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Canonical wrapper-key cascade — mirrors CnContactsTab.unwrapList.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tasync onLinkSelected(payload) {\n\t\t\tif (!payload || !payload.formId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('[CnFormsTab] link failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showPicker = false\n\t\t\t\tawait this.fetchForms()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] link error', err)\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tif (!payload || !payload.title) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.creating = true\n\t\t\tthis.createError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.createError = data?.error || `${response.status} ${response.statusText}`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreate = false\n\t\t\t\tawait this.fetchForms()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] create error', err)\n\t\t\t\tthis.createError = String(err?.message || err)\n\t\t\t} finally {\n\t\t\t\tthis.creating = false\n\t\t\t}\n\t\t},\n\n\t\tonCreateClose() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.createError = ''\n\t\t},\n\n\t\tasync unlink(form) {\n\t\t\tif (!form?.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/${encodeURIComponent(form.id)}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('[CnFormsTab] unlink failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.rawRows = this.rawRows.filter((r) => String(r.id ?? r.formId ?? '') !== String(form.id))\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] unlink error', err)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-tab {\n\tpadding: 12px;\n}\n\n.cn-forms-tab__header {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: flex-end;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-forms-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-forms-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-forms-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-forms-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Forms brand accent on the row icon, mirroring the real Forms app. */\n.cn-forms-tab__row-icon {\n\tcolor: var(--cn-forms-accent, #0d7000);\n\tflex-shrink: 0;\n}\n\n.cn-forms-tab__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-tab__row--closed .cn-forms-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__description {\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__details {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n}\n\n.cn-forms-tab__expiry {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n/* Status chip colours layered on top of CnStatusBadge variants so the\n open/closed/draft state reads at a glance, like the real Forms app. */\n.cn-forms-tab__status--open {\n\t--cn-forms-status: var(--color-success, #46ba61);\n}\n\n.cn-forms-tab__status--closed {\n\t--cn-forms-status: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__status--draft {\n\t--cn-forms-status: var(--color-warning, #e9a40f);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnFormsTab.vue3.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsTab.vue?vue&type=template&id=ea0d6578&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnFormsTab — bespoke sidebar tab for the Tier-2 `forms` integration.\n -\n - Replaces the generic CnIntegrationTab for the `forms` leaf: renders a\n - list of linked NC Forms forms styled to mirror the real Forms app —\n - each row is an NcListItem with a clipboard/form icon, the bold form\n - title, an optional description, a response-count bubble\n - (NcCounterBubble), an open/closed/draft state chip (CnStatusBadge),\n - and an expiry date (NcDateTime) when the form has one. Per-row \"Open\n - in Forms\" + unlink live in the row actions menu, with Tier-2\n - link/unlink + create-new actions on the header.\n -\n - Talks to the OpenRegister Tier-2 forms link surface:\n - GET /api/objects/{r}/{s}/{id}/forms — list linked\n - POST /api/objects/{r}/{s}/{id}/forms — link existing\n - POST /api/objects/{r}/{s}/{id}/forms/new — create + link\n - DELETE /api/objects/{r}/{s}/{id}/forms/{id} — unlink form\n -\n - The Tier-2 endpoint returns canonical form rows enriched with\n - submission roll-ups; pre-Tier-2 forms (still on the marker scan)\n - keep working through the same endpoint via the provider's\n - backwards-compat fallback.\n -\n - Per ADR-004 (modal isolation) the picker + create modals live in\n - their own .vue files (CnFormPicker, CnFormCreate) and are wired\n - in here rather than inlined.\n -\n - @spec openspec/changes/integration-forms/specs/integrations/forms/spec.md\n -->\n<template>\n\t<div class=\"cn-sidebar-tab cn-forms-tab\">\n\t\t<div class=\"cn-forms-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:aria-label=\"linkExistingLabel\"\n\t\t\t\t@click=\"showPicker = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariantPlus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\t:aria-label=\"createNewLabel\"\n\t\t\t\t@click=\"showCreate = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ createNewLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"degraded\" class=\"cn-forms-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<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-forms-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-sidebar-tab__empty cn-forms-tab__empty\">\n\t\t\t<ClipboardText :size=\"32\" class=\"cn-forms-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=\"formsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClipboardText :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openFormsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-forms-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"form in forms\"\n\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\tclass=\"cn-forms-tab__row\"\n\t\t\t\t:class=\"{ 'cn-forms-tab__row--closed': isClosed(form) }\"\n\t\t\t\t:name=\"formTitle(form)\"\n\t\t\t\t:bold=\"false\"\n\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClipboardText :size=\"40\" class=\"cn-forms-tab__row-icon\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"formDescription(form)\" #subname>\n\t\t\t\t\t<span class=\"cn-forms-tab__description\">{{ formDescription(form) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #details>\n\t\t\t\t\t<div class=\"cn-forms-tab__details\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\"\n\t\t\t\t\t\t\t:label=\"statusLabel(form)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(form)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tv-if=\"expiresAtMs(form) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__expiry\"\n\t\t\t\t\t\t\t:timestamp=\"expiresAtMs(form)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"submissionCount(form) > 0\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-forms-tab__count\"\n\t\t\t\t\t\t:aria-label=\"responsesAriaLabel(form)\">\n\t\t\t\t\t\t{{ submissionCount(form) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"formUrl(form)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ openInFormsLabel }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-forms-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlink(form)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnFormPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onLinkSelected\"\n\t\t\t@close=\"showPicker = false\" />\n\n\t\t<CnFormCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:submitting=\"creating\"\n\t\t\t:submitError=\"createError\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"onCreateClose\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariantPlus from 'vue-material-design-icons/LinkVariantPlus.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnFormCreate from './CnFormCreate.vue'\nimport CnFormPicker from './CnFormPicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.\n *\n * Renders rows pulled from the OR Tier-2 forms link endpoint, styled to\n * mirror NC Forms (form icon, bold title, description, response-count\n * bubble, state chip, expiry date), with inline \"Link existing form\" +\n * \"Create new form\" actions on the header and per-row open/unlink in the\n * row actions menu.\n */\nexport default {\n\tname: 'CnFormsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClipboardText,\n\t\tClose,\n\t\tLinkVariantPlus,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnFormCreate,\n\t\tCnFormPicker,\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 `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\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 forms linked yet') },\n\t\t/** Pre-translated label for the \"Open in Forms\" CTA. */\n\t\topenFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Forms') },\n\t\t/** Pre-translated label for the per-row \"Open in Forms\" action. */\n\t\topenInFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Forms') },\n\t\t/** Pre-translated banner when Forms is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** Pre-translated label for the link-existing action. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing form') },\n\t\t/** Pre-translated label for the create-new action. */\n\t\tcreateNewLabel: { type: String, default: () => t('nextcloud-vue', 'Create new form') },\n\t\t/** Pre-translated label for the per-row unlink action. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink form') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t\tcreating: false,\n\t\t\tcreateError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Group provider rows into a per-form list with attached\n\t\t * submissions. The Tier-2 endpoint already emits per-form rows\n\t\t * with `submissionCount` rolled up; this method also handles\n\t\t * the legacy flat shape (rows with `type: 'submission'`) for\n\t\t * forms that pre-date the link table.\n\t\t *\n\t\t * @return {object[]} Per-form display rows.\n\t\t */\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t\tif (row.lastUpdated && (!target.lastSubmissionAt || row.lastUpdated > target.lastSubmissionAt)) {\n\t\t\t\t\t\t\ttarget.lastSubmissionAt = row.lastUpdated\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\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.fetchForms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchForms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchForms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/forms`)\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? row.formId ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.formHash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t\tlastSubmissionAt: null,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformDescription(form) {\n\t\t\treturn form.description || ''\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\tformUrl(form) {\n\t\t\tif (form.url && safeHref(form.url) !== '#') {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Resolve the form's expiry as a millisecond epoch, or null when\n\t\t * the form never expires. Accepts both second- and millisecond-\n\t\t * granularity numbers and ISO strings.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {?number}\n\t\t */\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\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\tisClosed(form) {\n\t\t\tif (form.status === 'closed' || form.status === 'archived') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-tab__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-tab__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-tab__status--open'\n\t\t},\n\n\t\t/**\n\t\t * Map the form's state to a CnStatusBadge colour variant —\n\t\t * open = success (green), draft = warning, closed = default.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tstatusVariant(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'success'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tresponsesAriaLabel(form) {\n\t\t\treturn t('nextcloud-vue', '{n} submissions', { n: this.submissionCount(form) })\n\t\t},\n\n\t\tasync fetchForms() {\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\tthis.rawRows = this.unwrapList(data)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Canonical wrapper-key cascade — mirrors CnContactsTab.unwrapList.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tasync onLinkSelected(payload) {\n\t\t\tif (!payload || !payload.formId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('[CnFormsTab] link failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showPicker = false\n\t\t\t\tawait this.fetchForms()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] link error', err)\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tif (!payload || !payload.title) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.creating = true\n\t\t\tthis.createError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.createError = data?.error || `${response.status} ${response.statusText}`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreate = false\n\t\t\t\tawait this.fetchForms()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] create error', err)\n\t\t\t\tthis.createError = String(err?.message || err)\n\t\t\t} finally {\n\t\t\t\tthis.creating = false\n\t\t\t}\n\t\t},\n\n\t\tonCreateClose() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.createError = ''\n\t\t},\n\n\t\tasync unlink(form) {\n\t\t\tif (!form?.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/${encodeURIComponent(form.id)}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('[CnFormsTab] unlink failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.rawRows = this.rawRows.filter((r) => String(r.id ?? r.formId ?? '') !== String(form.id))\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnFormsTab] unlink error', err)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-tab {\n\tpadding: 12px;\n}\n\n.cn-forms-tab__header {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: flex-end;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-forms-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-forms-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-forms-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-forms-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Forms brand accent on the row icon, mirroring the real Forms app. */\n.cn-forms-tab__row-icon {\n\tcolor: var(--cn-forms-accent, #0d7000);\n\tflex-shrink: 0;\n}\n\n.cn-forms-tab__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-tab__row--closed .cn-forms-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__description {\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__details {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n}\n\n.cn-forms-tab__expiry {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n/* Status chip colours layered on top of CnStatusBadge variants so the\n open/closed/draft state reads at a glance, like the real Forms app. */\n.cn-forms-tab__status--open {\n\t--cn-forms-status: var(--color-success, #46ba61);\n}\n\n.cn-forms-tab__status--closed {\n\t--cn-forms-status: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__status--draft {\n\t--cn-forms-status: var(--color-warning, #e9a40f);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"mappings":"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"}
|