@azurioh/discord-kernel 0.1.0
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/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/authz/authorizer.d.ts +45 -0
- package/dist/authz/authorizer.d.ts.map +1 -0
- package/dist/authz/authorizer.js +31 -0
- package/dist/authz/authorizer.js.map +1 -0
- package/dist/authz/require-level-guard.d.ts +23 -0
- package/dist/authz/require-level-guard.d.ts.map +1 -0
- package/dist/authz/require-level-guard.js +37 -0
- package/dist/authz/require-level-guard.js.map +1 -0
- package/dist/clock.d.ts +13 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +13 -0
- package/dist/clock.js.map +1 -0
- package/dist/concurrency/keyed-queue.d.ts +18 -0
- package/dist/concurrency/keyed-queue.d.ts.map +1 -0
- package/dist/concurrency/keyed-queue.js +37 -0
- package/dist/concurrency/keyed-queue.js.map +1 -0
- package/dist/concurrency/run-with-concurrency.d.ts +15 -0
- package/dist/concurrency/run-with-concurrency.d.ts.map +1 -0
- package/dist/concurrency/run-with-concurrency.js +40 -0
- package/dist/concurrency/run-with-concurrency.js.map +1 -0
- package/dist/config/env.d.ts +17 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +49 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/errors.d.ts +14 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +25 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/datetime.d.ts +34 -0
- package/dist/datetime.d.ts.map +1 -0
- package/dist/datetime.js +188 -0
- package/dist/datetime.js.map +1 -0
- package/dist/discord/api-errors.d.ts +20 -0
- package/dist/discord/api-errors.d.ts.map +1 -0
- package/dist/discord/api-errors.js +90 -0
- package/dist/discord/api-errors.js.map +1 -0
- package/dist/discord/channel-exporter.d.ts +42 -0
- package/dist/discord/channel-exporter.d.ts.map +1 -0
- package/dist/discord/channel-exporter.js +3 -0
- package/dist/discord/channel-exporter.js.map +1 -0
- package/dist/discord/command/autocomplete-dispatcher.d.ts +26 -0
- package/dist/discord/command/autocomplete-dispatcher.d.ts.map +1 -0
- package/dist/discord/command/autocomplete-dispatcher.js +41 -0
- package/dist/discord/command/autocomplete-dispatcher.js.map +1 -0
- package/dist/discord/command/autocomplete.d.ts +18 -0
- package/dist/discord/command/autocomplete.d.ts.map +1 -0
- package/dist/discord/command/autocomplete.js +65 -0
- package/dist/discord/command/autocomplete.js.map +1 -0
- package/dist/discord/command/context.d.ts +33 -0
- package/dist/discord/command/context.d.ts.map +1 -0
- package/dist/discord/command/context.js +48 -0
- package/dist/discord/command/context.js.map +1 -0
- package/dist/discord/command/cooldown-guard.d.ts +25 -0
- package/dist/discord/command/cooldown-guard.d.ts.map +1 -0
- package/dist/discord/command/cooldown-guard.js +60 -0
- package/dist/discord/command/cooldown-guard.js.map +1 -0
- package/dist/discord/command/create-command.d.ts +100 -0
- package/dist/discord/command/create-command.d.ts.map +1 -0
- package/dist/discord/command/create-command.js +245 -0
- package/dist/discord/command/create-command.js.map +1 -0
- package/dist/discord/command/create-context-menu-command.d.ts +51 -0
- package/dist/discord/command/create-context-menu-command.d.ts.map +1 -0
- package/dist/discord/command/create-context-menu-command.js +128 -0
- package/dist/discord/command/create-context-menu-command.js.map +1 -0
- package/dist/discord/command/errors.d.ts +19 -0
- package/dist/discord/command/errors.d.ts.map +1 -0
- package/dist/discord/command/errors.js +27 -0
- package/dist/discord/command/errors.js.map +1 -0
- package/dist/discord/command/guard.d.ts +30 -0
- package/dist/discord/command/guard.d.ts.map +1 -0
- package/dist/discord/command/guard.js +34 -0
- package/dist/discord/command/guard.js.map +1 -0
- package/dist/discord/command/localization.d.ts +9 -0
- package/dist/discord/command/localization.d.ts.map +1 -0
- package/dist/discord/command/localization.js +13 -0
- package/dist/discord/command/localization.js.map +1 -0
- package/dist/discord/command/options.d.ts +113 -0
- package/dist/discord/command/options.d.ts.map +1 -0
- package/dist/discord/command/options.js +509 -0
- package/dist/discord/command/options.js.map +1 -0
- package/dist/discord/command/permission-guard.d.ts +21 -0
- package/dist/discord/command/permission-guard.d.ts.map +1 -0
- package/dist/discord/command/permission-guard.js +69 -0
- package/dist/discord/command/permission-guard.js.map +1 -0
- package/dist/discord/command/router.d.ts +48 -0
- package/dist/discord/command/router.d.ts.map +1 -0
- package/dist/discord/command/router.js +118 -0
- package/dist/discord/command/router.js.map +1 -0
- package/dist/discord/command/types.d.ts +51 -0
- package/dist/discord/command/types.d.ts.map +1 -0
- package/dist/discord/command/types.js +3 -0
- package/dist/discord/command/types.js.map +1 -0
- package/dist/discord/components/component-router.d.ts +116 -0
- package/dist/discord/components/component-router.d.ts.map +1 -0
- package/dist/discord/components/component-router.js +123 -0
- package/dist/discord/components/component-router.js.map +1 -0
- package/dist/discord/components/interactive-message/index.d.ts +3 -0
- package/dist/discord/components/interactive-message/index.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/index.js +11 -0
- package/dist/discord/components/interactive-message/index.js.map +1 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts +195 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.js +151 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.js.map +1 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts +30 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.js +44 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.js.map +1 -0
- package/dist/discord/components/paginator/index.d.ts +5 -0
- package/dist/discord/components/paginator/index.d.ts.map +1 -0
- package/dist/discord/components/paginator/index.js +15 -0
- package/dist/discord/components/paginator/index.js.map +1 -0
- package/dist/discord/components/paginator/mount-paginator.d.ts +25 -0
- package/dist/discord/components/paginator/mount-paginator.d.ts.map +1 -0
- package/dist/discord/components/paginator/mount-paginator.js +41 -0
- package/dist/discord/components/paginator/mount-paginator.js.map +1 -0
- package/dist/discord/components/paginator/page-state.d.ts +21 -0
- package/dist/discord/components/paginator/page-state.d.ts.map +1 -0
- package/dist/discord/components/paginator/page-state.js +44 -0
- package/dist/discord/components/paginator/page-state.js.map +1 -0
- package/dist/discord/components/paginator/paginator-buttons.d.ts +17 -0
- package/dist/discord/components/paginator/paginator-buttons.d.ts.map +1 -0
- package/dist/discord/components/paginator/paginator-buttons.js +45 -0
- package/dist/discord/components/paginator/paginator-buttons.js.map +1 -0
- package/dist/discord/components/paginator/paginator-view.d.ts +26 -0
- package/dist/discord/components/paginator/paginator-view.d.ts.map +1 -0
- package/dist/discord/components/paginator/paginator-view.js +28 -0
- package/dist/discord/components/paginator/paginator-view.js.map +1 -0
- package/dist/discord/components/persistent-paginator.d.ts +47 -0
- package/dist/discord/components/persistent-paginator.d.ts.map +1 -0
- package/dist/discord/components/persistent-paginator.js +137 -0
- package/dist/discord/components/persistent-paginator.js.map +1 -0
- package/dist/discord/components/settings-editor/index.d.ts +7 -0
- package/dist/discord/components/settings-editor/index.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/index.js +22 -0
- package/dist/discord/components/settings-editor/index.js.map +1 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts +140 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.js +344 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.js.map +1 -0
- package/dist/discord/components/settings-editor/navigation.d.ts +49 -0
- package/dist/discord/components/settings-editor/navigation.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/navigation.js +39 -0
- package/dist/discord/components/settings-editor/navigation.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts +56 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.js +284 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts +69 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js +353 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts +216 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.js +66 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts +303 -0
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor.view.js +316 -0
- package/dist/discord/components/settings-editor/settings-editor.view.js.map +1 -0
- package/dist/discord/events/create-event.d.ts +5 -0
- package/dist/discord/events/create-event.d.ts.map +1 -0
- package/dist/discord/events/create-event.js +8 -0
- package/dist/discord/events/create-event.js.map +1 -0
- package/dist/discord/events/event-router.d.ts +16 -0
- package/dist/discord/events/event-router.d.ts.map +1 -0
- package/dist/discord/events/event-router.js +51 -0
- package/dist/discord/events/event-router.js.map +1 -0
- package/dist/discord/events/types.d.ts +16 -0
- package/dist/discord/events/types.d.ts.map +1 -0
- package/dist/discord/events/types.js +3 -0
- package/dist/discord/events/types.js.map +1 -0
- package/dist/discord/i18n.d.ts +58 -0
- package/dist/discord/i18n.d.ts.map +1 -0
- package/dist/discord/i18n.js +173 -0
- package/dist/discord/i18n.js.map +1 -0
- package/dist/discord/interaction/button.d.ts +39 -0
- package/dist/discord/interaction/button.d.ts.map +1 -0
- package/dist/discord/interaction/button.js +36 -0
- package/dist/discord/interaction/button.js.map +1 -0
- package/dist/discord/interaction/interaction-router.d.ts +21 -0
- package/dist/discord/interaction/interaction-router.d.ts.map +1 -0
- package/dist/discord/interaction/interaction-router.js +24 -0
- package/dist/discord/interaction/interaction-router.js.map +1 -0
- package/dist/discord/interaction/modal.d.ts +171 -0
- package/dist/discord/interaction/modal.d.ts.map +1 -0
- package/dist/discord/interaction/modal.js +264 -0
- package/dist/discord/interaction/modal.js.map +1 -0
- package/dist/discord/interaction/select-menu.d.ts +104 -0
- package/dist/discord/interaction/select-menu.d.ts.map +1 -0
- package/dist/discord/interaction/select-menu.js +126 -0
- package/dist/discord/interaction/select-menu.js.map +1 -0
- package/dist/discord/permissions.d.ts +32 -0
- package/dist/discord/permissions.d.ts.map +1 -0
- package/dist/discord/permissions.js +40 -0
- package/dist/discord/permissions.js.map +1 -0
- package/dist/discord/placeholder-registry-errors.d.ts +10 -0
- package/dist/discord/placeholder-registry-errors.d.ts.map +1 -0
- package/dist/discord/placeholder-registry-errors.js +18 -0
- package/dist/discord/placeholder-registry-errors.js.map +1 -0
- package/dist/discord/placeholder-registry.d.ts +53 -0
- package/dist/discord/placeholder-registry.d.ts.map +1 -0
- package/dist/discord/placeholder-registry.js +52 -0
- package/dist/discord/placeholder-registry.js.map +1 -0
- package/dist/discord/presenter.d.ts +26 -0
- package/dist/discord/presenter.d.ts.map +1 -0
- package/dist/discord/presenter.js +3 -0
- package/dist/discord/presenter.js.map +1 -0
- package/dist/discord/ui/card.d.ts +114 -0
- package/dist/discord/ui/card.d.ts.map +1 -0
- package/dist/discord/ui/card.js +162 -0
- package/dist/discord/ui/card.js.map +1 -0
- package/dist/discord/ui/color-input-errors.d.ts +12 -0
- package/dist/discord/ui/color-input-errors.d.ts.map +1 -0
- package/dist/discord/ui/color-input-errors.js +21 -0
- package/dist/discord/ui/color-input-errors.js.map +1 -0
- package/dist/discord/ui/color-input.d.ts +37 -0
- package/dist/discord/ui/color-input.d.ts.map +1 -0
- package/dist/discord/ui/color-input.js +166 -0
- package/dist/discord/ui/color-input.js.map +1 -0
- package/dist/discord/ui/colors.d.ts +44 -0
- package/dist/discord/ui/colors.d.ts.map +1 -0
- package/dist/discord/ui/colors.js +45 -0
- package/dist/discord/ui/colors.js.map +1 -0
- package/dist/discord/ui/embed.d.ts +16 -0
- package/dist/discord/ui/embed.d.ts.map +1 -0
- package/dist/discord/ui/embed.js +53 -0
- package/dist/discord/ui/embed.js.map +1 -0
- package/dist/discord/ui/message-attachments.d.ts +40 -0
- package/dist/discord/ui/message-attachments.d.ts.map +1 -0
- package/dist/discord/ui/message-attachments.js +27 -0
- package/dist/discord/ui/message-attachments.js.map +1 -0
- package/dist/errors/business-error.d.ts +41 -0
- package/dist/errors/business-error.d.ts.map +1 -0
- package/dist/errors/business-error.js +39 -0
- package/dist/errors/business-error.js.map +1 -0
- package/dist/errors/describe-error.d.ts +29 -0
- package/dist/errors/describe-error.d.ts.map +1 -0
- package/dist/errors/describe-error.js +49 -0
- package/dist/errors/describe-error.js.map +1 -0
- package/dist/errors/incident-ref.d.ts +7 -0
- package/dist/errors/incident-ref.d.ts.map +1 -0
- package/dist/errors/incident-ref.js +13 -0
- package/dist/errors/incident-ref.js.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +15 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/i18n/business-message.d.ts +14 -0
- package/dist/i18n/business-message.d.ts.map +1 -0
- package/dist/i18n/business-message.js +19 -0
- package/dist/i18n/business-message.js.map +1 -0
- package/dist/i18n/catalog.d.ts +27 -0
- package/dist/i18n/catalog.d.ts.map +1 -0
- package/dist/i18n/catalog.js +25 -0
- package/dist/i18n/catalog.js.map +1 -0
- package/dist/i18n/errors.d.ts +10 -0
- package/dist/i18n/errors.d.ts.map +1 -0
- package/dist/i18n/errors.js +18 -0
- package/dist/i18n/errors.js.map +1 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +16 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/locale.d.ts +22 -0
- package/dist/i18n/locale.d.ts.map +1 -0
- package/dist/i18n/locale.js +40 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/translator.d.ts +35 -0
- package/dist/i18n/translator.d.ts.map +1 -0
- package/dist/i18n/translator.js +40 -0
- package/dist/i18n/translator.js.map +1 -0
- package/dist/logger.d.ts +28 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -0
- package/dist/module/module.d.ts +42 -0
- package/dist/module/module.d.ts.map +1 -0
- package/dist/module/module.js +3 -0
- package/dist/module/module.js.map +1 -0
- package/dist/persistence/database.d.ts +24 -0
- package/dist/persistence/database.d.ts.map +1 -0
- package/dist/persistence/database.js +3 -0
- package/dist/persistence/database.js.map +1 -0
- package/dist/resilience/retry.d.ts +36 -0
- package/dist/resilience/retry.d.ts.map +1 -0
- package/dist/resilience/retry.js +78 -0
- package/dist/resilience/retry.js.map +1 -0
- package/dist/scheduler/errors.d.ts +25 -0
- package/dist/scheduler/errors.d.ts.map +1 -0
- package/dist/scheduler/errors.js +45 -0
- package/dist/scheduler/errors.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +68 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +162 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/package.json +89 -0
- package/src/authz/authorizer.ts +76 -0
- package/src/authz/require-level-guard.ts +42 -0
- package/src/clock.ts +18 -0
- package/src/concurrency/keyed-queue.ts +50 -0
- package/src/concurrency/run-with-concurrency.ts +44 -0
- package/src/config/env.ts +58 -0
- package/src/config/errors.ts +21 -0
- package/src/datetime.ts +221 -0
- package/src/discord/api-errors.ts +91 -0
- package/src/discord/channel-exporter.ts +48 -0
- package/src/discord/command/autocomplete-dispatcher.ts +54 -0
- package/src/discord/command/autocomplete.ts +76 -0
- package/src/discord/command/context.ts +85 -0
- package/src/discord/command/cooldown-guard.ts +81 -0
- package/src/discord/command/create-command.ts +431 -0
- package/src/discord/command/create-context-menu-command.ts +227 -0
- package/src/discord/command/errors.ts +22 -0
- package/src/discord/command/guard.ts +54 -0
- package/src/discord/command/localization.ts +11 -0
- package/src/discord/command/options.ts +847 -0
- package/src/discord/command/permission-guard.ts +74 -0
- package/src/discord/command/router.ts +142 -0
- package/src/discord/command/types.ts +52 -0
- package/src/discord/components/component-router.ts +213 -0
- package/src/discord/components/interactive-message/index.ts +18 -0
- package/src/discord/components/interactive-message/interactive-message-collector.ts +374 -0
- package/src/discord/components/interactive-message/mount-interactive-message.ts +71 -0
- package/src/discord/components/paginator/index.ts +17 -0
- package/src/discord/components/paginator/mount-paginator.ts +69 -0
- package/src/discord/components/paginator/page-state.ts +45 -0
- package/src/discord/components/paginator/paginator-buttons.ts +60 -0
- package/src/discord/components/paginator/paginator-view.ts +61 -0
- package/src/discord/components/persistent-paginator.ts +197 -0
- package/src/discord/components/settings-editor/index.ts +41 -0
- package/src/discord/components/settings-editor/mount-settings-editor.ts +646 -0
- package/src/discord/components/settings-editor/navigation.ts +67 -0
- package/src/discord/components/settings-editor/settings-editor-card.view.ts +344 -0
- package/src/discord/components/settings-editor/settings-editor-field-modal.ts +538 -0
- package/src/discord/components/settings-editor/settings-editor-fields.ts +290 -0
- package/src/discord/components/settings-editor/settings-editor.view.ts +654 -0
- package/src/discord/events/create-event.ts +7 -0
- package/src/discord/events/event-router.ts +53 -0
- package/src/discord/events/types.ts +17 -0
- package/src/discord/i18n.ts +173 -0
- package/src/discord/interaction/button.ts +75 -0
- package/src/discord/interaction/interaction-router.ts +28 -0
- package/src/discord/interaction/modal.ts +505 -0
- package/src/discord/interaction/select-menu.ts +246 -0
- package/src/discord/permissions.ts +57 -0
- package/src/discord/placeholder-registry-errors.ts +11 -0
- package/src/discord/placeholder-registry.ts +77 -0
- package/src/discord/presenter.ts +26 -0
- package/src/discord/ui/card.ts +303 -0
- package/src/discord/ui/color-input-errors.ts +17 -0
- package/src/discord/ui/color-input.ts +179 -0
- package/src/discord/ui/colors.ts +58 -0
- package/src/discord/ui/embed.ts +64 -0
- package/src/discord/ui/message-attachments.ts +57 -0
- package/src/errors/business-error.ts +49 -0
- package/src/errors/describe-error.ts +73 -0
- package/src/errors/incident-ref.ts +10 -0
- package/src/errors/index.ts +12 -0
- package/src/i18n/business-message.ts +23 -0
- package/src/i18n/catalog.ts +41 -0
- package/src/i18n/errors.ts +11 -0
- package/src/i18n/index.ts +11 -0
- package/src/i18n/locale.ts +41 -0
- package/src/i18n/translator.ts +78 -0
- package/src/logger.ts +28 -0
- package/src/module/module.ts +42 -0
- package/src/persistence/database.ts +23 -0
- package/src/resilience/retry.ts +102 -0
- package/src/scheduler/errors.ts +40 -0
- package/src/scheduler/scheduler.ts +248 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ActionRowBuilder, ButtonBuilder, EmbedBuilder } from "discord.js";
|
|
2
|
+
import type {
|
|
3
|
+
InteractiveMessagePayload,
|
|
4
|
+
InteractiveView,
|
|
5
|
+
} from "@/discord/components/interactive-message";
|
|
6
|
+
import { type PageState, selectPageItems } from "@/discord/components/paginator/page-state";
|
|
7
|
+
import { type Button, createActionRow } from "@/discord/interaction/button";
|
|
8
|
+
|
|
9
|
+
/** Builds the embed shown for one page; supplied by the caller. */
|
|
10
|
+
export type PaginatorRender<T> = (
|
|
11
|
+
pageItems: T[],
|
|
12
|
+
page: number,
|
|
13
|
+
count: number,
|
|
14
|
+
total: number,
|
|
15
|
+
) => EmbedBuilder;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The message payload shape shared by the initial reply and every update — the
|
|
19
|
+
* generic interactive-message payload, named here for the paginator's callers.
|
|
20
|
+
*/
|
|
21
|
+
export type PaginatorPayload = InteractiveMessagePayload<PageState>;
|
|
22
|
+
|
|
23
|
+
export interface PaginatorViewDeps<T> {
|
|
24
|
+
items: T[];
|
|
25
|
+
pageSize: number;
|
|
26
|
+
count: number;
|
|
27
|
+
render: PaginatorRender<T>;
|
|
28
|
+
buttons: Button<PageState>[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Turns a {@link PageState} into a message payload. Everything the paginator
|
|
33
|
+
* sends to Discord goes through here, so the collector never builds a payload of
|
|
34
|
+
* its own and the two renders can never drift apart.
|
|
35
|
+
*/
|
|
36
|
+
export type PaginatorView = InteractiveView<PageState>;
|
|
37
|
+
|
|
38
|
+
export function createPaginatorView<T>(deps: PaginatorViewDeps<T>): PaginatorView {
|
|
39
|
+
const { items, pageSize, count, render, buttons } = deps;
|
|
40
|
+
|
|
41
|
+
function controls(state: PageState, disableAll: boolean): ActionRowBuilder<ButtonBuilder>[] {
|
|
42
|
+
// A single page has nothing to navigate to, so it carries no action row at all.
|
|
43
|
+
if (count <= 1) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return [createActionRow(buttons, state, { disableAll })];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
render(state) {
|
|
51
|
+
const pageItems = selectPageItems(items, state, pageSize);
|
|
52
|
+
return {
|
|
53
|
+
embeds: [render(pageItems, state.page, count, items.length)],
|
|
54
|
+
components: controls(state, false),
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
disabledControls(state) {
|
|
58
|
+
return { components: controls(state, true) };
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { ActionRowBuilder, ButtonBuilder, EmbedBuilder } from "discord.js";
|
|
2
|
+
import {
|
|
3
|
+
type ComponentHandler,
|
|
4
|
+
type ComponentRuntime,
|
|
5
|
+
createComponentHandler,
|
|
6
|
+
openToAnyone,
|
|
7
|
+
type RoutableInteraction,
|
|
8
|
+
} from "@/discord/components/component-router";
|
|
9
|
+
import { toMessageEditOptions } from "@/discord/components/interactive-message";
|
|
10
|
+
import {
|
|
11
|
+
clampPage,
|
|
12
|
+
isFirstPage,
|
|
13
|
+
isLastPage,
|
|
14
|
+
nextPage,
|
|
15
|
+
type PageState,
|
|
16
|
+
previousPage,
|
|
17
|
+
} from "@/discord/components/paginator/page-state";
|
|
18
|
+
import {
|
|
19
|
+
type PaginatorLabels,
|
|
20
|
+
resolveLabels,
|
|
21
|
+
} from "@/discord/components/paginator/paginator-buttons";
|
|
22
|
+
import type { PaginatorPayload } from "@/discord/components/paginator/paginator-view";
|
|
23
|
+
import { createActionRow, createButton } from "@/discord/interaction/button";
|
|
24
|
+
|
|
25
|
+
/** Separator the {@link ComponentRouter} already matches a handler prefix on. */
|
|
26
|
+
const ID_SEPARATOR = ":";
|
|
27
|
+
/** Marks the segment holding the encoded page, so other state can be added later. */
|
|
28
|
+
const PAGE_SEGMENT = "page";
|
|
29
|
+
/** Pages are 1-based, mirroring {@link PageState}. */
|
|
30
|
+
const FIRST_PAGE = 1;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A page total is whatever the source reports, which may be 0 for an emptied
|
|
34
|
+
* dataset or non-finite from a miscounted query. Both would push the 1-based
|
|
35
|
+
* page index out of range, so treat them as a single empty page.
|
|
36
|
+
*/
|
|
37
|
+
function normalisePageCount(pageCount: number): number {
|
|
38
|
+
if (!Number.isFinite(pageCount)) {
|
|
39
|
+
return FIRST_PAGE;
|
|
40
|
+
}
|
|
41
|
+
return Math.max(FIRST_PAGE, Math.floor(pageCount));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One rendered page: what the caller's data source produces for a page index. */
|
|
45
|
+
export interface PersistentPage {
|
|
46
|
+
embed: EmbedBuilder;
|
|
47
|
+
/** Total number of pages, re-evaluated on every fetch since the data may have changed. */
|
|
48
|
+
pageCount: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Produces the page at `page` (1-based). Asynchronous and injected because a
|
|
53
|
+
* persistent paginator cannot keep the dataset in memory: after a restart the
|
|
54
|
+
* only surviving state is the customId, so every click re-reads the source.
|
|
55
|
+
*/
|
|
56
|
+
export type PersistentPageFetcher = (page: number) => Promise<PersistentPage>;
|
|
57
|
+
|
|
58
|
+
export interface PersistentPaginatorOptions {
|
|
59
|
+
/** Handler prefix; navigation buttons extend it with the target page. */
|
|
60
|
+
customId: string;
|
|
61
|
+
fetchPage: PersistentPageFetcher;
|
|
62
|
+
labels?: Partial<PaginatorLabels>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface PersistentPaginator {
|
|
66
|
+
readonly customId: string;
|
|
67
|
+
/** Payload for the first message; the caller sends it however it replies. */
|
|
68
|
+
render(page?: number): Promise<PaginatorPayload>;
|
|
69
|
+
/** Register this on the module's `components` so clicks keep working after a restart. */
|
|
70
|
+
readonly handler: ComponentHandler;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Build the customId a navigation button carries: `<prefix>:page:<n>`. */
|
|
74
|
+
export function encodePaginatorPage(customId: string, page: number): string {
|
|
75
|
+
return [customId, PAGE_SEGMENT, String(page)].join(ID_SEPARATOR);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read the page back out of a clicked customId. Returns `null` for anything that
|
|
80
|
+
* is not a well-formed page reference — a message from an older deploy may carry
|
|
81
|
+
* a shape this build no longer writes, and that must not throw.
|
|
82
|
+
*/
|
|
83
|
+
export function decodePaginatorPage(customId: string, clickedId: string): number | null {
|
|
84
|
+
const prefix = `${customId}${ID_SEPARATOR}${PAGE_SEGMENT}${ID_SEPARATOR}`;
|
|
85
|
+
if (!clickedId.startsWith(prefix)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const raw = clickedId.slice(prefix.length);
|
|
89
|
+
// `Number` alone accepts "", " " and "1e3"; only plain digits are ours.
|
|
90
|
+
if (!/^\d+$/.test(raw)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const page = Number(raw);
|
|
94
|
+
if (page < FIRST_PAGE) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return page;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A paginator whose whole state lives in the component customId, routed by the
|
|
102
|
+
* {@link ComponentRouter}. Unlike `mountPaginator` — a collector holding items
|
|
103
|
+
* and page in a closure, owner-scoped and dead after an idle timeout — this one
|
|
104
|
+
* has no memory: it survives restarts and never expires, at the cost of refetching
|
|
105
|
+
* the page on every click. Use it for long-lived, shared messages; use the
|
|
106
|
+
* collector for transient, single-user flows.
|
|
107
|
+
*/
|
|
108
|
+
export function createPersistentPaginator(
|
|
109
|
+
options: PersistentPaginatorOptions,
|
|
110
|
+
): PersistentPaginator {
|
|
111
|
+
const { customId, fetchPage } = options;
|
|
112
|
+
const labels = resolveLabels(options.labels);
|
|
113
|
+
|
|
114
|
+
function controls(state: PageState, count: number): ActionRowBuilder<ButtonBuilder>[] {
|
|
115
|
+
// A single page has nothing to navigate to, so it carries no action row at all.
|
|
116
|
+
if (count <= 1) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
// No `onClick`: behaviour is carried by the encoded customId and resolved by
|
|
120
|
+
// the router, which is what lets these buttons outlive the process.
|
|
121
|
+
const buttons = [
|
|
122
|
+
createButton<PageState>({
|
|
123
|
+
id: encodePaginatorPage(customId, previousPage(state, count).page),
|
|
124
|
+
label: labels.previous,
|
|
125
|
+
emoji: labels.previousEmoji,
|
|
126
|
+
style: "secondary",
|
|
127
|
+
disabled: (current) => isFirstPage(current),
|
|
128
|
+
}),
|
|
129
|
+
createButton<PageState>({
|
|
130
|
+
id: encodePaginatorPage(customId, nextPage(state, count).page),
|
|
131
|
+
label: labels.next,
|
|
132
|
+
emoji: labels.nextEmoji,
|
|
133
|
+
style: "secondary",
|
|
134
|
+
disabled: (current) => isLastPage(current, count),
|
|
135
|
+
}),
|
|
136
|
+
];
|
|
137
|
+
return [createActionRow(buttons, state)];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Fetch a page, then clamp it against the freshly returned total. The total is
|
|
142
|
+
* only known after a fetch, so an out-of-range request (a stale button, a
|
|
143
|
+
* shrunken dataset) costs one extra read rather than an empty page.
|
|
144
|
+
*/
|
|
145
|
+
async function loadPage(requested: number): Promise<PaginatorPayload> {
|
|
146
|
+
const asked = Math.max(requested, FIRST_PAGE);
|
|
147
|
+
const fetched = await fetchPage(asked);
|
|
148
|
+
// A source that reports no page (emptied dataset) or a nonsensical total
|
|
149
|
+
// must not clamp the index to 0: the contract is 1-based, and refetching an
|
|
150
|
+
// index the caller was never told it could receive is worse than rendering
|
|
151
|
+
// the empty first page it already returned.
|
|
152
|
+
const pageCount = normalisePageCount(fetched.pageCount);
|
|
153
|
+
const page = clampPage(asked, pageCount);
|
|
154
|
+
const resolved = page === asked ? fetched : await fetchPage(page);
|
|
155
|
+
return {
|
|
156
|
+
embeds: [resolved.embed],
|
|
157
|
+
components: controls({ page }, normalisePageCount(resolved.pageCount)),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function handleClick(
|
|
162
|
+
interaction: RoutableInteraction,
|
|
163
|
+
runtime: ComponentRuntime,
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
if (!interaction.isButton()) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const decoded = decodePaginatorPage(customId, interaction.customId);
|
|
169
|
+
if (decoded === null) {
|
|
170
|
+
runtime.logger.warn(
|
|
171
|
+
{ customId: interaction.customId },
|
|
172
|
+
"Unreadable paginator page, falling back to the first page",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
// Acknowledge before fetching: a persistent paginator reads its source on
|
|
176
|
+
// every click by design, and Discord invalidates an interaction left
|
|
177
|
+
// unanswered for 3s — the click would be lost with the user still looking
|
|
178
|
+
// at the stale page. `deferUpdate` buys the full edit window instead.
|
|
179
|
+
await interaction.deferUpdate();
|
|
180
|
+
const payload = await loadPage(decoded ?? FIRST_PAGE);
|
|
181
|
+
await interaction.editReply(
|
|
182
|
+
toMessageEditOptions(payload, [...interaction.message.attachments.values()]),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
customId,
|
|
188
|
+
render: (page = FIRST_PAGE) => loadPage(page),
|
|
189
|
+
handler: createComponentHandler({
|
|
190
|
+
customId,
|
|
191
|
+
// Navigation only re-renders what the message already showed to
|
|
192
|
+
// whoever can see the channel; it grants no access of its own.
|
|
193
|
+
authorize: openToAnyone("paging a message reveals nothing its reader could not already see"),
|
|
194
|
+
handle: handleClick,
|
|
195
|
+
}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export {
|
|
2
|
+
mountSettingsEditor,
|
|
3
|
+
type SettingsEditorOptions,
|
|
4
|
+
type SettingsEditorWrite,
|
|
5
|
+
} from "@/discord/components/settings-editor/mount-settings-editor";
|
|
6
|
+
export {
|
|
7
|
+
currentLevelKey,
|
|
8
|
+
type SettingsEditorLevel,
|
|
9
|
+
} from "@/discord/components/settings-editor/navigation";
|
|
10
|
+
export {
|
|
11
|
+
editorComponentIds,
|
|
12
|
+
MAX_SELECT_OPTION_TEXT,
|
|
13
|
+
MAX_SELECT_OPTIONS,
|
|
14
|
+
SETTINGS_EDITOR_RESET_CONFIRMING,
|
|
15
|
+
type SettingsEditorCardChrome,
|
|
16
|
+
type SettingsEditorChrome,
|
|
17
|
+
type SettingsEditorEmbedsChrome,
|
|
18
|
+
type SettingsEditorIds,
|
|
19
|
+
type SettingsEditorState,
|
|
20
|
+
} from "@/discord/components/settings-editor/settings-editor.view";
|
|
21
|
+
export {
|
|
22
|
+
fieldButtonId,
|
|
23
|
+
MAX_CARD_BUTTON_LEVEL_ENTRIES,
|
|
24
|
+
MAX_CARD_LEVEL_ENTRIES,
|
|
25
|
+
} from "@/discord/components/settings-editor/settings-editor-card.view";
|
|
26
|
+
export type {
|
|
27
|
+
SettingsEditorGroupSubmission,
|
|
28
|
+
SettingsEditorSubmission,
|
|
29
|
+
} from "@/discord/components/settings-editor/settings-editor-field-modal";
|
|
30
|
+
export {
|
|
31
|
+
MAX_MODAL_COMPONENTS,
|
|
32
|
+
MAX_MODAL_INPUT_LENGTH,
|
|
33
|
+
pickedFieldValue,
|
|
34
|
+
type SettingsEditorChoice,
|
|
35
|
+
type SettingsEditorField,
|
|
36
|
+
type SettingsEditorFieldValue,
|
|
37
|
+
type SettingsEditorGroupField,
|
|
38
|
+
type SettingsEditorPickerField,
|
|
39
|
+
type SettingsEditorValueField,
|
|
40
|
+
textFieldValue,
|
|
41
|
+
} from "@/discord/components/settings-editor/settings-editor-fields";
|