gloomberb 0.16.1 → 0.16.2
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 +2 -1
- package/package.json +2 -1
- package/src/api-client/beneficial-owners.ts +92 -0
- package/src/api-client/data.ts +26 -8
- package/src/api-client/errors.ts +12 -0
- package/src/api-client/geo.ts +363 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/paths.ts +30 -1
- package/src/api-client/perps.ts +37 -0
- package/src/api-client/request.ts +5 -2
- package/src/api-client/types.ts +2 -0
- package/src/brokers/account-cache.ts +24 -0
- package/src/cli/commands/command-utils.ts +8 -0
- package/src/cli/commands/crypto-hints.ts +75 -0
- package/src/cli/commands/market.ts +414 -106
- package/src/cli/commands/overview.ts +77 -32
- package/src/cli/commands/ticker.ts +101 -43
- package/src/cli/desktop-pane-shot.ts +34 -4
- package/src/cli/freshness.ts +61 -0
- package/src/cli/help.ts +1 -0
- package/src/cli/helpers.ts +4 -8
- package/src/cli/index.ts +12 -2
- package/src/cli/listing-arg.ts +321 -0
- package/src/cli/pane-functions/access-gate.ts +42 -0
- package/src/cli/pane-functions/catalog.ts +28 -1
- package/src/cli/pane-functions/data.ts +3 -1
- package/src/cli/pane-functions/dom.ts +20 -2
- package/src/cli/pane-functions/freshness.ts +400 -0
- package/src/cli/pane-functions/function-help.ts +27 -20
- package/src/cli/pane-functions/headless.ts +39 -11
- package/src/cli/pane-functions/index.ts +15 -4
- package/src/cli/pane-functions/options.ts +3 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +35 -7
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +7 -3
- package/src/cli/result.ts +41 -27
- package/src/cli/ticker-resolution.ts +7 -5
- package/src/components/chart/composite/composite-chart.tsx +88 -2536
- package/src/components/chart/composite/drawing-store.ts +59 -0
- package/src/components/chart/composite/legend.tsx +319 -0
- package/src/components/chart/composite/panel-bitmap.ts +139 -0
- package/src/components/chart/composite/panel-gestures.ts +59 -0
- package/src/components/chart/composite/panel-labels.ts +107 -0
- package/src/components/chart/composite/panel-surface.tsx +876 -0
- package/src/components/chart/composite/tool-catalog.ts +98 -0
- package/src/components/chart/composite/toolbar.tsx +186 -0
- package/src/components/chart/composite/tools.ts +8 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
- package/src/components/chart/composite/use-composite-viewport.ts +143 -0
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/list/view.tsx +16 -3
- package/src/components/command-bar/pane-templates/workflow.ts +1 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/command-bar/workflow/tickers.ts +6 -0
- package/src/components/data-table/view.tsx +29 -10
- package/src/components/feedback-dialog.tsx +2 -1
- package/src/components/form-modal/field-row.tsx +2 -2
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/pane/content.tsx +23 -10
- package/src/components/layout/pane/error-boundary.tsx +103 -0
- package/src/components/layout/pane/footer/registration.tsx +68 -23
- package/src/components/layout/pane/header.tsx +11 -11
- package/src/components/layout/shell/menu.ts +2 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +14 -3
- package/src/components/metric-treemap/heat-surface.tsx +43 -16
- package/src/components/metric-treemap/heat-tiles.ts +18 -0
- package/src/components/metric-treemap/layout.ts +2 -0
- package/src/components/onboarding/onboarding-keyboard.ts +311 -0
- package/src/components/onboarding/onboarding-wizard.tsx +101 -788
- package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
- package/src/components/onboarding/use-onboarding-progress.ts +298 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
- package/src/components/ticker/list-table-view.tsx +13 -1
- package/src/components/ui/fields.tsx +8 -0
- package/src/components/ui/range-track.tsx +10 -3
- package/src/components/ui/status.tsx +8 -2
- package/src/components/ui/tabs.tsx +7 -4
- package/src/components/ui/text-prompt-dialog.tsx +4 -2
- package/src/data/config/store/normalize.ts +17 -1
- package/src/i18n/es.ts +19 -1
- package/src/i18n/ja.ts +19 -1
- package/src/i18n/ko.ts +19 -1
- package/src/i18n/zh-cn.ts +19 -1
- package/src/i18n/zh-tw.ts +19 -1
- package/src/market-data/coordinator/chart.ts +1 -1
- package/src/market-data/coordinator/quotes.ts +23 -4
- package/src/market-data/fx-closes.ts +72 -0
- package/src/market-data/market/freshness.ts +78 -6
- package/src/market-data/market/session-close-note.ts +57 -0
- package/src/market-data/market/status.ts +151 -0
- package/src/market-data/published-apac-sessions.ts +66 -0
- package/src/market-data/quotes/contributions.ts +2 -0
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +49 -4
- package/src/plugins/builtin/account-management/pane.tsx +4 -0
- package/src/plugins/builtin/alerts/cli.ts +10 -5
- package/src/plugins/builtin/alerts/index.tsx +1 -0
- package/src/plugins/builtin/analytics/metrics.ts +8 -3
- package/src/plugins/builtin/analytics/risk-client.ts +191 -66
- package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +19 -13
- package/src/plugins/builtin/analytics/risk-model.ts +45 -17
- package/src/plugins/builtin/analytics/risk-pane.tsx +1 -0
- package/src/plugins/builtin/analytics/risk-test-data.ts +25 -1
- package/src/plugins/builtin/attention/headless.ts +6 -4
- package/src/plugins/builtin/attention/index.tsx +1 -1
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/awards/headless.ts +1 -0
- package/src/plugins/builtin/awards/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/headless.ts +1 -0
- package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
- package/src/plugins/builtin/buildout/index.tsx +1 -0
- package/src/plugins/builtin/catalysts/headless.ts +3 -1
- package/src/plugins/builtin/cds/headless.ts +2 -0
- package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
- package/src/plugins/builtin/chart-composer/headless.ts +38 -3
- package/src/plugins/builtin/chart-composer/index.tsx +3 -1
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
- package/src/plugins/builtin/chat/channels.ts +19 -3
- package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
- package/src/plugins/builtin/chat/content/footer.tsx +15 -6
- package/src/plugins/builtin/chat/content/index.tsx +120 -21
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
- package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
- package/src/plugins/builtin/chat/direct-messages.ts +200 -0
- package/src/plugins/builtin/chat/index.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
- package/src/plugins/builtin/chat/message/header.tsx +16 -6
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
- package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
- package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
- package/src/plugins/builtin/chat/message/types.ts +14 -2
- package/src/plugins/builtin/chat/profile-popover.ts +18 -13
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/cloud/team/module.tsx +1 -0
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
- package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
- package/src/plugins/builtin/comment-letters/index.tsx +1 -0
- package/src/plugins/builtin/company-attention/headless.ts +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/headless.ts +1 -0
- package/src/plugins/builtin/congress-trades/headless.ts +1 -0
- package/src/plugins/builtin/correlation/compute.ts +59 -16
- package/src/plugins/builtin/correlation/geo.ts +34 -0
- package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
- package/src/plugins/builtin/correlation/headless.ts +42 -8
- package/src/plugins/builtin/correlation/history.ts +2 -0
- package/src/plugins/builtin/correlation/index.tsx +68 -14
- package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
- package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
- package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
- package/src/plugins/builtin/correlation/settings.ts +1 -1
- package/src/plugins/builtin/cot/headless.ts +6 -3
- package/src/plugins/builtin/cot/index.tsx +3 -3
- package/src/plugins/builtin/cot/model.ts +36 -3
- package/src/plugins/builtin/cpi/headless.ts +4 -0
- package/src/plugins/builtin/credit-boards/headless.ts +2 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
- package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
- package/src/plugins/builtin/credit-documents/headless.ts +2 -0
- package/src/plugins/builtin/crypto-board/headless.ts +3 -0
- package/src/plugins/builtin/custom-view/loader.ts +1 -1
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
- package/src/plugins/builtin/doe/headless.ts +7 -0
- package/src/plugins/builtin/doe/range-bar.tsx +7 -37
- package/src/plugins/builtin/earnings/headless.ts +5 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
- package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
- package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
- package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
- package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
- package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
- package/src/plugins/builtin/econ/index.tsx +1 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
- package/src/plugins/builtin/equity-screener/headless.ts +2 -0
- package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
- package/src/plugins/builtin/executives/index.tsx +1 -0
- package/src/plugins/builtin/exposure/headless.ts +3 -1
- package/src/plugins/builtin/fear-greed/headless.ts +2 -0
- package/src/plugins/builtin/filing-events/index.tsx +1 -0
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +39 -10
- package/src/plugins/builtin/futures-curve/index.tsx +3 -3
- package/src/plugins/builtin/futures-curve/model.ts +188 -4
- package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
- package/src/plugins/builtin/gpu/headless.ts +7 -3
- package/src/plugins/builtin/gpu/ladder.tsx +26 -30
- package/src/plugins/builtin/gpu/model.ts +27 -1
- package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
- package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
- package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
- package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
- package/src/plugins/builtin/holders/headless.ts +139 -5
- package/src/plugins/builtin/holders/index.tsx +10 -3
- package/src/plugins/builtin/holders/pane.tsx +20 -3
- package/src/plugins/builtin/holders/report-header.ts +95 -0
- package/src/plugins/builtin/holders/table-model.ts +20 -5
- package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
- package/src/plugins/builtin/holders/types.ts +2 -1
- package/src/plugins/builtin/insider/headless.ts +2 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
- package/src/plugins/builtin/iv-history/headless.ts +4 -1
- package/src/plugins/builtin/jobs/headless.ts +1 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
- package/src/plugins/builtin/macro-day/headless.ts +2 -0
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-heatmap/data.ts +2 -0
- package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +39 -46
- package/src/plugins/builtin/market-heatmap/model.ts +146 -0
- package/src/plugins/builtin/market-heatmap/portfolio.ts +23 -4
- package/src/plugins/builtin/market-movers/headless.ts +2 -0
- package/src/plugins/builtin/market-valuation/headless.ts +2 -0
- package/src/plugins/builtin/members/client.ts +20 -3
- package/src/plugins/builtin/members/headless.ts +5 -3
- package/src/plugins/builtin/members/model.ts +10 -2
- package/src/plugins/builtin/members/pane.tsx +13 -6
- package/src/plugins/builtin/mna/distress/headless.ts +1 -0
- package/src/plugins/builtin/mna/headless.ts +1 -0
- package/src/plugins/builtin/money-markets/headless.ts +1 -0
- package/src/plugins/builtin/news/headless.ts +2 -0
- package/src/plugins/builtin/news/wire/index.tsx +8 -1
- package/src/plugins/builtin/notes/cli.ts +14 -6
- package/src/plugins/builtin/notes/index.tsx +1 -0
- package/src/plugins/builtin/openfda/index.tsx +1 -0
- package/src/plugins/builtin/options-calculator/headless.ts +2 -0
- package/src/plugins/builtin/options-positioning/headless.ts +3 -0
- package/src/plugins/builtin/options-scenario/headless.ts +5 -0
- package/src/plugins/builtin/pe-band/client.ts +48 -3
- package/src/plugins/builtin/pe-band/headless.ts +25 -7
- package/src/plugins/builtin/pe-band/model.ts +92 -16
- package/src/plugins/builtin/pe-band/pane.tsx +33 -16
- package/src/plugins/builtin/perps/client.ts +50 -3
- package/src/plugins/builtin/perps/headless.ts +144 -16
- package/src/plugins/builtin/perps/index.tsx +6 -6
- package/src/plugins/builtin/perps/model.ts +189 -12
- package/src/plugins/builtin/perps/pane.tsx +248 -47
- package/src/plugins/builtin/portfolio-list/allocation.ts +245 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +33 -0
- package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
- package/src/plugins/builtin/portfolio-list/cli/render.ts +230 -39
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
- package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
- package/src/plugins/builtin/portfolio-list/headless.ts +98 -16
- package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
- package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +47 -13
- package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +24 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
- package/src/plugins/builtin/power/headless.ts +3 -1
- package/src/plugins/builtin/rate-path/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
- package/src/plugins/builtin/research/analyst-headless.ts +3 -0
- package/src/plugins/builtin/research/events-headless.ts +2 -0
- package/src/plugins/builtin/research/headless.ts +2 -0
- package/src/plugins/builtin/research/index.tsx +1 -0
- package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
- package/src/plugins/builtin/research/relative-valuation-model.ts +11 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/research-search/index.tsx +1 -0
- package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
- package/src/plugins/builtin/risk-factors/headless.ts +2 -0
- package/src/plugins/builtin/scanner/flow-history.ts +2 -13
- package/src/plugins/builtin/scanner/headless.ts +14 -0
- package/src/plugins/builtin/seasonality/headless.ts +2 -0
- package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
- package/src/plugins/builtin/sec/filing-content.ts +42 -17
- package/src/plugins/builtin/sec/forms.ts +12 -0
- package/src/plugins/builtin/sec/headless.ts +2 -0
- package/src/plugins/builtin/sectors/client.ts +1 -0
- package/src/plugins/builtin/sectors/headless.ts +9 -1
- package/src/plugins/builtin/sectors/sector-model.ts +2 -0
- package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
- package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
- package/src/plugins/builtin/shared/report-freshness.ts +57 -0
- package/src/plugins/builtin/shared/research-cells.tsx +4 -9
- package/src/plugins/builtin/short-interest/headless.ts +2 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -0
- package/src/plugins/builtin/supply-chain/headless.ts +7 -1
- package/src/plugins/builtin/supply-chain/index.tsx +10 -1
- package/src/plugins/builtin/supply-chain/model.ts +27 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
- package/src/plugins/builtin/themes/headless.ts +3 -0
- package/src/plugins/builtin/thirteenf/headless.ts +110 -25
- package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
- package/src/plugins/builtin/ticker-detail/headless.ts +7 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
- package/src/plugins/builtin/time-sales/headless.ts +5 -1
- package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
- package/src/plugins/builtin/volatility/headless.ts +6 -1
- package/src/plugins/builtin/world-indices/headless.ts +2 -0
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +157 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +214 -0
- package/src/plugins/builtin/world-venue-map/client.ts +45 -0
- package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
- package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
- package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
- package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
- package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +447 -0
- package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
- package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +67 -4
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +552 -0
- package/src/plugins/builtin/world-venue-map/layers.ts +348 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +381 -45
- package/src/plugins/builtin/world-venue-map/model.ts +7 -4
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +199 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +28 -43
- package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
- package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
- package/src/plugins/builtin/yield-curve/headless.ts +16 -0
- package/src/plugins/loader.ts +13 -1
- package/src/plugins/registry/pane-settings.ts +1 -0
- package/src/renderers/dom/build-assets.ts +81 -3
- package/src/renderers/dom/data-table/row.tsx +2 -0
- package/src/renderers/dom/desktop/controls.tsx +7 -3
- package/src/renderers/dom/desktop/list-view.tsx +35 -10
- package/src/renderers/dom/desktop/popover.tsx +73 -14
- package/src/renderers/dom/host/input.tsx +14 -3
- package/src/renderers/dom/styles.css +6 -7
- package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
- package/src/renderers/opentui/host.tsx +5 -0
- package/src/sources/gloomberb-cloud/index.ts +25 -4
- package/src/sources/gloomberb-cloud/normalizers.ts +27 -3
- package/src/sources/provider-router/cached-routes.ts +4 -1
- package/src/sources/provider-router/financials.ts +8 -7
- package/src/sources/provider-router/history.ts +15 -10
- package/src/sources/sec-registrant.ts +75 -0
- package/src/state/app/context/index.tsx +24 -8
- package/src/state/hooks/quote-streaming.ts +41 -26
- package/src/sync/core-contributors.ts +4 -0
- package/src/telemetry/crash-reports.ts +4 -1
- package/src/test-support/cli-context.ts +10 -2
- package/src/theme/heat-colors.ts +266 -84
- package/src/time-series/resolve.ts +1 -1
- package/src/time-series/use-chart-resolution.ts +1 -1
- package/src/types/context-menu.ts +6 -0
- package/src/types/data-provider.ts +2 -0
- package/src/types/financials.ts +3 -0
- package/src/types/headless.ts +79 -2
- package/src/types/plugin.ts +20 -1
- package/src/types/ticker.ts +15 -0
- package/src/ui/host.tsx +14 -0
- package/src/updater/index.ts +9 -11
- package/src/utils/async-deadline.ts +21 -0
- package/src/utils/cli-output.ts +5 -1
- package/src/utils/crypto-pair.ts +41 -0
- package/src/utils/currency-units.ts +14 -0
- package/src/utils/depositary-receipt.ts +26 -0
- package/src/utils/exchanges.ts +72 -0
- package/src/utils/fire-and-forget.ts +24 -0
- package/src/utils/format.ts +13 -0
- package/src/utils/price-earnings.ts +70 -3
- package/src/utils/price-history.ts +58 -13
- package/src/utils/rpc-timeout-error.ts +27 -0
- package/src/utils/sec.ts +13 -1
- package/src/utils/utc-time.ts +79 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { ChatChannel, ChatUserSummary } from "../../../api-client";
|
|
2
|
+
import { ApiRequestError } from "../../../api-client/errors";
|
|
3
|
+
import { t, tf } from "../../../i18n";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The server's code for "this user only takes DMs from people they have
|
|
7
|
+
* written to first". Any other refusal, a block among them, stays the neutral
|
|
8
|
+
* generic line so the reason never reads as "they blocked you".
|
|
9
|
+
*/
|
|
10
|
+
const DMS_CLOSED_CODE = "dms_closed";
|
|
11
|
+
|
|
12
|
+
/** Why a DM cannot be started: the user's own setting, or an account nobody can DM (a bot, a bridged Discord user). */
|
|
13
|
+
type DirectMessageRefusal = "dms-off" | "no-dms";
|
|
14
|
+
|
|
15
|
+
export type DirectMessageAvailability =
|
|
16
|
+
| { kind: "self" }
|
|
17
|
+
/** A DM with this user exists; opening it needs no new conversation. */
|
|
18
|
+
| { kind: "open"; channelId: string }
|
|
19
|
+
| { kind: "start" }
|
|
20
|
+
| { kind: "refused"; reason: DirectMessageRefusal };
|
|
21
|
+
|
|
22
|
+
function normalizedUsername(user: Pick<ChatUserSummary, "username">): string | null {
|
|
23
|
+
return user.username?.trim().replace(/^@+/, "").toLowerCase() || null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function findDirectChannelWith(channels: readonly ChatChannel[], user: Pick<ChatUserSummary, "id" | "username">): ChatChannel | null {
|
|
27
|
+
const username = normalizedUsername(user);
|
|
28
|
+
return channels.find((channel) => {
|
|
29
|
+
if (channel.kind !== "direct" || !channel.dmUser) return false;
|
|
30
|
+
if (channel.dmUser.id === user.id) return true;
|
|
31
|
+
return !!username && normalizedUsername(channel.dmUser) === username;
|
|
32
|
+
}) ?? null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function refusalOf(user: ChatUserSummary): DirectMessageRefusal | null {
|
|
36
|
+
if (user.accountType === "managed_llm" || user.accountType === "discord") return "no-dms";
|
|
37
|
+
if (user.acceptUnknownDms === false) return "dms-off";
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* What "Message @name" does for this user: open the DM you already share,
|
|
43
|
+
* start one, or nothing. The server only lets you start a DM with someone
|
|
44
|
+
* whose settings take DMs from people they have not talked to; one that
|
|
45
|
+
* already exists stays open to both sides. A summary from an older server
|
|
46
|
+
* that leaves the setting out is assumed to take DMs.
|
|
47
|
+
*/
|
|
48
|
+
export function directMessageAvailability(
|
|
49
|
+
user: ChatUserSummary,
|
|
50
|
+
{ currentUserId, channels }: { currentUserId: string | null | undefined; channels: readonly ChatChannel[] },
|
|
51
|
+
): DirectMessageAvailability {
|
|
52
|
+
if (currentUserId && user.id === currentUserId) return { kind: "self" };
|
|
53
|
+
const existing = findDirectChannelWith(channels, user);
|
|
54
|
+
if (existing) return { kind: "open", channelId: existing.id };
|
|
55
|
+
const reason = refusalOf(user);
|
|
56
|
+
return reason ? { kind: "refused", reason } : { kind: "start" };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface DirectMessageAction {
|
|
60
|
+
/**
|
|
61
|
+
* Opens the DM you already share, starts one, or cannot: their settings or
|
|
62
|
+
* the account refuse a new DM. A menu shows a refused one disabled, the
|
|
63
|
+
* card of someone with no public profile says why, and a profile card or
|
|
64
|
+
* the pane menu leaves it out.
|
|
65
|
+
*/
|
|
66
|
+
kind: "open" | "start" | "refused";
|
|
67
|
+
username: string;
|
|
68
|
+
/** For a menu: "Message @name", or "Open DM with @name" when one exists. */
|
|
69
|
+
menuLabel: string;
|
|
70
|
+
/** For a button beside the name: "Message" or "Open DM". */
|
|
71
|
+
buttonLabel: string;
|
|
72
|
+
/**
|
|
73
|
+
* A refused one: why, for a line where the button would be. It leaves the
|
|
74
|
+
* name out; the card it sits on already shows it.
|
|
75
|
+
*/
|
|
76
|
+
refusal?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The "Message" action on a user's name, card or message. Null for yourself
|
|
81
|
+
* and before you can send. Refused for a bot, a bridged Discord user, and
|
|
82
|
+
* someone who takes no DMs from people they have not talked to, unless you
|
|
83
|
+
* already share one.
|
|
84
|
+
*/
|
|
85
|
+
export function directMessageAction(
|
|
86
|
+
user: ChatUserSummary,
|
|
87
|
+
context: { currentUserId: string | null | undefined; channels: readonly ChatChannel[]; canSend: boolean },
|
|
88
|
+
): DirectMessageAction | null {
|
|
89
|
+
const username = normalizedUsername(user);
|
|
90
|
+
if (!context.canSend || !username) return null;
|
|
91
|
+
const availability = directMessageAvailability(user, context);
|
|
92
|
+
if (availability.kind === "open") {
|
|
93
|
+
return {
|
|
94
|
+
kind: "open",
|
|
95
|
+
username,
|
|
96
|
+
menuLabel: tf("Open DM with @{username}", { username }),
|
|
97
|
+
buttonLabel: t("Open DM"),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (availability.kind === "self") return null;
|
|
101
|
+
return {
|
|
102
|
+
kind: availability.kind,
|
|
103
|
+
username,
|
|
104
|
+
menuLabel: tf("Message @{username}", { username }),
|
|
105
|
+
buttonLabel: t("Message"),
|
|
106
|
+
...(availability.kind === "refused" ? {
|
|
107
|
+
refusal: availability.reason === "dms-off"
|
|
108
|
+
? t("Only takes DMs from people they have written to first.")
|
|
109
|
+
: t("Does not take DMs."),
|
|
110
|
+
} : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function directMessageRefusalText(username: string, reason: DirectMessageRefusal): string {
|
|
115
|
+
return reason === "dms-off"
|
|
116
|
+
? tf("@{username} only takes DMs from people they have written to first.", { username })
|
|
117
|
+
: tf("@{username} does not take DMs.", { username });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The first named user a group chat cannot include. Unlike a direct message,
|
|
122
|
+
* a DM already shared with them does not help: every member of a new group
|
|
123
|
+
* has to take DMs from people they have not talked to.
|
|
124
|
+
*/
|
|
125
|
+
function groupChatRefusal(
|
|
126
|
+
usernames: readonly string[],
|
|
127
|
+
userByUsername: ReadonlyMap<string, ChatUserSummary>,
|
|
128
|
+
): { username: string; reason: DirectMessageRefusal } | null {
|
|
129
|
+
for (const username of usernames) {
|
|
130
|
+
const user = userByUsername.get(username);
|
|
131
|
+
const reason = user ? refusalOf(user) : null;
|
|
132
|
+
if (reason) return { username, reason };
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* What the app knows, before asking the server, about why these names cannot
|
|
139
|
+
* start a conversation. Null when it knows of no reason (an unknown name, an
|
|
140
|
+
* existing DM, or users who take DMs).
|
|
141
|
+
*/
|
|
142
|
+
export function knownConversationRefusal(
|
|
143
|
+
usernames: readonly string[],
|
|
144
|
+
{
|
|
145
|
+
userByUsername,
|
|
146
|
+
currentUserId,
|
|
147
|
+
channels,
|
|
148
|
+
}: {
|
|
149
|
+
userByUsername: ReadonlyMap<string, ChatUserSummary>;
|
|
150
|
+
currentUserId: string | null | undefined;
|
|
151
|
+
channels: readonly ChatChannel[];
|
|
152
|
+
},
|
|
153
|
+
): string | null {
|
|
154
|
+
if (usernames.length === 1) {
|
|
155
|
+
const user = userByUsername.get(usernames[0]!);
|
|
156
|
+
if (!user) return null;
|
|
157
|
+
const availability = directMessageAvailability(user, { currentUserId, channels });
|
|
158
|
+
return availability.kind === "refused" ? directMessageRefusalText(usernames[0]!, availability.reason) : null;
|
|
159
|
+
}
|
|
160
|
+
const refusal = groupChatRefusal(usernames, userByUsername);
|
|
161
|
+
return refusal ? directMessageRefusalText(refusal.username, refusal.reason) : null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function joinUsernames(usernames: readonly string[]): string {
|
|
165
|
+
return usernames.map((username) => `@${username}`).join(", ");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The line shown when opening a DM or group chat failed. A name the server
|
|
170
|
+
* does not know is named; a user whose settings refuse DMs from new people is
|
|
171
|
+
* named with that reason; every other refusal, a block included, stays
|
|
172
|
+
* neutral and only carries the status.
|
|
173
|
+
*/
|
|
174
|
+
export function describeConversationStartError(
|
|
175
|
+
error: unknown,
|
|
176
|
+
usernames: readonly string[],
|
|
177
|
+
userByUsername: ReadonlyMap<string, ChatUserSummary>,
|
|
178
|
+
): string {
|
|
179
|
+
const status = error instanceof ApiRequestError ? error.status : undefined;
|
|
180
|
+
if (status === 404) {
|
|
181
|
+
// The server does not say which member of a group it could not find; the
|
|
182
|
+
// names this app has never seen are the candidates.
|
|
183
|
+
const missing = usernames.length === 1 ? usernames : usernames.filter((username) => !userByUsername.has(username));
|
|
184
|
+
if (missing.length === 1) return tf("@{username} was not found.", { username: missing[0]! });
|
|
185
|
+
if (missing.length > 1) return tf("{usernames} were not found.", { usernames: joinUsernames(missing) });
|
|
186
|
+
return t("One of those users was not found.");
|
|
187
|
+
}
|
|
188
|
+
if (status === 403 && error instanceof ApiRequestError && error.code === DMS_CLOSED_CODE) {
|
|
189
|
+
const named = typeof error.details?.username === "string" ? error.details.username.toLowerCase() : null;
|
|
190
|
+
const username = named
|
|
191
|
+
?? (usernames.length === 1 ? usernames[0] : groupChatRefusal(usernames, userByUsername)?.username)
|
|
192
|
+
?? null;
|
|
193
|
+
return username
|
|
194
|
+
? directMessageRefusalText(username, "dms-off")
|
|
195
|
+
: t("Someone in that group only takes DMs from people they have written to first.");
|
|
196
|
+
}
|
|
197
|
+
return status
|
|
198
|
+
? tf("Could not start the conversation (error {status}).", { status })
|
|
199
|
+
: t("Could not start the conversation. Check your connection and try again.");
|
|
200
|
+
}
|
|
@@ -28,6 +28,7 @@ export const DesktopChatMessage = memo(function DesktopChatMessage({
|
|
|
28
28
|
onUserHover,
|
|
29
29
|
onUserHoverEnd,
|
|
30
30
|
onUserActivate,
|
|
31
|
+
onUserContextMenu,
|
|
31
32
|
beginReplyTo,
|
|
32
33
|
beginEditMessage,
|
|
33
34
|
jumpToMessage,
|
|
@@ -113,6 +114,7 @@ export const DesktopChatMessage = memo(function DesktopChatMessage({
|
|
|
113
114
|
onUserHover={onUserHover}
|
|
114
115
|
onUserHoverEnd={onUserHoverEnd}
|
|
115
116
|
onUserActivate={onUserActivate}
|
|
117
|
+
onUserContextMenu={onUserContextMenu}
|
|
116
118
|
{...actionProps}
|
|
117
119
|
/>
|
|
118
120
|
)}
|
|
@@ -134,6 +136,7 @@ export const DesktopChatMessage = memo(function DesktopChatMessage({
|
|
|
134
136
|
onUserHover={onUserHover}
|
|
135
137
|
onUserHoverEnd={onUserHoverEnd}
|
|
136
138
|
onUserActivate={onUserActivate}
|
|
139
|
+
onUserContextMenu={onUserContextMenu}
|
|
137
140
|
/>
|
|
138
141
|
</Box>
|
|
139
142
|
{state.grouped && <ChatMessageActions floating {...actionProps} />}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { Box, Text, useUiCapabilities, type BoxRenderable } from "../../../../ui";
|
|
2
3
|
import { t } from "../../../../i18n";
|
|
3
4
|
import { Button } from "../../../../components/ui";
|
|
4
5
|
import { MESSAGE_ACTION_WIDTH } from "../layout";
|
|
@@ -73,27 +74,36 @@ export function ChatMessageHeader({
|
|
|
73
74
|
onUserHover,
|
|
74
75
|
onUserHoverEnd,
|
|
75
76
|
onUserActivate,
|
|
77
|
+
onUserContextMenu,
|
|
76
78
|
...actionProps
|
|
77
|
-
}: ChatMessageActionProps & Pick<ChatMessageBaseProps, "msg" | "onUserHover" | "onUserHoverEnd" | "onUserActivate"> & {
|
|
79
|
+
}: ChatMessageActionProps & Pick<ChatMessageBaseProps, "msg" | "onUserHover" | "onUserHoverEnd" | "onUserActivate" | "onUserContextMenu"> & {
|
|
78
80
|
/** Host row props: width, background and hover or selection hooks. */
|
|
79
81
|
rowProps: Record<string, unknown>;
|
|
80
82
|
/** The terminal sizes the author cell to its label. */
|
|
81
83
|
fitAuthorWidth?: boolean;
|
|
82
84
|
}) {
|
|
83
85
|
const authorLabel = msg.user.username ?? "anon";
|
|
86
|
+
const { nativeContextMenu } = useUiCapabilities();
|
|
87
|
+
// The card opens beside the name the pointer is on.
|
|
88
|
+
const nameRef = useRef<BoxRenderable | null>(null);
|
|
84
89
|
return (
|
|
85
90
|
<Box {...rowProps} flexDirection="row" height={1} paddingLeft={1}>
|
|
86
91
|
<Box
|
|
92
|
+
ref={nameRef}
|
|
87
93
|
width={fitAuthorWidth ? authorLabel.length : undefined}
|
|
88
94
|
height={1}
|
|
89
|
-
onMouseOver={() => onUserHover(msg.user)}
|
|
90
|
-
onMouseMove={() => onUserHover(msg.user)}
|
|
95
|
+
onMouseOver={() => onUserHover(msg.user, nameRef.current)}
|
|
96
|
+
onMouseMove={() => onUserHover(msg.user, nameRef.current)}
|
|
91
97
|
onMouseOut={onUserHoverEnd}
|
|
92
|
-
|
|
98
|
+
data-gloom-context-menu-surface="true"
|
|
99
|
+
onMouseDown={(event: { button?: number; preventDefault?: () => void; stopPropagation?: () => void }) => {
|
|
93
100
|
event.preventDefault?.();
|
|
94
101
|
event.stopPropagation?.();
|
|
95
|
-
|
|
102
|
+
// A native menu opens on the right-click's contextmenu event instead.
|
|
103
|
+
if (event.button === 2 && nativeContextMenu === true) return;
|
|
104
|
+
onUserActivate?.(msg.user, nameRef.current);
|
|
96
105
|
}}
|
|
106
|
+
onContextMenu={(event: { preventDefault?: () => void; stopPropagation?: () => void }) => onUserContextMenu?.(msg.user, event, nameRef.current)}
|
|
97
107
|
style={{ cursor: "pointer" }}
|
|
98
108
|
>
|
|
99
109
|
<Text fg={state.authorColor} attributes={state.authorAttributes}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useMemo, useState } from "react";
|
|
2
|
-
import { Box, Text } from "../../../../ui";
|
|
1
|
+
import { useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Box, Text, useUiCapabilities, type BoxRenderable } from "../../../../ui";
|
|
3
3
|
import { TextAttributes } from "../../../../ui";
|
|
4
4
|
import { ExternalLinkText } from "../../../../components/ui";
|
|
5
5
|
import { InlineTickerBadge } from "../../../../components/ticker/badge";
|
|
@@ -8,6 +8,59 @@ import { blendHex, colors } from "../../../../theme/colors";
|
|
|
8
8
|
import type { ChatUserSummary } from "../../../../api-client";
|
|
9
9
|
import { tokenizeInlineContent, type InlineContentToken } from "../../../../utils/inline-content-tokenizer";
|
|
10
10
|
import { chatBadgeTextWidth } from "../layout";
|
|
11
|
+
import type { ChatUserAnchor, ChatUserContextMenuEvent } from "./types";
|
|
12
|
+
|
|
13
|
+
/** An @mention: a name that opens its user's card, when the chat knows the user. */
|
|
14
|
+
function UserMentionToken({
|
|
15
|
+
value,
|
|
16
|
+
user,
|
|
17
|
+
nativeContextMenu,
|
|
18
|
+
onUserHover,
|
|
19
|
+
onUserHoverEnd,
|
|
20
|
+
onUserActivate,
|
|
21
|
+
onUserContextMenu,
|
|
22
|
+
}: {
|
|
23
|
+
value: string;
|
|
24
|
+
user: ChatUserSummary | null;
|
|
25
|
+
nativeContextMenu: boolean;
|
|
26
|
+
onUserHover?: (user: ChatUserSummary, anchor?: ChatUserAnchor) => void;
|
|
27
|
+
onUserHoverEnd?: () => void;
|
|
28
|
+
onUserActivate?: (user: ChatUserSummary, anchor?: ChatUserAnchor) => void;
|
|
29
|
+
onUserContextMenu?: (user: ChatUserSummary, event: ChatUserContextMenuEvent, anchor?: ChatUserAnchor) => void;
|
|
30
|
+
}) {
|
|
31
|
+
// The card opens beside the mention the pointer is on.
|
|
32
|
+
const tokenRef = useRef<BoxRenderable | null>(null);
|
|
33
|
+
return (
|
|
34
|
+
<Box
|
|
35
|
+
ref={tokenRef}
|
|
36
|
+
height={1}
|
|
37
|
+
flexDirection="row"
|
|
38
|
+
backgroundColor={blendHex(colors.panel, colors.positive, 0.24)}
|
|
39
|
+
onMouseOver={() => {
|
|
40
|
+
if (user) onUserHover?.(user, tokenRef.current);
|
|
41
|
+
}}
|
|
42
|
+
onMouseOut={() => {
|
|
43
|
+
if (user) onUserHoverEnd?.();
|
|
44
|
+
}}
|
|
45
|
+
// Only a mention of someone the chat has a summary for has a card or a menu to open.
|
|
46
|
+
onMouseDown={user && onUserActivate ? (event: { button?: number; preventDefault?: () => void; stopPropagation?: () => void }) => {
|
|
47
|
+
event.preventDefault?.();
|
|
48
|
+
event.stopPropagation?.();
|
|
49
|
+
if (event.button === 2 && nativeContextMenu) return;
|
|
50
|
+
onUserActivate(user, tokenRef.current);
|
|
51
|
+
} : undefined}
|
|
52
|
+
{...(user && onUserContextMenu ? {
|
|
53
|
+
"data-gloom-context-menu-surface": "true",
|
|
54
|
+
onContextMenu: (event: ChatUserContextMenuEvent) => onUserContextMenu(user, event, tokenRef.current),
|
|
55
|
+
} : {})}
|
|
56
|
+
style={user && onUserActivate ? { cursor: "pointer" } : undefined}
|
|
57
|
+
>
|
|
58
|
+
<Text fg={colors.positive} attributes={TextAttributes.BOLD}>
|
|
59
|
+
{value}
|
|
60
|
+
</Text>
|
|
61
|
+
</Box>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
11
64
|
|
|
12
65
|
export function ResponsiveTickerBadgeText({
|
|
13
66
|
text = "",
|
|
@@ -20,6 +73,7 @@ export function ResponsiveTickerBadgeText({
|
|
|
20
73
|
onUserHover,
|
|
21
74
|
onUserHoverEnd,
|
|
22
75
|
onUserActivate,
|
|
76
|
+
onUserContextMenu,
|
|
23
77
|
}: {
|
|
24
78
|
text?: string;
|
|
25
79
|
tokens?: readonly InlineContentToken[];
|
|
@@ -28,10 +82,12 @@ export function ResponsiveTickerBadgeText({
|
|
|
28
82
|
textColor: string;
|
|
29
83
|
openTicker: (symbol: string) => void;
|
|
30
84
|
userByUsername?: Map<string, ChatUserSummary>;
|
|
31
|
-
onUserHover?: (user: ChatUserSummary) => void;
|
|
85
|
+
onUserHover?: (user: ChatUserSummary, anchor?: ChatUserAnchor) => void;
|
|
32
86
|
onUserHoverEnd?: () => void;
|
|
33
|
-
onUserActivate?: (user: ChatUserSummary) => void;
|
|
87
|
+
onUserActivate?: (user: ChatUserSummary, anchor?: ChatUserAnchor) => void;
|
|
88
|
+
onUserContextMenu?: (user: ChatUserSummary, event: ChatUserContextMenuEvent, anchor?: ChatUserAnchor) => void;
|
|
34
89
|
}) {
|
|
90
|
+
const { nativeContextMenu } = useUiCapabilities();
|
|
35
91
|
const [hoveredSymbol, setHoveredSymbol] = useState<string | null>(null);
|
|
36
92
|
const tokens = useMemo(() => providedTokens ?? tokenizeInlineContent(text), [providedTokens, text]);
|
|
37
93
|
const renderTextToken = (value: string, tokenIndex: number) => {
|
|
@@ -53,34 +109,18 @@ export function ResponsiveTickerBadgeText({
|
|
|
53
109
|
</Text>
|
|
54
110
|
);
|
|
55
111
|
};
|
|
56
|
-
const renderUsernameToken = (username: string, value: string, tokenIndex: number) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (user) onUserHoverEnd?.();
|
|
69
|
-
}}
|
|
70
|
-
// Only a mention of someone the chat has a summary for has a card to open.
|
|
71
|
-
onMouseDown={user && onUserActivate ? (event: { preventDefault?: () => void; stopPropagation?: () => void }) => {
|
|
72
|
-
event.preventDefault?.();
|
|
73
|
-
event.stopPropagation?.();
|
|
74
|
-
onUserActivate(user);
|
|
75
|
-
} : undefined}
|
|
76
|
-
style={user && onUserActivate ? { cursor: "pointer" } : undefined}
|
|
77
|
-
>
|
|
78
|
-
<Text fg={colors.positive} attributes={TextAttributes.BOLD}>
|
|
79
|
-
{value}
|
|
80
|
-
</Text>
|
|
81
|
-
</Box>
|
|
82
|
-
);
|
|
83
|
-
};
|
|
112
|
+
const renderUsernameToken = (username: string, value: string, tokenIndex: number) => (
|
|
113
|
+
<UserMentionToken
|
|
114
|
+
key={`mention:${tokenIndex}:${username}`}
|
|
115
|
+
value={value}
|
|
116
|
+
user={userByUsername?.get(username.toLowerCase()) ?? null}
|
|
117
|
+
nativeContextMenu={nativeContextMenu === true}
|
|
118
|
+
onUserHover={onUserHover}
|
|
119
|
+
onUserHoverEnd={onUserHoverEnd}
|
|
120
|
+
onUserActivate={onUserActivate}
|
|
121
|
+
onUserContextMenu={onUserContextMenu}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
84
124
|
|
|
85
125
|
return (
|
|
86
126
|
<Box
|
|
@@ -3,10 +3,15 @@ import { Box, Text, useUiCapabilities } from "../../../../ui";
|
|
|
3
3
|
import { TextAttributes } from "../../../../ui";
|
|
4
4
|
import { colors } from "../../../../theme/colors";
|
|
5
5
|
import type { ChatUserSummary, PublicPortfolioAnalytics } from "../../../../api-client";
|
|
6
|
-
import { formatNumber } from "../../../../utils/format";
|
|
6
|
+
import { displayWidth, formatNumber, truncateToDisplayWidth } from "../../../../utils/format";
|
|
7
7
|
import { truncateWithEllipsis } from "../../../../utils/text-wrap";
|
|
8
|
+
import type { ChatUserAnchor } from "./types";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* How long a card the pointer left stays: long enough to move from the name
|
|
12
|
+
* onto the card beside it, whose own hover then keeps it open.
|
|
13
|
+
*/
|
|
14
|
+
export const PROFILE_POPOVER_CLOSE_DELAY_MS = 250;
|
|
10
15
|
|
|
11
16
|
function hasPortfolioAnalytics(analytics: PublicPortfolioAnalytics | null | undefined): boolean {
|
|
12
17
|
return Boolean(
|
|
@@ -122,31 +127,92 @@ function HeaderAnalyticsStats({
|
|
|
122
127
|
);
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
/** The narrowest name-only card, so a short name still has a button to aim at. */
|
|
131
|
+
const NAME_CARD_MIN_WIDTH = 18;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A name-only card's one row: the handle, then the display name where it says
|
|
135
|
+
* something the handle does not.
|
|
136
|
+
*/
|
|
137
|
+
function nameCardRow(user: ChatUserSummary): { handle: string; name: string | null } {
|
|
138
|
+
const displayName = user.displayName?.trim() ?? "";
|
|
139
|
+
if (!user.username) return { handle: displayName, name: null };
|
|
140
|
+
const same = displayName.replace(/^@+/, "").toLowerCase() === user.username.toLowerCase();
|
|
141
|
+
return { handle: `@${user.username}`, name: displayName && !same ? displayName : null };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function nameCardNaturalWidth(user: ChatUserSummary): number {
|
|
145
|
+
const { handle, name } = nameCardRow(user);
|
|
146
|
+
return displayWidth(handle) + (name ? 1 + displayWidth(name) : 0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function NameCardRow({ user, width }: { user: ChatUserSummary; width: number }) {
|
|
150
|
+
const { handle, name } = nameCardRow(user);
|
|
151
|
+
const handleText = truncateToDisplayWidth(handle, width);
|
|
152
|
+
const nameWidth = width - displayWidth(handleText) - 1;
|
|
153
|
+
return (
|
|
154
|
+
<Box height={1} width={width} flexDirection="row">
|
|
155
|
+
<Text fg={colors.positive} attributes={TextAttributes.BOLD}>{handleText}</Text>
|
|
156
|
+
{name && nameWidth >= 4 ? <Text fg={colors.textDim}>{` ${truncateToDisplayWidth(name, nameWidth)}`}</Text> : null}
|
|
157
|
+
</Box>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
125
161
|
export function UserProfilePopover({
|
|
126
162
|
user,
|
|
163
|
+
anchor = null,
|
|
127
164
|
width,
|
|
128
165
|
onClose,
|
|
129
166
|
onDismiss = onClose,
|
|
130
167
|
onKeepOpen,
|
|
131
168
|
isOwnProfile = false,
|
|
132
169
|
onSetUpProfile,
|
|
170
|
+
messageAction,
|
|
171
|
+
messageRefusal,
|
|
133
172
|
}: {
|
|
134
173
|
user: ChatUserSummary;
|
|
174
|
+
/**
|
|
175
|
+
* The name or @mention it opened from. The desktop and the web place the
|
|
176
|
+
* card just below it, or above it near the pane's foot; without one, and in
|
|
177
|
+
* the terminal, it sits in the pane's top-right corner.
|
|
178
|
+
*/
|
|
179
|
+
anchor?: ChatUserAnchor;
|
|
135
180
|
width: number;
|
|
136
181
|
/** The pointer left the card. */
|
|
137
182
|
onClose: () => void;
|
|
138
183
|
/** A click outside the card or Esc closes it, even a pinned one. */
|
|
139
184
|
onDismiss?: () => void;
|
|
185
|
+
/** The pointer came onto the card: it stays open while it is used. */
|
|
140
186
|
onKeepOpen: () => void;
|
|
141
187
|
isOwnProfile?: boolean;
|
|
142
188
|
onSetUpProfile?: () => void;
|
|
189
|
+
/** "Message" or "Open DM" for someone you can write to; left out for anyone else. */
|
|
190
|
+
messageAction?: { label: string; onPress: () => void } | null;
|
|
191
|
+
/**
|
|
192
|
+
* Why someone takes no DM from you. The name-only card says it where the
|
|
193
|
+
* button would be; a profile card leaves it out.
|
|
194
|
+
*/
|
|
195
|
+
messageRefusal?: string | null;
|
|
143
196
|
}) {
|
|
144
|
-
const
|
|
197
|
+
const { nativePaneChrome } = useUiCapabilities();
|
|
198
|
+
// The terminal card's border and padding take two cells a side; the desktop
|
|
199
|
+
// popover pads itself, and its content keeps the width it always had.
|
|
200
|
+
const chromeWidth = nativePaneChrome ? 2 : 4;
|
|
201
|
+
const maxPopoverWidth = Math.max(24, Math.min(38, width - 4));
|
|
202
|
+
// Anyone without a public profile still gets a card: the name and a way to write to them.
|
|
203
|
+
const nameOnly = !isOwnProfile && !hasPublicChatProfileInfo(user);
|
|
204
|
+
const popoverWidth = nameOnly
|
|
205
|
+
? Math.min(maxPopoverWidth, Math.max(
|
|
206
|
+
NAME_CARD_MIN_WIDTH,
|
|
207
|
+
nameCardNaturalWidth(user),
|
|
208
|
+
messageRefusal ? displayWidth(messageRefusal) : 0,
|
|
209
|
+
) + chromeWidth)
|
|
210
|
+
: maxPopoverWidth;
|
|
145
211
|
const meta = [user.title, user.company].filter(Boolean).join(" · ");
|
|
146
212
|
const bio = user.bio?.trim();
|
|
147
213
|
const analytics = user.portfolioAnalytics;
|
|
148
214
|
const metrics = analytics ? analyticsMetrics(analytics) : [];
|
|
149
|
-
const headerWidth = Math.max(1, popoverWidth -
|
|
215
|
+
const headerWidth = Math.max(1, popoverWidth - chromeWidth);
|
|
150
216
|
const maxStatsWidth = Math.max(0, headerWidth - 8);
|
|
151
217
|
const statsWidth = metrics.length > 0 && maxStatsWidth >= 8
|
|
152
218
|
? Math.min(headerMetricsNaturalWidth(metrics), maxStatsWidth)
|
|
@@ -154,8 +220,16 @@ export function UserProfilePopover({
|
|
|
154
220
|
const usernameWidth = Math.max(1, headerWidth - (statsWidth > 0 ? statsWidth + 1 : 0));
|
|
155
221
|
const showSetupAction = shouldOfferChatProfileSetup(user, isOwnProfile) && !!onSetUpProfile;
|
|
156
222
|
|
|
157
|
-
const
|
|
158
|
-
|
|
223
|
+
const content = nameOnly ? (
|
|
224
|
+
<>
|
|
225
|
+
<NameCardRow user={user} width={headerWidth} />
|
|
226
|
+
{messageAction ? (
|
|
227
|
+
<Button label={messageAction.label} width={headerWidth} variant="ghost" compact stopPropagation onPress={messageAction.onPress} />
|
|
228
|
+
) : messageRefusal ? (
|
|
229
|
+
<Text fg={colors.textDim} wrapText width={headerWidth}>{messageRefusal}</Text>
|
|
230
|
+
) : null}
|
|
231
|
+
</>
|
|
232
|
+
) : (
|
|
159
233
|
<>
|
|
160
234
|
<Box height={1} width={headerWidth} flexDirection="row">
|
|
161
235
|
<Box width={usernameWidth}>
|
|
@@ -171,33 +245,42 @@ export function UserProfilePopover({
|
|
|
171
245
|
) : null}
|
|
172
246
|
<Box flexGrow={1} />
|
|
173
247
|
</Box>
|
|
174
|
-
{meta ? <Text fg={colors.textDim}>{truncateWithEllipsis(meta,
|
|
248
|
+
{meta ? <Text fg={colors.textDim}>{truncateWithEllipsis(meta, headerWidth)}</Text> : null}
|
|
175
249
|
{bio ? (
|
|
176
|
-
<Text fg={colors.text} wrapText width={
|
|
250
|
+
<Text fg={colors.text} wrapText width={headerWidth}>
|
|
177
251
|
{bio}
|
|
178
252
|
</Text>
|
|
179
253
|
) : null}
|
|
180
254
|
{showSetupAction ? (
|
|
181
255
|
<Button label="Set up profile" width={headerWidth} variant="ghost" compact stopPropagation onPress={onSetUpProfile} />
|
|
182
256
|
) : null}
|
|
257
|
+
{messageAction ? (
|
|
258
|
+
<Button label={messageAction.label} width={headerWidth} variant="ghost" compact stopPropagation onPress={messageAction.onPress} />
|
|
259
|
+
) : null}
|
|
183
260
|
</>
|
|
184
261
|
);
|
|
185
262
|
|
|
186
263
|
if (nativePaneChrome) {
|
|
187
|
-
// The kit popover,
|
|
188
|
-
//
|
|
264
|
+
// The kit popover, beside the name it opened from so the pointer reaches
|
|
265
|
+
// it in one short move, and inside the pane. Opened with no name (the
|
|
266
|
+
// keyboard on a message that is not on screen), it takes the pane's
|
|
267
|
+
// top-right corner like the terminal card. It stays open while pointed at.
|
|
189
268
|
return (
|
|
190
269
|
<Box position="absolute" top={1} right={2} width={1} height={0}>
|
|
191
270
|
<Popover
|
|
192
271
|
open
|
|
193
272
|
onOpenChange={(open) => { if (!open) onDismiss(); }}
|
|
194
273
|
trigger={<Box width={1} height={0} />}
|
|
195
|
-
|
|
274
|
+
anchor={anchor}
|
|
275
|
+
boundary="pane"
|
|
276
|
+
placement={anchor ? "bottom-start" : "bottom-end"}
|
|
196
277
|
minWidth={0}
|
|
197
278
|
focusOnOpen={false}
|
|
198
279
|
label="User profile"
|
|
280
|
+
onPointerEnter={onKeepOpen}
|
|
281
|
+
onPointerLeave={onClose}
|
|
199
282
|
>
|
|
200
|
-
<Box flexDirection="column" width={headerWidth}
|
|
283
|
+
<Box flexDirection="column" width={headerWidth}>
|
|
201
284
|
{content}
|
|
202
285
|
</Box>
|
|
203
286
|
</Popover>
|
|
@@ -216,6 +299,7 @@ export function UserProfilePopover({
|
|
|
216
299
|
border
|
|
217
300
|
borderColor={colors.borderFocused}
|
|
218
301
|
paddingX={1}
|
|
302
|
+
onMouseOver={onKeepOpen}
|
|
219
303
|
onMouseMove={onKeepOpen}
|
|
220
304
|
onMouseOut={onClose}
|
|
221
305
|
style={{ zIndex: 4 }}
|
|
@@ -30,6 +30,7 @@ export function TerminalChatMessage({
|
|
|
30
30
|
onUserHover,
|
|
31
31
|
onUserHoverEnd,
|
|
32
32
|
onUserActivate,
|
|
33
|
+
onUserContextMenu,
|
|
33
34
|
beginReplyTo,
|
|
34
35
|
beginEditMessage,
|
|
35
36
|
jumpToMessage,
|
|
@@ -97,6 +98,7 @@ export function TerminalChatMessage({
|
|
|
97
98
|
onUserHover={onUserHover}
|
|
98
99
|
onUserHoverEnd={onUserHoverEnd}
|
|
99
100
|
onUserActivate={onUserActivate}
|
|
101
|
+
onUserContextMenu={onUserContextMenu}
|
|
100
102
|
{...actionProps}
|
|
101
103
|
/>
|
|
102
104
|
)}
|
|
@@ -120,6 +122,7 @@ export function TerminalChatMessage({
|
|
|
120
122
|
onUserHover={onUserHover}
|
|
121
123
|
onUserHoverEnd={onUserHoverEnd}
|
|
122
124
|
onUserActivate={onUserActivate}
|
|
125
|
+
onUserContextMenu={onUserContextMenu}
|
|
123
126
|
/>
|
|
124
127
|
</Box>
|
|
125
128
|
{lineIndex === 0 && state.grouped && <ChatMessageActions floating {...actionProps} />}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { InlineTickerCatalogEntry } from "../../../../state/hooks/inline-tickers";
|
|
2
2
|
import type { ChatAttachment, ChatMessage, ChatUserSummary } from "../../../../api-client";
|
|
3
|
+
import type { BoxRenderable } from "../../../../ui";
|
|
4
|
+
|
|
5
|
+
/** The name or @mention a user's card opens beside, where the renderer places cards next to it. */
|
|
6
|
+
export type ChatUserAnchor = BoxRenderable | null;
|
|
7
|
+
|
|
8
|
+
/** The pointer event a user menu opens from. */
|
|
9
|
+
export interface ChatUserContextMenuEvent {
|
|
10
|
+
preventDefault?: () => void;
|
|
11
|
+
stopPropagation?: () => void;
|
|
12
|
+
}
|
|
3
13
|
|
|
4
14
|
export interface ChatMessageBaseProps {
|
|
5
15
|
msg: ChatMessage;
|
|
@@ -11,9 +21,11 @@ export interface ChatMessageBaseProps {
|
|
|
11
21
|
catalog: Record<string, InlineTickerCatalogEntry>;
|
|
12
22
|
userByUsername: Map<string, ChatUserSummary>;
|
|
13
23
|
openTicker: (symbol: string) => void;
|
|
14
|
-
onUserHover: (user: ChatUserSummary) => void;
|
|
24
|
+
onUserHover: (user: ChatUserSummary, anchor?: ChatUserAnchor) => void;
|
|
15
25
|
onUserHoverEnd: () => void;
|
|
16
|
-
onUserActivate?: (user: ChatUserSummary) => void;
|
|
26
|
+
onUserActivate?: (user: ChatUserSummary, anchor?: ChatUserAnchor) => void;
|
|
27
|
+
/** A right-click on a name or @mention: the user's own menu. */
|
|
28
|
+
onUserContextMenu?: (user: ChatUserSummary, event: ChatUserContextMenuEvent, anchor?: ChatUserAnchor) => void;
|
|
17
29
|
beginReplyTo: (index: number, options?: { deferFocus?: boolean }) => void;
|
|
18
30
|
beginEditMessage: (index: number, options?: { deferFocus?: boolean }) => boolean;
|
|
19
31
|
jumpToMessage: (messageId: string) => void;
|