@celestia-island/hikari 0.4.4 → 0.30.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/README.md +1 -1
- package/package.json +22 -7
- package/src/DemoApp.tsx +491 -0
- package/src/animation/registerAnimations.test.ts +94 -0
- package/src/animation/registerAnimations.ts +103 -0
- package/src/components/HkAboutModal.scss +95 -0
- package/src/components/HkAboutModal.tsx +114 -0
- package/src/components/HkAdaptiveDialog.scss +22 -0
- package/src/components/HkAdaptiveDialog.test.tsx +271 -0
- package/src/components/HkAdaptiveDialog.tsx +167 -0
- package/src/components/HkAdminHeader.test.tsx +185 -0
- package/src/components/HkAdminHeader.tsx +308 -0
- package/src/components/HkAdminShell.test.tsx +237 -0
- package/src/components/HkAdminShell.tsx +129 -0
- package/src/components/HkAdminTablePage.tsx +70 -0
- package/src/components/HkAdminWidgets.test.ts +89 -0
- package/src/components/HkAlert.tsx +1 -1
- package/src/components/HkAltSignIn.scss +55 -0
- package/src/components/HkAltSignIn.test.tsx +88 -0
- package/src/components/HkAltSignIn.tsx +92 -0
- package/src/components/HkAttachmentModal.scss +49 -0
- package/src/components/HkAttachmentModal.tsx +318 -0
- package/src/components/HkAuthCard.tsx +50 -0
- package/src/components/HkAuthSubmitButton.scss +12 -0
- package/src/components/HkAuthSubmitButton.tsx +94 -0
- package/src/components/HkAvatar.scss +93 -0
- package/src/components/HkAvatar.tsx +2 -2
- package/src/components/HkBlockingToast.scss +123 -0
- package/src/components/HkBlockingToast.test.tsx +221 -0
- package/src/components/HkBlockingToast.tsx +182 -0
- package/src/components/HkBreadcrumb.scss +624 -0
- package/src/components/HkBreadcrumb.tsx +2 -2
- package/src/components/HkButton.scss +16 -2
- package/src/components/HkCaptchaModal.scss +14 -0
- package/src/components/HkCaptchaModal.tsx +62 -0
- package/src/components/HkCaptchaWidget.scss +30 -0
- package/src/components/HkCaptchaWidget.tsx +181 -0
- package/src/components/HkCard.scss +1 -1
- package/src/components/HkChatTypes.ts +65 -0
- package/src/components/HkCheckbox.scss +4 -9
- package/src/components/HkCheckbox.tsx +18 -6
- package/src/components/HkColorPicker.scss +120 -3
- package/src/components/HkColorPicker.tsx +130 -19
- package/src/components/HkColorPickerTouch.test.tsx +145 -0
- package/src/components/HkColorSchemeDialog.scss +52 -0
- package/src/components/HkColorSchemeDialog.test.ts +201 -0
- package/src/components/HkColorSchemeDialog.tsx +96 -0
- package/src/components/HkColorSchemeEditor.test.ts +270 -0
- package/src/components/HkColorSchemeEditor.tsx +410 -0
- package/src/components/HkConfirmDialog.scss +43 -3
- package/src/components/HkConfirmDialog.tsx +7 -4
- package/src/components/HkConnectionInfo.ts +80 -0
- package/src/components/HkConnectionStatus.tsx +158 -0
- package/src/components/HkCookieConsent.scss +22 -0
- package/src/components/HkCookieConsent.test.tsx +70 -0
- package/src/components/HkCookieConsent.tsx +56 -0
- package/src/components/HkCountdownDigit.tsx +105 -0
- package/src/components/HkDatePicker.scss +361 -0
- package/src/components/HkDatePicker.test.ts +459 -0
- package/src/components/HkDatePicker.tsx +568 -0
- package/src/components/HkDateTimePicker.scss +65 -37
- package/src/components/HkDateTimePicker.test.ts +344 -0
- package/src/components/HkDateTimePicker.tsx +249 -82
- package/src/components/HkDraggableGrid.scss +4 -1
- package/src/components/HkDraggableList.scss +4 -1
- package/src/components/HkDrawer.scss +27 -13
- package/src/components/HkDrawer.test.tsx +73 -0
- package/src/components/HkDrawer.tsx +157 -10
- package/src/components/HkErrorBoundary.scss +5 -2
- package/src/components/HkErrorBoundary.tsx +7 -4
- package/src/components/HkExpansionPanel.scss +143 -0
- package/src/components/HkExpansionPanel.test.tsx +109 -0
- package/src/components/HkExpansionPanel.tsx +101 -0
- package/src/components/HkFab.scss +162 -0
- package/src/components/HkFab.test.tsx +217 -0
- package/src/components/HkFab.tsx +214 -0
- package/src/components/HkHoverRevealAction.scss +6 -0
- package/src/components/HkHoverRevealAction.test.ts +68 -0
- package/src/components/HkHoverRevealAction.tsx +84 -6
- package/src/components/HkIcon.scss +123 -0
- package/src/components/HkIcon.tsx +7 -23
- package/src/components/HkIconButton.scss +170 -0
- package/src/components/HkIconButton.test.tsx +75 -0
- package/src/components/HkIconButton.tsx +4 -3
- package/src/components/HkIconButtonVars.scss +180 -0
- package/src/components/HkInput.affix.test.ts +253 -0
- package/src/components/HkInput.enter.test.ts +88 -0
- package/src/components/HkInput.scss +127 -8
- package/src/components/HkInput.tsx +245 -13
- package/src/components/HkKeywordSearchModal.scss +13 -0
- package/src/components/HkKeywordSearchModal.tsx +46 -9
- package/src/components/HkLabel.scss +34 -0
- package/src/components/HkLabel.test.tsx +61 -0
- package/src/components/HkLabel.tsx +45 -0
- package/src/components/HkListTransition.scss +26 -0
- package/src/components/HkListTransition.tsx +57 -0
- package/src/components/HkLocalePicker.scss +37 -0
- package/src/components/HkLocalePicker.tsx +133 -0
- package/src/components/HkLocalePickerPopup.tsx +35 -19
- package/src/components/HkLocalizedInput.scss +277 -0
- package/src/components/HkLocalizedInput.test.tsx +693 -0
- package/src/components/HkLocalizedInput.tsx +495 -0
- package/src/components/HkLogWindow.scss +121 -0
- package/src/components/HkLogWindow.tsx +195 -0
- package/src/components/HkLogo.scss +1 -1
- package/src/components/HkLogo.tsx +1 -1
- package/src/components/HkMarkdownRenderer.scss +22 -2
- package/src/components/HkMarkdownRenderer.tsx +33 -3
- package/src/components/HkMediaControlBar.tsx +20 -13
- package/src/components/HkMenu.scss +200 -0
- package/src/components/HkMenu.test.ts +779 -0
- package/src/components/HkMenu.tsx +770 -0
- package/src/components/HkMenuActionItem.scss +58 -0
- package/src/components/HkMenuActionItem.tsx +41 -0
- package/src/components/HkMenuIdentityItem.scss +70 -0
- package/src/components/HkMenuIdentityItem.tsx +42 -0
- package/src/components/HkMenuPanel.scss +20 -0
- package/src/components/HkMenuPanel.test.tsx +118 -0
- package/src/components/HkMenuPanel.tsx +32 -0
- package/src/components/HkMinimap.tsx +16 -8
- package/src/components/HkModal.autofollow.test.ts +44 -0
- package/src/components/HkModal.scss +170 -53
- package/src/components/HkModal.sheetgap.test.ts +82 -0
- package/src/components/HkModal.test.tsx +334 -0
- package/src/components/HkModal.tsx +314 -68
- package/src/components/HkModalBreadcrumb.scss +4 -1
- package/src/components/HkModalBreadcrumb.test.tsx +111 -0
- package/src/components/HkModalBreadcrumb.tsx +34 -11
- package/src/components/HkModelCatalog.ts +98 -0
- package/src/components/HkModelTag.scss +130 -0
- package/src/components/HkModelTag.test.tsx +111 -0
- package/src/components/HkModelTag.tsx +190 -0
- package/src/components/HkNavItem.scss +13 -2
- package/src/components/HkNavItem.tsx +2 -1
- package/src/components/HkNavSidebar.tsx +99 -0
- package/src/components/HkNumberInput.scss +12 -0
- package/src/components/HkNumberInput.tsx +15 -8
- package/src/components/HkPageHeader.scss +79 -0
- package/src/components/HkPageHeader.test.ts +67 -0
- package/src/components/HkPageHeader.tsx +51 -0
- package/src/components/HkPasswordInput.scss +29 -17
- package/src/components/HkPasswordInput.test.ts +281 -0
- package/src/components/HkPasswordInput.tsx +337 -59
- package/src/components/HkPickerPane.scss +158 -0
- package/src/components/HkPlaceholderMarquee.scss +109 -0
- package/src/components/HkPlaceholderMarquee.test.ts +305 -0
- package/src/components/HkPlaceholderMarquee.tsx +170 -0
- package/src/components/HkPopover.scss +134 -10
- package/src/components/HkPopover.sheetdock.test.ts +34 -0
- package/src/components/HkPopover.surfacevars.test.ts +20 -0
- package/src/components/HkPopover.titleSuppress.test.tsx +265 -0
- package/src/components/HkPopover.tsx +225 -50
- package/src/components/HkPopoverSheet.test.tsx +130 -0
- package/src/components/HkPopupSelect.scss +30 -10
- package/src/components/HkPopupSelect.tsx +21 -1
- package/src/components/HkProgressDialog.scss +31 -0
- package/src/components/HkProgressDialog.tsx +37 -19
- package/src/components/HkProtocolModal.scss +26 -0
- package/src/components/HkProtocolModal.tsx +112 -0
- package/src/components/HkRadio.scss +1 -16
- package/src/components/HkRadio.tsx +5 -2
- package/src/components/HkRichInput.scss +162 -0
- package/src/components/HkRichInput.tsx +335 -0
- package/src/components/HkRollingNumber.test.ts +90 -0
- package/src/components/HkRollingNumber.tsx +39 -1
- package/src/components/HkScrollContainer.scss +71 -54
- package/src/components/HkScrollContainer.test.ts +160 -0
- package/src/components/HkScrollContainer.tsx +187 -160
- package/src/components/HkSearchInput.tsx +3 -3
- package/src/components/HkSecretRevealModal.scss +61 -0
- package/src/components/HkSecretRevealModal.tsx +91 -0
- package/src/components/HkSelect.scss +263 -12
- package/src/components/HkSelect.sheetdock.test.ts +30 -0
- package/src/components/HkSelect.test.tsx +88 -0
- package/src/components/HkSelect.tsx +59 -74
- package/src/components/HkSelectPanel.test.tsx +666 -0
- package/src/components/HkSelectPanel.tsx +595 -0
- package/src/components/HkSelectSheet.test.tsx +132 -0
- package/src/components/HkSelectionGrid.scss +1 -1
- package/src/components/HkSelectionGrid.tsx +2 -1
- package/src/components/HkSelectionWaterfall.scss +1 -1
- package/src/components/HkShareBar.scss +40 -0
- package/src/components/HkShareBar.tsx +50 -0
- package/src/components/HkSidebar.scss +15 -10
- package/src/components/HkSidebar.tsx +26 -2
- package/src/components/HkSignInCard.test.ts +154 -0
- package/src/components/HkSignInCard.tsx +155 -0
- package/src/components/HkSlider.scss +99 -0
- package/src/components/HkSlider.test.tsx +155 -0
- package/src/components/HkSlider.tsx +173 -0
- package/src/components/HkSpinner.scss +36 -1
- package/src/components/HkSpinner.test.tsx +63 -0
- package/src/components/HkSpinner.tsx +16 -1
- package/src/components/HkSplash.scss +1 -1
- package/src/components/HkSplash.tsx +3 -1
- package/src/components/HkStatCard.scss +73 -0
- package/src/components/HkStatCard.tsx +46 -0
- package/src/components/HkStatusBar.test.tsx +178 -0
- package/src/components/HkStatusBar.tsx +379 -0
- package/src/components/HkStatusPill.scss +73 -0
- package/src/components/HkStatusPill.tsx +41 -0
- package/src/components/HkStepFlow.scss +75 -0
- package/src/components/HkStepFlow.test.tsx +246 -0
- package/src/components/HkStepFlow.tsx +115 -0
- package/src/components/HkSwitch.scss +5 -17
- package/src/components/HkSwitch.tsx +6 -3
- package/src/components/HkTable.scss +20 -1
- package/src/components/HkTable.tsx +28 -3
- package/src/components/HkTabs.scss +222 -48
- package/src/components/HkTabs.test.ts +517 -0
- package/src/components/HkTabs.tsx +567 -56
- package/src/components/HkTag.tsx +2 -1
- package/src/components/HkTextarea.tsx +5 -0
- package/src/components/HkThemeToggle.scss +232 -0
- package/src/components/HkThemeToggle.test.ts +231 -0
- package/src/components/HkThemeToggle.tsx +289 -0
- package/src/components/HkTimeline.scss +134 -1
- package/src/components/HkTimeline.test.tsx +319 -0
- package/src/components/HkTimeline.tsx +303 -54
- package/src/components/HkToast.scss +34 -5
- package/src/components/HkToast.test.tsx +109 -0
- package/src/components/HkToast.tsx +57 -8
- package/src/components/HkTokenUsageBadge.scss +53 -0
- package/src/components/HkTokenUsageBadge.test.tsx +59 -0
- package/src/components/HkTokenUsageBadge.tsx +54 -0
- package/src/components/HkTokenUsagePanel.scss +95 -0
- package/src/components/HkTokenUsagePanel.tsx +140 -0
- package/src/components/HkToolBlock.scss +428 -0
- package/src/components/HkToolBlock.tsx +639 -0
- package/src/components/HkTooltip.scss +14 -2
- package/src/components/HkTooltip.tsx +25 -2
- package/src/components/HkTrendChart.tsx +27 -16
- package/src/components/HkVoiceInputPopup.scss +77 -0
- package/src/components/HkVoiceInputPopup.tsx +97 -0
- package/src/components/HkZoomToolbar.scss +4 -1
- package/src/components/HkZoomToolbar.tsx +7 -4
- package/src/components/_hikari-vars.scss +6 -0
- package/src/composables/highlightLanguages.ts +109 -0
- package/src/composables/iconRegistry.ts +127 -0
- package/src/composables/messaging/browserTransport.ts +96 -0
- package/src/composables/messaging/index.ts +204 -0
- package/src/composables/messaging/messaging.test.ts +256 -0
- package/src/composables/messaging/toastTransport.ts +26 -0
- package/src/composables/messaging/types.ts +115 -0
- package/src/composables/useActionBar.ts +66 -0
- package/src/composables/useAttachments.ts +154 -0
- package/src/composables/useConnectionProbe.ts +142 -0
- package/src/composables/useDeferredTransition.ts +16 -0
- package/src/composables/useEngineHealth.ts +69 -0
- package/src/composables/useHighlight.ts +124 -0
- package/src/composables/useMeasuredHighlight.ts +150 -0
- package/src/composables/useOverlayScrollbar.test.ts +221 -0
- package/src/composables/useOverlayScrollbar.ts +296 -0
- package/src/composables/usePageTitle.ts +37 -0
- package/src/composables/useReducedMotion.ts +34 -0
- package/src/composables/useResourceListModal.tsx +147 -0
- package/src/composables/useSafeArea.ts +137 -0
- package/src/composables/useSendShortcut.ts +35 -0
- package/src/composables/useSizeMorph.test.ts +156 -0
- package/src/composables/useSizeMorph.ts +149 -0
- package/src/composables/useSurfaceTransition.test.ts +96 -0
- package/src/composables/useSurfaceTransition.ts +71 -0
- package/src/composables/useZoomPan.ts +192 -0
- package/src/demo.scss +9 -1
- package/src/demo.ts +5 -1
- package/src/i18n/locales/ar/chat.json +38 -0
- package/src/i18n/locales/ar/components.json +141 -40
- package/src/i18n/locales/ar/components.toml +59 -0
- package/src/i18n/locales/ar/platform.json +65 -0
- package/src/i18n/locales/de/chat.json +38 -0
- package/src/i18n/locales/de/components.json +139 -38
- package/src/i18n/locales/de/components.toml +59 -0
- package/src/i18n/locales/de/platform.json +65 -0
- package/src/i18n/locales/en/chat.json +38 -0
- package/src/i18n/locales/en/components.json +104 -3
- package/src/i18n/locales/en/components.toml +59 -0
- package/src/i18n/locales/en/platform.json +65 -0
- package/src/i18n/locales/es/chat.json +38 -0
- package/src/i18n/locales/es/components.json +141 -40
- package/src/i18n/locales/es/components.toml +59 -0
- package/src/i18n/locales/es/platform.json +65 -0
- package/src/i18n/locales/fr/chat.json +38 -0
- package/src/i18n/locales/fr/components.json +137 -36
- package/src/i18n/locales/fr/components.toml +59 -0
- package/src/i18n/locales/fr/platform.json +65 -0
- package/src/i18n/locales/ja/chat.json +38 -0
- package/src/i18n/locales/ja/components.json +104 -3
- package/src/i18n/locales/ja/components.toml +59 -0
- package/src/i18n/locales/ja/platform.json +65 -0
- package/src/i18n/locales/ko/chat.json +38 -0
- package/src/i18n/locales/ko/components.json +104 -3
- package/src/i18n/locales/ko/components.toml +59 -0
- package/src/i18n/locales/ko/platform.json +65 -0
- package/src/i18n/locales/pt/chat.json +38 -0
- package/src/i18n/locales/pt/components.json +141 -40
- package/src/i18n/locales/pt/components.toml +59 -0
- package/src/i18n/locales/pt/platform.json +65 -0
- package/src/i18n/locales/ru/chat.json +38 -0
- package/src/i18n/locales/ru/components.json +141 -40
- package/src/i18n/locales/ru/components.toml +59 -0
- package/src/i18n/locales/ru/platform.json +65 -0
- package/src/i18n/locales/zh-Hans/chat.json +38 -0
- package/src/i18n/locales/zh-Hans/components.json +109 -4
- package/src/i18n/locales/zh-Hans/components.toml +59 -0
- package/src/i18n/locales/zh-Hans/platform.json +65 -0
- package/src/i18n/locales/zh-Hant/chat.json +38 -0
- package/src/i18n/locales/zh-Hant/components.json +109 -4
- package/src/i18n/locales/zh-Hant/components.toml +59 -0
- package/src/i18n/locales/zh-Hant/platform.json +65 -0
- package/src/index.ts +225 -6
- package/src/plugins/bootScriptsPlugin.ts +47 -0
- package/src/plugins/buildHashPlugin.ts +67 -0
- package/src/plugins/index.ts +2 -0
- package/src/render.ts +1 -1
- package/src/runtime/animationBus.test.ts +112 -0
- package/src/runtime/animationBus.ts +33 -2
- package/src/runtime/backStack.test.ts +334 -0
- package/src/runtime/backStack.ts +377 -0
- package/src/runtime/env.ts +4 -0
- package/src/runtime/index.ts +15 -3
- package/src/runtime/intervalBus.test.ts +118 -0
- package/src/runtime/intervalBus.ts +189 -0
- package/src/runtime/mobileViewport.test.ts +132 -0
- package/src/runtime/mobileViewport.ts +157 -0
- package/src/runtime/pageLifecycle.ts +91 -0
- package/src/runtime/useBlockingToast.ts +112 -0
- package/src/runtime/useClipboard.ts +28 -0
- package/src/runtime/useConfirm.ts +15 -7
- package/src/runtime/useMediaQuery.test.ts +67 -0
- package/src/runtime/useMediaQuery.ts +89 -0
- package/src/runtime/useOverlay.test.ts +160 -0
- package/src/runtime/useOverlay.ts +59 -12
- package/src/runtime/usePopupManager.test.ts +200 -0
- package/src/runtime/usePopupManager.ts +106 -10
- package/src/runtime/useToast.test.ts +90 -0
- package/src/runtime/useToast.ts +20 -2
- package/src/styles/admin-tokens.scss +744 -0
- package/src/styles/index.scss +5 -3
- package/src/styles/theme/_glass.scss +33 -0
- package/src/styles/theme/_layout.scss +123 -0
- package/src/styles/theme/_scrollbar.scss +73 -0
- package/src/styles/theme/_tokens.scss +69 -0
- package/src/styles/theme/base.scss +617 -0
- package/src/styles/theme/foundation.scss +219 -0
- package/src/styles/theme/mixins.scss +793 -0
- package/src/styles/theme/themes.scss +25 -0
- package/src/styles/theme/variables.scss +125 -0
- package/src/theme/_field.scss +116 -0
- package/src/theme/fontContext.test.ts +179 -0
- package/src/theme/fontContext.ts +169 -0
- package/src/theme/index.ts +19 -2
- package/src/theme/presets.ts +86 -18
- package/src/theme/theme.scss +61 -1
- package/src/theme/tokenGroups.test.ts +534 -0
- package/src/theme/tokenGroups.ts +552 -0
- package/src/theme/useSolarTime.test.ts +113 -0
- package/src/theme/useSolarTime.ts +95 -20
- package/src/theme/useTheme.test.ts +72 -0
- package/src/theme/useTheme.ts +144 -9
- package/src/tokens.scss +110 -0
- package/src/utils/base64.ts +31 -0
- package/src/utils/connectivity.ts +51 -0
- package/src/utils/dom.test.ts +131 -0
- package/src/utils/dom.ts +133 -0
- package/src/utils/download.ts +22 -0
- package/src/utils/fileType.ts +127 -0
- package/src/utils/format.ts +98 -0
- package/src/utils/fuzzy.ts +77 -0
- package/src/utils/fuzzySearch.test.ts +87 -0
- package/src/utils/healthProbe.ts +64 -0
- package/src/utils/objectPath.ts +59 -0
- package/src/utils/password.ts +53 -0
- package/src/utils/pow.test.ts +45 -0
- package/src/utils/pow.ts +226 -0
- package/src/utils/powNonce.ts +95 -0
- package/src/utils/validation.test.ts +78 -0
- package/src/DemoApp.vue +0 -400
- package/src/components/HkCollapse.tsx +0 -48
- package/src/components/HkMorphingTabs.scss +0 -141
- package/src/components/HkMorphingTabs.tsx +0 -113
- package/src/composables/useBreakpoint.ts +0 -41
- package/src/composables/useClipboard.ts +0 -37
- package/src/composables/useConfirm.ts +0 -35
- package/src/composables/useToast.ts +0 -2
- package/src/runtime/useAsyncData.ts +0 -30
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { defineComponent, ref } from "vue";
|
|
2
|
+
import HkPopover from "./HkPopover";
|
|
3
|
+
import HkMenuActionItem from "./HkMenuActionItem";
|
|
4
|
+
import "./HkAltSignIn.scss";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared "alternative sign-in" entry for auth cards (P77).
|
|
8
|
+
*
|
|
9
|
+
* A small muted trigger row (styled in the `.s-auth-footer` grammar) that
|
|
10
|
+
* opens a popover listing third-party/alternative credential entries —
|
|
11
|
+
* platform passkey on shittim-chest, feishu OAuth on easy-hydro-erp — so
|
|
12
|
+
* both apps share one implementation and only differ in content.
|
|
13
|
+
*
|
|
14
|
+
* Entries render as [`HkMenuActionItem`] rows inside an [`HkPopover`]
|
|
15
|
+
* (glass panel, flip/escape/backdrop close, bottom-sheet on mobile), which
|
|
16
|
+
* gives keyboard and pointer handling for free.
|
|
17
|
+
*/
|
|
18
|
+
export default defineComponent({
|
|
19
|
+
name: "HkAltSignIn",
|
|
20
|
+
props: {
|
|
21
|
+
/** Trigger row text, e.g. "其他方式登录". Consumer-localized on
|
|
22
|
+
* purpose: hikari ships no dictionary dependency onto hosts here. */
|
|
23
|
+
label: { type: String, required: true },
|
|
24
|
+
/** Alternative sign-in entries offered in the dropdown. */
|
|
25
|
+
entries: {
|
|
26
|
+
type: Array as unknown as () => Array<{
|
|
27
|
+
key: string;
|
|
28
|
+
label: string;
|
|
29
|
+
icon?: Record<string, unknown>;
|
|
30
|
+
danger?: boolean;
|
|
31
|
+
}>,
|
|
32
|
+
required: true,
|
|
33
|
+
},
|
|
34
|
+
disabled: { type: Boolean, default: false },
|
|
35
|
+
/** Popover placement around the trigger row. */
|
|
36
|
+
placement: {
|
|
37
|
+
type: String,
|
|
38
|
+
default: "top",
|
|
39
|
+
} as const,
|
|
40
|
+
},
|
|
41
|
+
emits: {
|
|
42
|
+
/** An entry was chosen; the popover closes automatically. */
|
|
43
|
+
select: (_key: string) => true,
|
|
44
|
+
},
|
|
45
|
+
setup(props, { emit }) {
|
|
46
|
+
const open = ref(false);
|
|
47
|
+
const anchor = ref<HTMLElement | null>(null);
|
|
48
|
+
|
|
49
|
+
function choose(key: string): void {
|
|
50
|
+
open.value = false;
|
|
51
|
+
emit("select", key);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return () => (
|
|
55
|
+
<div class="hk-alt-signin">
|
|
56
|
+
<button
|
|
57
|
+
ref={(el) => (anchor.value = el as HTMLElement | null)}
|
|
58
|
+
type="button"
|
|
59
|
+
class="hk-alt-signin__trigger"
|
|
60
|
+
aria-haspopup="menu"
|
|
61
|
+
aria-expanded={open.value}
|
|
62
|
+
disabled={props.disabled}
|
|
63
|
+
onClick={() => {
|
|
64
|
+
if (!props.disabled) open.value = !open.value;
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<span class="hk-alt-signin__rule" />
|
|
68
|
+
<span class="hk-alt-signin__label">{props.label}</span>
|
|
69
|
+
<span class="hk-alt-signin__rule" />
|
|
70
|
+
</button>
|
|
71
|
+
<HkPopover
|
|
72
|
+
modelValue={open.value}
|
|
73
|
+
onUpdate:modelValue={(v: boolean) => (open.value = v)}
|
|
74
|
+
placement={props.placement as never}
|
|
75
|
+
anchorRef={anchor.value}
|
|
76
|
+
>
|
|
77
|
+
<div class="hk-alt-signin__panel" role="menu">
|
|
78
|
+
{props.entries.map((entry) => (
|
|
79
|
+
<HkMenuActionItem
|
|
80
|
+
key={entry.key}
|
|
81
|
+
icon={entry.icon}
|
|
82
|
+
label={entry.label}
|
|
83
|
+
danger={entry.danger === true}
|
|
84
|
+
onClick={() => choose(entry.key)}
|
|
85
|
+
/>
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
</HkPopover>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.s-attachment-modal {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--space-12, 0.75rem);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.s-attachment-modal-text {
|
|
8
|
+
max-height: 48vh;
|
|
9
|
+
padding: var(--space-12, 0.75rem);
|
|
10
|
+
border: 1px solid var(--border-faint, rgb(var(--color-border) / 10%));
|
|
11
|
+
border-radius: var(--radius-md, 8px);
|
|
12
|
+
background: rgb(var(--color-surface) / 0.5);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.s-attachment-modal-text-empty {
|
|
16
|
+
margin: 0;
|
|
17
|
+
font-size: var(--text-xs, 0.75rem);
|
|
18
|
+
color: rgb(var(--color-muted));
|
|
19
|
+
font-style: italic;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.s-attachment-modal-text-error {
|
|
23
|
+
margin: var(--space-8, 0.5rem) 0 0;
|
|
24
|
+
font-size: var(--text-xs, 0.75rem);
|
|
25
|
+
color: rgb(var(--color-error));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.s-attachment-modal-file {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: var(--space-8, 0.5rem);
|
|
33
|
+
padding: var(--space-24, 1.5rem);
|
|
34
|
+
color: rgb(var(--color-muted));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.s-attachment-modal-file-name {
|
|
38
|
+
margin: 0;
|
|
39
|
+
font-size: var(--text-sm, 0.8125rem);
|
|
40
|
+
max-width: 100%;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
text-overflow: ellipsis;
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.s-attachment-modal-meta {
|
|
47
|
+
display: flex;
|
|
48
|
+
gap: var(--space-8, 0.5rem);
|
|
49
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { computed, defineComponent, ref, watch, type PropType } from "vue";
|
|
2
|
+
import { Film, File as FileIcon, Image as ImageIcon, Music } from "lucide-vue-next";
|
|
3
|
+
import {
|
|
4
|
+
HBadge,
|
|
5
|
+
HImageViewer,
|
|
6
|
+
HMarkdownRenderer,
|
|
7
|
+
HMediaPlayer,
|
|
8
|
+
HModal,
|
|
9
|
+
HScrollContainer,
|
|
10
|
+
} from "@celestia-island/hikari";
|
|
11
|
+
|
|
12
|
+
import { useI18n } from "../i18n/context";
|
|
13
|
+
|
|
14
|
+
import "./HkAttachmentModal.scss";
|
|
15
|
+
|
|
16
|
+
const CODE_LANGS: Record<string, string> = {
|
|
17
|
+
".ts": "typescript", ".tsx": "typescript", ".js": "javascript",
|
|
18
|
+
".jsx": "javascript", ".json": "json", ".rs": "rust", ".go": "go",
|
|
19
|
+
".py": "python", ".java": "java", ".c": "c", ".cpp": "cpp", ".h": "c",
|
|
20
|
+
".cs": "csharp", ".sh": "bash", ".bash": "bash", ".yaml": "yaml",
|
|
21
|
+
".yml": "yaml", ".toml": "toml", ".html": "html", ".css": "css",
|
|
22
|
+
".scss": "scss", ".sql": "sql", ".xml": "xml", ".ini": "ini",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function codeLanguage(name: string): string | undefined {
|
|
26
|
+
const idx = name.lastIndexOf(".");
|
|
27
|
+
if (idx < 0) return undefined;
|
|
28
|
+
return CODE_LANGS[name.slice(idx).toLowerCase()];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TEXT_EXTS = new Set([".md", ".markdown", ".txt", ".log", ".csv"]);
|
|
32
|
+
const CODE_EXTS = new Set(Object.keys(CODE_LANGS));
|
|
33
|
+
|
|
34
|
+
/** Attachment row in the HRichInput strip. Parent-owned upload state. */
|
|
35
|
+
export interface HAttachmentItem {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
type: string;
|
|
39
|
+
size: number;
|
|
40
|
+
preview?: string;
|
|
41
|
+
/** Upload progress 0-100; omitted/100 = ready. */
|
|
42
|
+
progress?: number;
|
|
43
|
+
status?: "uploading" | "done" | "error";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Attachment payload for HAttachmentModal preview. */
|
|
47
|
+
export interface HAttachmentDetail {
|
|
48
|
+
name: string;
|
|
49
|
+
type: string;
|
|
50
|
+
size: number;
|
|
51
|
+
preview?: string;
|
|
52
|
+
url?: string;
|
|
53
|
+
/** Original File handle — used to read text/code content for preview. */
|
|
54
|
+
file?: File;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Kind of rich preview to render; inferred from MIME unless hinted. */
|
|
58
|
+
export type HAttachmentPreviewType = "image" | "video" | "audio" | "other";
|
|
59
|
+
|
|
60
|
+
/** Resolve the preview kind: an explicit hint wins, MIME prefix otherwise. */
|
|
61
|
+
export function previewKindFor(
|
|
62
|
+
att: Pick<HAttachmentDetail, "type"> | null | undefined,
|
|
63
|
+
hint?: HAttachmentPreviewType,
|
|
64
|
+
): HAttachmentPreviewType {
|
|
65
|
+
if (hint) return hint;
|
|
66
|
+
const type = att?.type ?? "";
|
|
67
|
+
if (type.startsWith("image/")) return "image";
|
|
68
|
+
if (type.startsWith("video/")) return "video";
|
|
69
|
+
if (type.startsWith("audio/")) return "audio";
|
|
70
|
+
return "other";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isTextFile(att: HAttachmentDetail): boolean {
|
|
74
|
+
const ext = att.name.slice(att.name.lastIndexOf(".")).toLowerCase();
|
|
75
|
+
return att.type.startsWith("text/") || TEXT_EXTS.has(ext) || CODE_EXTS.has(ext);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** "512" -> "512B", "1536" -> "1.5KB", "2621440" -> "2.5MB". */
|
|
79
|
+
function formatBytes(bytes: number): string {
|
|
80
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "0B";
|
|
81
|
+
if (bytes < 1024) return `${Math.floor(bytes)}B`;
|
|
82
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
83
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
84
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* HkAttachmentModal — generic file-picker preview modal.
|
|
89
|
+
* (Upstreamed from shittim-chest's plana-legacy layer.)
|
|
90
|
+
*
|
|
91
|
+
* Renders an attachment by preview kind: image (HImageViewer), video/audio
|
|
92
|
+
* (HMediaPlayer), text/code (HMarkdownRenderer, with syntax fences for
|
|
93
|
+
* known code extensions) or a generic file chip.
|
|
94
|
+
*
|
|
95
|
+
* URL handling is transport-agnostic: when the attachment carries no
|
|
96
|
+
* `preview`/`url`, the consumer may pass `resolveUrl` (its own API client /
|
|
97
|
+
* transport knows how to turn a backend file name into an authed URL).
|
|
98
|
+
* The resolved URL is used for media previews, the text fetch and the
|
|
99
|
+
* download action. Text content is read via the attachment's `file` handle
|
|
100
|
+
* when provided.
|
|
101
|
+
*/
|
|
102
|
+
export const HkAttachmentModal = defineComponent({
|
|
103
|
+
name: "HkAttachmentModal",
|
|
104
|
+
props: {
|
|
105
|
+
modelValue: { type: Boolean, default: false },
|
|
106
|
+
attachment: { type: Object as PropType<HAttachmentDetail | null>, default: null },
|
|
107
|
+
/** Transport-provided URL resolver; called with the file name when the
|
|
108
|
+
* attachment has neither `preview` nor `url`. */
|
|
109
|
+
resolveUrl: { type: Function as PropType<(name: string) => Promise<string>>, default: undefined },
|
|
110
|
+
/** Preview kind hint; inferred from the MIME type when omitted. */
|
|
111
|
+
previewType: { type: String as PropType<HAttachmentPreviewType>, default: undefined },
|
|
112
|
+
},
|
|
113
|
+
emits: {
|
|
114
|
+
"update:modelValue": (_v: boolean) => true,
|
|
115
|
+
},
|
|
116
|
+
setup(props, { emit }) {
|
|
117
|
+
const { t } = useI18n();
|
|
118
|
+
|
|
119
|
+
const kind = computed(() => previewKindFor(props.attachment, props.previewType));
|
|
120
|
+
const isText = computed(() => (props.attachment ? isTextFile(props.attachment) : false));
|
|
121
|
+
|
|
122
|
+
/* ── URL resolution ──────────────────────────────────────────── */
|
|
123
|
+
const resolvedSrc = ref("");
|
|
124
|
+
const srcLoading = ref(false);
|
|
125
|
+
const srcError = ref<string | null>(null);
|
|
126
|
+
|
|
127
|
+
async function resolveSrc() {
|
|
128
|
+
const att = props.attachment;
|
|
129
|
+
srcLoading.value = false;
|
|
130
|
+
srcError.value = null;
|
|
131
|
+
if (!att) {
|
|
132
|
+
resolvedSrc.value = "";
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (att.preview || att.url) {
|
|
136
|
+
resolvedSrc.value = att.preview || att.url || "";
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (props.resolveUrl) {
|
|
140
|
+
srcLoading.value = true;
|
|
141
|
+
try {
|
|
142
|
+
const resolved = await props.resolveUrl(att.name);
|
|
143
|
+
// A resolver that misses (map lookup, stale cache) may return
|
|
144
|
+
// undefined — store the empty string so every src binding stays
|
|
145
|
+
// a real string and the srcError path explains the empty preview.
|
|
146
|
+
if (typeof resolved !== "string" || !resolved) {
|
|
147
|
+
resolvedSrc.value = "";
|
|
148
|
+
srcError.value = `no URL resolved for "${att.name}"`;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
resolvedSrc.value = resolved;
|
|
152
|
+
} catch (e) {
|
|
153
|
+
resolvedSrc.value = "";
|
|
154
|
+
srcError.value = e instanceof Error ? e.message : String(e);
|
|
155
|
+
} finally {
|
|
156
|
+
srcLoading.value = false;
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
resolvedSrc.value = "";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
watch(
|
|
164
|
+
() => [props.attachment, props.resolveUrl, props.previewType] as const,
|
|
165
|
+
() => { void resolveSrc(); },
|
|
166
|
+
{ immediate: true },
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
/* ── Text / code preview ─────────────────────────────────────── */
|
|
170
|
+
const textContent = ref("");
|
|
171
|
+
const textPlain = ref(false);
|
|
172
|
+
const textLoading = ref(false);
|
|
173
|
+
const textError = ref<string | null>(null);
|
|
174
|
+
|
|
175
|
+
async function loadText() {
|
|
176
|
+
const att = props.attachment;
|
|
177
|
+
if (!att) return;
|
|
178
|
+
textLoading.value = true;
|
|
179
|
+
textError.value = null;
|
|
180
|
+
try {
|
|
181
|
+
let raw: string;
|
|
182
|
+
if (att.file) {
|
|
183
|
+
raw = await att.file.text();
|
|
184
|
+
} else if (resolvedSrc.value) {
|
|
185
|
+
// The resolved URL comes from the consumer's transport (via
|
|
186
|
+
// `resolveUrl`) or was already a usable preview/object URL.
|
|
187
|
+
const res = await fetch(resolvedSrc.value);
|
|
188
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
189
|
+
raw = await res.text();
|
|
190
|
+
} else {
|
|
191
|
+
raw = "";
|
|
192
|
+
}
|
|
193
|
+
const lang = codeLanguage(att.name);
|
|
194
|
+
const isMd = /\.(md|markdown)$/i.test(att.name);
|
|
195
|
+
if (isMd) {
|
|
196
|
+
textContent.value = raw;
|
|
197
|
+
textPlain.value = false;
|
|
198
|
+
} else if (lang) {
|
|
199
|
+
textContent.value = "```" + lang + "\n" + raw + "\n```";
|
|
200
|
+
textPlain.value = false;
|
|
201
|
+
} else {
|
|
202
|
+
textContent.value = raw;
|
|
203
|
+
textPlain.value = true;
|
|
204
|
+
}
|
|
205
|
+
} catch (e) {
|
|
206
|
+
textError.value = e instanceof Error ? e.message : String(e);
|
|
207
|
+
} finally {
|
|
208
|
+
textLoading.value = false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function startTextLoad() {
|
|
213
|
+
if (!props.modelValue || !props.attachment) return;
|
|
214
|
+
if (isText.value) void loadText();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
watch(
|
|
218
|
+
() => [props.modelValue, props.attachment] as const,
|
|
219
|
+
() => startTextLoad(),
|
|
220
|
+
{ immediate: true },
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Re-fetch text once the transport URL lands (resolveUrl is async).
|
|
224
|
+
watch(resolvedSrc, () => {
|
|
225
|
+
if (!props.attachment?.file) startTextLoad();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
function download() {
|
|
229
|
+
if (!resolvedSrc.value) return;
|
|
230
|
+
const a = document.createElement("a");
|
|
231
|
+
a.href = resolvedSrc.value;
|
|
232
|
+
a.download = props.attachment?.name || "download";
|
|
233
|
+
a.click();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function fileIcon() {
|
|
237
|
+
const att = props.attachment;
|
|
238
|
+
if (!att) return <FileIcon size={40} />;
|
|
239
|
+
if (att.type.startsWith("image/")) return <ImageIcon size={40} />;
|
|
240
|
+
if (att.type.startsWith("video/")) return <Film size={40} />;
|
|
241
|
+
if (att.type.startsWith("audio/")) return <Music size={40} />;
|
|
242
|
+
return <FileIcon size={40} />;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return () => (
|
|
246
|
+
<HModal
|
|
247
|
+
modelValue={props.modelValue}
|
|
248
|
+
onUpdate:modelValue={(v: boolean) => emit("update:modelValue", v)}
|
|
249
|
+
title={props.attachment?.name}
|
|
250
|
+
width="44rem"
|
|
251
|
+
footerActions={[
|
|
252
|
+
{ label: t("hikari::attachment.download", "Download"), onClick: download, disabled: !resolvedSrc.value },
|
|
253
|
+
{ label: t("hikari::attachment.close", "Close"), variant: "secondary", onClick: () => emit("update:modelValue", false) },
|
|
254
|
+
]}
|
|
255
|
+
>
|
|
256
|
+
<div class="s-attachment-modal">
|
|
257
|
+
{srcLoading.value && (
|
|
258
|
+
<p class="s-attachment-modal-text-empty">{t("hikari::attachment.loading", "Loading…")}</p>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
{/* Image — zoomable viewer with minimap navigator */}
|
|
262
|
+
{!srcLoading.value && kind.value === "image" && resolvedSrc.value && (
|
|
263
|
+
<HImageViewer src={resolvedSrc.value} alt={props.attachment?.name ?? ""} />
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
{/* Video — hikari media player with control bar */}
|
|
267
|
+
{!srcLoading.value && kind.value === "video" && resolvedSrc.value && (
|
|
268
|
+
<HMediaPlayer type="video" src={resolvedSrc.value} />
|
|
269
|
+
)}
|
|
270
|
+
|
|
271
|
+
{/* Audio — hikari media player with visualizer + control bar */}
|
|
272
|
+
{!srcLoading.value && kind.value === "audio" && resolvedSrc.value && (
|
|
273
|
+
<HMediaPlayer type="audio" src={resolvedSrc.value} />
|
|
274
|
+
)}
|
|
275
|
+
|
|
276
|
+
{/* Text / code — markdown + highlight.js via HMarkdownRenderer */}
|
|
277
|
+
{isText.value && (
|
|
278
|
+
<HScrollContainer class="s-attachment-modal-text">
|
|
279
|
+
{props.attachment?.file || resolvedSrc.value ? (
|
|
280
|
+
<HMarkdownRenderer
|
|
281
|
+
content={textContent.value}
|
|
282
|
+
loading={textLoading.value}
|
|
283
|
+
plain={textPlain.value}
|
|
284
|
+
/>
|
|
285
|
+
) : (
|
|
286
|
+
<p class="s-attachment-modal-text-empty">
|
|
287
|
+
{srcError.value
|
|
288
|
+
? srcError.value
|
|
289
|
+
: t("hikari::attachment.noPreview", "No preview available.")}
|
|
290
|
+
</p>
|
|
291
|
+
)}
|
|
292
|
+
{textError.value && (
|
|
293
|
+
<p class="s-attachment-modal-text-error">{textError.value}</p>
|
|
294
|
+
)}
|
|
295
|
+
</HScrollContainer>
|
|
296
|
+
)}
|
|
297
|
+
|
|
298
|
+
{/* Generic file */}
|
|
299
|
+
{!srcLoading.value && kind.value === "other" && !isText.value && (
|
|
300
|
+
<div class="s-attachment-modal-file">
|
|
301
|
+
{fileIcon()}
|
|
302
|
+
<p class="s-attachment-modal-file-name">{props.attachment?.name}</p>
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
|
|
306
|
+
{srcError.value && kind.value !== "other" && (
|
|
307
|
+
<p class="s-attachment-modal-text-error">{srcError.value}</p>
|
|
308
|
+
)}
|
|
309
|
+
|
|
310
|
+
<div class="s-attachment-modal-meta">
|
|
311
|
+
<HBadge variant="muted">{props.attachment?.type || "unknown"}</HBadge>
|
|
312
|
+
<HBadge variant="muted">{formatBytes(props.attachment?.size ?? 0)}</HBadge>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
</HModal>
|
|
316
|
+
);
|
|
317
|
+
},
|
|
318
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineComponent, type PropType } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HkAuthCard — the shared shell of every Celestia auth screen (login,
|
|
5
|
+
* register, setup): centered header (logo/title/subtitle) over a column
|
|
6
|
+
* form body.
|
|
7
|
+
*
|
|
8
|
+
* Slot layout contract:
|
|
9
|
+
* - `footer` renders into `.s-auth-footer`, a flex column sized to its
|
|
10
|
+
* widest row and centered in the card: every slot child is its own row
|
|
11
|
+
* (remember-me, protocol consent, a sign-in link…) and the rows share
|
|
12
|
+
* one left edge. Children must not assume a shared inline line or
|
|
13
|
+
* per-row centering; content that must sit on one row belongs inside
|
|
14
|
+
* one child element.
|
|
15
|
+
* - `logo` swaps the header's logo slot; `default` is the form body.
|
|
16
|
+
*/
|
|
17
|
+
export const HkAuthCard = defineComponent({
|
|
18
|
+
name: "HkAuthCard",
|
|
19
|
+
props: {
|
|
20
|
+
title: { type: String, required: true },
|
|
21
|
+
subtitle: { type: String, default: "" },
|
|
22
|
+
},
|
|
23
|
+
setup(props, { slots }) {
|
|
24
|
+
return () => (
|
|
25
|
+
<div class="s-auth-card">
|
|
26
|
+
{slots.logo && (
|
|
27
|
+
<div class="s-auth-header">
|
|
28
|
+
{slots.logo()}
|
|
29
|
+
<h1 class="s-auth-title">{props.title}</h1>
|
|
30
|
+
{props.subtitle && <p class="s-auth-subtitle">{props.subtitle}</p>}
|
|
31
|
+
</div>
|
|
32
|
+
)}
|
|
33
|
+
{!slots.logo && (
|
|
34
|
+
<div class="s-auth-header">
|
|
35
|
+
<h1 class="s-auth-title">{props.title}</h1>
|
|
36
|
+
{props.subtitle && <p class="s-auth-subtitle">{props.subtitle}</p>}
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
<div class="s-auth-form">
|
|
40
|
+
{slots.default?.()}
|
|
41
|
+
</div>
|
|
42
|
+
{slots.footer && (
|
|
43
|
+
<div class="s-auth-footer">
|
|
44
|
+
{slots.footer()}
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { computed, defineComponent, ref, type PropType } from "vue";
|
|
2
|
+
import { HButton, useI18n } from "@celestia-island/hikari";
|
|
3
|
+
import { solvePow, type PowChallenge, type PowSolution } from "../utils/pow";
|
|
4
|
+
|
|
5
|
+
export interface AuthSubmitContext {
|
|
6
|
+
/** Solved PoW for the challenge the backend issued (if any). */
|
|
7
|
+
pow?: PowSolution;
|
|
8
|
+
/** Captcha verification token (if the flow requires one). */
|
|
9
|
+
captchaToken?: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Submit button with an auth-challenge lifecycle (the plana-ui half of the
|
|
14
|
+
* auth kit; hikari provides the base input variants, the backend provides
|
|
15
|
+
* the challenge/captcha facilities this wraps):
|
|
16
|
+
*
|
|
17
|
+
* 1. `onGetChallenge` (optional) — ask the backend for a `{ seed, bits }`
|
|
18
|
+
* PoW challenge.
|
|
19
|
+
* 2. Solve it with the built-in `solvePow` (or a custom `solver`).
|
|
20
|
+
* 3. `onCaptcha` (optional) — acquire a captcha verification token.
|
|
21
|
+
* 4. Call `onSubmit` with the assembled `AuthSubmitContext`.
|
|
22
|
+
*
|
|
23
|
+
* While solving, the button shows its own spinner; `loading` stays
|
|
24
|
+
* external so callers can reflect server-side submission progress.
|
|
25
|
+
*/
|
|
26
|
+
export default defineComponent({
|
|
27
|
+
name: "HkAuthSubmitButton",
|
|
28
|
+
props: {
|
|
29
|
+
label: { type: String, required: true },
|
|
30
|
+
loading: { type: Boolean, default: false },
|
|
31
|
+
disabled: { type: Boolean, default: false },
|
|
32
|
+
error: { type: String, default: undefined },
|
|
33
|
+
block: { type: Boolean, default: false },
|
|
34
|
+
size: { type: String as () => "sm" | "md" | "lg", default: "md" },
|
|
35
|
+
variant: { type: String as () => "primary" | "secondary" | "ghost", default: "primary" },
|
|
36
|
+
/** Fetch the backend PoW challenge; return null to skip PoW. */
|
|
37
|
+
onGetChallenge: { type: Function as PropType<() => Promise<PowChallenge | null>>, default: undefined },
|
|
38
|
+
/** Custom solver; defaults to the built-in subtle-based solvePow. */
|
|
39
|
+
solver: { type: Function as PropType<(c: PowChallenge) => Promise<number>>, default: undefined },
|
|
40
|
+
/** Acquire a captcha token; return null to skip captcha. */
|
|
41
|
+
onCaptcha: { type: Function as PropType<() => Promise<string | null>>, default: undefined },
|
|
42
|
+
/** Invoked with the assembled challenge context when ready. */
|
|
43
|
+
doSubmit: { type: Function as PropType<(ctx: AuthSubmitContext) => Promise<void>>, required: true },
|
|
44
|
+
},
|
|
45
|
+
emits: {
|
|
46
|
+
error: (_message: string) => true,
|
|
47
|
+
},
|
|
48
|
+
setup(props, { emit }) {
|
|
49
|
+
const { t } = useI18n();
|
|
50
|
+
const solving = ref(false);
|
|
51
|
+
|
|
52
|
+
const busy = computed(() => props.loading || solving.value);
|
|
53
|
+
|
|
54
|
+
async function handleClick() {
|
|
55
|
+
if (busy.value) return;
|
|
56
|
+
solving.value = true;
|
|
57
|
+
try {
|
|
58
|
+
const ctx: AuthSubmitContext = { captchaToken: null };
|
|
59
|
+
const challenge = props.onGetChallenge ? await props.onGetChallenge() : null;
|
|
60
|
+
if (challenge && challenge.bits > 0) {
|
|
61
|
+
const solve = props.solver ?? solvePow;
|
|
62
|
+
const counter = await solve(challenge);
|
|
63
|
+
ctx.pow = { seed: challenge.seed, bits: challenge.bits, counter };
|
|
64
|
+
}
|
|
65
|
+
if (props.onCaptcha) {
|
|
66
|
+
ctx.captchaToken = await props.onCaptcha();
|
|
67
|
+
}
|
|
68
|
+
await props.doSubmit(ctx);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
emit("error", e instanceof Error ? e.message : String(e));
|
|
71
|
+
} finally {
|
|
72
|
+
solving.value = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return () => (
|
|
77
|
+
<div class="s-auth-submit">
|
|
78
|
+
<HButton
|
|
79
|
+
variant={props.variant}
|
|
80
|
+
size={props.size}
|
|
81
|
+
block={props.block}
|
|
82
|
+
loading={busy.value}
|
|
83
|
+
disabled={props.disabled || busy.value}
|
|
84
|
+
onClick={() => void handleClick()}
|
|
85
|
+
>
|
|
86
|
+
{solving.value
|
|
87
|
+
? t("hikari::auth.solvingChallenge", "Verifying challenge…")
|
|
88
|
+
: props.label}
|
|
89
|
+
</HButton>
|
|
90
|
+
{props.error && <p class="s-auth-submit-error">{props.error}</p>}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
});
|