@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":"CnDeckTab.vue3.js","sources":["../../../../../src/integrations/builtin/deck/CnDeckTab.vue?vue&type=template&id=3501a864&lang.js"],"sourcesContent":["<!--\n CnDeckTab — bespoke sidebar tab for the `deck` integration.\n\n Replaces the generic CnIntegrationTab for the `deck` leaf: renders a\n kanban-mini view of the cards linked to the parent OR object, grouped\n by stack (\"To Do\" / \"Doing\" / \"Done\" or whatever stacks the cards live\n in). Each card row shows title, optional due date, and optional label\n chips. Clicking a row deep-links to the card in NC Deck.\n\n Talks to the OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/deck`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\DeckProvider`\n (which delegates to `OCA\\OpenRegister\\Service\\DeckCardService`).\n\n Surface behaviour (per ADR-017 graceful degradation):\n - Empty state with \"Open Deck\" CTA when no linked cards.\n - Loading spinner during fetch.\n - 503 \"currently unavailable\" banner when Deck is down.\n - Generic error label when fetch throws.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link list\n which loses Deck's primary signal — *where the card sits in the\n workflow* (stack name). Grouping by stack reproduces the kanban affordance\n inline so case handlers can see \"card X is in Doing\" at a glance.\n\n See `openregister/openspec/changes/integration-deck/` for the spec\n delta, ADR-019 (registry mechanism) and ADR-022 (sidebar tab contract).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-deck-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-deck-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-deck-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing card') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new card') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-deck-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-sidebar-tab__empty cn-deck-tab__empty\">\n\t\t\t<ViewColumnOutline :size=\"32\" class=\"cn-deck-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=\"deckAppUrl\"\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<ViewColumnOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openDeckLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<div v-else class=\"cn-deck-tab__kanban\">\n\t\t\t<section\n\t\t\t\tv-for=\"stack in stackColumns\"\n\t\t\t\t:key=\"stack.key\"\n\t\t\t\tclass=\"cn-deck-tab__column\">\n\t\t\t\t<header class=\"cn-deck-tab__column-header\">\n\t\t\t\t\t<span class=\"cn-deck-tab__column-title\">{{ stack.label }}</span>\n\t\t\t\t\t<span class=\"cn-deck-tab__column-count\">{{ stack.cards.length }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-deck-tab__column-list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"card in stack.cards\"\n\t\t\t\t\t\t:key=\"cardKey(card)\"\n\t\t\t\t\t\tclass=\"cn-deck-tab__card\"\n\t\t\t\t\t\t:class=\"{ 'cn-deck-tab__card--overdue': isOverdue(card) }\">\n\t\t\t\t\t\t<!-- Colored label strip along the top edge, like a real Deck card. -->\n\t\t\t\t\t\t<div v-if=\"cardLabels(card).length > 0\" class=\"cn-deck-tab__card-labels\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"label in cardLabels(card)\"\n\t\t\t\t\t\t\t\t:key=\"label.id || label.title\"\n\t\t\t\t\t\t\t\tclass=\"cn-deck-tab__chip\"\n\t\t\t\t\t\t\t\t:style=\"chipStyle(label)\"\n\t\t\t\t\t\t\t\t:title=\"chipText(label)\">\n\t\t\t\t\t\t\t\t{{ chipText(label) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<!-- Board / stack context badge above the title. -->\n\t\t\t\t\t\t<span v-if=\"boardBadge(card)\" class=\"cn-deck-tab__card-board\">\n\t\t\t\t\t\t\t<ViewColumnOutline :size=\"12\" />\n\t\t\t\t\t\t\t{{ boardBadge(card) }}\n\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"cardUrl(card)\"\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-deck-tab__card-title\">{{ cardTitle(card) }}</a>\n\n\t\t\t\t\t\t<!-- Footer: due-date chip + assignee avatars, like Deck's card footer. -->\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tv-if=\"dueLabel(card) || cardAssignees(card).length > 0\"\n\t\t\t\t\t\t\tclass=\"cn-deck-tab__card-footer\">\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"dueLabel(card)\"\n\t\t\t\t\t\t\t\t:label=\"dueLabel(card)\"\n\t\t\t\t\t\t\t\t:variant=\"dueVariant(card)\"\n\t\t\t\t\t\t\t\tsize=\"small\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<ClockOutline :size=\"12\" class=\"cn-deck-tab__due-icon\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t</CnStatusBadge>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tv-if=\"cardAssignees(card).length > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-deck-tab__avatars\">\n\t\t\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t\t\tv-for=\"assignee in visibleAssignees(card)\"\n\t\t\t\t\t\t\t\t\t:key=\"assigneeKey(assignee)\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-deck-tab__avatar\"\n\t\t\t\t\t\t\t\t\t:size=\"24\"\n\t\t\t\t\t\t\t\t\t:displayName=\"assigneeName(assignee)\"\n\t\t\t\t\t\t\t\t\t:user=\"assigneeSeed(assignee)\"\n\t\t\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t\t\t\t:disableTooltip=\"false\"\n\t\t\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-if=\"assigneeOverflow(card) > 0\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-deck-tab__avatar-overflow\"\n\t\t\t\t\t\t\t\t\t:title=\"overflowTitle(card)\">+{{ assigneeOverflow(card) }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<CnDeckCardPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnDeckCardCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.vue'\nimport CnDeckCardCreate from '../../../components/CnDeckCardCreate/CnDeckCardCreate.vue'\nimport CnDeckCardPicker from '../../../components/CnDeckCardPicker/CnDeckCardPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/** Maximum assignee avatars shown before collapsing the rest into a +N overflow badge. */\nconst MAX_VISIBLE_ASSIGNEES = 3\n\n/**\n * CnDeckTab — kanban-mini sidebar tab for the `deck` integration.\n *\n * Groups linked cards by stack so case handlers see workflow position\n * inline; deep-links to NC Deck for editing. See file-level docblock\n * for surface behaviour.\n */\nexport default {\n\tname: 'CnDeckTab',\n\n\tcomponents: {\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClockOutline,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tViewColumnOutline,\n\t\tCnDeckCardPicker,\n\t\tCnDeckCardCreate,\n\t\tCnStatusBadge,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'deck'`). */\n\t\tintegrationId: { type: String, default: 'deck' },\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 cards linked yet') },\n\t\t/** Pre-translated label for the \"Open Deck\" CTA. */\n\t\topenDeckLabel: { type: String, default: () => t('nextcloud-vue', 'Open Deck') },\n\t\t/** Pre-translated banner when Deck is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Deck is currently unavailable.') },\n\t\t/** URL of the NC Deck app entry. */\n\t\tdeckAppUrl: { type: String, default: '/index.php/apps/deck' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tcards: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Cards grouped by stack — preserves stack order via first-seen ordering.\n\t\t *\n\t\t * Each column is `{ key, label, cards: [] }`. The `key` is the\n\t\t * numeric stack id (or 'unstacked' for cards without one); the\n\t\t * `label` is the stack title when the provider returned one,\n\t\t * otherwise a placeholder.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tstackColumns() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const card of this.cards) {\n\t\t\t\tconst stackId = card.stackId ?? card.stack?.id ?? 'unstacked'\n\t\t\t\tconst stackLabel = card.stackTitle\n\t\t\t\t\t?? card.stack?.title\n\t\t\t\t\t?? (stackId === 'unstacked'\n\t\t\t\t\t\t? t('nextcloud-vue', 'No stack')\n\t\t\t\t\t\t: t('nextcloud-vue', 'Stack {n}', { n: stackId }))\n\t\t\t\tconst key = String(stackId)\n\t\t\t\tif (!groups.has(key)) {\n\t\t\t\t\tgroups.set(key, { key, label: stackLabel, cards: [] })\n\t\t\t\t}\n\t\t\t\tgroups.get(key).cards.push(card)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchCards()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchCards()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchCards()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\t/**\n\t\t * Base for the Tier-2 deck endpoints (link/new). The provider's\n\t\t * generic `/integrations/deck` surface accepts the same POST\n\t\t * shape, but the bespoke Tier-2 routes are nicer for the\n\t\t * picker's `/deck/new` create-flow.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tdeckEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/deck`\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.deckEndpoint()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchCards()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This card is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link card.')\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('[CnDeckTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link card.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.deckEndpoint()}/new`), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchCards()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create card.')\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('[CnDeckTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create card.')\n\t\t\t}\n\t\t},\n\n\t\tcardKey(card) {\n\t\t\treturn card.cardId ?? card.id ?? ''\n\t\t},\n\n\t\tcardTitle(card) {\n\t\t\treturn card.cardTitle ?? card.title ?? this.cardKey(card)\n\t\t},\n\n\t\tcardUrl(card) {\n\t\t\tif (card.url) {\n\t\t\t\treturn card.url\n\t\t\t}\n\t\t\tconst boardId = card.boardId ?? card.board?.id ?? ''\n\t\t\tconst cardId = card.cardId ?? card.id ?? ''\n\t\t\tif (boardId && cardId) {\n\t\t\t\treturn `/index.php/apps/deck/board/${boardId}/card/${cardId}`\n\t\t\t}\n\t\t\treturn this.deckAppUrl\n\t\t},\n\n\t\tcardLabels(card) {\n\t\t\tconst labels = card.labels ?? card.tags ?? []\n\t\t\treturn Array.isArray(labels) ? labels : []\n\t\t},\n\n\t\tchipText(label) {\n\t\t\tif (typeof label === 'string') {\n\t\t\t\treturn label\n\t\t\t}\n\t\t\treturn label.title ?? label.name ?? ''\n\t\t},\n\n\t\tchipStyle(label) {\n\t\t\tif (typeof label === 'object' && label.color) {\n\t\t\t\tconst colour = String(label.color)\n\t\t\t\tconst fill = colour.startsWith('#') ? colour : `#${colour}`\n\t\t\t\treturn { background: fill, color: this.readableTextColour(fill) }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Pick black or white text for a hex background so Deck's coloured\n\t\t * label chips stay legible (per-channel luminance, ITU-R BT.601).\n\t\t *\n\t\t * @param {string} hex background colour, e.g. `#31cc7c`\n\t\t * @return {string} `#000000` or `#ffffff`\n\t\t */\n\t\treadableTextColour(hex) {\n\t\t\tconst clean = hex.replace('#', '')\n\t\t\tconst full = clean.length === 3\n\t\t\t\t? clean.split('').map((c) => c + c).join('')\n\t\t\t\t: clean\n\t\t\tif (full.length < 6) {\n\t\t\t\treturn '#ffffff'\n\t\t\t}\n\t\t\tconst r = parseInt(full.slice(0, 2), 16)\n\t\t\tconst g = parseInt(full.slice(2, 4), 16)\n\t\t\tconst b = parseInt(full.slice(4, 6), 16)\n\t\t\tconst luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255\n\t\t\treturn luminance > 0.6 ? '#000000' : '#ffffff'\n\t\t},\n\n\t\tisOverdue(card) {\n\t\t\tconst due = card.duedate ?? card.dueDate ?? null\n\t\t\tif (!due) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst date = new Date(due)\n\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn date.getTime() < Date.now()\n\t\t},\n\n\t\tdueLabel(card) {\n\t\t\tconst due = card.duedate ?? card.dueDate ?? null\n\t\t\tif (!due) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst date = new Date(due)\n\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn date.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t},\n\n\t\t/**\n\t\t * Status-badge variant for the due-date chip: red when overdue,\n\t\t * neutral otherwise — mirrors Deck's own overdue-date highlight.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {string} CnStatusBadge variant\n\t\t */\n\t\tdueVariant(card) {\n\t\t\treturn this.isOverdue(card) ? 'error' : 'default'\n\t\t},\n\n\t\t/**\n\t\t * Short board/stack context label rendered as a badge above the\n\t\t * title — the \"where does this card live\" signal a real Deck card\n\t\t * carries via its board colour bar + stack column.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {string} board (or board · stack) label, or '' when unknown\n\t\t */\n\t\tboardBadge(card) {\n\t\t\tconst board = card.boardTitle ?? card.board?.title ?? ''\n\t\t\tconst stack = card.stackTitle ?? card.stack?.title ?? ''\n\t\t\tif (board && stack) {\n\t\t\t\treturn `${board} · ${stack}`\n\t\t\t}\n\t\t\treturn board || stack || ''\n\t\t},\n\n\t\t/**\n\t\t * Normalised assignee list for a card. Accepts Deck's\n\t\t * `assignedUsers` (objects with a nested `participant`) as well as\n\t\t * a plain `assignees` array of strings/objects.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {Array} assignee entries\n\t\t */\n\t\tcardAssignees(card) {\n\t\t\tconst raw = card.assignedUsers ?? card.assignees ?? card.users ?? []\n\t\t\treturn Array.isArray(raw) ? raw : []\n\t\t},\n\n\t\t/**\n\t\t * The first {@link MAX_VISIBLE_ASSIGNEES} assignees to render as\n\t\t * avatars; the remainder collapse into a +N overflow badge.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {Array} visible assignee entries\n\t\t */\n\t\tvisibleAssignees(card) {\n\t\t\treturn this.cardAssignees(card).slice(0, MAX_VISIBLE_ASSIGNEES)\n\t\t},\n\n\t\t/**\n\t\t * Count of assignees hidden behind the +N overflow badge.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {number} overflow count (0 when all fit)\n\t\t */\n\t\tassigneeOverflow(card) {\n\t\t\tconst extra = this.cardAssignees(card).length - MAX_VISIBLE_ASSIGNEES\n\t\t\treturn extra > 0 ? extra : 0\n\t\t},\n\n\t\t/**\n\t\t * Tooltip listing the assignees collapsed into the overflow badge.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {string} comma-separated overflow assignee names\n\t\t */\n\t\toverflowTitle(card) {\n\t\t\treturn this.cardAssignees(card)\n\t\t\t\t.slice(MAX_VISIBLE_ASSIGNEES)\n\t\t\t\t.map((a) => this.assigneeName(a))\n\t\t\t\t.join(', ')\n\t\t},\n\n\t\t/**\n\t\t * Unwrap an assignee entry to its participant payload — Deck nests\n\t\t * the user under `participant`; flat shapes pass through.\n\t\t *\n\t\t * @param {object|string} assignee raw assignee entry\n\t\t * @return {object} normalised participant object\n\t\t */\n\t\tassigneeParticipant(assignee) {\n\t\t\tif (typeof assignee === 'string') {\n\t\t\t\treturn { uid: assignee, displayname: assignee }\n\t\t\t}\n\t\t\treturn assignee.participant ?? assignee\n\t\t},\n\n\t\tassigneeName(assignee) {\n\t\t\tconst p = this.assigneeParticipant(assignee)\n\t\t\treturn p.displayname ?? p.displayName ?? p.uid ?? p.id ?? t('nextcloud-vue', 'Unknown')\n\t\t},\n\n\t\tassigneeSeed(assignee) {\n\t\t\tconst p = this.assigneeParticipant(assignee)\n\t\t\treturn String(p.uid ?? p.id ?? p.displayname ?? p.displayName ?? '')\n\t\t},\n\n\t\tassigneeKey(assignee) {\n\t\t\tconst p = this.assigneeParticipant(assignee)\n\t\t\treturn String(p.uid ?? p.id ?? p.displayname ?? p.displayName ?? Math.random())\n\t\t},\n\n\t\tasync fetchCards() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.cards = rows\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.cards = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.cards = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load cards.')\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('[CnDeckTab] failed to fetch cards', err)\n\t\t\t\tthis.cards = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load cards.')\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-deck-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-deck-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-deck-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-deck-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-deck-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-tab__kanban {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-deck-tab__column {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tpadding: 8px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-deck-tab__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tpadding: 0 2px;\n}\n\n.cn-deck-tab__column-title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-tab__column-count {\n\tfont-size: 0.75em;\n\tcolor: var(--color-text-maxcontrast);\n\tbackground: var(--color-background-dark);\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n}\n\n.cn-deck-tab__column-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-deck-tab__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 5px;\n\tpadding: 8px 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tbox-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);\n\ttransition: box-shadow 0.1s ease-in-out, border-color 0.1s ease-in-out;\n}\n\n.cn-deck-tab__card:hover {\n\tbox-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n\tborder-color: var(--color-border-dark);\n}\n\n.cn-deck-tab__card--overdue {\n\tborder-color: var(--color-error);\n}\n\n.cn-deck-tab__card-board {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n\talign-self: flex-start;\n\tmax-width: 100%;\n\tpadding: 1px 7px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n\tborder-radius: 10px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-tab__card-title {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\tfont-weight: 600;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-deck-tab__card-title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-tab__card-labels {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n\n/* Deck label chips: solid card-label colour with a readable text shade. */\n.cn-deck-tab__chip {\n\tdisplay: inline-block;\n\tmax-width: 100%;\n\tpadding: 1px 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n\tline-height: 1.5;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-tab__card-footer {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmargin-top: 2px;\n}\n\n.cn-deck-tab__due-icon {\n\tdisplay: inline-flex;\n\tmargin-right: 2px;\n\tvertical-align: text-bottom;\n}\n\n.cn-deck-tab__avatars {\n\tdisplay: flex;\n\talign-items: center;\n\tmargin-left: auto;\n}\n\n.cn-deck-tab__avatar {\n\tborder: 2px solid var(--color-main-background);\n\tborder-radius: 50%;\n}\n\n.cn-deck-tab__avatar:not(:first-child) {\n\tmargin-left: -8px;\n}\n\n.cn-deck-tab__avatar-overflow {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 24px;\n\theight: 24px;\n\tmargin-left: -8px;\n\tborder: 2px solid var(--color-main-background);\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.65em;\n\tfont-weight: 600;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_createBlock","_toDisplayString","_renderList","_normalizeClass","_Fragment","_normalizeStyle"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnDeckTab.vue3.js","sources":["../../../../../src/integrations/builtin/deck/CnDeckTab.vue?vue&type=template&id=3501a864&lang.js"],"sourcesContent":["<!--\n CnDeckTab — bespoke sidebar tab for the `deck` integration.\n\n Replaces the generic CnIntegrationTab for the `deck` leaf: renders a\n kanban-mini view of the cards linked to the parent OR object, grouped\n by stack (\"To Do\" / \"Doing\" / \"Done\" or whatever stacks the cards live\n in). Each card row shows title, optional due date, and optional label\n chips. Clicking a row deep-links to the card in NC Deck.\n\n Talks to the OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/deck`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\DeckProvider`\n (which delegates to `OCA\\OpenRegister\\Service\\DeckCardService`).\n\n Surface behaviour (per ADR-017 graceful degradation):\n - Empty state with \"Open Deck\" CTA when no linked cards.\n - Loading spinner during fetch.\n - 503 \"currently unavailable\" banner when Deck is down.\n - Generic error label when fetch throws.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link list\n which loses Deck's primary signal — *where the card sits in the\n workflow* (stack name). Grouping by stack reproduces the kanban affordance\n inline so case handlers can see \"card X is in Doing\" at a glance.\n\n See `openregister/openspec/changes/integration-deck/` for the spec\n delta, ADR-019 (registry mechanism) and ADR-022 (sidebar tab contract).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-deck-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-deck-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-deck-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing card') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new card') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-deck-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-sidebar-tab__empty cn-deck-tab__empty\">\n\t\t\t<ViewColumnOutline :size=\"32\" class=\"cn-deck-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=\"deckAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ViewColumnOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openDeckLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<div v-else class=\"cn-deck-tab__kanban\">\n\t\t\t<section\n\t\t\t\tv-for=\"stack in stackColumns\"\n\t\t\t\t:key=\"stack.key\"\n\t\t\t\tclass=\"cn-deck-tab__column\">\n\t\t\t\t<header class=\"cn-deck-tab__column-header\">\n\t\t\t\t\t<span class=\"cn-deck-tab__column-title\">{{ stack.label }}</span>\n\t\t\t\t\t<span class=\"cn-deck-tab__column-count\">{{ stack.cards.length }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-deck-tab__column-list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"card in stack.cards\"\n\t\t\t\t\t\t:key=\"cardKey(card)\"\n\t\t\t\t\t\tclass=\"cn-deck-tab__card\"\n\t\t\t\t\t\t:class=\"{ 'cn-deck-tab__card--overdue': isOverdue(card) }\">\n\t\t\t\t\t\t<!-- Colored label strip along the top edge, like a real Deck card. -->\n\t\t\t\t\t\t<div v-if=\"cardLabels(card).length > 0\" class=\"cn-deck-tab__card-labels\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"label in cardLabels(card)\"\n\t\t\t\t\t\t\t\t:key=\"label.id || label.title\"\n\t\t\t\t\t\t\t\tclass=\"cn-deck-tab__chip\"\n\t\t\t\t\t\t\t\t:style=\"chipStyle(label)\"\n\t\t\t\t\t\t\t\t:title=\"chipText(label)\">\n\t\t\t\t\t\t\t\t{{ chipText(label) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<!-- Board / stack context badge above the title. -->\n\t\t\t\t\t\t<span v-if=\"boardBadge(card)\" class=\"cn-deck-tab__card-board\">\n\t\t\t\t\t\t\t<ViewColumnOutline :size=\"12\" />\n\t\t\t\t\t\t\t{{ boardBadge(card) }}\n\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"cardUrl(card)\"\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-deck-tab__card-title\">{{ cardTitle(card) }}</a>\n\n\t\t\t\t\t\t<!-- Footer: due-date chip + assignee avatars, like Deck's card footer. -->\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tv-if=\"dueLabel(card) || cardAssignees(card).length > 0\"\n\t\t\t\t\t\t\tclass=\"cn-deck-tab__card-footer\">\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"dueLabel(card)\"\n\t\t\t\t\t\t\t\t:label=\"dueLabel(card)\"\n\t\t\t\t\t\t\t\t:variant=\"dueVariant(card)\"\n\t\t\t\t\t\t\t\tsize=\"small\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<ClockOutline :size=\"12\" class=\"cn-deck-tab__due-icon\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t</CnStatusBadge>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tv-if=\"cardAssignees(card).length > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-deck-tab__avatars\">\n\t\t\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t\t\tv-for=\"assignee in visibleAssignees(card)\"\n\t\t\t\t\t\t\t\t\t:key=\"assigneeKey(assignee)\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-deck-tab__avatar\"\n\t\t\t\t\t\t\t\t\t:size=\"24\"\n\t\t\t\t\t\t\t\t\t:displayName=\"assigneeName(assignee)\"\n\t\t\t\t\t\t\t\t\t:user=\"assigneeSeed(assignee)\"\n\t\t\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t\t\t\t:disableTooltip=\"false\"\n\t\t\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-if=\"assigneeOverflow(card) > 0\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-deck-tab__avatar-overflow\"\n\t\t\t\t\t\t\t\t\t:title=\"overflowTitle(card)\">+{{ assigneeOverflow(card) }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<CnDeckCardPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnDeckCardCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.vue'\nimport CnDeckCardCreate from '../../../components/CnDeckCardCreate/CnDeckCardCreate.vue'\nimport CnDeckCardPicker from '../../../components/CnDeckCardPicker/CnDeckCardPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/** Maximum assignee avatars shown before collapsing the rest into a +N overflow badge. */\nconst MAX_VISIBLE_ASSIGNEES = 3\n\n/**\n * CnDeckTab — kanban-mini sidebar tab for the `deck` integration.\n *\n * Groups linked cards by stack so case handlers see workflow position\n * inline; deep-links to NC Deck for editing. See file-level docblock\n * for surface behaviour.\n */\nexport default {\n\tname: 'CnDeckTab',\n\n\tcomponents: {\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClockOutline,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tViewColumnOutline,\n\t\tCnDeckCardPicker,\n\t\tCnDeckCardCreate,\n\t\tCnStatusBadge,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'deck'`). */\n\t\tintegrationId: { type: String, default: 'deck' },\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 cards linked yet') },\n\t\t/** Pre-translated label for the \"Open Deck\" CTA. */\n\t\topenDeckLabel: { type: String, default: () => t('nextcloud-vue', 'Open Deck') },\n\t\t/** Pre-translated banner when Deck is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Deck is currently unavailable.') },\n\t\t/** URL of the NC Deck app entry. */\n\t\tdeckAppUrl: { type: String, default: '/index.php/apps/deck' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tcards: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Cards grouped by stack — preserves stack order via first-seen ordering.\n\t\t *\n\t\t * Each column is `{ key, label, cards: [] }`. The `key` is the\n\t\t * numeric stack id (or 'unstacked' for cards without one); the\n\t\t * `label` is the stack title when the provider returned one,\n\t\t * otherwise a placeholder.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tstackColumns() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const card of this.cards) {\n\t\t\t\tconst stackId = card.stackId ?? card.stack?.id ?? 'unstacked'\n\t\t\t\tconst stackLabel = card.stackTitle\n\t\t\t\t\t?? card.stack?.title\n\t\t\t\t\t?? (stackId === 'unstacked'\n\t\t\t\t\t\t? t('nextcloud-vue', 'No stack')\n\t\t\t\t\t\t: t('nextcloud-vue', 'Stack {n}', { n: stackId }))\n\t\t\t\tconst key = String(stackId)\n\t\t\t\tif (!groups.has(key)) {\n\t\t\t\t\tgroups.set(key, { key, label: stackLabel, cards: [] })\n\t\t\t\t}\n\t\t\t\tgroups.get(key).cards.push(card)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchCards()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchCards()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchCards()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\t/**\n\t\t * Base for the Tier-2 deck endpoints (link/new). The provider's\n\t\t * generic `/integrations/deck` surface accepts the same POST\n\t\t * shape, but the bespoke Tier-2 routes are nicer for the\n\t\t * picker's `/deck/new` create-flow.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tdeckEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/deck`\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.deckEndpoint()), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchCards()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This card is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link card.')\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('[CnDeckTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link card.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.deckEndpoint()}/new`), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchCards()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create card.')\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('[CnDeckTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create card.')\n\t\t\t}\n\t\t},\n\n\t\tcardKey(card) {\n\t\t\treturn card.cardId ?? card.id ?? ''\n\t\t},\n\n\t\tcardTitle(card) {\n\t\t\treturn card.cardTitle ?? card.title ?? this.cardKey(card)\n\t\t},\n\n\t\tcardUrl(card) {\n\t\t\tif (card.url && safeHref(card.url) !== '#') {\n\t\t\t\treturn card.url\n\t\t\t}\n\t\t\tconst boardId = card.boardId ?? card.board?.id ?? ''\n\t\t\tconst cardId = card.cardId ?? card.id ?? ''\n\t\t\tif (boardId && cardId) {\n\t\t\t\treturn `/index.php/apps/deck/board/${boardId}/card/${cardId}`\n\t\t\t}\n\t\t\treturn this.deckAppUrl\n\t\t},\n\n\t\tcardLabels(card) {\n\t\t\tconst labels = card.labels ?? card.tags ?? []\n\t\t\treturn Array.isArray(labels) ? labels : []\n\t\t},\n\n\t\tchipText(label) {\n\t\t\tif (typeof label === 'string') {\n\t\t\t\treturn label\n\t\t\t}\n\t\t\treturn label.title ?? label.name ?? ''\n\t\t},\n\n\t\tchipStyle(label) {\n\t\t\tif (typeof label === 'object' && label.color) {\n\t\t\t\tconst colour = String(label.color)\n\t\t\t\tconst fill = colour.startsWith('#') ? colour : `#${colour}`\n\t\t\t\treturn { background: fill, color: this.readableTextColour(fill) }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Pick black or white text for a hex background so Deck's coloured\n\t\t * label chips stay legible (per-channel luminance, ITU-R BT.601).\n\t\t *\n\t\t * @param {string} hex background colour, e.g. `#31cc7c`\n\t\t * @return {string} `#000000` or `#ffffff`\n\t\t */\n\t\treadableTextColour(hex) {\n\t\t\tconst clean = hex.replace('#', '')\n\t\t\tconst full = clean.length === 3\n\t\t\t\t? clean.split('').map((c) => c + c).join('')\n\t\t\t\t: clean\n\t\t\tif (full.length < 6) {\n\t\t\t\treturn '#ffffff'\n\t\t\t}\n\t\t\tconst r = parseInt(full.slice(0, 2), 16)\n\t\t\tconst g = parseInt(full.slice(2, 4), 16)\n\t\t\tconst b = parseInt(full.slice(4, 6), 16)\n\t\t\tconst luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255\n\t\t\treturn luminance > 0.6 ? '#000000' : '#ffffff'\n\t\t},\n\n\t\tisOverdue(card) {\n\t\t\tconst due = card.duedate ?? card.dueDate ?? null\n\t\t\tif (!due) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst date = new Date(due)\n\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn date.getTime() < Date.now()\n\t\t},\n\n\t\tdueLabel(card) {\n\t\t\tconst due = card.duedate ?? card.dueDate ?? null\n\t\t\tif (!due) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst date = new Date(due)\n\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn date.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t},\n\n\t\t/**\n\t\t * Status-badge variant for the due-date chip: red when overdue,\n\t\t * neutral otherwise — mirrors Deck's own overdue-date highlight.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {string} CnStatusBadge variant\n\t\t */\n\t\tdueVariant(card) {\n\t\t\treturn this.isOverdue(card) ? 'error' : 'default'\n\t\t},\n\n\t\t/**\n\t\t * Short board/stack context label rendered as a badge above the\n\t\t * title — the \"where does this card live\" signal a real Deck card\n\t\t * carries via its board colour bar + stack column.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {string} board (or board · stack) label, or '' when unknown\n\t\t */\n\t\tboardBadge(card) {\n\t\t\tconst board = card.boardTitle ?? card.board?.title ?? ''\n\t\t\tconst stack = card.stackTitle ?? card.stack?.title ?? ''\n\t\t\tif (board && stack) {\n\t\t\t\treturn `${board} · ${stack}`\n\t\t\t}\n\t\t\treturn board || stack || ''\n\t\t},\n\n\t\t/**\n\t\t * Normalised assignee list for a card. Accepts Deck's\n\t\t * `assignedUsers` (objects with a nested `participant`) as well as\n\t\t * a plain `assignees` array of strings/objects.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {Array} assignee entries\n\t\t */\n\t\tcardAssignees(card) {\n\t\t\tconst raw = card.assignedUsers ?? card.assignees ?? card.users ?? []\n\t\t\treturn Array.isArray(raw) ? raw : []\n\t\t},\n\n\t\t/**\n\t\t * The first {@link MAX_VISIBLE_ASSIGNEES} assignees to render as\n\t\t * avatars; the remainder collapse into a +N overflow badge.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {Array} visible assignee entries\n\t\t */\n\t\tvisibleAssignees(card) {\n\t\t\treturn this.cardAssignees(card).slice(0, MAX_VISIBLE_ASSIGNEES)\n\t\t},\n\n\t\t/**\n\t\t * Count of assignees hidden behind the +N overflow badge.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {number} overflow count (0 when all fit)\n\t\t */\n\t\tassigneeOverflow(card) {\n\t\t\tconst extra = this.cardAssignees(card).length - MAX_VISIBLE_ASSIGNEES\n\t\t\treturn extra > 0 ? extra : 0\n\t\t},\n\n\t\t/**\n\t\t * Tooltip listing the assignees collapsed into the overflow badge.\n\t\t *\n\t\t * @param {object} card the card row\n\t\t * @return {string} comma-separated overflow assignee names\n\t\t */\n\t\toverflowTitle(card) {\n\t\t\treturn this.cardAssignees(card)\n\t\t\t\t.slice(MAX_VISIBLE_ASSIGNEES)\n\t\t\t\t.map((a) => this.assigneeName(a))\n\t\t\t\t.join(', ')\n\t\t},\n\n\t\t/**\n\t\t * Unwrap an assignee entry to its participant payload — Deck nests\n\t\t * the user under `participant`; flat shapes pass through.\n\t\t *\n\t\t * @param {object|string} assignee raw assignee entry\n\t\t * @return {object} normalised participant object\n\t\t */\n\t\tassigneeParticipant(assignee) {\n\t\t\tif (typeof assignee === 'string') {\n\t\t\t\treturn { uid: assignee, displayname: assignee }\n\t\t\t}\n\t\t\treturn assignee.participant ?? assignee\n\t\t},\n\n\t\tassigneeName(assignee) {\n\t\t\tconst p = this.assigneeParticipant(assignee)\n\t\t\treturn p.displayname ?? p.displayName ?? p.uid ?? p.id ?? t('nextcloud-vue', 'Unknown')\n\t\t},\n\n\t\tassigneeSeed(assignee) {\n\t\t\tconst p = this.assigneeParticipant(assignee)\n\t\t\treturn String(p.uid ?? p.id ?? p.displayname ?? p.displayName ?? '')\n\t\t},\n\n\t\tassigneeKey(assignee) {\n\t\t\tconst p = this.assigneeParticipant(assignee)\n\t\t\treturn String(p.uid ?? p.id ?? p.displayname ?? p.displayName ?? Math.random())\n\t\t},\n\n\t\tasync fetchCards() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.cards = rows\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.cards = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.cards = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load cards.')\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('[CnDeckTab] failed to fetch cards', err)\n\t\t\t\tthis.cards = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load cards.')\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-deck-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-deck-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-deck-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-deck-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-deck-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-tab__kanban {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-deck-tab__column {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tpadding: 8px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-deck-tab__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tpadding: 0 2px;\n}\n\n.cn-deck-tab__column-title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-tab__column-count {\n\tfont-size: 0.75em;\n\tcolor: var(--color-text-maxcontrast);\n\tbackground: var(--color-background-dark);\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n}\n\n.cn-deck-tab__column-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-deck-tab__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 5px;\n\tpadding: 8px 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tbox-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);\n\ttransition: box-shadow 0.1s ease-in-out, border-color 0.1s ease-in-out;\n}\n\n.cn-deck-tab__card:hover {\n\tbox-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n\tborder-color: var(--color-border-dark);\n}\n\n.cn-deck-tab__card--overdue {\n\tborder-color: var(--color-error);\n}\n\n.cn-deck-tab__card-board {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n\talign-self: flex-start;\n\tmax-width: 100%;\n\tpadding: 1px 7px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n\tborder-radius: 10px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-tab__card-title {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\tfont-weight: 600;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-deck-tab__card-title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-tab__card-labels {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n\n/* Deck label chips: solid card-label colour with a readable text shade. */\n.cn-deck-tab__chip {\n\tdisplay: inline-block;\n\tmax-width: 100%;\n\tpadding: 1px 8px;\n\tfont-size: 0.7em;\n\tfont-weight: 500;\n\tline-height: 1.5;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-tab__card-footer {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmargin-top: 2px;\n}\n\n.cn-deck-tab__due-icon {\n\tdisplay: inline-flex;\n\tmargin-right: 2px;\n\tvertical-align: text-bottom;\n}\n\n.cn-deck-tab__avatars {\n\tdisplay: flex;\n\talign-items: center;\n\tmargin-left: auto;\n}\n\n.cn-deck-tab__avatar {\n\tborder: 2px solid var(--color-main-background);\n\tborder-radius: 50%;\n}\n\n.cn-deck-tab__avatar:not(:first-child) {\n\tmargin-left: -8px;\n}\n\n.cn-deck-tab__avatar-overflow {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 24px;\n\theight: 24px;\n\tmargin-left: -8px;\n\tborder: 2px solid var(--color-main-background);\n\tborder-radius: 50%;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.65em;\n\tfont-weight: 600;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_toDisplayString","_renderList","_normalizeClass","_Fragment","_normalizeStyle","_createBlock"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnEmailCard.vue2.js","sources":["../../../../../src/integrations/builtin/email/CnEmailCard.vue"],"sourcesContent":["<!--\n CnEmailCard — compact email widget for the integration registry.\n\n Surface-aware shell around the `email` integration (NC Mail link-only\n per AD-2). Fetches linked email messages for an OR object via the\n registry endpoint and renders them in a `CnDetailCard`. Per umbrella\n change `pluggable-integration-registry` AD-19 (surface fallback) one\n component handles all four surfaces — `surface` is forwarded so the\n component can branch internally.\n\n Storage strategy: link-table (`openregister_email_links`). Backend\n pages results; the card itself only shows the first `maxDisplay`\n entries and emits `show-all` for the full sidebar view.\n-->\n<template>\n\t<CnDetailCard :title=\"resolvedTitle\" :icon=\"Email\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"errored === true\" class=\"cn-email-card__empty\">\n\t\t\t{{ errorLabel }}\n\t\t</div>\n\t\t<div v-else-if=\"messages.length === 0\" class=\"cn-email-card__empty\">\n\t\t\t{{ noMessagesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-email-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in displayedRows\"\n\t\t\t\t:key=\"row.id\"\n\t\t\t\tclass=\"cn-email-card__row\"\n\t\t\t\t:class=\"{ 'cn-email-card__row--unread': row.unread }\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"row.href ? 'a' : 'div'\"\n\t\t\t\t\tclass=\"cn-email-card__open\"\n\t\t\t\t\t:title=\"row.href ? openInMailLabel : undefined\"\n\t\t\t\t\tv-bind=\"row.href ? { href: row.href, target: '_blank', rel: 'noopener noreferrer' } : {}\">\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\tclass=\"cn-email-card__avatar\"\n\t\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t\t:displayName=\"row.sender\"\n\t\t\t\t\t\t:user=\"row.avatarUser\"\n\t\t\t\t\t\t:isNoUser=\"true\" />\n\t\t\t\t\t<div class=\"cn-email-card__text\">\n\t\t\t\t\t\t<div class=\"cn-email-card__row-head\">\n\t\t\t\t\t\t\t<strong class=\"cn-email-card__subject\">{{ row.subject }}</strong>\n\t\t\t\t\t\t\t<span class=\"cn-email-card__when\">{{ row.when }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-email-card__row-body\">\n\t\t\t\t\t\t\t<span class=\"cn-email-card__sender\">{{ row.sender }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"row.snippet\" class=\"cn-email-card__snippet\">\n\t\t\t\t\t\t\t{{ row.snippet }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</component>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"messages.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-email-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ total || messages.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport Email from 'vue-material-design-icons/Email.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnEmailCard — compact widget for the `email` integration leaf.\n *\n * Backed by OpenRegister's `EmailService` via the registry's\n * `/integrations/email` endpoint. Renders the latest linked Nextcloud\n * Mail messages with subject + sender + date. Clicking a row deep-links\n * into the NC Mail app.\n *\n * Basic usage\n * ```vue\n * <CnEmailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n *\n * @event show-all Emitted when the user clicks the \"Show all\" overflow\n * control — parents typically open the Emails sidebar tab.\n */\nexport default {\n\tname: 'CnEmailCard',\n\n\tcomponents: { CnDetailCard, NcAvatar, NcLoadingIcon },\n\n\tprops: {\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/**\n\t\t * Rendering surface — passed for AD-19 surface fallback consumers.\n\t\t * `single-entity` shows only the most-recent message; the other\n\t\t * three surfaces render a list up to `maxDisplay`.\n\t\t */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoMessagesLabel: { type: String, default: () => t('nextcloud-vue', 'No linked emails yet') },\n\t\t/** Pre-translated error label. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load emails') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated row tooltip / aria label. */\n\t\topenInMailLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Mail') },\n\t\t/** Pre-translated fallback subject label. */\n\t\tnoSubjectLabel: { type: String, default: () => t('nextcloud-vue', '(no subject)') },\n\t\t/** Pre-translated fallback sender label. */\n\t\tunknownSenderLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown sender') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tEmail: markRaw(Email),\n\t\t\tmessages: [],\n\t\t\ttotal: 0,\n\t\t\tloading: false,\n\t\t\terrored: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Emails')\n\t\t},\n\n\t\teffectiveMax() {\n\t\t\treturn this.surface === 'single-entity' ? 1 : this.maxDisplay\n\t\t},\n\n\t\tdisplayedMessages() {\n\t\t\treturn this.messages.slice(0, this.effectiveMax)\n\t\t},\n\n\t\t/**\n\t\t * Template-safe view rows; keeps `?.`/`??` out of the buble template.\n\t\t *\n\t\t * @return {object[]} One descriptor per displayed message.\n\t\t */\n\t\tdisplayedRows() {\n\t\t\treturn this.displayedMessages.map((message) => ({\n\t\t\t\tid: message.id,\n\t\t\t\tmessage,\n\t\t\t\tsubject: this.formatSubject(message),\n\t\t\t\tsender: this.formatSender(message),\n\t\t\t\tsnippet: this.formatSnippet(message),\n\t\t\t\twhen: this.formatWhen(message),\n\t\t\t\tunread: this.isUnread(message),\n\t\t\t\tavatarUser: this.senderEmail(message),\n\t\t\t\thref: this.threadUrl(message),\n\t\t\t}))\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchMessages()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tasync fetchMessages() {\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.errored = false\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ _limit: String(this.effectiveMax) })\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/email?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.messages = Array.isArray(list) === true ? list : []\n\t\t\t\t\tthis.total = typeof data.total === 'number' ? data.total : this.messages.length\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\n\t\t\t\t\tthis.messages = []\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('[CnEmailCard] failed to fetch linked emails', err)\n\t\t\t\tthis.errored = true\n\t\t\t\tthis.messages = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatSubject(message) {\n\t\t\tconst raw = message.subject\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.noSubjectLabel\n\t\t},\n\n\t\tformatSender(message) {\n\t\t\tconst raw = message.senderName || message.fromName || message.sender || message.from\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.unknownSenderLabel\n\t\t},\n\n\t\tsenderEmail(message) {\n\t\t\tconst candidate = message.senderEmail || message.fromEmail || message.sender || message.from\n\t\t\tif (typeof candidate === 'string' && candidate.indexOf('@') !== -1) {\n\t\t\t\treturn candidate.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tformatSnippet(message) {\n\t\t\tconst raw = message.preview || message.snippet || message.summary || message.bodyPreview\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tisUnread(message) {\n\t\t\tif (typeof message.unread === 'boolean') {\n\t\t\t\treturn message.unread\n\t\t\t}\n\t\t\tif (typeof message.isRead === 'boolean') {\n\t\t\t\treturn message.isRead === false\n\t\t\t}\n\t\t\tif (typeof message.seen === 'boolean') {\n\t\t\t\treturn message.seen === false\n\t\t\t}\n\t\t\tif (Array.isArray(message.flags)) {\n\t\t\t\treturn message.flags.indexOf('seen') === -1 && message.flags.indexOf('\\\\Seen') === -1\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tformatWhen(message) {\n\t\t\tconst raw = message.mailDate || message.date || message.linkedAt\n\t\t\tif (raw === undefined || raw === null || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n\t\t},\n\n\t\t// Thread deep-link into NC Mail; undefined when the row lacks ids.\n\t\tthreadUrl(message) {\n\t\t\tconst accountId = message.mailAccountId\n\t\t\tconst messageId = message.mailMessageId\n\t\t\tif (accountId === undefined || accountId === null || messageId === undefined || messageId === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl(`/apps/mail/box/${accountId}/thread/${messageId}`)\n\t\t\t\t: `/index.php/apps/mail/box/${accountId}/thread/${messageId}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-email-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-email-card__row {\n\tpadding: 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-email-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-email-card__open {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\twidth: 100%;\n\tbackground: none;\n\tborder: none;\n\tpadding: 6px 0;\n\tcursor: pointer;\n\ttext-align: left;\n\tcolor: inherit;\n\tfont: inherit;\n\ttext-decoration: none;\n\tbox-sizing: border-box;\n}\n\n.cn-email-card__open:hover .cn-email-card__subject {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-email-card__avatar {\n\tflex: 0 0 auto;\n}\n\n.cn-email-card__text {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-email-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-email-card__subject {\n\tcolor: var(--color-main-text);\n\tfont-weight: normal;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-email-card__row--unread .cn-email-card__subject {\n\tfont-weight: bold;\n}\n\n.cn-email-card__snippet {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-email-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-email-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnEmailCard.vue2.js","sources":["../../../../../src/integrations/builtin/email/CnEmailCard.vue"],"sourcesContent":["<!--\n CnEmailCard — compact email widget for the integration registry.\n\n Surface-aware shell around the `email` integration (NC Mail link-only\n per AD-2). Fetches linked email messages for an OR object via the\n registry endpoint and renders them in a `CnDetailCard`. Per umbrella\n change `pluggable-integration-registry` AD-19 (surface fallback) one\n component handles all four surfaces — `surface` is forwarded so the\n component can branch internally.\n\n Storage strategy: link-table (`openregister_email_links`). Backend\n pages results; the card itself only shows the first `maxDisplay`\n entries and emits `show-all` for the full sidebar view.\n-->\n<template>\n\t<CnDetailCard :title=\"resolvedTitle\" :icon=\"Email\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"errored === true\" class=\"cn-email-card__empty\">\n\t\t\t{{ errorLabel }}\n\t\t</div>\n\t\t<div v-else-if=\"messages.length === 0\" class=\"cn-email-card__empty\">\n\t\t\t{{ noMessagesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-email-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in displayedRows\"\n\t\t\t\t:key=\"row.id\"\n\t\t\t\tclass=\"cn-email-card__row\"\n\t\t\t\t:class=\"{ 'cn-email-card__row--unread': row.unread }\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"row.href ? 'a' : 'div'\"\n\t\t\t\t\tclass=\"cn-email-card__open\"\n\t\t\t\t\t:title=\"row.href ? openInMailLabel : undefined\"\n\t\t\t\t\tv-bind=\"row.href ? { href: row.href, target: '_blank', rel: 'noopener noreferrer' } : {}\">\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\tclass=\"cn-email-card__avatar\"\n\t\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t\t:displayName=\"row.sender\"\n\t\t\t\t\t\t:user=\"row.avatarUser\"\n\t\t\t\t\t\t:isNoUser=\"true\" />\n\t\t\t\t\t<div class=\"cn-email-card__text\">\n\t\t\t\t\t\t<div class=\"cn-email-card__row-head\">\n\t\t\t\t\t\t\t<strong class=\"cn-email-card__subject\">{{ row.subject }}</strong>\n\t\t\t\t\t\t\t<span class=\"cn-email-card__when\">{{ row.when }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-email-card__row-body\">\n\t\t\t\t\t\t\t<span class=\"cn-email-card__sender\">{{ row.sender }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"row.snippet\" class=\"cn-email-card__snippet\">\n\t\t\t\t\t\t\t{{ row.snippet }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</component>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"messages.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-email-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ total || messages.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport Email from 'vue-material-design-icons/Email.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnEmailCard — compact widget for the `email` integration leaf.\n *\n * Backed by OpenRegister's `EmailService` via the registry's\n * `/integrations/email` endpoint. Renders the latest linked Nextcloud\n * Mail messages with subject + sender + date. Clicking a row deep-links\n * into the NC Mail app.\n *\n * Basic usage\n * ```vue\n * <CnEmailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n *\n * @event show-all Emitted when the user clicks the \"Show all\" overflow\n * control — parents typically open the Emails sidebar tab.\n */\nexport default {\n\tname: 'CnEmailCard',\n\n\tcomponents: { CnDetailCard, NcAvatar, NcLoadingIcon },\n\n\tprops: {\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/**\n\t\t * Rendering surface — passed for AD-19 surface fallback consumers.\n\t\t * `single-entity` shows only the most-recent message; the other\n\t\t * three surfaces render a list up to `maxDisplay`.\n\t\t */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoMessagesLabel: { type: String, default: () => t('nextcloud-vue', 'No linked emails yet') },\n\t\t/** Pre-translated error label. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load emails') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated row tooltip / aria label. */\n\t\topenInMailLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Mail') },\n\t\t/** Pre-translated fallback subject label. */\n\t\tnoSubjectLabel: { type: String, default: () => t('nextcloud-vue', '(no subject)') },\n\t\t/** Pre-translated fallback sender label. */\n\t\tunknownSenderLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown sender') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tEmail: markRaw(Email),\n\t\t\tmessages: [],\n\t\t\ttotal: 0,\n\t\t\tloading: false,\n\t\t\terrored: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Emails')\n\t\t},\n\n\t\teffectiveMax() {\n\t\t\treturn this.surface === 'single-entity' ? 1 : this.maxDisplay\n\t\t},\n\n\t\tdisplayedMessages() {\n\t\t\treturn this.messages.slice(0, this.effectiveMax)\n\t\t},\n\n\t\t/**\n\t\t * Template-safe view rows; keeps `?.`/`??` out of the buble template.\n\t\t *\n\t\t * @return {object[]} One descriptor per displayed message.\n\t\t */\n\t\tdisplayedRows() {\n\t\t\treturn this.displayedMessages.map((message) => ({\n\t\t\t\tid: message.id,\n\t\t\t\tmessage,\n\t\t\t\tsubject: this.formatSubject(message),\n\t\t\t\tsender: this.formatSender(message),\n\t\t\t\tsnippet: this.formatSnippet(message),\n\t\t\t\twhen: this.formatWhen(message),\n\t\t\t\tunread: this.isUnread(message),\n\t\t\t\tavatarUser: this.senderEmail(message),\n\t\t\t\thref: this.threadUrl(message),\n\t\t\t}))\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchMessages()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tasync fetchMessages() {\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.errored = false\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ _limit: String(this.effectiveMax) })\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/email?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.messages = Array.isArray(list) === true ? list : []\n\t\t\t\t\tthis.total = typeof data.total === 'number' ? data.total : this.messages.length\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\n\t\t\t\t\tthis.messages = []\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('[CnEmailCard] failed to fetch linked emails', err)\n\t\t\t\tthis.errored = true\n\t\t\t\tthis.messages = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatSubject(message) {\n\t\t\tconst raw = message.subject\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.noSubjectLabel\n\t\t},\n\n\t\tformatSender(message) {\n\t\t\tconst raw = message.senderName || message.fromName || message.sender || message.from\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.unknownSenderLabel\n\t\t},\n\n\t\tsenderEmail(message) {\n\t\t\tconst candidate = message.senderEmail || message.fromEmail || message.sender || message.from\n\t\t\tif (typeof candidate === 'string' && candidate.indexOf('@') !== -1) {\n\t\t\t\treturn candidate.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tformatSnippet(message) {\n\t\t\tconst raw = message.preview || message.snippet || message.summary || message.bodyPreview\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tisUnread(message) {\n\t\t\tif (typeof message.unread === 'boolean') {\n\t\t\t\treturn message.unread\n\t\t\t}\n\t\t\tif (typeof message.isRead === 'boolean') {\n\t\t\t\treturn message.isRead === false\n\t\t\t}\n\t\t\tif (typeof message.seen === 'boolean') {\n\t\t\t\treturn message.seen === false\n\t\t\t}\n\t\t\tif (Array.isArray(message.flags)) {\n\t\t\t\treturn message.flags.indexOf('seen') === -1 && message.flags.indexOf('\\\\Seen') === -1\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tformatWhen(message) {\n\t\t\tconst raw = message.mailDate || message.date || message.linkedAt\n\t\t\tif (raw === undefined || raw === null || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n\t\t},\n\n\t\t// Thread deep-link into NC Mail; undefined when the row lacks ids.\n\t\tthreadUrl(message) {\n\t\t\tconst accountId = message.mailAccountId\n\t\t\tconst messageId = message.mailMessageId\n\t\t\tif (accountId === undefined || accountId === null || messageId === undefined || messageId === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl(`/apps/mail/box/${accountId}/thread/${messageId}`)\n\t\t\t\t: `/index.php/apps/mail/box/${accountId}/thread/${messageId}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-email-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-email-card__row {\n\tpadding: 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-email-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-email-card__open {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\twidth: 100%;\n\tbackground: none;\n\tborder: none;\n\tpadding: 6px 0;\n\ttext-align: left;\n\tcolor: inherit;\n\tfont: inherit;\n\ttext-decoration: none;\n\tbox-sizing: border-box;\n}\n\n/* Only a row with Mail ids is a link; the rest must not look clickable. */\na.cn-email-card__open {\n\tcursor: pointer;\n}\n\na.cn-email-card__open:hover .cn-email-card__subject {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-email-card__avatar {\n\tflex: 0 0 auto;\n}\n\n.cn-email-card__text {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-email-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-email-card__subject {\n\tcolor: var(--color-main-text);\n\tfont-weight: normal;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-email-card__row--unread .cn-email-card__subject {\n\tfont-weight: bold;\n}\n\n.cn-email-card__snippet {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-email-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-email-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnEmailCard.vue3.js","sources":["../../../../../src/integrations/builtin/email/CnEmailCard.vue?vue&type=template&id=fda51a6a&lang.js"],"sourcesContent":["<!--\n CnEmailCard — compact email widget for the integration registry.\n\n Surface-aware shell around the `email` integration (NC Mail link-only\n per AD-2). Fetches linked email messages for an OR object via the\n registry endpoint and renders them in a `CnDetailCard`. Per umbrella\n change `pluggable-integration-registry` AD-19 (surface fallback) one\n component handles all four surfaces — `surface` is forwarded so the\n component can branch internally.\n\n Storage strategy: link-table (`openregister_email_links`). Backend\n pages results; the card itself only shows the first `maxDisplay`\n entries and emits `show-all` for the full sidebar view.\n-->\n<template>\n\t<CnDetailCard :title=\"resolvedTitle\" :icon=\"Email\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"errored === true\" class=\"cn-email-card__empty\">\n\t\t\t{{ errorLabel }}\n\t\t</div>\n\t\t<div v-else-if=\"messages.length === 0\" class=\"cn-email-card__empty\">\n\t\t\t{{ noMessagesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-email-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in displayedRows\"\n\t\t\t\t:key=\"row.id\"\n\t\t\t\tclass=\"cn-email-card__row\"\n\t\t\t\t:class=\"{ 'cn-email-card__row--unread': row.unread }\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"row.href ? 'a' : 'div'\"\n\t\t\t\t\tclass=\"cn-email-card__open\"\n\t\t\t\t\t:title=\"row.href ? openInMailLabel : undefined\"\n\t\t\t\t\tv-bind=\"row.href ? { href: row.href, target: '_blank', rel: 'noopener noreferrer' } : {}\">\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\tclass=\"cn-email-card__avatar\"\n\t\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t\t:displayName=\"row.sender\"\n\t\t\t\t\t\t:user=\"row.avatarUser\"\n\t\t\t\t\t\t:isNoUser=\"true\" />\n\t\t\t\t\t<div class=\"cn-email-card__text\">\n\t\t\t\t\t\t<div class=\"cn-email-card__row-head\">\n\t\t\t\t\t\t\t<strong class=\"cn-email-card__subject\">{{ row.subject }}</strong>\n\t\t\t\t\t\t\t<span class=\"cn-email-card__when\">{{ row.when }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-email-card__row-body\">\n\t\t\t\t\t\t\t<span class=\"cn-email-card__sender\">{{ row.sender }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"row.snippet\" class=\"cn-email-card__snippet\">\n\t\t\t\t\t\t\t{{ row.snippet }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</component>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"messages.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-email-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ total || messages.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport Email from 'vue-material-design-icons/Email.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnEmailCard — compact widget for the `email` integration leaf.\n *\n * Backed by OpenRegister's `EmailService` via the registry's\n * `/integrations/email` endpoint. Renders the latest linked Nextcloud\n * Mail messages with subject + sender + date. Clicking a row deep-links\n * into the NC Mail app.\n *\n * Basic usage\n * ```vue\n * <CnEmailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n *\n * @event show-all Emitted when the user clicks the \"Show all\" overflow\n * control — parents typically open the Emails sidebar tab.\n */\nexport default {\n\tname: 'CnEmailCard',\n\n\tcomponents: { CnDetailCard, NcAvatar, NcLoadingIcon },\n\n\tprops: {\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/**\n\t\t * Rendering surface — passed for AD-19 surface fallback consumers.\n\t\t * `single-entity` shows only the most-recent message; the other\n\t\t * three surfaces render a list up to `maxDisplay`.\n\t\t */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoMessagesLabel: { type: String, default: () => t('nextcloud-vue', 'No linked emails yet') },\n\t\t/** Pre-translated error label. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load emails') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated row tooltip / aria label. */\n\t\topenInMailLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Mail') },\n\t\t/** Pre-translated fallback subject label. */\n\t\tnoSubjectLabel: { type: String, default: () => t('nextcloud-vue', '(no subject)') },\n\t\t/** Pre-translated fallback sender label. */\n\t\tunknownSenderLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown sender') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tEmail: markRaw(Email),\n\t\t\tmessages: [],\n\t\t\ttotal: 0,\n\t\t\tloading: false,\n\t\t\terrored: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Emails')\n\t\t},\n\n\t\teffectiveMax() {\n\t\t\treturn this.surface === 'single-entity' ? 1 : this.maxDisplay\n\t\t},\n\n\t\tdisplayedMessages() {\n\t\t\treturn this.messages.slice(0, this.effectiveMax)\n\t\t},\n\n\t\t/**\n\t\t * Template-safe view rows; keeps `?.`/`??` out of the buble template.\n\t\t *\n\t\t * @return {object[]} One descriptor per displayed message.\n\t\t */\n\t\tdisplayedRows() {\n\t\t\treturn this.displayedMessages.map((message) => ({\n\t\t\t\tid: message.id,\n\t\t\t\tmessage,\n\t\t\t\tsubject: this.formatSubject(message),\n\t\t\t\tsender: this.formatSender(message),\n\t\t\t\tsnippet: this.formatSnippet(message),\n\t\t\t\twhen: this.formatWhen(message),\n\t\t\t\tunread: this.isUnread(message),\n\t\t\t\tavatarUser: this.senderEmail(message),\n\t\t\t\thref: this.threadUrl(message),\n\t\t\t}))\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchMessages()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tasync fetchMessages() {\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.errored = false\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ _limit: String(this.effectiveMax) })\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/email?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.messages = Array.isArray(list) === true ? list : []\n\t\t\t\t\tthis.total = typeof data.total === 'number' ? data.total : this.messages.length\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\n\t\t\t\t\tthis.messages = []\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('[CnEmailCard] failed to fetch linked emails', err)\n\t\t\t\tthis.errored = true\n\t\t\t\tthis.messages = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatSubject(message) {\n\t\t\tconst raw = message.subject\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.noSubjectLabel\n\t\t},\n\n\t\tformatSender(message) {\n\t\t\tconst raw = message.senderName || message.fromName || message.sender || message.from\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.unknownSenderLabel\n\t\t},\n\n\t\tsenderEmail(message) {\n\t\t\tconst candidate = message.senderEmail || message.fromEmail || message.sender || message.from\n\t\t\tif (typeof candidate === 'string' && candidate.indexOf('@') !== -1) {\n\t\t\t\treturn candidate.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tformatSnippet(message) {\n\t\t\tconst raw = message.preview || message.snippet || message.summary || message.bodyPreview\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tisUnread(message) {\n\t\t\tif (typeof message.unread === 'boolean') {\n\t\t\t\treturn message.unread\n\t\t\t}\n\t\t\tif (typeof message.isRead === 'boolean') {\n\t\t\t\treturn message.isRead === false\n\t\t\t}\n\t\t\tif (typeof message.seen === 'boolean') {\n\t\t\t\treturn message.seen === false\n\t\t\t}\n\t\t\tif (Array.isArray(message.flags)) {\n\t\t\t\treturn message.flags.indexOf('seen') === -1 && message.flags.indexOf('\\\\Seen') === -1\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tformatWhen(message) {\n\t\t\tconst raw = message.mailDate || message.date || message.linkedAt\n\t\t\tif (raw === undefined || raw === null || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n\t\t},\n\n\t\t// Thread deep-link into NC Mail; undefined when the row lacks ids.\n\t\tthreadUrl(message) {\n\t\t\tconst accountId = message.mailAccountId\n\t\t\tconst messageId = message.mailMessageId\n\t\t\tif (accountId === undefined || accountId === null || messageId === undefined || messageId === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl(`/apps/mail/box/${accountId}/thread/${messageId}`)\n\t\t\t\t: `/index.php/apps/mail/box/${accountId}/thread/${messageId}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-email-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-email-card__row {\n\tpadding: 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-email-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-email-card__open {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\twidth: 100%;\n\tbackground: none;\n\tborder: none;\n\tpadding: 6px 0;\n\tcursor: pointer;\n\ttext-align: left;\n\tcolor: inherit;\n\tfont: inherit;\n\ttext-decoration: none;\n\tbox-sizing: border-box;\n}\n\n.cn-email-card__open:hover .cn-email-card__subject {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-email-card__avatar {\n\tflex: 0 0 auto;\n}\n\n.cn-email-card__text {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-email-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-email-card__subject {\n\tcolor: var(--color-main-text);\n\tfont-weight: normal;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-email-card__row--unread .cn-email-card__subject {\n\tfont-weight: bold;\n}\n\n.cn-email-card__snippet {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-email-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-email-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_toDisplayString","_createElementBlock","_normalizeClass","_createElementVNode"],"mappings":";;;;AA+B+B,EAAA,KAAA,EAAA,sBAAA;;;;AAGlB,EAAA,KAAA,EAAA,sBAAA;;;;AAGA,EAAA,KAAK,EAAC,qBAAW;;AAiBxB,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,qBAAA,GAAA;0BACU,EAAC,yBAAyB,GAAA;AAC3B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,wBAAwB,GAAA;AAChC,MAAA,UAAA,GAAA,EAAA,KAAK,EAAE,qBAAoB,GAAA;AAE7B,MAAA,UAAA,GAAA,EAAA,KAAC,EAAA,yBAAA,GAAA;AACC,MAAA,UAAA,GAAA,EAAA,KAAE,EAAA,uBAAA,GAAA;;;;;;;;;;;uBA/BXA;AAAS,IAAA,KAAA,EAAA,QAAA,CAAA,aAAA;AAAA,IAAA,IAAA,EAAA,KAAA,CAAA,KAAA;AAAA,IAAA,WAAA,EAAA,MAAA,CAAA,WAAA;;2BACmB;OAAT,KAAO,CAAA,OAAA;mCAAE,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;WACd,KAAO,CAAA,OAAA,KAAA,IAAA;4CAEjB,CAAA,KAAA,YAAA,EAAAC,eAAA,CADF,MAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;AAEH,YAAA,CAAA,KAAA,CAAA,QAAA,CAAA,MAAA,KAAA,CAAA;4BAARC,kBAEG,CAAA,KAAA,EAFH,UAEG,EAAAD,eAAA,CADF,MAAe,CAAA,eAAA,CAAA,EAAA,CAAA,YAAA;;kCAGfC,8CACY,QAAM,CAAA,aAAA,EAAA,CAAb,GAAG,KAAA;uCADRA;oBAED,GAAG,EAAE,GAAG,CAAC,EAAE;AACV,oBAAA,KAAG,EAAEC,cAAA,CAAA,CAAA,oBAAmB,EACc,EAAA,4BAAA,EAAA,GAAG,CAAA,MAAO,EAAA,CAAA,CAAA;;qEAE5C,CAAA,GAAC,CAAA,IAAA;AACL,sBAAA,KAAK,EAAA,qBAAqB;2BACrB,EAAE,GAAG,CAAC,IAAI,GAAG,MAAA,CAAA,eAAe,GAAG,SAAS;AACxC,qBAAA,EAAA,EAAA,OAAA,EAAA,IAAA,EAAA,EAAA,GAAA,CAAA,IAAA,GAAA,EAAA,IAAA,EAAA,GAAA,CAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EAAA,GAAA,EAAA,qBAAA,EAAA,GAAA,EAAA,CAAA,EAAA;6CAMJ;mCAAA,CAAA,mBAAA,EAAA;AAJA,0BAAA,KAAI,EAAC,uBAAW;AAChB,0BAAA,IAAI,EAAA,EAAA;0BACJ,WAAG,EAAA,GAAA,CAAA,MAAA;0BACH,IAAI,EAAE,GAAE,CAAA,UAAA;AACR,0BAAA,QAAA,EAAA,IAAA;;AACF,wBAAAC,kBAAA,QAAA;AACC,0BAAAA,kBAAA,CAGK,OAHL,UAGK,EAAA;AAFJ,4BAAAA,kBAAA,CAA8D,QAA9D,EAAA,UAA8D,EAApBH,eAAA,CAAA,GAAG,CAAC,OAAO,CAAA,EAAA,CAAA,YAAA;AACrD,4BAAAG,kBAAA,CAA+C,MAA/C,EAAA,UAA+C,EAAVH,eAAA,CAAA,GAAG,CAAC,IAAI,CAAA,EAAA,CAAA,YAAA;;AAE9C,0BAAAG,kBAAA,QAAA;AACC,4BAAAA,kBAAA,CAAQ,MAAR,EAAA,UAAQ,EAAAH,eAAA,CAAA,GAAA,CAAA,MAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnEmailCard.vue3.js","sources":["../../../../../src/integrations/builtin/email/CnEmailCard.vue?vue&type=template&id=fda51a6a&lang.js"],"sourcesContent":["<!--\n CnEmailCard — compact email widget for the integration registry.\n\n Surface-aware shell around the `email` integration (NC Mail link-only\n per AD-2). Fetches linked email messages for an OR object via the\n registry endpoint and renders them in a `CnDetailCard`. Per umbrella\n change `pluggable-integration-registry` AD-19 (surface fallback) one\n component handles all four surfaces — `surface` is forwarded so the\n component can branch internally.\n\n Storage strategy: link-table (`openregister_email_links`). Backend\n pages results; the card itself only shows the first `maxDisplay`\n entries and emits `show-all` for the full sidebar view.\n-->\n<template>\n\t<CnDetailCard :title=\"resolvedTitle\" :icon=\"Email\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"errored === true\" class=\"cn-email-card__empty\">\n\t\t\t{{ errorLabel }}\n\t\t</div>\n\t\t<div v-else-if=\"messages.length === 0\" class=\"cn-email-card__empty\">\n\t\t\t{{ noMessagesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-email-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in displayedRows\"\n\t\t\t\t:key=\"row.id\"\n\t\t\t\tclass=\"cn-email-card__row\"\n\t\t\t\t:class=\"{ 'cn-email-card__row--unread': row.unread }\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"row.href ? 'a' : 'div'\"\n\t\t\t\t\tclass=\"cn-email-card__open\"\n\t\t\t\t\t:title=\"row.href ? openInMailLabel : undefined\"\n\t\t\t\t\tv-bind=\"row.href ? { href: row.href, target: '_blank', rel: 'noopener noreferrer' } : {}\">\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\tclass=\"cn-email-card__avatar\"\n\t\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t\t:displayName=\"row.sender\"\n\t\t\t\t\t\t:user=\"row.avatarUser\"\n\t\t\t\t\t\t:isNoUser=\"true\" />\n\t\t\t\t\t<div class=\"cn-email-card__text\">\n\t\t\t\t\t\t<div class=\"cn-email-card__row-head\">\n\t\t\t\t\t\t\t<strong class=\"cn-email-card__subject\">{{ row.subject }}</strong>\n\t\t\t\t\t\t\t<span class=\"cn-email-card__when\">{{ row.when }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-email-card__row-body\">\n\t\t\t\t\t\t\t<span class=\"cn-email-card__sender\">{{ row.sender }}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"row.snippet\" class=\"cn-email-card__snippet\">\n\t\t\t\t\t\t\t{{ row.snippet }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</component>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"messages.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-email-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ total || messages.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport Email from 'vue-material-design-icons/Email.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnEmailCard — compact widget for the `email` integration leaf.\n *\n * Backed by OpenRegister's `EmailService` via the registry's\n * `/integrations/email` endpoint. Renders the latest linked Nextcloud\n * Mail messages with subject + sender + date. Clicking a row deep-links\n * into the NC Mail app.\n *\n * Basic usage\n * ```vue\n * <CnEmailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n *\n * @event show-all Emitted when the user clicks the \"Show all\" overflow\n * control — parents typically open the Emails sidebar tab.\n */\nexport default {\n\tname: 'CnEmailCard',\n\n\tcomponents: { CnDetailCard, NcAvatar, NcLoadingIcon },\n\n\tprops: {\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/**\n\t\t * Rendering surface — passed for AD-19 surface fallback consumers.\n\t\t * `single-entity` shows only the most-recent message; the other\n\t\t * three surfaces render a list up to `maxDisplay`.\n\t\t */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoMessagesLabel: { type: String, default: () => t('nextcloud-vue', 'No linked emails yet') },\n\t\t/** Pre-translated error label. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load emails') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated row tooltip / aria label. */\n\t\topenInMailLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Mail') },\n\t\t/** Pre-translated fallback subject label. */\n\t\tnoSubjectLabel: { type: String, default: () => t('nextcloud-vue', '(no subject)') },\n\t\t/** Pre-translated fallback sender label. */\n\t\tunknownSenderLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown sender') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tEmail: markRaw(Email),\n\t\t\tmessages: [],\n\t\t\ttotal: 0,\n\t\t\tloading: false,\n\t\t\terrored: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Emails')\n\t\t},\n\n\t\teffectiveMax() {\n\t\t\treturn this.surface === 'single-entity' ? 1 : this.maxDisplay\n\t\t},\n\n\t\tdisplayedMessages() {\n\t\t\treturn this.messages.slice(0, this.effectiveMax)\n\t\t},\n\n\t\t/**\n\t\t * Template-safe view rows; keeps `?.`/`??` out of the buble template.\n\t\t *\n\t\t * @return {object[]} One descriptor per displayed message.\n\t\t */\n\t\tdisplayedRows() {\n\t\t\treturn this.displayedMessages.map((message) => ({\n\t\t\t\tid: message.id,\n\t\t\t\tmessage,\n\t\t\t\tsubject: this.formatSubject(message),\n\t\t\t\tsender: this.formatSender(message),\n\t\t\t\tsnippet: this.formatSnippet(message),\n\t\t\t\twhen: this.formatWhen(message),\n\t\t\t\tunread: this.isUnread(message),\n\t\t\t\tavatarUser: this.senderEmail(message),\n\t\t\t\thref: this.threadUrl(message),\n\t\t\t}))\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchMessages()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tasync fetchMessages() {\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.errored = false\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ _limit: String(this.effectiveMax) })\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/email?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.messages = Array.isArray(list) === true ? list : []\n\t\t\t\t\tthis.total = typeof data.total === 'number' ? data.total : this.messages.length\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\n\t\t\t\t\tthis.messages = []\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('[CnEmailCard] failed to fetch linked emails', err)\n\t\t\t\tthis.errored = true\n\t\t\t\tthis.messages = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatSubject(message) {\n\t\t\tconst raw = message.subject\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.noSubjectLabel\n\t\t},\n\n\t\tformatSender(message) {\n\t\t\tconst raw = message.senderName || message.fromName || message.sender || message.from\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.unknownSenderLabel\n\t\t},\n\n\t\tsenderEmail(message) {\n\t\t\tconst candidate = message.senderEmail || message.fromEmail || message.sender || message.from\n\t\t\tif (typeof candidate === 'string' && candidate.indexOf('@') !== -1) {\n\t\t\t\treturn candidate.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tformatSnippet(message) {\n\t\t\tconst raw = message.preview || message.snippet || message.summary || message.bodyPreview\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tisUnread(message) {\n\t\t\tif (typeof message.unread === 'boolean') {\n\t\t\t\treturn message.unread\n\t\t\t}\n\t\t\tif (typeof message.isRead === 'boolean') {\n\t\t\t\treturn message.isRead === false\n\t\t\t}\n\t\t\tif (typeof message.seen === 'boolean') {\n\t\t\t\treturn message.seen === false\n\t\t\t}\n\t\t\tif (Array.isArray(message.flags)) {\n\t\t\t\treturn message.flags.indexOf('seen') === -1 && message.flags.indexOf('\\\\Seen') === -1\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tformatWhen(message) {\n\t\t\tconst raw = message.mailDate || message.date || message.linkedAt\n\t\t\tif (raw === undefined || raw === null || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n\t\t},\n\n\t\t// Thread deep-link into NC Mail; undefined when the row lacks ids.\n\t\tthreadUrl(message) {\n\t\t\tconst accountId = message.mailAccountId\n\t\t\tconst messageId = message.mailMessageId\n\t\t\tif (accountId === undefined || accountId === null || messageId === undefined || messageId === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl(`/apps/mail/box/${accountId}/thread/${messageId}`)\n\t\t\t\t: `/index.php/apps/mail/box/${accountId}/thread/${messageId}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-email-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-email-card__row {\n\tpadding: 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-email-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-email-card__open {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\twidth: 100%;\n\tbackground: none;\n\tborder: none;\n\tpadding: 6px 0;\n\ttext-align: left;\n\tcolor: inherit;\n\tfont: inherit;\n\ttext-decoration: none;\n\tbox-sizing: border-box;\n}\n\n/* Only a row with Mail ids is a link; the rest must not look clickable. */\na.cn-email-card__open {\n\tcursor: pointer;\n}\n\na.cn-email-card__open:hover .cn-email-card__subject {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-email-card__avatar {\n\tflex: 0 0 auto;\n}\n\n.cn-email-card__text {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-email-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-email-card__subject {\n\tcolor: var(--color-main-text);\n\tfont-weight: normal;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-email-card__row--unread .cn-email-card__subject {\n\tfont-weight: bold;\n}\n\n.cn-email-card__snippet {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-email-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-email-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_toDisplayString","_createElementBlock","_normalizeClass","_createElementVNode"],"mappings":";;;;AA+B+B,EAAA,KAAA,EAAA,sBAAA;;;;AAGlB,EAAA,KAAA,EAAA,sBAAA;;;;AAGA,EAAA,KAAK,EAAC,qBAAW;;AAiBxB,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,qBAAA,GAAA;0BACU,EAAC,yBAAyB,GAAA;AAC3B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,wBAAwB,GAAA;AAChC,MAAA,UAAA,GAAA,EAAA,KAAK,EAAE,qBAAoB,GAAA;AAE7B,MAAA,UAAA,GAAA,EAAA,KAAC,EAAA,yBAAA,GAAA;AACC,MAAA,UAAA,GAAA,EAAA,KAAE,EAAA,uBAAA,GAAA;;;;;;;;;;;uBA/BXA;AAAS,IAAA,KAAA,EAAA,QAAA,CAAA,aAAA;AAAA,IAAA,IAAA,EAAA,KAAA,CAAA,KAAA;AAAA,IAAA,WAAA,EAAA,MAAA,CAAA,WAAA;;2BACmB;OAAT,KAAO,CAAA,OAAA;mCAAE,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;WACd,KAAO,CAAA,OAAA,KAAA,IAAA;4CAEjB,CAAA,KAAA,YAAA,EAAAC,eAAA,CADF,MAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;AAEH,YAAA,CAAA,KAAA,CAAA,QAAA,CAAA,MAAA,KAAA,CAAA;4BAARC,kBAEG,CAAA,KAAA,EAFH,UAEG,EAAAD,eAAA,CADF,MAAe,CAAA,eAAA,CAAA,EAAA,CAAA,YAAA;;kCAGfC,8CACY,QAAM,CAAA,aAAA,EAAA,CAAb,GAAG,KAAA;uCADRA;oBAED,GAAG,EAAE,GAAG,CAAC,EAAE;AACV,oBAAA,KAAG,EAAEC,cAAA,CAAA,CAAA,oBAAmB,EACc,EAAA,4BAAA,EAAA,GAAG,CAAA,MAAO,EAAA,CAAA,CAAA;;qEAE5C,CAAA,GAAC,CAAA,IAAA;AACL,sBAAA,KAAK,EAAA,qBAAqB;2BACrB,EAAE,GAAG,CAAC,IAAI,GAAG,MAAA,CAAA,eAAe,GAAG,SAAS;AACxC,qBAAA,EAAA,EAAA,OAAA,EAAA,IAAA,EAAA,EAAA,GAAA,CAAA,IAAA,GAAA,EAAA,IAAA,EAAA,GAAA,CAAA,IAAA,EAAA,MAAA,EAAA,QAAA,EAAA,GAAA,EAAA,qBAAA,EAAA,GAAA,EAAA,CAAA,EAAA;6CAMJ;mCAAA,CAAA,mBAAA,EAAA;AAJA,0BAAA,KAAI,EAAC,uBAAW;AAChB,0BAAA,IAAI,EAAA,EAAA;0BACJ,WAAG,EAAA,GAAA,CAAA,MAAA;0BACH,IAAI,EAAE,GAAE,CAAA,UAAA;AACR,0BAAA,QAAA,EAAA,IAAA;;AACF,wBAAAC,kBAAA,QAAA;AACC,0BAAAA,kBAAA,CAGK,OAHL,UAGK,EAAA;AAFJ,4BAAAA,kBAAA,CAA8D,QAA9D,EAAA,UAA8D,EAApBH,eAAA,CAAA,GAAG,CAAC,OAAO,CAAA,EAAA,CAAA,YAAA;AACrD,4BAAAG,kBAAA,CAA+C,MAA/C,EAAA,UAA+C,EAAVH,eAAA,CAAA,GAAG,CAAC,IAAI,CAAA,EAAA,CAAA,YAAA;;AAE9C,0BAAAG,kBAAA,QAAA;AACC,4BAAAA,kBAAA,CAAQ,MAAR,EAAA,UAAQ,EAAAH,eAAA,CAAA,GAAA,CAAA,MAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnEmailTab.vue2.js","sources":["../../../../../src/integrations/builtin/email/CnEmailTab.vue"],"sourcesContent":["<!--\n CnEmailTab — sidebar tab for the `email` integration.\n\n Renders the full list of Nextcloud Mail messages linked to an OR\n object. Backed by `EmailService` via the registry endpoint\n (`/integrations/email`). Paged via load-more; rows deep-link into the\n NC Mail app per AD-2 (Mail owns compose, OR owns the link).\n\n Visual fidelity: mirrors the real NC Mail message list — a sender\n avatar, a bold subject, a \"sender · date\" subline, a one-line snippet\n preview, and an unread indicator (accent dot + bold subject) when the\n message has not been read.\n\n Storage strategy: link-table (`openregister_email_links`). Per design\n AD-1 the tab does NOT compose — users open Mail, send, return and\n link via the \"Link existing\" picker (compose UI tracked separately).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-email-tab\">\n\t\t<div class=\"cn-email-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"composeUrl\"\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<EmailEditOutline :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ composeLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"cn-sidebar-tab__loading\">\n\t\t\t<NcLoadingIcon />\n\t\t</div>\n\t\t<div v-else-if=\"errored === true\" class=\"cn-sidebar-tab__empty cn-sidebar-tab__empty--error\">\n\t\t\t{{ errorLabel }}\n\t\t</div>\n\t\t<div v-else-if=\"messages.length === 0\" class=\"cn-sidebar-tab__empty\">\n\t\t\t{{ noMessagesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-email-tab__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"row.id\"\n\t\t\t\tclass=\"cn-email-tab__item\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"row.href ? 'a' : 'div'\"\n\t\t\t\t\tclass=\"cn-email-tab__row\"\n\t\t\t\t\t:class=\"{ 'cn-email-tab__row--unread': row.unread }\"\n\t\t\t\t\tv-bind=\"row.href ? { href: row.href, target: '_blank', rel: 'noopener noreferrer' } : {}\">\n\t\t\t\t\t<span class=\"cn-email-tab__unread-dot\" :class=\"{ 'is-shown': row.unread }\" />\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\tclass=\"cn-email-tab__avatar\"\n\t\t\t\t\t\t:size=\"36\"\n\t\t\t\t\t\t:displayName=\"row.avatarName\"\n\t\t\t\t\t\t:user=\"row.avatarUser\"\n\t\t\t\t\t\t:isNoUser=\"true\" />\n\t\t\t\t\t<div class=\"cn-email-tab__body\">\n\t\t\t\t\t\t<div class=\"cn-email-tab__line\">\n\t\t\t\t\t\t\t<span class=\"cn-email-tab__subject\" :title=\"row.subject\">{{ row.subject }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-email-tab__date\">\n\t\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\t\tv-if=\"row.dateValid\"\n\t\t\t\t\t\t\t\t\t:timestamp=\"row.dateMs\"\n\t\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t\t\t<template v-else>{{ row.dateRaw }}</template>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-email-tab__sender\" :title=\"row.sender\">\n\t\t\t\t\t\t\t{{ row.sender }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"row.snippet\" class=\"cn-email-tab__snippet\" :title=\"row.snippet\">\n\t\t\t\t\t\t\t{{ row.snippet }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</component>\n\t\t\t</li>\n\t\t</ul>\n\t\t<NcButton\n\t\t\tv-if=\"hasMore === true\"\n\t\t\tvariant=\"tertiary\"\n\t\t\t:wide=\"true\"\n\t\t\t:disabled=\"loadingMore\"\n\t\t\tclass=\"cn-sidebar-tab__load-more\"\n\t\t\t@click=\"loadMore\">\n\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ loadMoreLabel }}\n\t\t</NcButton>\n\n\t\t<CnEmailPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcDateTime, NcLoadingIcon } from '@nextcloud/vue'\nimport EmailEditOutline from 'vue-material-design-icons/EmailEditOutline.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport CnEmailPicker from '../../../components/CnEmailPicker/CnEmailPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnEmailTab — sidebar tab rendering the full list of Nextcloud Mail\n * messages linked to an OpenRegister object.\n *\n * Basic usage\n * ```vue\n * <CnEmailTab\n * :object-id=\"objectId\"\n * :register=\"registerId\"\n * :schema=\"schemaId\" />\n * ```\n */\nexport default {\n\tname: 'CnEmailTab',\n\n\tcomponents: {\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcLoadingIcon,\n\t\tEmailEditOutline,\n\t\tLinkVariant,\n\t\tCnEmailPicker,\n\t},\n\n\tprops: {\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/** Page size for paged fetches. */\n\t\tpageSize: { type: Number, default: 25 },\n\t\t/** Pre-translated empty label. */\n\t\tnoMessagesLabel: { type: String, default: () => t('nextcloud-vue', 'No linked emails yet') },\n\t\t/** Pre-translated error label. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load emails') },\n\t\t/** Pre-translated load-more label. */\n\t\tloadMoreLabel: { type: String, default: () => t('nextcloud-vue', 'Load more') },\n\t\t/** Pre-translated fallback subject label. */\n\t\tnoSubjectLabel: { type: String, default: () => t('nextcloud-vue', '(no subject)') },\n\t\t/** Pre-translated fallback sender label. */\n\t\tunknownSenderLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown sender') },\n\t\t/** Pre-translated link-existing CTA label. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing email') },\n\t\t/** Pre-translated compose-in-Mail CTA label. */\n\t\tcomposeLabel: { type: String, default: () => t('nextcloud-vue', 'Compose in Mail') },\n\t\t/** Base path for the Mail app (for the compose deep-link). */\n\t\tmailAppPath: { type: String, default: '/index.php/apps/mail' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tmessages: [],\n\t\t\ttotal: 0,\n\t\t\tnextCursor: null,\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\terrored: false,\n\t\t\tpickerOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether a further page is available.\n\t\t *\n\t\t * Prefers the backend's `nextCursor` (Tier-3 standardized\n\t\t * `{items, total, nextCursor}` envelope from the generic\n\t\t * integrations endpoint). Falls back to the count comparison\n\t\t * for any backend that still omits the cursor.\n\t\t *\n\t\t * @return {boolean} True when more messages can be loaded.\n\t\t */\n\t\thasMore() {\n\t\t\tif (this.nextCursor !== null) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.messages.length < this.total\n\t\t},\n\n\t\t/**\n\t\t * Pre-computed, template-safe view rows mirroring NC Mail.\n\t\t *\n\t\t * All optional-chaining / nullish logic lives here (in JS) so the\n\t\t * buble-compiled Vue 2 template stays free of `?.` / `??`.\n\t\t *\n\t\t * @return {object[]} One descriptor per message.\n\t\t */\n\t\trows() {\n\t\t\treturn this.messages.map((message) => {\n\t\t\t\tconst dateRaw = this.rawWhen(message)\n\t\t\t\tconst d = dateRaw === '' ? null : new Date(dateRaw)\n\t\t\t\tconst dateValid = d !== null && Number.isNaN(d.getTime()) === false\n\t\t\t\tconst sender = this.formatSender(message)\n\t\t\t\treturn {\n\t\t\t\t\tid: message.id,\n\t\t\t\t\tmessage,\n\t\t\t\t\tsubject: this.formatSubject(message),\n\t\t\t\t\tsender,\n\t\t\t\t\tsnippet: this.formatSnippet(message),\n\t\t\t\t\tunread: this.isUnread(message),\n\t\t\t\t\tdateValid,\n\t\t\t\t\tdateMs: dateValid ? d.getTime() : 0,\n\t\t\t\t\tdateRaw: String(dateRaw),\n\t\t\t\t\tavatarName: sender,\n\t\t\t\t\tavatarUser: this.senderEmail(message),\n\t\t\t\t\thref: this.threadUrl(message),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Deep-link into NC Mail's new-message composer.\n\t\t *\n\t\t * AD-2 (Mail owns compose / OR owns the link): we do NOT\n\t\t * implement an in-app composer. \"Compose in Mail\" opens NC Mail\n\t\t * in a new tab with the new-message view; once the user has\n\t\t * sent, they return to OR and link the sent message via the\n\t\t * picker.\n\t\t *\n\t\t * @return {string} The composer URL.\n\t\t */\n\t\tcomposeUrl() {\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl('/apps/mail/box/draft')\n\t\t\t\t: `${this.mailAppPath}/box/draft`\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.reset()\n\t\t\t\t\tthis.fetchMessages()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\treset() {\n\t\t\tthis.messages = []\n\t\t\tthis.total = 0\n\t\t\tthis.nextCursor = null\n\t\t\tthis.errored = false\n\t\t},\n\n\t\tasync fetchMessages(append = false) {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (append === true) {\n\t\t\t\tthis.loadingMore = true\n\t\t\t} else {\n\t\t\t\tthis.loading = true\n\t\t\t}\n\t\t\tthis.errored = false\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ _limit: String(this.pageSize) })\n\t\t\t\t// The Tier-3 envelope's `nextCursor` is the next zero-indexed\n\t\t\t\t// page number consumed by `_page`; walk it on append.\n\t\t\t\tif (append === true && this.nextCursor !== null) {\n\t\t\t\t\tparams.set('_page', String(this.nextCursor))\n\t\t\t\t} else {\n\t\t\t\t\tparams.set('_page', '0')\n\t\t\t\t}\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/email?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.items || data.results || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tconst items = Array.isArray(list) === true ? list : []\n\t\t\t\t\tthis.messages = append === true ? [...this.messages, ...items] : items\n\t\t\t\t\tthis.total = typeof data.total === 'number' ? data.total : this.messages.length\n\t\t\t\t\tconst next = data.nextCursor\n\t\t\t\t\tthis.nextCursor = (next === null || next === undefined) ? null : next\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\n\t\t\t\t\tif (append !== true) {\n\t\t\t\t\t\tthis.messages = []\n\t\t\t\t\t}\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('[CnEmailTab] failed to fetch linked emails', err)\n\t\t\t\tthis.errored = true\n\t\t\t\tif (append !== true) {\n\t\t\t\t\tthis.messages = []\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t},\n\n\t\tloadMore() {\n\t\t\tif (this.nextCursor === null && this.messages.length >= this.total) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchMessages(true)\n\t\t},\n\n\t\tformatSubject(message) {\n\t\t\tconst raw = message.subject\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.noSubjectLabel\n\t\t},\n\n\t\tformatSender(message) {\n\t\t\tconst raw = message.senderName || message.fromName || message.sender || message.from\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.unknownSenderLabel\n\t\t},\n\n\t\t/**\n\t\t * Best-effort bare email address for the NcAvatar `user` seed so\n\t\t * gravatar/initials are stable per correspondent.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {string} An email-ish identifier, or '' when unknown.\n\t\t */\n\t\tsenderEmail(message) {\n\t\t\tconst candidate = message.senderEmail || message.fromEmail || message.sender || message.from\n\t\t\tif (typeof candidate === 'string' && candidate.indexOf('@') !== -1) {\n\t\t\t\treturn candidate.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * One-line preview snippet, mirroring NC Mail's message list.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {string} A trimmed snippet, or '' when none is available.\n\t\t */\n\t\tformatSnippet(message) {\n\t\t\tconst raw = message.preview || message.snippet || message.summary || message.bodyPreview\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the message is unread, tolerant of the several shapes\n\t\t * the backend may report read-state in.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {boolean} True when the message has not been read.\n\t\t */\n\t\tisUnread(message) {\n\t\t\tif (typeof message.unread === 'boolean') {\n\t\t\t\treturn message.unread\n\t\t\t}\n\t\t\tif (typeof message.isRead === 'boolean') {\n\t\t\t\treturn message.isRead === false\n\t\t\t}\n\t\t\tif (typeof message.seen === 'boolean') {\n\t\t\t\treturn message.seen === false\n\t\t\t}\n\t\t\tif (Array.isArray(message.flags)) {\n\t\t\t\treturn message.flags.indexOf('seen') === -1 && message.flags.indexOf('\\\\Seen') === -1\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\trawWhen(message) {\n\t\t\tconst raw = message.mailDate || message.date || message.linkedAt\n\t\t\tif (raw === undefined || raw === null || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t// Thread deep-link into NC Mail; undefined when the row lacks ids.\n\t\tthreadUrl(message) {\n\t\t\tconst accountId = message.mailAccountId\n\t\t\tconst messageId = message.mailMessageId\n\t\t\tif (accountId === undefined || accountId === null || messageId === undefined || messageId === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl(`/apps/mail/box/${accountId}/thread/${messageId}`)\n\t\t\t\t: `/index.php/apps/mail/box/${accountId}/thread/${messageId}`\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/emails`),\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tif (response.ok === true || response.status === 409) {\n\t\t\t\t\t// 409 = already-linked; treated as success since the row\n\t\t\t\t\t// already exists for this object.\n\t\t\t\t\tthis.reset()\n\t\t\t\t\tawait this.fetchMessages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\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('[CnEmailTab] link failed', err)\n\t\t\t\tthis.errored = true\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-sidebar-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-sidebar-tab__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 24px 0;\n}\n\n.cn-sidebar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-sidebar-tab__empty--error {\n\tcolor: var(--color-error);\n}\n\n.cn-sidebar-tab__load-more {\n\tmargin-top: 8px;\n}\n\n.cn-email-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmargin-bottom: 8px;\n}\n\n/* NC-Mail-style message list */\n.cn-email-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-email-tab__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\tpadding: 8px 6px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tcolor: inherit;\n\ttext-decoration: none;\n\tcursor: pointer;\n\tposition: relative;\n}\n\n.cn-email-tab__row:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-email-tab__unread-dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\talign-self: center;\n\tvisibility: hidden;\n}\n\n.cn-email-tab__unread-dot.is-shown {\n\tvisibility: visible;\n}\n\n.cn-email-tab__unread-dot.is-shown::before {\n\tcontent: '';\n\tdisplay: block;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground-color: var(--cn-email-accent, var(--color-primary-element));\n}\n\n.cn-email-tab__avatar {\n\tflex: 0 0 auto;\n}\n\n.cn-email-tab__body {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-email-tab__line {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 8px;\n}\n\n.cn-email-tab__subject {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__row--unread .cn-email-tab__subject {\n\tfont-weight: bold;\n}\n\n.cn-email-tab__date {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__sender {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__snippet {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmargin-top: 1px;\n}\n</style>\n"],"names":["CnEmailPicker","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnEmailTab.vue2.js","sources":["../../../../../src/integrations/builtin/email/CnEmailTab.vue"],"sourcesContent":["<!--\n CnEmailTab — sidebar tab for the `email` integration.\n\n Renders the full list of Nextcloud Mail messages linked to an OR\n object. Backed by `EmailService` via the registry endpoint\n (`/integrations/email`). Paged via load-more; rows deep-link into the\n NC Mail app per AD-2 (Mail owns compose, OR owns the link).\n\n Visual fidelity: mirrors the real NC Mail message list — a sender\n avatar, a bold subject, a \"sender · date\" subline, a one-line snippet\n preview, and an unread indicator (accent dot + bold subject) when the\n message has not been read.\n\n Storage strategy: link-table (`openregister_email_links`). Per design\n AD-1 the tab does NOT compose — users open Mail, send, return and\n link via the \"Link existing\" picker (compose UI tracked separately).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-email-tab\">\n\t\t<div class=\"cn-email-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"composeUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<EmailEditOutline :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ composeLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"cn-sidebar-tab__loading\">\n\t\t\t<NcLoadingIcon />\n\t\t</div>\n\t\t<div v-else-if=\"errored === true\" class=\"cn-sidebar-tab__empty cn-sidebar-tab__empty--error\">\n\t\t\t{{ errorLabel }}\n\t\t</div>\n\t\t<div v-else-if=\"messages.length === 0\" class=\"cn-sidebar-tab__empty\">\n\t\t\t{{ noMessagesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-email-tab__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"row.id\"\n\t\t\t\tclass=\"cn-email-tab__item\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"row.href ? 'a' : 'div'\"\n\t\t\t\t\tclass=\"cn-email-tab__row\"\n\t\t\t\t\t:class=\"{ 'cn-email-tab__row--unread': row.unread }\"\n\t\t\t\t\tv-bind=\"row.href ? { href: row.href, target: '_blank', rel: 'noopener noreferrer' } : {}\">\n\t\t\t\t\t<span class=\"cn-email-tab__unread-dot\" :class=\"{ 'is-shown': row.unread }\" />\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\tclass=\"cn-email-tab__avatar\"\n\t\t\t\t\t\t:size=\"36\"\n\t\t\t\t\t\t:displayName=\"row.avatarName\"\n\t\t\t\t\t\t:user=\"row.avatarUser\"\n\t\t\t\t\t\t:isNoUser=\"true\" />\n\t\t\t\t\t<div class=\"cn-email-tab__body\">\n\t\t\t\t\t\t<div class=\"cn-email-tab__line\">\n\t\t\t\t\t\t\t<span class=\"cn-email-tab__subject\" :title=\"row.subject\">{{ row.subject }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-email-tab__date\">\n\t\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\t\tv-if=\"row.dateValid\"\n\t\t\t\t\t\t\t\t\t:timestamp=\"row.dateMs\"\n\t\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t\t\t<template v-else>{{ row.dateRaw }}</template>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-email-tab__sender\" :title=\"row.sender\">\n\t\t\t\t\t\t\t{{ row.sender }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"row.snippet\" class=\"cn-email-tab__snippet\" :title=\"row.snippet\">\n\t\t\t\t\t\t\t{{ row.snippet }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</component>\n\t\t\t</li>\n\t\t</ul>\n\t\t<NcButton\n\t\t\tv-if=\"hasMore === true\"\n\t\t\tvariant=\"tertiary\"\n\t\t\t:wide=\"true\"\n\t\t\t:disabled=\"loadingMore\"\n\t\t\tclass=\"cn-sidebar-tab__load-more\"\n\t\t\t@click=\"loadMore\">\n\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ loadMoreLabel }}\n\t\t</NcButton>\n\n\t\t<CnEmailPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcDateTime, NcLoadingIcon } from '@nextcloud/vue'\nimport EmailEditOutline from 'vue-material-design-icons/EmailEditOutline.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport CnEmailPicker from '../../../components/CnEmailPicker/CnEmailPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnEmailTab — sidebar tab rendering the full list of Nextcloud Mail\n * messages linked to an OpenRegister object.\n *\n * Basic usage\n * ```vue\n * <CnEmailTab\n * :object-id=\"objectId\"\n * :register=\"registerId\"\n * :schema=\"schemaId\" />\n * ```\n */\nexport default {\n\tname: 'CnEmailTab',\n\n\tcomponents: {\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcLoadingIcon,\n\t\tEmailEditOutline,\n\t\tLinkVariant,\n\t\tCnEmailPicker,\n\t},\n\n\tprops: {\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/** Page size for paged fetches. */\n\t\tpageSize: { type: Number, default: 25 },\n\t\t/** Pre-translated empty label. */\n\t\tnoMessagesLabel: { type: String, default: () => t('nextcloud-vue', 'No linked emails yet') },\n\t\t/** Pre-translated error label. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load emails') },\n\t\t/** Pre-translated load-more label. */\n\t\tloadMoreLabel: { type: String, default: () => t('nextcloud-vue', 'Load more') },\n\t\t/** Pre-translated fallback subject label. */\n\t\tnoSubjectLabel: { type: String, default: () => t('nextcloud-vue', '(no subject)') },\n\t\t/** Pre-translated fallback sender label. */\n\t\tunknownSenderLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown sender') },\n\t\t/** Pre-translated link-existing CTA label. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing email') },\n\t\t/** Pre-translated compose-in-Mail CTA label. */\n\t\tcomposeLabel: { type: String, default: () => t('nextcloud-vue', 'Compose in Mail') },\n\t\t/** Base path for the Mail app (for the compose deep-link). */\n\t\tmailAppPath: { type: String, default: '/index.php/apps/mail' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tmessages: [],\n\t\t\ttotal: 0,\n\t\t\tnextCursor: null,\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\terrored: false,\n\t\t\tpickerOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether a further page is available.\n\t\t *\n\t\t * Prefers the backend's `nextCursor` (Tier-3 standardized\n\t\t * `{items, total, nextCursor}` envelope from the generic\n\t\t * integrations endpoint). Falls back to the count comparison\n\t\t * for any backend that still omits the cursor.\n\t\t *\n\t\t * @return {boolean} True when more messages can be loaded.\n\t\t */\n\t\thasMore() {\n\t\t\tif (this.nextCursor !== null) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.messages.length < this.total\n\t\t},\n\n\t\t/**\n\t\t * Pre-computed, template-safe view rows mirroring NC Mail.\n\t\t *\n\t\t * All optional-chaining / nullish logic lives here (in JS) so the\n\t\t * buble-compiled Vue 2 template stays free of `?.` / `??`.\n\t\t *\n\t\t * @return {object[]} One descriptor per message.\n\t\t */\n\t\trows() {\n\t\t\treturn this.messages.map((message) => {\n\t\t\t\tconst dateRaw = this.rawWhen(message)\n\t\t\t\tconst d = dateRaw === '' ? null : new Date(dateRaw)\n\t\t\t\tconst dateValid = d !== null && Number.isNaN(d.getTime()) === false\n\t\t\t\tconst sender = this.formatSender(message)\n\t\t\t\treturn {\n\t\t\t\t\tid: message.id,\n\t\t\t\t\tmessage,\n\t\t\t\t\tsubject: this.formatSubject(message),\n\t\t\t\t\tsender,\n\t\t\t\t\tsnippet: this.formatSnippet(message),\n\t\t\t\t\tunread: this.isUnread(message),\n\t\t\t\t\tdateValid,\n\t\t\t\t\tdateMs: dateValid ? d.getTime() : 0,\n\t\t\t\t\tdateRaw: String(dateRaw),\n\t\t\t\t\tavatarName: sender,\n\t\t\t\t\tavatarUser: this.senderEmail(message),\n\t\t\t\t\thref: this.threadUrl(message),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Deep-link into NC Mail's new-message composer.\n\t\t *\n\t\t * AD-2 (Mail owns compose / OR owns the link): we do NOT\n\t\t * implement an in-app composer. \"Compose in Mail\" opens NC Mail\n\t\t * in a new tab with the new-message view; once the user has\n\t\t * sent, they return to OR and link the sent message via the\n\t\t * picker.\n\t\t *\n\t\t * @return {string} The composer URL.\n\t\t */\n\t\tcomposeUrl() {\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl('/apps/mail/box/draft')\n\t\t\t\t: `${this.mailAppPath}/box/draft`\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.reset()\n\t\t\t\t\tthis.fetchMessages()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\treset() {\n\t\t\tthis.messages = []\n\t\t\tthis.total = 0\n\t\t\tthis.nextCursor = null\n\t\t\tthis.errored = false\n\t\t},\n\n\t\tasync fetchMessages(append = false) {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (append === true) {\n\t\t\t\tthis.loadingMore = true\n\t\t\t} else {\n\t\t\t\tthis.loading = true\n\t\t\t}\n\t\t\tthis.errored = false\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ _limit: String(this.pageSize) })\n\t\t\t\t// The Tier-3 envelope's `nextCursor` is the next zero-indexed\n\t\t\t\t// page number consumed by `_page`; walk it on append.\n\t\t\t\tif (append === true && this.nextCursor !== null) {\n\t\t\t\t\tparams.set('_page', String(this.nextCursor))\n\t\t\t\t} else {\n\t\t\t\t\tparams.set('_page', '0')\n\t\t\t\t}\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/email?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.items || data.results || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tconst items = Array.isArray(list) === true ? list : []\n\t\t\t\t\tthis.messages = append === true ? [...this.messages, ...items] : items\n\t\t\t\t\tthis.total = typeof data.total === 'number' ? data.total : this.messages.length\n\t\t\t\t\tconst next = data.nextCursor\n\t\t\t\t\tthis.nextCursor = (next === null || next === undefined) ? null : next\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\n\t\t\t\t\tif (append !== true) {\n\t\t\t\t\t\tthis.messages = []\n\t\t\t\t\t}\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('[CnEmailTab] failed to fetch linked emails', err)\n\t\t\t\tthis.errored = true\n\t\t\t\tif (append !== true) {\n\t\t\t\t\tthis.messages = []\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t},\n\n\t\tloadMore() {\n\t\t\tif (this.nextCursor === null && this.messages.length >= this.total) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchMessages(true)\n\t\t},\n\n\t\tformatSubject(message) {\n\t\t\tconst raw = message.subject\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.noSubjectLabel\n\t\t},\n\n\t\tformatSender(message) {\n\t\t\tconst raw = message.senderName || message.fromName || message.sender || message.from\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.unknownSenderLabel\n\t\t},\n\n\t\t/**\n\t\t * Best-effort bare email address for the NcAvatar `user` seed so\n\t\t * gravatar/initials are stable per correspondent.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {string} An email-ish identifier, or '' when unknown.\n\t\t */\n\t\tsenderEmail(message) {\n\t\t\tconst candidate = message.senderEmail || message.fromEmail || message.sender || message.from\n\t\t\tif (typeof candidate === 'string' && candidate.indexOf('@') !== -1) {\n\t\t\t\treturn candidate.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * One-line preview snippet, mirroring NC Mail's message list.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {string} A trimmed snippet, or '' when none is available.\n\t\t */\n\t\tformatSnippet(message) {\n\t\t\tconst raw = message.preview || message.snippet || message.summary || message.bodyPreview\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the message is unread, tolerant of the several shapes\n\t\t * the backend may report read-state in.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {boolean} True when the message has not been read.\n\t\t */\n\t\tisUnread(message) {\n\t\t\tif (typeof message.unread === 'boolean') {\n\t\t\t\treturn message.unread\n\t\t\t}\n\t\t\tif (typeof message.isRead === 'boolean') {\n\t\t\t\treturn message.isRead === false\n\t\t\t}\n\t\t\tif (typeof message.seen === 'boolean') {\n\t\t\t\treturn message.seen === false\n\t\t\t}\n\t\t\tif (Array.isArray(message.flags)) {\n\t\t\t\treturn message.flags.indexOf('seen') === -1 && message.flags.indexOf('\\\\Seen') === -1\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\trawWhen(message) {\n\t\t\tconst raw = message.mailDate || message.date || message.linkedAt\n\t\t\tif (raw === undefined || raw === null || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t// Thread deep-link into NC Mail; undefined when the row lacks ids.\n\t\tthreadUrl(message) {\n\t\t\tconst accountId = message.mailAccountId\n\t\t\tconst messageId = message.mailMessageId\n\t\t\tif (accountId === undefined || accountId === null || messageId === undefined || messageId === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl(`/apps/mail/box/${accountId}/thread/${messageId}`)\n\t\t\t\t: `/index.php/apps/mail/box/${accountId}/thread/${messageId}`\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/emails`),\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tif (response.ok === true || response.status === 409) {\n\t\t\t\t\t// 409 = already-linked; treated as success since the row\n\t\t\t\t\t// already exists for this object.\n\t\t\t\t\tthis.reset()\n\t\t\t\t\tawait this.fetchMessages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\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('[CnEmailTab] link failed', err)\n\t\t\t\tthis.errored = true\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-sidebar-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-sidebar-tab__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 24px 0;\n}\n\n.cn-sidebar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-sidebar-tab__empty--error {\n\tcolor: var(--color-error);\n}\n\n.cn-sidebar-tab__load-more {\n\tmargin-top: 8px;\n}\n\n.cn-email-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmargin-bottom: 8px;\n}\n\n/* NC-Mail-style message list */\n.cn-email-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-email-tab__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\tpadding: 8px 6px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tcolor: inherit;\n\ttext-decoration: none;\n\tposition: relative;\n}\n\n/* Only a row with Mail ids is a link; the rest must not look clickable. */\na.cn-email-tab__row {\n\tcursor: pointer;\n}\n\na.cn-email-tab__row:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-email-tab__unread-dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\talign-self: center;\n\tvisibility: hidden;\n}\n\n.cn-email-tab__unread-dot.is-shown {\n\tvisibility: visible;\n}\n\n.cn-email-tab__unread-dot.is-shown::before {\n\tcontent: '';\n\tdisplay: block;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground-color: var(--cn-email-accent, var(--color-primary-element));\n}\n\n.cn-email-tab__avatar {\n\tflex: 0 0 auto;\n}\n\n.cn-email-tab__body {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-email-tab__line {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 8px;\n}\n\n.cn-email-tab__subject {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__row--unread .cn-email-tab__subject {\n\tfont-weight: bold;\n}\n\n.cn-email-tab__date {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__sender {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__snippet {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmargin-top: 1px;\n}\n</style>\n"],"names":["CnEmailPicker","t"],"mappings":"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"}
|
|
@@ -51,8 +51,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
51
51
|
createVNode(_component_NcButton, {
|
|
52
52
|
variant: "primary",
|
|
53
53
|
href: $options.composeUrl,
|
|
54
|
-
target: "_blank"
|
|
55
|
-
rel: "noopener noreferrer"
|
|
54
|
+
target: "_blank"
|
|
56
55
|
}, {
|
|
57
56
|
icon: withCtx(() => [
|
|
58
57
|
createVNode(_component_EmailEditOutline, { size: 18 })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnEmailTab.vue3.js","sources":["../../../../../src/integrations/builtin/email/CnEmailTab.vue?vue&type=template&id=0bcebbac&lang.js"],"sourcesContent":["<!--\n CnEmailTab — sidebar tab for the `email` integration.\n\n Renders the full list of Nextcloud Mail messages linked to an OR\n object. Backed by `EmailService` via the registry endpoint\n (`/integrations/email`). Paged via load-more; rows deep-link into the\n NC Mail app per AD-2 (Mail owns compose, OR owns the link).\n\n Visual fidelity: mirrors the real NC Mail message list — a sender\n avatar, a bold subject, a \"sender · date\" subline, a one-line snippet\n preview, and an unread indicator (accent dot + bold subject) when the\n message has not been read.\n\n Storage strategy: link-table (`openregister_email_links`). Per design\n AD-1 the tab does NOT compose — users open Mail, send, return and\n link via the \"Link existing\" picker (compose UI tracked separately).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-email-tab\">\n\t\t<div class=\"cn-email-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"composeUrl\"\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<EmailEditOutline :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ composeLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"cn-sidebar-tab__loading\">\n\t\t\t<NcLoadingIcon />\n\t\t</div>\n\t\t<div v-else-if=\"errored === true\" class=\"cn-sidebar-tab__empty cn-sidebar-tab__empty--error\">\n\t\t\t{{ errorLabel }}\n\t\t</div>\n\t\t<div v-else-if=\"messages.length === 0\" class=\"cn-sidebar-tab__empty\">\n\t\t\t{{ noMessagesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-email-tab__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"row.id\"\n\t\t\t\tclass=\"cn-email-tab__item\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"row.href ? 'a' : 'div'\"\n\t\t\t\t\tclass=\"cn-email-tab__row\"\n\t\t\t\t\t:class=\"{ 'cn-email-tab__row--unread': row.unread }\"\n\t\t\t\t\tv-bind=\"row.href ? { href: row.href, target: '_blank', rel: 'noopener noreferrer' } : {}\">\n\t\t\t\t\t<span class=\"cn-email-tab__unread-dot\" :class=\"{ 'is-shown': row.unread }\" />\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\tclass=\"cn-email-tab__avatar\"\n\t\t\t\t\t\t:size=\"36\"\n\t\t\t\t\t\t:displayName=\"row.avatarName\"\n\t\t\t\t\t\t:user=\"row.avatarUser\"\n\t\t\t\t\t\t:isNoUser=\"true\" />\n\t\t\t\t\t<div class=\"cn-email-tab__body\">\n\t\t\t\t\t\t<div class=\"cn-email-tab__line\">\n\t\t\t\t\t\t\t<span class=\"cn-email-tab__subject\" :title=\"row.subject\">{{ row.subject }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-email-tab__date\">\n\t\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\t\tv-if=\"row.dateValid\"\n\t\t\t\t\t\t\t\t\t:timestamp=\"row.dateMs\"\n\t\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t\t\t<template v-else>{{ row.dateRaw }}</template>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-email-tab__sender\" :title=\"row.sender\">\n\t\t\t\t\t\t\t{{ row.sender }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"row.snippet\" class=\"cn-email-tab__snippet\" :title=\"row.snippet\">\n\t\t\t\t\t\t\t{{ row.snippet }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</component>\n\t\t\t</li>\n\t\t</ul>\n\t\t<NcButton\n\t\t\tv-if=\"hasMore === true\"\n\t\t\tvariant=\"tertiary\"\n\t\t\t:wide=\"true\"\n\t\t\t:disabled=\"loadingMore\"\n\t\t\tclass=\"cn-sidebar-tab__load-more\"\n\t\t\t@click=\"loadMore\">\n\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ loadMoreLabel }}\n\t\t</NcButton>\n\n\t\t<CnEmailPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcDateTime, NcLoadingIcon } from '@nextcloud/vue'\nimport EmailEditOutline from 'vue-material-design-icons/EmailEditOutline.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport CnEmailPicker from '../../../components/CnEmailPicker/CnEmailPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnEmailTab — sidebar tab rendering the full list of Nextcloud Mail\n * messages linked to an OpenRegister object.\n *\n * Basic usage\n * ```vue\n * <CnEmailTab\n * :object-id=\"objectId\"\n * :register=\"registerId\"\n * :schema=\"schemaId\" />\n * ```\n */\nexport default {\n\tname: 'CnEmailTab',\n\n\tcomponents: {\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcLoadingIcon,\n\t\tEmailEditOutline,\n\t\tLinkVariant,\n\t\tCnEmailPicker,\n\t},\n\n\tprops: {\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/** Page size for paged fetches. */\n\t\tpageSize: { type: Number, default: 25 },\n\t\t/** Pre-translated empty label. */\n\t\tnoMessagesLabel: { type: String, default: () => t('nextcloud-vue', 'No linked emails yet') },\n\t\t/** Pre-translated error label. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load emails') },\n\t\t/** Pre-translated load-more label. */\n\t\tloadMoreLabel: { type: String, default: () => t('nextcloud-vue', 'Load more') },\n\t\t/** Pre-translated fallback subject label. */\n\t\tnoSubjectLabel: { type: String, default: () => t('nextcloud-vue', '(no subject)') },\n\t\t/** Pre-translated fallback sender label. */\n\t\tunknownSenderLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown sender') },\n\t\t/** Pre-translated link-existing CTA label. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing email') },\n\t\t/** Pre-translated compose-in-Mail CTA label. */\n\t\tcomposeLabel: { type: String, default: () => t('nextcloud-vue', 'Compose in Mail') },\n\t\t/** Base path for the Mail app (for the compose deep-link). */\n\t\tmailAppPath: { type: String, default: '/index.php/apps/mail' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tmessages: [],\n\t\t\ttotal: 0,\n\t\t\tnextCursor: null,\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\terrored: false,\n\t\t\tpickerOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether a further page is available.\n\t\t *\n\t\t * Prefers the backend's `nextCursor` (Tier-3 standardized\n\t\t * `{items, total, nextCursor}` envelope from the generic\n\t\t * integrations endpoint). Falls back to the count comparison\n\t\t * for any backend that still omits the cursor.\n\t\t *\n\t\t * @return {boolean} True when more messages can be loaded.\n\t\t */\n\t\thasMore() {\n\t\t\tif (this.nextCursor !== null) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.messages.length < this.total\n\t\t},\n\n\t\t/**\n\t\t * Pre-computed, template-safe view rows mirroring NC Mail.\n\t\t *\n\t\t * All optional-chaining / nullish logic lives here (in JS) so the\n\t\t * buble-compiled Vue 2 template stays free of `?.` / `??`.\n\t\t *\n\t\t * @return {object[]} One descriptor per message.\n\t\t */\n\t\trows() {\n\t\t\treturn this.messages.map((message) => {\n\t\t\t\tconst dateRaw = this.rawWhen(message)\n\t\t\t\tconst d = dateRaw === '' ? null : new Date(dateRaw)\n\t\t\t\tconst dateValid = d !== null && Number.isNaN(d.getTime()) === false\n\t\t\t\tconst sender = this.formatSender(message)\n\t\t\t\treturn {\n\t\t\t\t\tid: message.id,\n\t\t\t\t\tmessage,\n\t\t\t\t\tsubject: this.formatSubject(message),\n\t\t\t\t\tsender,\n\t\t\t\t\tsnippet: this.formatSnippet(message),\n\t\t\t\t\tunread: this.isUnread(message),\n\t\t\t\t\tdateValid,\n\t\t\t\t\tdateMs: dateValid ? d.getTime() : 0,\n\t\t\t\t\tdateRaw: String(dateRaw),\n\t\t\t\t\tavatarName: sender,\n\t\t\t\t\tavatarUser: this.senderEmail(message),\n\t\t\t\t\thref: this.threadUrl(message),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Deep-link into NC Mail's new-message composer.\n\t\t *\n\t\t * AD-2 (Mail owns compose / OR owns the link): we do NOT\n\t\t * implement an in-app composer. \"Compose in Mail\" opens NC Mail\n\t\t * in a new tab with the new-message view; once the user has\n\t\t * sent, they return to OR and link the sent message via the\n\t\t * picker.\n\t\t *\n\t\t * @return {string} The composer URL.\n\t\t */\n\t\tcomposeUrl() {\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl('/apps/mail/box/draft')\n\t\t\t\t: `${this.mailAppPath}/box/draft`\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.reset()\n\t\t\t\t\tthis.fetchMessages()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\treset() {\n\t\t\tthis.messages = []\n\t\t\tthis.total = 0\n\t\t\tthis.nextCursor = null\n\t\t\tthis.errored = false\n\t\t},\n\n\t\tasync fetchMessages(append = false) {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (append === true) {\n\t\t\t\tthis.loadingMore = true\n\t\t\t} else {\n\t\t\t\tthis.loading = true\n\t\t\t}\n\t\t\tthis.errored = false\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ _limit: String(this.pageSize) })\n\t\t\t\t// The Tier-3 envelope's `nextCursor` is the next zero-indexed\n\t\t\t\t// page number consumed by `_page`; walk it on append.\n\t\t\t\tif (append === true && this.nextCursor !== null) {\n\t\t\t\t\tparams.set('_page', String(this.nextCursor))\n\t\t\t\t} else {\n\t\t\t\t\tparams.set('_page', '0')\n\t\t\t\t}\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/email?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.items || data.results || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tconst items = Array.isArray(list) === true ? list : []\n\t\t\t\t\tthis.messages = append === true ? [...this.messages, ...items] : items\n\t\t\t\t\tthis.total = typeof data.total === 'number' ? data.total : this.messages.length\n\t\t\t\t\tconst next = data.nextCursor\n\t\t\t\t\tthis.nextCursor = (next === null || next === undefined) ? null : next\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\n\t\t\t\t\tif (append !== true) {\n\t\t\t\t\t\tthis.messages = []\n\t\t\t\t\t}\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('[CnEmailTab] failed to fetch linked emails', err)\n\t\t\t\tthis.errored = true\n\t\t\t\tif (append !== true) {\n\t\t\t\t\tthis.messages = []\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t},\n\n\t\tloadMore() {\n\t\t\tif (this.nextCursor === null && this.messages.length >= this.total) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchMessages(true)\n\t\t},\n\n\t\tformatSubject(message) {\n\t\t\tconst raw = message.subject\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.noSubjectLabel\n\t\t},\n\n\t\tformatSender(message) {\n\t\t\tconst raw = message.senderName || message.fromName || message.sender || message.from\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.unknownSenderLabel\n\t\t},\n\n\t\t/**\n\t\t * Best-effort bare email address for the NcAvatar `user` seed so\n\t\t * gravatar/initials are stable per correspondent.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {string} An email-ish identifier, or '' when unknown.\n\t\t */\n\t\tsenderEmail(message) {\n\t\t\tconst candidate = message.senderEmail || message.fromEmail || message.sender || message.from\n\t\t\tif (typeof candidate === 'string' && candidate.indexOf('@') !== -1) {\n\t\t\t\treturn candidate.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * One-line preview snippet, mirroring NC Mail's message list.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {string} A trimmed snippet, or '' when none is available.\n\t\t */\n\t\tformatSnippet(message) {\n\t\t\tconst raw = message.preview || message.snippet || message.summary || message.bodyPreview\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the message is unread, tolerant of the several shapes\n\t\t * the backend may report read-state in.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {boolean} True when the message has not been read.\n\t\t */\n\t\tisUnread(message) {\n\t\t\tif (typeof message.unread === 'boolean') {\n\t\t\t\treturn message.unread\n\t\t\t}\n\t\t\tif (typeof message.isRead === 'boolean') {\n\t\t\t\treturn message.isRead === false\n\t\t\t}\n\t\t\tif (typeof message.seen === 'boolean') {\n\t\t\t\treturn message.seen === false\n\t\t\t}\n\t\t\tif (Array.isArray(message.flags)) {\n\t\t\t\treturn message.flags.indexOf('seen') === -1 && message.flags.indexOf('\\\\Seen') === -1\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\trawWhen(message) {\n\t\t\tconst raw = message.mailDate || message.date || message.linkedAt\n\t\t\tif (raw === undefined || raw === null || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t// Thread deep-link into NC Mail; undefined when the row lacks ids.\n\t\tthreadUrl(message) {\n\t\t\tconst accountId = message.mailAccountId\n\t\t\tconst messageId = message.mailMessageId\n\t\t\tif (accountId === undefined || accountId === null || messageId === undefined || messageId === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl(`/apps/mail/box/${accountId}/thread/${messageId}`)\n\t\t\t\t: `/index.php/apps/mail/box/${accountId}/thread/${messageId}`\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/emails`),\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tif (response.ok === true || response.status === 409) {\n\t\t\t\t\t// 409 = already-linked; treated as success since the row\n\t\t\t\t\t// already exists for this object.\n\t\t\t\t\tthis.reset()\n\t\t\t\t\tawait this.fetchMessages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\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('[CnEmailTab] link failed', err)\n\t\t\t\tthis.errored = true\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-sidebar-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-sidebar-tab__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 24px 0;\n}\n\n.cn-sidebar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-sidebar-tab__empty--error {\n\tcolor: var(--color-error);\n}\n\n.cn-sidebar-tab__load-more {\n\tmargin-top: 8px;\n}\n\n.cn-email-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmargin-bottom: 8px;\n}\n\n/* NC-Mail-style message list */\n.cn-email-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-email-tab__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\tpadding: 8px 6px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tcolor: inherit;\n\ttext-decoration: none;\n\tcursor: pointer;\n\tposition: relative;\n}\n\n.cn-email-tab__row:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-email-tab__unread-dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\talign-self: center;\n\tvisibility: hidden;\n}\n\n.cn-email-tab__unread-dot.is-shown {\n\tvisibility: visible;\n}\n\n.cn-email-tab__unread-dot.is-shown::before {\n\tcontent: '';\n\tdisplay: block;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground-color: var(--cn-email-accent, var(--color-primary-element));\n}\n\n.cn-email-tab__avatar {\n\tflex: 0 0 auto;\n}\n\n.cn-email-tab__body {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-email-tab__line {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 8px;\n}\n\n.cn-email-tab__subject {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__row--unread .cn-email-tab__subject {\n\tfont-weight: bold;\n}\n\n.cn-email-tab__date {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__sender {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__snippet {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmargin-top: 1px;\n}\n</style>\n"],"names":["_createElementVNode","_createVNode","_createTextVNode","_createElementBlock","_toDisplayString","_openBlock","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_mergeProps","_normalizeClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnEmailTab.vue3.js","sources":["../../../../../src/integrations/builtin/email/CnEmailTab.vue?vue&type=template&id=0bcebbac&lang.js"],"sourcesContent":["<!--\n CnEmailTab — sidebar tab for the `email` integration.\n\n Renders the full list of Nextcloud Mail messages linked to an OR\n object. Backed by `EmailService` via the registry endpoint\n (`/integrations/email`). Paged via load-more; rows deep-link into the\n NC Mail app per AD-2 (Mail owns compose, OR owns the link).\n\n Visual fidelity: mirrors the real NC Mail message list — a sender\n avatar, a bold subject, a \"sender · date\" subline, a one-line snippet\n preview, and an unread indicator (accent dot + bold subject) when the\n message has not been read.\n\n Storage strategy: link-table (`openregister_email_links`). Per design\n AD-1 the tab does NOT compose — users open Mail, send, return and\n link via the \"Link existing\" picker (compose UI tracked separately).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-email-tab\">\n\t\t<div class=\"cn-email-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"composeUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<EmailEditOutline :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ composeLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"cn-sidebar-tab__loading\">\n\t\t\t<NcLoadingIcon />\n\t\t</div>\n\t\t<div v-else-if=\"errored === true\" class=\"cn-sidebar-tab__empty cn-sidebar-tab__empty--error\">\n\t\t\t{{ errorLabel }}\n\t\t</div>\n\t\t<div v-else-if=\"messages.length === 0\" class=\"cn-sidebar-tab__empty\">\n\t\t\t{{ noMessagesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-email-tab__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"row.id\"\n\t\t\t\tclass=\"cn-email-tab__item\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"row.href ? 'a' : 'div'\"\n\t\t\t\t\tclass=\"cn-email-tab__row\"\n\t\t\t\t\t:class=\"{ 'cn-email-tab__row--unread': row.unread }\"\n\t\t\t\t\tv-bind=\"row.href ? { href: row.href, target: '_blank', rel: 'noopener noreferrer' } : {}\">\n\t\t\t\t\t<span class=\"cn-email-tab__unread-dot\" :class=\"{ 'is-shown': row.unread }\" />\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\tclass=\"cn-email-tab__avatar\"\n\t\t\t\t\t\t:size=\"36\"\n\t\t\t\t\t\t:displayName=\"row.avatarName\"\n\t\t\t\t\t\t:user=\"row.avatarUser\"\n\t\t\t\t\t\t:isNoUser=\"true\" />\n\t\t\t\t\t<div class=\"cn-email-tab__body\">\n\t\t\t\t\t\t<div class=\"cn-email-tab__line\">\n\t\t\t\t\t\t\t<span class=\"cn-email-tab__subject\" :title=\"row.subject\">{{ row.subject }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-email-tab__date\">\n\t\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\t\tv-if=\"row.dateValid\"\n\t\t\t\t\t\t\t\t\t:timestamp=\"row.dateMs\"\n\t\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t\t\t<template v-else>{{ row.dateRaw }}</template>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-email-tab__sender\" :title=\"row.sender\">\n\t\t\t\t\t\t\t{{ row.sender }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div v-if=\"row.snippet\" class=\"cn-email-tab__snippet\" :title=\"row.snippet\">\n\t\t\t\t\t\t\t{{ row.snippet }}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</component>\n\t\t\t</li>\n\t\t</ul>\n\t\t<NcButton\n\t\t\tv-if=\"hasMore === true\"\n\t\t\tvariant=\"tertiary\"\n\t\t\t:wide=\"true\"\n\t\t\t:disabled=\"loadingMore\"\n\t\t\tclass=\"cn-sidebar-tab__load-more\"\n\t\t\t@click=\"loadMore\">\n\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ loadMoreLabel }}\n\t\t</NcButton>\n\n\t\t<CnEmailPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcDateTime, NcLoadingIcon } from '@nextcloud/vue'\nimport EmailEditOutline from 'vue-material-design-icons/EmailEditOutline.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport CnEmailPicker from '../../../components/CnEmailPicker/CnEmailPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnEmailTab — sidebar tab rendering the full list of Nextcloud Mail\n * messages linked to an OpenRegister object.\n *\n * Basic usage\n * ```vue\n * <CnEmailTab\n * :object-id=\"objectId\"\n * :register=\"registerId\"\n * :schema=\"schemaId\" />\n * ```\n */\nexport default {\n\tname: 'CnEmailTab',\n\n\tcomponents: {\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcLoadingIcon,\n\t\tEmailEditOutline,\n\t\tLinkVariant,\n\t\tCnEmailPicker,\n\t},\n\n\tprops: {\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/** Page size for paged fetches. */\n\t\tpageSize: { type: Number, default: 25 },\n\t\t/** Pre-translated empty label. */\n\t\tnoMessagesLabel: { type: String, default: () => t('nextcloud-vue', 'No linked emails yet') },\n\t\t/** Pre-translated error label. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load emails') },\n\t\t/** Pre-translated load-more label. */\n\t\tloadMoreLabel: { type: String, default: () => t('nextcloud-vue', 'Load more') },\n\t\t/** Pre-translated fallback subject label. */\n\t\tnoSubjectLabel: { type: String, default: () => t('nextcloud-vue', '(no subject)') },\n\t\t/** Pre-translated fallback sender label. */\n\t\tunknownSenderLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown sender') },\n\t\t/** Pre-translated link-existing CTA label. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing email') },\n\t\t/** Pre-translated compose-in-Mail CTA label. */\n\t\tcomposeLabel: { type: String, default: () => t('nextcloud-vue', 'Compose in Mail') },\n\t\t/** Base path for the Mail app (for the compose deep-link). */\n\t\tmailAppPath: { type: String, default: '/index.php/apps/mail' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tmessages: [],\n\t\t\ttotal: 0,\n\t\t\tnextCursor: null,\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\terrored: false,\n\t\t\tpickerOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether a further page is available.\n\t\t *\n\t\t * Prefers the backend's `nextCursor` (Tier-3 standardized\n\t\t * `{items, total, nextCursor}` envelope from the generic\n\t\t * integrations endpoint). Falls back to the count comparison\n\t\t * for any backend that still omits the cursor.\n\t\t *\n\t\t * @return {boolean} True when more messages can be loaded.\n\t\t */\n\t\thasMore() {\n\t\t\tif (this.nextCursor !== null) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.messages.length < this.total\n\t\t},\n\n\t\t/**\n\t\t * Pre-computed, template-safe view rows mirroring NC Mail.\n\t\t *\n\t\t * All optional-chaining / nullish logic lives here (in JS) so the\n\t\t * buble-compiled Vue 2 template stays free of `?.` / `??`.\n\t\t *\n\t\t * @return {object[]} One descriptor per message.\n\t\t */\n\t\trows() {\n\t\t\treturn this.messages.map((message) => {\n\t\t\t\tconst dateRaw = this.rawWhen(message)\n\t\t\t\tconst d = dateRaw === '' ? null : new Date(dateRaw)\n\t\t\t\tconst dateValid = d !== null && Number.isNaN(d.getTime()) === false\n\t\t\t\tconst sender = this.formatSender(message)\n\t\t\t\treturn {\n\t\t\t\t\tid: message.id,\n\t\t\t\t\tmessage,\n\t\t\t\t\tsubject: this.formatSubject(message),\n\t\t\t\t\tsender,\n\t\t\t\t\tsnippet: this.formatSnippet(message),\n\t\t\t\t\tunread: this.isUnread(message),\n\t\t\t\t\tdateValid,\n\t\t\t\t\tdateMs: dateValid ? d.getTime() : 0,\n\t\t\t\t\tdateRaw: String(dateRaw),\n\t\t\t\t\tavatarName: sender,\n\t\t\t\t\tavatarUser: this.senderEmail(message),\n\t\t\t\t\thref: this.threadUrl(message),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Deep-link into NC Mail's new-message composer.\n\t\t *\n\t\t * AD-2 (Mail owns compose / OR owns the link): we do NOT\n\t\t * implement an in-app composer. \"Compose in Mail\" opens NC Mail\n\t\t * in a new tab with the new-message view; once the user has\n\t\t * sent, they return to OR and link the sent message via the\n\t\t * picker.\n\t\t *\n\t\t * @return {string} The composer URL.\n\t\t */\n\t\tcomposeUrl() {\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl('/apps/mail/box/draft')\n\t\t\t\t: `${this.mailAppPath}/box/draft`\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.reset()\n\t\t\t\t\tthis.fetchMessages()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\treset() {\n\t\t\tthis.messages = []\n\t\t\tthis.total = 0\n\t\t\tthis.nextCursor = null\n\t\t\tthis.errored = false\n\t\t},\n\n\t\tasync fetchMessages(append = false) {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (append === true) {\n\t\t\t\tthis.loadingMore = true\n\t\t\t} else {\n\t\t\t\tthis.loading = true\n\t\t\t}\n\t\t\tthis.errored = false\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ _limit: String(this.pageSize) })\n\t\t\t\t// The Tier-3 envelope's `nextCursor` is the next zero-indexed\n\t\t\t\t// page number consumed by `_page`; walk it on append.\n\t\t\t\tif (append === true && this.nextCursor !== null) {\n\t\t\t\t\tparams.set('_page', String(this.nextCursor))\n\t\t\t\t} else {\n\t\t\t\t\tparams.set('_page', '0')\n\t\t\t\t}\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/email?${params.toString()}`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (response.ok === true) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.items || data.results || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tconst items = Array.isArray(list) === true ? list : []\n\t\t\t\t\tthis.messages = append === true ? [...this.messages, ...items] : items\n\t\t\t\t\tthis.total = typeof data.total === 'number' ? data.total : this.messages.length\n\t\t\t\t\tconst next = data.nextCursor\n\t\t\t\t\tthis.nextCursor = (next === null || next === undefined) ? null : next\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\n\t\t\t\t\tif (append !== true) {\n\t\t\t\t\t\tthis.messages = []\n\t\t\t\t\t}\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('[CnEmailTab] failed to fetch linked emails', err)\n\t\t\t\tthis.errored = true\n\t\t\t\tif (append !== true) {\n\t\t\t\t\tthis.messages = []\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t},\n\n\t\tloadMore() {\n\t\t\tif (this.nextCursor === null && this.messages.length >= this.total) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchMessages(true)\n\t\t},\n\n\t\tformatSubject(message) {\n\t\t\tconst raw = message.subject\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.noSubjectLabel\n\t\t},\n\n\t\tformatSender(message) {\n\t\t\tconst raw = message.senderName || message.fromName || message.sender || message.from\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw\n\t\t\t}\n\t\t\treturn this.unknownSenderLabel\n\t\t},\n\n\t\t/**\n\t\t * Best-effort bare email address for the NcAvatar `user` seed so\n\t\t * gravatar/initials are stable per correspondent.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {string} An email-ish identifier, or '' when unknown.\n\t\t */\n\t\tsenderEmail(message) {\n\t\t\tconst candidate = message.senderEmail || message.fromEmail || message.sender || message.from\n\t\t\tif (typeof candidate === 'string' && candidate.indexOf('@') !== -1) {\n\t\t\t\treturn candidate.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * One-line preview snippet, mirroring NC Mail's message list.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {string} A trimmed snippet, or '' when none is available.\n\t\t */\n\t\tformatSnippet(message) {\n\t\t\tconst raw = message.preview || message.snippet || message.summary || message.bodyPreview\n\t\t\tif (typeof raw === 'string' && raw.trim() !== '') {\n\t\t\t\treturn raw.trim()\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the message is unread, tolerant of the several shapes\n\t\t * the backend may report read-state in.\n\t\t *\n\t\t * @param {object} message The message record.\n\t\t * @return {boolean} True when the message has not been read.\n\t\t */\n\t\tisUnread(message) {\n\t\t\tif (typeof message.unread === 'boolean') {\n\t\t\t\treturn message.unread\n\t\t\t}\n\t\t\tif (typeof message.isRead === 'boolean') {\n\t\t\t\treturn message.isRead === false\n\t\t\t}\n\t\t\tif (typeof message.seen === 'boolean') {\n\t\t\t\treturn message.seen === false\n\t\t\t}\n\t\t\tif (Array.isArray(message.flags)) {\n\t\t\t\treturn message.flags.indexOf('seen') === -1 && message.flags.indexOf('\\\\Seen') === -1\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\trawWhen(message) {\n\t\t\tconst raw = message.mailDate || message.date || message.linkedAt\n\t\t\tif (raw === undefined || raw === null || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t// Thread deep-link into NC Mail; undefined when the row lacks ids.\n\t\tthreadUrl(message) {\n\t\t\tconst accountId = message.mailAccountId\n\t\t\tconst messageId = message.mailMessageId\n\t\t\tif (accountId === undefined || accountId === null || messageId === undefined || messageId === null) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn (typeof OC !== 'undefined' && typeof OC.generateUrl === 'function')\n\t\t\t\t? OC.generateUrl(`/apps/mail/box/${accountId}/thread/${messageId}`)\n\t\t\t\t: `/index.php/apps/mail/box/${accountId}/thread/${messageId}`\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/emails`),\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tif (response.ok === true || response.status === 409) {\n\t\t\t\t\t// 409 = already-linked; treated as success since the row\n\t\t\t\t\t// already exists for this object.\n\t\t\t\t\tthis.reset()\n\t\t\t\t\tawait this.fetchMessages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.errored = true\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('[CnEmailTab] link failed', err)\n\t\t\t\tthis.errored = true\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-sidebar-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-sidebar-tab__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 24px 0;\n}\n\n.cn-sidebar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-sidebar-tab__empty--error {\n\tcolor: var(--color-error);\n}\n\n.cn-sidebar-tab__load-more {\n\tmargin-top: 8px;\n}\n\n.cn-email-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmargin-bottom: 8px;\n}\n\n/* NC-Mail-style message list */\n.cn-email-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-email-tab__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\tpadding: 8px 6px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tcolor: inherit;\n\ttext-decoration: none;\n\tposition: relative;\n}\n\n/* Only a row with Mail ids is a link; the rest must not look clickable. */\na.cn-email-tab__row {\n\tcursor: pointer;\n}\n\na.cn-email-tab__row:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-email-tab__unread-dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\talign-self: center;\n\tvisibility: hidden;\n}\n\n.cn-email-tab__unread-dot.is-shown {\n\tvisibility: visible;\n}\n\n.cn-email-tab__unread-dot.is-shown::before {\n\tcontent: '';\n\tdisplay: block;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground-color: var(--cn-email-accent, var(--color-primary-element));\n}\n\n.cn-email-tab__avatar {\n\tflex: 0 0 auto;\n}\n\n.cn-email-tab__body {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-email-tab__line {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 8px;\n}\n\n.cn-email-tab__subject {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__row--unread .cn-email-tab__subject {\n\tfont-weight: bold;\n}\n\n.cn-email-tab__date {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__sender {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-email-tab__snippet {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmargin-top: 1px;\n}\n</style>\n"],"names":["_createVNode","_createTextVNode","_createElementBlock","_toDisplayString","_openBlock","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_mergeProps","_normalizeClass","_createElementVNode"],"mappings":"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"}
|