@oneuptime/common 12.0.13 → 12.0.14
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/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +14 -0
- package/Models/DatabaseModels/UserCall.ts +94 -3
- package/Models/DatabaseModels/UserEmail.ts +94 -3
- package/Models/DatabaseModels/UserIncomingCallNumber.ts +94 -3
- package/Models/DatabaseModels/UserPush.ts +37 -0
- package/Models/DatabaseModels/UserSMS.ts +94 -3
- package/Models/DatabaseModels/UserWhatsApp.ts +93 -2
- package/Server/API/BaseAPI.ts +79 -14
- package/Server/API/UserCallAPI.ts +53 -45
- package/Server/API/UserEmailAPI.ts +53 -44
- package/Server/API/UserIncomingCallNumberAPI.ts +48 -38
- package/Server/API/UserNotificationMethodAdminAPI.ts +481 -0
- package/Server/API/UserPushAPI.ts +126 -2
- package/Server/API/UserSmsAPI.ts +53 -44
- package/Server/API/UserWhatsAppAPI.ts +53 -50
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.ts +154 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.ts +45 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/VerificationCodeRateLimit.ts +586 -0
- package/Server/Services/AnalyticsDatabaseService.ts +169 -0
- package/Server/Services/DatabaseService.ts +101 -0
- package/Server/Services/LogAggregationService.ts +5 -0
- package/Server/Services/PushNotificationService.ts +127 -12
- package/Server/Services/TeamMemberService.ts +53 -13
- package/Server/Services/UserCallService.ts +55 -24
- package/Server/Services/UserEmailService.ts +83 -17
- package/Server/Services/UserIncomingCallNumberService.ts +49 -85
- package/Server/Services/UserNotificationMethodAdminService.ts +1527 -0
- package/Server/Services/UserNotificationRuleService.ts +26 -0
- package/Server/Services/UserPushService.ts +89 -0
- package/Server/Services/UserSmsService.ts +50 -17
- package/Server/Services/UserWhatsAppService.ts +48 -15
- package/Server/Types/Database/QueryHelper.ts +30 -0
- package/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.ts +59 -0
- package/Server/Utils/ChannelVerification.ts +510 -0
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +13 -16
- package/Server/Utils/Monitor/MonitorAlert.ts +24 -21
- package/Server/Utils/Monitor/MonitorIncident.ts +21 -18
- package/Server/Utils/Monitor/MonitorResourceContext.ts +107 -0
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +442 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +3 -3
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +96 -28
- package/Server/Utils/TotpAuth.ts +137 -9
- package/Server/Utils/UserRegistrationToken.ts +182 -0
- package/Server/Utils/VerificationCode.ts +134 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +173 -94
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +1151 -0
- package/Tests/App/Dashboard/AdminUserOnCallPages.test.tsx +905 -0
- package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +338 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1 -1
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +28 -0
- package/Tests/Models/UserPushCriticalAlertColumn.test.ts +172 -0
- package/Tests/Server/API/BaseAPIGetListParallel.test.ts +519 -0
- package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +543 -0
- package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +108 -18
- package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +963 -0
- package/Tests/Server/API/UserPushCriticalAlertsApi.test.ts +114 -0
- package/Tests/Server/API/UserSmsApi.test.ts +107 -14
- package/Tests/Server/API/UserTotpAuthAPI.test.ts +427 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +315 -0
- package/Tests/Server/Middleware/VerificationCodeRateLimit.test.ts +775 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +10 -62
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +33 -13
- package/Tests/Server/Services/AnalyticsDatabaseSortKeyBoundary.test.ts +658 -0
- package/Tests/Server/Services/CriticalOnCallAlertDelivery.test.ts +458 -0
- package/Tests/Server/Services/CriticalPushAlertPayload.test.ts +301 -0
- package/Tests/Server/Services/DatabaseServiceAtomicIncrement.test.ts +143 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1 -0
- package/Tests/Server/Services/LogAggregationScanMemory.test.ts +421 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
- package/Tests/Server/Services/NotificationChannelCodeIssuance.test.ts +401 -0
- package/Tests/Server/Services/PushNotificationDeliveryOptions.test.ts +204 -0
- package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +21 -1
- package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +290 -0
- package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +948 -0
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1 -0
- package/Tests/Server/Services/UserPushCriticalAlertToggle.test.ts +255 -0
- package/Tests/Server/Services/UserTotpAuthEnrolment.test.ts +218 -0
- package/Tests/Server/TestingUtils/AuthenticatorApp.ts +248 -0
- package/Tests/Server/Types/Database/Permissions/NotificationChannelVerificationColumns.test.ts +135 -0
- package/Tests/Server/Types/Database/Permissions/OnCallScheduleRelationSelect.test.ts +504 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAnyOf.test.ts +91 -0
- package/Tests/Server/Types/Workflow/Components/JsonToText.test.ts +384 -0
- package/Tests/Server/Types/Workflow/Components/MergeJson.test.ts +403 -0
- package/Tests/Server/Utils/Browser.test.ts +162 -0
- package/Tests/Server/Utils/ChannelVerification.test.ts +689 -0
- package/Tests/Server/Utils/Express.test.ts +371 -0
- package/Tests/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.test.ts +707 -0
- package/Tests/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.test.ts +698 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingEmailBodyCriteria.test.ts +243 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestBodyCriteria.test.ts +278 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +160 -20
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +13 -8
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +162 -18
- package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +349 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +690 -0
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +7 -2
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +92 -18
- package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +267 -0
- package/Tests/Server/Utils/Telemetry/OneuptimeLabel.test.ts +439 -0
- package/Tests/Server/Utils/TotpAuth.test.ts +719 -0
- package/Tests/Server/Utils/UserRegistrationToken.test.ts +322 -0
- package/Tests/Server/Utils/VerificationCode.test.ts +296 -0
- package/Tests/Types/AutoRemediation/AutoRemediationSuggestionStatus.test.ts +192 -0
- package/Tests/Types/Call/CallRequest.test.ts +189 -0
- package/Tests/Types/Dashboard/DashboardSize.test.ts +226 -0
- package/Tests/Types/Incident/IncidentSlaStatus.test.ts +191 -0
- package/Tests/Types/Metrics/MetricDashboardMetricType.test.ts +278 -0
- package/Tests/Types/Metrics/MetricPipelineRuleType.test.ts +517 -0
- package/Tests/Types/Monitor/IncomingMonitorDefaultCriteria.test.ts +470 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +6 -4
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +262 -5
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
- package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +905 -0
- package/Tests/Types/Service/ServiceLanguage.test.ts +255 -0
- package/Tests/Types/TextRandomGeneration.test.ts +211 -0
- package/Tests/UI/Components/Button.test.tsx +175 -1
- package/Tests/UI/Components/CodeBlockLanguages.test.tsx +236 -0
- package/Tests/UI/Components/CommandPalette/CommandPalette.test.tsx +568 -0
- package/Tests/UI/Components/CommandPalette/CommandPaletteKeyboard.test.tsx +239 -0
- package/Tests/UI/Components/CommandPalette/CommandPaletteProviders.test.tsx +400 -0
- package/Tests/UI/Components/CommandPalette/PaletteFilter.test.ts +234 -0
- package/Tests/UI/Components/CommandPalette/RecentCommands.test.ts +97 -0
- package/Tests/UI/Components/CountModelSideMenuItemGuard.test.tsx +198 -0
- package/Tests/UI/Components/FeedItemSafeMode.test.tsx +1 -1
- package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +267 -0
- package/Tests/UI/Components/List.test.tsx +9 -2
- package/Tests/UI/Components/ListLoadingStates.test.tsx +173 -0
- package/Tests/UI/Components/MarkdownLazy.test.tsx +113 -0
- package/Tests/UI/Components/MarkdownMermaidRetry.test.tsx +110 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +8 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +692 -0
- package/Tests/UI/Components/MoreMenuMotion.test.tsx +118 -0
- package/Tests/UI/Components/NavBarCommandKGating.test.tsx +178 -0
- package/Tests/UI/Components/OrderedStatesList.test.tsx +19 -2
- package/Tests/UI/Components/SideOverMotion.test.tsx +109 -0
- package/Tests/UI/Components/Skeleton.test.tsx +71 -0
- package/Tests/UI/Components/TableLoadingStates.test.tsx +279 -0
- package/Tests/UI/Components/Tabs.test.tsx +24 -0
- package/Tests/UI/Components/Toast.test.tsx +130 -7
- package/Tests/UI/Components/ToastStacking.test.tsx +198 -0
- package/Tests/UI/Utils/Dropdown.test.ts +423 -0
- package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +122 -0
- package/Tests/UI/Utils/ModelListCache.test.ts +338 -0
- package/Tests/UI/Utils/ProjectListCacheInvalidation.test.ts +225 -0
- package/Tests/Utils/API.test.ts +16 -2
- package/Tests/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.test.ts +347 -0
- package/Tests/Utils/Dashboard/Components/DashboardListSharedArgs.test.ts +259 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +466 -0
- package/Tests/Utils/Rum/UrlScrubberSegments.test.ts +362 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +41 -20
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +131 -8
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +48 -8
- package/Types/Monitor/ServiceAlertTemplates.ts +1352 -0
- package/Types/Permission.ts +35 -0
- package/Types/PushNotification/AndroidNotificationChannel.ts +26 -0
- package/Types/PushNotification/PushNotificationMessage.ts +15 -0
- package/Types/PushNotification/PushNotificationRequest.ts +8 -16
- package/Types/Service/ServiceLanguage.ts +144 -0
- package/Types/Text.ts +111 -19
- package/UI/Components/Button/Button.tsx +27 -14
- package/UI/Components/CodeBlock/CodeBlock.tsx +54 -7
- package/UI/Components/CodeBlock/LanguageRegistry.ts +103 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +790 -0
- package/UI/Components/CommandPalette/PaletteFilter.ts +196 -0
- package/UI/Components/CommandPalette/PaletteRow.tsx +173 -0
- package/UI/Components/CommandPalette/RecentCommands.ts +57 -0
- package/UI/Components/CommandPalette/Types.ts +49 -0
- package/UI/Components/CommandPalette/UseProviderSearch.ts +129 -0
- package/UI/Components/Dropdown/Dropdown.tsx +6 -1
- package/UI/Components/Feed/FeedItem.tsx +1 -1
- package/UI/Components/List/List.tsx +37 -5
- package/UI/Components/List/ListSkeleton.tsx +54 -0
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +6 -1
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +71 -26
- package/UI/Components/Modal/Modal.tsx +2 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +11 -5
- package/UI/Components/MoreMenu/MoreMenu.tsx +39 -1
- package/UI/Components/Navbar/NavBar.tsx +14 -1
- package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +50 -5
- package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +40 -1
- package/UI/Components/SideOver/SideOver.tsx +37 -2
- package/UI/Components/Skeleton/Skeleton.tsx +53 -0
- package/UI/Components/Table/Table.tsx +74 -44
- package/UI/Components/Table/TableSkeletonRows.tsx +141 -0
- package/UI/Components/Tabs/Tab.tsx +1 -1
- package/UI/Components/Toast/Toast.tsx +118 -53
- package/UI/Components/Toast/ToastInit.tsx +82 -32
- package/UI/Styles/Theme.css +40 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +20 -10
- package/UI/Utils/ModelListCache.ts +188 -0
- package/UI/Utils/Project.ts +29 -0
- package/Utils/API.ts +6 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +14 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserCall.js +101 -3
- package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserEmail.js +101 -3
- package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js +101 -3
- package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserPush.js +38 -0
- package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSMS.js +101 -3
- package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js +100 -2
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +69 -13
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/UserCallAPI.js +42 -38
- package/build/dist/Server/API/UserCallAPI.js.map +1 -1
- package/build/dist/Server/API/UserEmailAPI.js +42 -38
- package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js +37 -32
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
- package/build/dist/Server/API/UserNotificationMethodAdminAPI.js +341 -0
- package/build/dist/Server/API/UserNotificationMethodAdminAPI.js.map +1 -0
- package/build/dist/Server/API/UserPushAPI.js +95 -2
- package/build/dist/Server/API/UserPushAPI.js.map +1 -1
- package/build/dist/Server/API/UserSmsAPI.js +42 -38
- package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
- package/build/dist/Server/API/UserWhatsAppAPI.js +42 -40
- package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js +89 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/VerificationCodeRateLimit.js +393 -0
- package/build/dist/Server/Middleware/VerificationCodeRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +136 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +74 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +5 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/PushNotificationService.js +69 -30
- package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +43 -7
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserCallService.js +54 -22
- package/build/dist/Server/Services/UserCallService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +78 -17
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserIncomingCallNumberService.js +45 -74
- package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1117 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js +20 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserPushService.js +77 -0
- package/build/dist/Server/Services/UserPushService.js.map +1 -1
- package/build/dist/Server/Services/UserSmsService.js +50 -17
- package/build/dist/Server/Services/UserSmsService.js.map +1 -1
- package/build/dist/Server/Services/UserWhatsAppService.js +48 -15
- package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +27 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js +46 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js.map +1 -1
- package/build/dist/Server/Utils/ChannelVerification.js +348 -0
- package/build/dist/Server/Utils/ChannelVerification.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +13 -12
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +20 -17
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +17 -14
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +95 -0
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +313 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +3 -3
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +67 -26
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
- package/build/dist/Server/Utils/TotpAuth.js +127 -8
- package/build/dist/Server/Utils/TotpAuth.js.map +1 -1
- package/build/dist/Server/Utils/UserRegistrationToken.js +170 -0
- package/build/dist/Server/Utils/UserRegistrationToken.js.map +1 -0
- package/build/dist/Server/Utils/VerificationCode.js +120 -0
- package/build/dist/Server/Utils/VerificationCode.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +42 -21
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +77 -5
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +18 -6
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js +1028 -0
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -0
- package/build/dist/Types/Permission.js +33 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/PushNotification/AndroidNotificationChannel.js +27 -0
- package/build/dist/Types/PushNotification/AndroidNotificationChannel.js.map +1 -0
- package/build/dist/Types/Service/ServiceLanguage.js +96 -0
- package/build/dist/Types/Service/ServiceLanguage.js.map +1 -0
- package/build/dist/Types/Text.js +82 -17
- package/build/dist/Types/Text.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +22 -14
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js +35 -3
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js +57 -0
- package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +439 -0
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/PaletteFilter.js +135 -0
- package/build/dist/UI/Components/CommandPalette/PaletteFilter.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/PaletteRow.js +83 -0
- package/build/dist/UI/Components/CommandPalette/PaletteRow.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/RecentCommands.js +47 -0
- package/build/dist/UI/Components/CommandPalette/RecentCommands.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/Types.js +2 -0
- package/build/dist/UI/Components/CommandPalette/Types.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js +88 -0
- package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js.map +1 -0
- package/build/dist/UI/Components/Dropdown/Dropdown.js +6 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/List/List.js +19 -5
- package/build/dist/UI/Components/List/List.js.map +1 -1
- package/build/dist/UI/Components/List/ListSkeleton.js +21 -0
- package/build/dist/UI/Components/List/ListSkeleton.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +52 -25
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +2 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +9 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +32 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +5 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +29 -5
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideOver/SideOver.js +23 -2
- package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
- package/build/dist/UI/Components/Skeleton/Skeleton.js +23 -0
- package/build/dist/UI/Components/Skeleton/Skeleton.js.map +1 -0
- package/build/dist/UI/Components/Table/Table.js +35 -24
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableSkeletonRows.js +52 -0
- package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -0
- package/build/dist/UI/Components/Tabs/Tab.js +1 -1
- package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
- package/build/dist/UI/Components/Toast/Toast.js +77 -23
- package/build/dist/UI/Components/Toast/Toast.js.map +1 -1
- package/build/dist/UI/Components/Toast/ToastInit.js +37 -15
- package/build/dist/UI/Components/Toast/ToastInit.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -9
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/ModelListCache.js +126 -0
- package/build/dist/UI/Utils/ModelListCache.js.map +1 -0
- package/build/dist/UI/Utils/Project.js +24 -1
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/Utils/API.js +6 -1
- package/build/dist/Utils/API.js.map +1 -1
- package/jest.config.json +1 -1
- package/package.json +2 -3
- package/Server/Utils/Monitor/MonitorClusterContext.ts +0 -182
- package/Tests/__mocks__/otpauth.js +0 -30
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +0 -141
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +0 -1
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import Icon from "../Icon/Icon";
|
|
2
|
+
import KeyboardShortcut, {
|
|
3
|
+
KeyboardShortcutSize,
|
|
4
|
+
KeyboardShortcutVariant,
|
|
5
|
+
} from "../KeyboardShortcut/KeyboardShortcut";
|
|
6
|
+
import KeyboardKey, {
|
|
7
|
+
KeyboardShortcutKey,
|
|
8
|
+
} from "../KeyboardShortcut/KeyboardKey";
|
|
9
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
10
|
+
import useTranslateValue from "../../Utils/Translation";
|
|
11
|
+
import { lockPageScroll, unlockPageScroll } from "../../Utils/PageScrollLock";
|
|
12
|
+
import {
|
|
13
|
+
PaletteCommand,
|
|
14
|
+
PaletteSearchProvider,
|
|
15
|
+
PaletteSearchResult,
|
|
16
|
+
} from "./Types";
|
|
17
|
+
import {
|
|
18
|
+
filterPaletteCommands,
|
|
19
|
+
getPaletteSectionId,
|
|
20
|
+
normalizePaletteQuery,
|
|
21
|
+
PaletteCommandMatch,
|
|
22
|
+
} from "./PaletteFilter";
|
|
23
|
+
import {
|
|
24
|
+
readRecentCommandIds,
|
|
25
|
+
RECENT_COMMANDS_LIMIT,
|
|
26
|
+
writeRecentCommandId,
|
|
27
|
+
} from "./RecentCommands";
|
|
28
|
+
import useProviderSearch, { ProviderSearchSection } from "./UseProviderSearch";
|
|
29
|
+
import PaletteRow from "./PaletteRow";
|
|
30
|
+
import React, {
|
|
31
|
+
FunctionComponent,
|
|
32
|
+
ReactElement,
|
|
33
|
+
useEffect,
|
|
34
|
+
useMemo,
|
|
35
|
+
useRef,
|
|
36
|
+
useState,
|
|
37
|
+
} from "react";
|
|
38
|
+
import { createPortal } from "react-dom";
|
|
39
|
+
|
|
40
|
+
export interface ComponentProps {
|
|
41
|
+
commands: Array<PaletteCommand>;
|
|
42
|
+
searchProviders?: Array<PaletteSearchProvider> | undefined;
|
|
43
|
+
isOpen: boolean;
|
|
44
|
+
onClose: () => void;
|
|
45
|
+
/** When set, selected command ids are persisted here and surface as "Recent". */
|
|
46
|
+
recentStorageKey?: string | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** One selectable row in the flattened keyboard order. */
|
|
50
|
+
interface FlatEntry {
|
|
51
|
+
/** Unique across the whole open palette (recents/provider results are prefixed). */
|
|
52
|
+
id: string;
|
|
53
|
+
title: string;
|
|
54
|
+
description?: string | undefined;
|
|
55
|
+
icon?: IconProp | undefined;
|
|
56
|
+
iconColor?: string | undefined;
|
|
57
|
+
shortcut?: Array<KeyboardShortcutKey> | undefined;
|
|
58
|
+
/** The id recorded into recents — only static commands carry one. */
|
|
59
|
+
recentCommandId: string | null;
|
|
60
|
+
onSelect: () => void;
|
|
61
|
+
flatIndex: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface PaletteSectionView {
|
|
65
|
+
/** Slug used in `command-palette-section-<id>` test ids. */
|
|
66
|
+
id: string;
|
|
67
|
+
title: string;
|
|
68
|
+
/** True while this (provider) section's search is still in flight. */
|
|
69
|
+
isPending: boolean;
|
|
70
|
+
entries: Array<FlatEntry>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const EMPTY_PROVIDERS: Array<PaletteSearchProvider> = [];
|
|
74
|
+
|
|
75
|
+
type HasPendingSectionFunction = (
|
|
76
|
+
sections: Array<PaletteSectionView>,
|
|
77
|
+
) => boolean;
|
|
78
|
+
|
|
79
|
+
const hasPendingSection: HasPendingSectionFunction = (
|
|
80
|
+
sections: Array<PaletteSectionView>,
|
|
81
|
+
): boolean => {
|
|
82
|
+
return sections.some((section: PaletteSectionView) => {
|
|
83
|
+
return section.isPending;
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
type CommandToEntryFunction = (
|
|
88
|
+
command: PaletteCommand,
|
|
89
|
+
idPrefix: string,
|
|
90
|
+
) => Omit<FlatEntry, "flatIndex">;
|
|
91
|
+
|
|
92
|
+
const commandToEntry: CommandToEntryFunction = (
|
|
93
|
+
command: PaletteCommand,
|
|
94
|
+
idPrefix: string,
|
|
95
|
+
): Omit<FlatEntry, "flatIndex"> => {
|
|
96
|
+
return {
|
|
97
|
+
id: `${idPrefix}${command.id}`,
|
|
98
|
+
title: command.title,
|
|
99
|
+
description: command.description,
|
|
100
|
+
icon: command.icon,
|
|
101
|
+
iconColor: command.iconColor,
|
|
102
|
+
shortcut: command.shortcut,
|
|
103
|
+
recentCommandId: command.id,
|
|
104
|
+
onSelect: command.onSelect,
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* The open palette lives in its own component so that every open is a fresh
|
|
110
|
+
* mount: the enter animation, the autofocus, the scroll lock, and the
|
|
111
|
+
* read-once recents snapshot all run per open instead of once per page.
|
|
112
|
+
*/
|
|
113
|
+
interface PanelProps {
|
|
114
|
+
commands: Array<PaletteCommand>;
|
|
115
|
+
searchProviders: Array<PaletteSearchProvider>;
|
|
116
|
+
onClose: () => void;
|
|
117
|
+
recentStorageKey?: string | undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const CommandPalettePanel: FunctionComponent<PanelProps> = (
|
|
121
|
+
props: PanelProps,
|
|
122
|
+
): ReactElement => {
|
|
123
|
+
const [query, setQuery] = useState<string>("");
|
|
124
|
+
const [activeIndex, setActiveIndex] = useState<number>(0);
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* The panel mounts in its closed state and is flipped open one frame later
|
|
128
|
+
* so the browser has something to transition from (Modal's pattern).
|
|
129
|
+
*/
|
|
130
|
+
const [hasEntered, setHasEntered] = useState<boolean>(false);
|
|
131
|
+
|
|
132
|
+
const inputRef: React.RefObject<HTMLInputElement> =
|
|
133
|
+
useRef<HTMLInputElement>(null);
|
|
134
|
+
const rowRefs: React.MutableRefObject<Array<HTMLDivElement | null>> = useRef<
|
|
135
|
+
Array<HTMLDivElement | null>
|
|
136
|
+
>([]);
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* Whatever had focus when the palette opened gets it back on close —
|
|
140
|
+
* captured at first render, before the autofocus effect moves focus into
|
|
141
|
+
* the input (Modal's pattern).
|
|
142
|
+
*/
|
|
143
|
+
const previouslyFocusedElementRef: React.MutableRefObject<HTMLElement | null> =
|
|
144
|
+
useRef<HTMLElement | null>(
|
|
145
|
+
typeof document !== "undefined" &&
|
|
146
|
+
document.activeElement instanceof HTMLElement
|
|
147
|
+
? document.activeElement
|
|
148
|
+
: null,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Kept fresh so the mount-once document listener never calls a stale close.
|
|
152
|
+
const onCloseRef: React.MutableRefObject<() => void> = useRef<() => void>(
|
|
153
|
+
props.onClose,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
onCloseRef.current = props.onClose;
|
|
158
|
+
}, [props.onClose]);
|
|
159
|
+
|
|
160
|
+
// Recently run command ids, read once when the palette opens.
|
|
161
|
+
const [recentCommandIds] = useState<Array<string>>(() => {
|
|
162
|
+
return props.recentStorageKey
|
|
163
|
+
? readRecentCommandIds(props.recentStorageKey)
|
|
164
|
+
: [];
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
/*
|
|
168
|
+
* Callers hand us plain English literals. Run every user-facing string
|
|
169
|
+
* through the same flat-key lookup the navbar uses so localised deployments
|
|
170
|
+
* render localised chrome.
|
|
171
|
+
*/
|
|
172
|
+
const { translateString } = useTranslateValue();
|
|
173
|
+
const tx: (value: string) => string = (value: string): string => {
|
|
174
|
+
return translateString(value) ?? value;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const open: () => void = (): void => {
|
|
179
|
+
setHasEntered(true);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const frame: number = requestAnimationFrame(open);
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
* A hidden or throttled tab never runs an animation frame; the timer is
|
|
186
|
+
* the safety net that skips the transition rather than the palette.
|
|
187
|
+
*/
|
|
188
|
+
const fallbackTimer: ReturnType<typeof setTimeout> = setTimeout(open, 80);
|
|
189
|
+
|
|
190
|
+
return () => {
|
|
191
|
+
cancelAnimationFrame(frame);
|
|
192
|
+
clearTimeout(fallbackTimer);
|
|
193
|
+
};
|
|
194
|
+
}, []);
|
|
195
|
+
|
|
196
|
+
// The page behind the palette must not scroll while it is open.
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
lockPageScroll();
|
|
199
|
+
|
|
200
|
+
return () => {
|
|
201
|
+
unlockPageScroll();
|
|
202
|
+
};
|
|
203
|
+
}, []);
|
|
204
|
+
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
inputRef.current?.focus();
|
|
207
|
+
}, []);
|
|
208
|
+
|
|
209
|
+
/*
|
|
210
|
+
* Escape must dismiss the palette even after focus has escaped the dialog
|
|
211
|
+
* subtree (a click on the backdrop scrollbar, a programmatic blur): the
|
|
212
|
+
* React onKeyDown on the dialog root only hears keys fired inside it, so a
|
|
213
|
+
* document-level listener backs it up while the panel is mounted (Modal's
|
|
214
|
+
* pattern). No double-fire: the React handler preventDefaults AND stops
|
|
215
|
+
* propagation, so an Escape it consumed either never reaches document or
|
|
216
|
+
* arrives flagged defaultPrevented — which is also the flag any other
|
|
217
|
+
* dialog above us would set.
|
|
218
|
+
*/
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
const handleDocumentKeyDown: (event: KeyboardEvent) => void = (
|
|
221
|
+
event: KeyboardEvent,
|
|
222
|
+
): void => {
|
|
223
|
+
if (event.defaultPrevented || event.key !== "Escape") {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Flag the key consumed for listeners after us (window-level ones too).
|
|
228
|
+
event.preventDefault();
|
|
229
|
+
onCloseRef.current();
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
document.addEventListener("keydown", handleDocumentKeyDown);
|
|
233
|
+
|
|
234
|
+
return () => {
|
|
235
|
+
document.removeEventListener("keydown", handleDocumentKeyDown);
|
|
236
|
+
};
|
|
237
|
+
}, []);
|
|
238
|
+
|
|
239
|
+
// Return focus to where it was before the palette opened (Modal's pattern).
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
return () => {
|
|
242
|
+
if (previouslyFocusedElementRef.current?.isConnected) {
|
|
243
|
+
previouslyFocusedElementRef.current.focus();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}, []);
|
|
247
|
+
|
|
248
|
+
const providerSections: Array<ProviderSearchSection> = useProviderSearch(
|
|
249
|
+
props.searchProviders,
|
|
250
|
+
query,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const normalizedQuery: string = normalizePaletteQuery(query);
|
|
254
|
+
|
|
255
|
+
const sections: Array<PaletteSectionView> = useMemo(() => {
|
|
256
|
+
const built: Array<PaletteSectionView> = [];
|
|
257
|
+
|
|
258
|
+
type PushSectionFunction = (
|
|
259
|
+
id: string,
|
|
260
|
+
title: string,
|
|
261
|
+
isPending: boolean,
|
|
262
|
+
entries: Array<Omit<FlatEntry, "flatIndex">>,
|
|
263
|
+
) => void;
|
|
264
|
+
|
|
265
|
+
const pushSection: PushSectionFunction = (
|
|
266
|
+
id: string,
|
|
267
|
+
title: string,
|
|
268
|
+
isPending: boolean,
|
|
269
|
+
entries: Array<Omit<FlatEntry, "flatIndex">>,
|
|
270
|
+
): void => {
|
|
271
|
+
built.push({
|
|
272
|
+
id,
|
|
273
|
+
title,
|
|
274
|
+
isPending,
|
|
275
|
+
// flatIndex is stamped in a single pass after all sections exist.
|
|
276
|
+
entries: entries as Array<FlatEntry>,
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
type GroupByCategoryFunction = (
|
|
281
|
+
commands: Array<PaletteCommand>,
|
|
282
|
+
) => Array<{ category: string; commands: Array<PaletteCommand> }>;
|
|
283
|
+
|
|
284
|
+
// Group commands by category, preserving first-seen order.
|
|
285
|
+
const groupByCategory: GroupByCategoryFunction = (
|
|
286
|
+
commands: Array<PaletteCommand>,
|
|
287
|
+
): Array<{ category: string; commands: Array<PaletteCommand> }> => {
|
|
288
|
+
const order: Array<string> = [];
|
|
289
|
+
const byCategory: Map<string, Array<PaletteCommand>> = new Map();
|
|
290
|
+
commands.forEach((command: PaletteCommand) => {
|
|
291
|
+
const category: string = command.category || "Other";
|
|
292
|
+
if (!byCategory.has(category)) {
|
|
293
|
+
byCategory.set(category, []);
|
|
294
|
+
order.push(category);
|
|
295
|
+
}
|
|
296
|
+
byCategory.get(category)!.push(command);
|
|
297
|
+
});
|
|
298
|
+
return order.map((category: string) => {
|
|
299
|
+
return { category, commands: byCategory.get(category)! };
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
if (!normalizedQuery) {
|
|
304
|
+
// Browsing: "Recent" first (when we have any), then the full catalog.
|
|
305
|
+
if (recentCommandIds.length > 0) {
|
|
306
|
+
const commandById: Map<string, PaletteCommand> = new Map();
|
|
307
|
+
props.commands.forEach((command: PaletteCommand) => {
|
|
308
|
+
commandById.set(command.id, command);
|
|
309
|
+
});
|
|
310
|
+
const recentCommands: Array<PaletteCommand> = [];
|
|
311
|
+
recentCommandIds.forEach((commandId: string) => {
|
|
312
|
+
const command: PaletteCommand | undefined =
|
|
313
|
+
commandById.get(commandId);
|
|
314
|
+
if (command) {
|
|
315
|
+
recentCommands.push(command);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
if (recentCommands.length > 0) {
|
|
319
|
+
pushSection(
|
|
320
|
+
"recent",
|
|
321
|
+
"Recent",
|
|
322
|
+
false,
|
|
323
|
+
recentCommands
|
|
324
|
+
.slice(0, RECENT_COMMANDS_LIMIT)
|
|
325
|
+
/*
|
|
326
|
+
* Recents repeat commands that also sit in their category
|
|
327
|
+
* section below — prefix the entry id so option test ids and
|
|
328
|
+
* DOM ids stay unique.
|
|
329
|
+
*/
|
|
330
|
+
.map((command: PaletteCommand) => {
|
|
331
|
+
return commandToEntry(command, "recent-");
|
|
332
|
+
}),
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
groupByCategory(props.commands).forEach(
|
|
338
|
+
(group: { category: string; commands: Array<PaletteCommand> }) => {
|
|
339
|
+
pushSection(
|
|
340
|
+
getPaletteSectionId(group.category),
|
|
341
|
+
group.category,
|
|
342
|
+
false,
|
|
343
|
+
group.commands.map((command: PaletteCommand) => {
|
|
344
|
+
return commandToEntry(command, "");
|
|
345
|
+
}),
|
|
346
|
+
);
|
|
347
|
+
},
|
|
348
|
+
);
|
|
349
|
+
} else {
|
|
350
|
+
/*
|
|
351
|
+
* Searching: ranked command matches grouped by category — the ranked
|
|
352
|
+
* order decides both row order within a section and which category
|
|
353
|
+
* surfaces first — then one section per async provider.
|
|
354
|
+
*/
|
|
355
|
+
const matches: Array<PaletteCommandMatch> = filterPaletteCommands(
|
|
356
|
+
props.commands,
|
|
357
|
+
query,
|
|
358
|
+
);
|
|
359
|
+
groupByCategory(
|
|
360
|
+
matches.map((match: PaletteCommandMatch) => {
|
|
361
|
+
return match.command;
|
|
362
|
+
}),
|
|
363
|
+
).forEach(
|
|
364
|
+
(group: { category: string; commands: Array<PaletteCommand> }) => {
|
|
365
|
+
pushSection(
|
|
366
|
+
getPaletteSectionId(group.category),
|
|
367
|
+
group.category,
|
|
368
|
+
false,
|
|
369
|
+
group.commands.map((command: PaletteCommand) => {
|
|
370
|
+
return commandToEntry(command, "");
|
|
371
|
+
}),
|
|
372
|
+
);
|
|
373
|
+
},
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
providerSections.forEach((providerSection: ProviderSearchSection) => {
|
|
377
|
+
// A provider with nothing to show (or that failed) has no section.
|
|
378
|
+
if (
|
|
379
|
+
!providerSection.isPending &&
|
|
380
|
+
providerSection.results.length === 0
|
|
381
|
+
) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
pushSection(
|
|
385
|
+
providerSection.provider.id,
|
|
386
|
+
providerSection.provider.title,
|
|
387
|
+
providerSection.isPending,
|
|
388
|
+
providerSection.results.map((result: PaletteSearchResult) => {
|
|
389
|
+
return {
|
|
390
|
+
id: `${providerSection.provider.id}-${result.id}`,
|
|
391
|
+
title: result.title,
|
|
392
|
+
description: result.description,
|
|
393
|
+
icon: result.icon,
|
|
394
|
+
iconColor: result.iconColor,
|
|
395
|
+
shortcut: undefined,
|
|
396
|
+
recentCommandId: null,
|
|
397
|
+
onSelect: result.onSelect,
|
|
398
|
+
};
|
|
399
|
+
}),
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Stamp each entry with its index in the flattened keyboard order.
|
|
405
|
+
let flatIndex: number = 0;
|
|
406
|
+
built.forEach((section: PaletteSectionView) => {
|
|
407
|
+
section.entries.forEach((entry: FlatEntry) => {
|
|
408
|
+
entry.flatIndex = flatIndex;
|
|
409
|
+
flatIndex++;
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
return built;
|
|
414
|
+
}, [
|
|
415
|
+
normalizedQuery,
|
|
416
|
+
query,
|
|
417
|
+
props.commands,
|
|
418
|
+
recentCommandIds,
|
|
419
|
+
providerSections,
|
|
420
|
+
]);
|
|
421
|
+
|
|
422
|
+
// Flattened list in render order — the keyboard walks this.
|
|
423
|
+
const flatEntries: Array<FlatEntry> = useMemo(() => {
|
|
424
|
+
return sections.reduce(
|
|
425
|
+
(accumulator: Array<FlatEntry>, section: PaletteSectionView) => {
|
|
426
|
+
section.entries.forEach((entry: FlatEntry) => {
|
|
427
|
+
accumulator.push(entry);
|
|
428
|
+
});
|
|
429
|
+
return accumulator;
|
|
430
|
+
},
|
|
431
|
+
[],
|
|
432
|
+
);
|
|
433
|
+
}, [sections]);
|
|
434
|
+
|
|
435
|
+
// A new query snaps the selection back to the best match.
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
setActiveIndex(0);
|
|
438
|
+
}, [query]);
|
|
439
|
+
|
|
440
|
+
/*
|
|
441
|
+
* Clamp the selection when async results shrink the list under it. The
|
|
442
|
+
* functional updater matters: on a query change this effect and the reset
|
|
443
|
+
* above run in the same pass, and reading activeIndex from the closure here
|
|
444
|
+
* would clobber the queued reset with a stale clamp.
|
|
445
|
+
*/
|
|
446
|
+
useEffect(() => {
|
|
447
|
+
if (flatEntries.length === 0) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
setActiveIndex((index: number) => {
|
|
451
|
+
return index > flatEntries.length - 1 ? flatEntries.length - 1 : index;
|
|
452
|
+
});
|
|
453
|
+
}, [flatEntries.length]);
|
|
454
|
+
|
|
455
|
+
// Keep the active row scrolled into view as the selection moves.
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
const row: HTMLDivElement | null = rowRefs.current[activeIndex] || null;
|
|
458
|
+
if (row && typeof row.scrollIntoView === "function") {
|
|
459
|
+
row.scrollIntoView({ block: "nearest" });
|
|
460
|
+
}
|
|
461
|
+
}, [activeIndex]);
|
|
462
|
+
|
|
463
|
+
const runEntry: (entry: FlatEntry) => void = (entry: FlatEntry): void => {
|
|
464
|
+
if (entry.recentCommandId && props.recentStorageKey) {
|
|
465
|
+
writeRecentCommandId(props.recentStorageKey, entry.recentCommandId);
|
|
466
|
+
}
|
|
467
|
+
entry.onSelect();
|
|
468
|
+
props.onClose();
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
/*
|
|
472
|
+
* One handler on the dialog root: keys fired in the input bubble here, and
|
|
473
|
+
* so do keys fired with focus anywhere else inside the palette. Every
|
|
474
|
+
* handled key calls preventDefault so listeners beneath the palette
|
|
475
|
+
* (page shortcuts, scrolling) can see the key was consumed. Escape goes
|
|
476
|
+
* one step further and also stops propagating: the topmost dialog consumes
|
|
477
|
+
* its dismiss key outright, so document/window Escape listeners underneath
|
|
478
|
+
* (the Ask AI slide-over, page-level shortcuts) never see it at all.
|
|
479
|
+
*/
|
|
480
|
+
const handleKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void = (
|
|
481
|
+
event: React.KeyboardEvent<HTMLDivElement>,
|
|
482
|
+
): void => {
|
|
483
|
+
if (event.key === "Escape") {
|
|
484
|
+
event.preventDefault();
|
|
485
|
+
event.stopPropagation();
|
|
486
|
+
props.onClose();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (flatEntries.length === 0) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (event.key === "ArrowDown") {
|
|
493
|
+
event.preventDefault();
|
|
494
|
+
setActiveIndex((index: number) => {
|
|
495
|
+
return (index + 1) % flatEntries.length;
|
|
496
|
+
});
|
|
497
|
+
} else if (event.key === "ArrowUp") {
|
|
498
|
+
event.preventDefault();
|
|
499
|
+
setActiveIndex((index: number) => {
|
|
500
|
+
return (index - 1 + flatEntries.length) % flatEntries.length;
|
|
501
|
+
});
|
|
502
|
+
} else if (event.key === "Home") {
|
|
503
|
+
event.preventDefault();
|
|
504
|
+
setActiveIndex(0);
|
|
505
|
+
} else if (event.key === "End") {
|
|
506
|
+
event.preventDefault();
|
|
507
|
+
setActiveIndex(flatEntries.length - 1);
|
|
508
|
+
} else if (event.key === "Enter") {
|
|
509
|
+
event.preventDefault();
|
|
510
|
+
const entry: FlatEntry | undefined = flatEntries[activeIndex];
|
|
511
|
+
if (entry) {
|
|
512
|
+
runEntry(entry);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
/*
|
|
518
|
+
* A mousedown on non-interactive panel chrome (section headers, the hints
|
|
519
|
+
* footer, the empty state) would move focus to the body and kill arrow-key
|
|
520
|
+
* navigation. preventDefault keeps focus in the input. Interactive targets
|
|
521
|
+
* keep their default: the input (caret/selection), option rows, and the
|
|
522
|
+
* scroll container itself — a scrollbar drag starts as a mousedown on it,
|
|
523
|
+
* and preventDefault would cancel the drag.
|
|
524
|
+
*/
|
|
525
|
+
const handlePanelMouseDown: (
|
|
526
|
+
event: React.MouseEvent<HTMLDivElement>,
|
|
527
|
+
) => void = (event: React.MouseEvent<HTMLDivElement>): void => {
|
|
528
|
+
const target: HTMLElement | null =
|
|
529
|
+
event.target instanceof HTMLElement ? event.target : null;
|
|
530
|
+
|
|
531
|
+
if (!target) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (target.id === "command-palette-listbox") {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (target.closest('input, textarea, select, button, a, [role="option"]')) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
event.preventDefault();
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const activeEntry: FlatEntry | undefined = flatEntries[activeIndex];
|
|
547
|
+
|
|
548
|
+
/*
|
|
549
|
+
* Once the panel has settled it carries no transform at all — a lingering
|
|
550
|
+
* scale would make it the containing block for position:fixed descendants
|
|
551
|
+
* (the Modal invariant).
|
|
552
|
+
*/
|
|
553
|
+
const panelTransitionClassName: string = hasEntered
|
|
554
|
+
? "opacity-100"
|
|
555
|
+
: "scale-[0.98] opacity-0";
|
|
556
|
+
|
|
557
|
+
const resultCount: number = flatEntries.length;
|
|
558
|
+
|
|
559
|
+
return (
|
|
560
|
+
<div
|
|
561
|
+
className="relative z-50"
|
|
562
|
+
role="dialog"
|
|
563
|
+
aria-modal="true"
|
|
564
|
+
aria-label={tx("Command palette")}
|
|
565
|
+
data-testid="command-palette"
|
|
566
|
+
onKeyDown={handleKeyDown}
|
|
567
|
+
>
|
|
568
|
+
{/* Backdrop */}
|
|
569
|
+
<div
|
|
570
|
+
className={`fixed inset-0 bg-gray-500/25 backdrop-blur-sm transition-opacity duration-150 ease-out motion-reduce:transition-none ${
|
|
571
|
+
hasEntered ? "opacity-100" : "opacity-0"
|
|
572
|
+
}`}
|
|
573
|
+
data-testid="command-palette-backdrop"
|
|
574
|
+
aria-hidden="true"
|
|
575
|
+
/>
|
|
576
|
+
|
|
577
|
+
{/* Scroll + click-away container */}
|
|
578
|
+
<div
|
|
579
|
+
className="fixed inset-0 z-50 overflow-y-auto"
|
|
580
|
+
onClick={props.onClose}
|
|
581
|
+
>
|
|
582
|
+
<div className="flex min-h-full items-start justify-center p-4 sm:p-6">
|
|
583
|
+
<div
|
|
584
|
+
className={`relative mt-[15vh] flex w-full max-w-xl flex-col overflow-hidden rounded-xl bg-white shadow-2xl ring-1 ring-black/5 transition duration-150 ease-out motion-reduce:transition-none ${panelTransitionClassName}`}
|
|
585
|
+
data-testid="command-palette-panel"
|
|
586
|
+
onClick={(event: React.MouseEvent<HTMLDivElement>) => {
|
|
587
|
+
event.stopPropagation();
|
|
588
|
+
}}
|
|
589
|
+
onMouseDown={handlePanelMouseDown}
|
|
590
|
+
>
|
|
591
|
+
{/* Search input */}
|
|
592
|
+
<div className="flex items-center gap-3 border-b border-gray-100 px-4 py-3.5">
|
|
593
|
+
<Icon
|
|
594
|
+
icon={IconProp.Search}
|
|
595
|
+
className="h-5 w-5 flex-shrink-0 text-gray-400"
|
|
596
|
+
/>
|
|
597
|
+
<input
|
|
598
|
+
ref={inputRef}
|
|
599
|
+
data-testid="command-palette-input"
|
|
600
|
+
type="text"
|
|
601
|
+
role="combobox"
|
|
602
|
+
aria-expanded={true}
|
|
603
|
+
aria-autocomplete="list"
|
|
604
|
+
aria-controls="command-palette-listbox"
|
|
605
|
+
aria-activedescendant={
|
|
606
|
+
activeEntry
|
|
607
|
+
? `command-palette-option-${activeEntry.id}`
|
|
608
|
+
: undefined
|
|
609
|
+
}
|
|
610
|
+
aria-label={tx("Search pages, actions…")}
|
|
611
|
+
autoComplete="off"
|
|
612
|
+
autoCorrect="off"
|
|
613
|
+
autoCapitalize="off"
|
|
614
|
+
spellCheck={false}
|
|
615
|
+
value={query}
|
|
616
|
+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
617
|
+
setQuery(event.target.value);
|
|
618
|
+
}}
|
|
619
|
+
placeholder={tx("Search pages, actions…")}
|
|
620
|
+
className="flex-1 border-0 bg-transparent p-0 text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-0"
|
|
621
|
+
/>
|
|
622
|
+
<KeyboardShortcut
|
|
623
|
+
keys={[KeyboardKey.Mod, "K"]}
|
|
624
|
+
size={KeyboardShortcutSize.Small}
|
|
625
|
+
className="hidden flex-shrink-0 sm:inline-flex"
|
|
626
|
+
/>
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
{/* Screen readers hear how many rows the query produced. */}
|
|
630
|
+
<p
|
|
631
|
+
aria-live="polite"
|
|
632
|
+
data-testid="command-palette-result-count"
|
|
633
|
+
className="sr-only"
|
|
634
|
+
>
|
|
635
|
+
{resultCount} {resultCount === 1 ? tx("result") : tx("results")}
|
|
636
|
+
</p>
|
|
637
|
+
|
|
638
|
+
{/* Results */}
|
|
639
|
+
<div
|
|
640
|
+
id="command-palette-listbox"
|
|
641
|
+
role="listbox"
|
|
642
|
+
aria-label={tx("Commands")}
|
|
643
|
+
className="max-h-[55vh] flex-1 overflow-y-auto overscroll-contain p-2"
|
|
644
|
+
>
|
|
645
|
+
{flatEntries.length === 0 && !hasPendingSection(sections) ? (
|
|
646
|
+
<div
|
|
647
|
+
data-testid="command-palette-empty"
|
|
648
|
+
className="flex flex-col items-center justify-center py-14 text-center"
|
|
649
|
+
>
|
|
650
|
+
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-full bg-gray-50 ring-1 ring-gray-100">
|
|
651
|
+
<Icon
|
|
652
|
+
icon={IconProp.Search}
|
|
653
|
+
className="h-4 w-4 text-gray-400"
|
|
654
|
+
/>
|
|
655
|
+
</div>
|
|
656
|
+
<p className="text-sm font-medium text-gray-900">
|
|
657
|
+
{tx("No results found.")}
|
|
658
|
+
</p>
|
|
659
|
+
{query.trim() && (
|
|
660
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
661
|
+
“{query.trim()}”
|
|
662
|
+
</p>
|
|
663
|
+
)}
|
|
664
|
+
</div>
|
|
665
|
+
) : (
|
|
666
|
+
sections.map((section: PaletteSectionView) => {
|
|
667
|
+
return (
|
|
668
|
+
<div
|
|
669
|
+
key={section.id}
|
|
670
|
+
role="group"
|
|
671
|
+
aria-label={section.title}
|
|
672
|
+
data-testid={`command-palette-section-${section.id}`}
|
|
673
|
+
className="mb-1 last:mb-0"
|
|
674
|
+
>
|
|
675
|
+
<div
|
|
676
|
+
aria-hidden="true"
|
|
677
|
+
className="flex items-center gap-2 px-3 pb-1 pt-2.5"
|
|
678
|
+
>
|
|
679
|
+
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-400">
|
|
680
|
+
{section.title}
|
|
681
|
+
</h3>
|
|
682
|
+
{section.isPending && (
|
|
683
|
+
<span
|
|
684
|
+
data-testid={`command-palette-section-pending-${section.id}`}
|
|
685
|
+
className="animate-pulse text-[10px] font-normal normal-case tracking-normal text-gray-400"
|
|
686
|
+
>
|
|
687
|
+
{tx("Searching…")}
|
|
688
|
+
</span>
|
|
689
|
+
)}
|
|
690
|
+
</div>
|
|
691
|
+
{section.entries.map((entry: FlatEntry) => {
|
|
692
|
+
return (
|
|
693
|
+
<PaletteRow
|
|
694
|
+
key={entry.id}
|
|
695
|
+
item={{
|
|
696
|
+
id: entry.id,
|
|
697
|
+
title: entry.title,
|
|
698
|
+
description: entry.description,
|
|
699
|
+
icon: entry.icon,
|
|
700
|
+
iconColor: entry.iconColor,
|
|
701
|
+
shortcut: entry.shortcut,
|
|
702
|
+
}}
|
|
703
|
+
query={query}
|
|
704
|
+
isActive={entry.flatIndex === activeIndex}
|
|
705
|
+
onActivate={() => {
|
|
706
|
+
setActiveIndex(entry.flatIndex);
|
|
707
|
+
}}
|
|
708
|
+
onSelect={() => {
|
|
709
|
+
runEntry(entry);
|
|
710
|
+
}}
|
|
711
|
+
setRowElement={(element: HTMLDivElement | null) => {
|
|
712
|
+
rowRefs.current[entry.flatIndex] = element;
|
|
713
|
+
}}
|
|
714
|
+
/>
|
|
715
|
+
);
|
|
716
|
+
})}
|
|
717
|
+
</div>
|
|
718
|
+
);
|
|
719
|
+
})
|
|
720
|
+
)}
|
|
721
|
+
</div>
|
|
722
|
+
|
|
723
|
+
{/* Keyboard hints */}
|
|
724
|
+
<div
|
|
725
|
+
data-testid="command-palette-footer"
|
|
726
|
+
className="flex items-center gap-4 border-t border-gray-100 px-4 py-2"
|
|
727
|
+
>
|
|
728
|
+
<span className="flex items-center gap-1.5 text-[11px] text-gray-400">
|
|
729
|
+
<KeyboardShortcut
|
|
730
|
+
keys={[KeyboardKey.ArrowUp, KeyboardKey.ArrowDown]}
|
|
731
|
+
size={KeyboardShortcutSize.ExtraSmall}
|
|
732
|
+
variant={KeyboardShortcutVariant.Ghost}
|
|
733
|
+
/>
|
|
734
|
+
{tx("to navigate")}
|
|
735
|
+
</span>
|
|
736
|
+
<span className="flex items-center gap-1.5 text-[11px] text-gray-400">
|
|
737
|
+
<KeyboardShortcut
|
|
738
|
+
keys={[KeyboardKey.Enter]}
|
|
739
|
+
size={KeyboardShortcutSize.ExtraSmall}
|
|
740
|
+
variant={KeyboardShortcutVariant.Ghost}
|
|
741
|
+
/>
|
|
742
|
+
{tx("to select")}
|
|
743
|
+
</span>
|
|
744
|
+
<span className="flex items-center gap-1.5 text-[11px] text-gray-400">
|
|
745
|
+
<KeyboardShortcut
|
|
746
|
+
keys={[KeyboardKey.Escape]}
|
|
747
|
+
size={KeyboardShortcutSize.ExtraSmall}
|
|
748
|
+
variant={KeyboardShortcutVariant.Ghost}
|
|
749
|
+
/>
|
|
750
|
+
{tx("to close")}
|
|
751
|
+
</span>
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
755
|
+
</div>
|
|
756
|
+
</div>
|
|
757
|
+
);
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Generic Linear-style command palette. Knows nothing about routes or models:
|
|
762
|
+
* callers pass a static command catalog (grouped by `category`), optional
|
|
763
|
+
* async search providers for entity results, and control open state.
|
|
764
|
+
*
|
|
765
|
+
* Portals to document.body at the modal z-tier (z-50).
|
|
766
|
+
*/
|
|
767
|
+
const CommandPalette: FunctionComponent<ComponentProps> = (
|
|
768
|
+
props: ComponentProps,
|
|
769
|
+
): ReactElement => {
|
|
770
|
+
/*
|
|
771
|
+
* No hooks in this component on purpose: the early return below would
|
|
772
|
+
* otherwise change hook order between renders. The panel handles all state
|
|
773
|
+
* and mounts fresh on every open.
|
|
774
|
+
*/
|
|
775
|
+
if (!props.isOpen) {
|
|
776
|
+
return <></>;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return createPortal(
|
|
780
|
+
<CommandPalettePanel
|
|
781
|
+
commands={props.commands}
|
|
782
|
+
searchProviders={props.searchProviders || EMPTY_PROVIDERS}
|
|
783
|
+
onClose={props.onClose}
|
|
784
|
+
recentStorageKey={props.recentStorageKey}
|
|
785
|
+
/>,
|
|
786
|
+
document.body,
|
|
787
|
+
);
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
export default CommandPalette;
|