gloomberb 0.7.2 → 0.7.4
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 +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +158 -47
- package/src/components/chart/chart-types.ts +20 -2
- package/src/components/chart/chart.test.ts +179 -13
- package/src/components/chart/comparison-chart-renderer.test.ts +14 -30
- package/src/components/chart/comparison-chart-renderer.ts +50 -11
- package/src/components/chart/comparison-stock-chart.tsx +129 -129
- package/src/components/chart/market-session.ts +187 -0
- package/src/components/chart/native/chart-rasterizer.ts +108 -19
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +97 -1
- package/src/components/chart/stock-chart.tsx +95 -49
- package/src/components/chart/time-axis-label.tsx +56 -2
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +535 -384
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/list-model.ts +8 -4
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +21 -0
- package/src/components/command-bar/view-model.ts +17 -4
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +21 -3
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +15 -66
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/core/state/session-persistence.ts +25 -0
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator-subscriptions.test.ts +102 -18
- package/src/market-data/coordinator.test.ts +122 -2
- package/src/market-data/coordinator.ts +200 -56
- package/src/market-data/hooks.test.tsx +31 -0
- package/src/market-data/hooks.tsx +1 -0
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/screener-pane.tsx +3 -0
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +263 -48
- package/src/plugins/builtin/chat.test.tsx +312 -6
- package/src/plugins/builtin/chat.tsx +245 -66
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/market-movers/index.tsx +72 -40
- package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +156 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -90
- package/src/plugins/builtin/news-wire/index.tsx +1 -3
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +13 -2
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +139 -23
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +311 -9
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +275 -43
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +18 -7
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +445 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/builtin/world-indices/index.tsx +58 -19
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +6 -4
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/sources/gloomberb-cloud.test.ts +55 -2
- package/src/sources/gloomberb-cloud.ts +180 -29
- package/src/sources/provider-router.test.ts +567 -10
- package/src/sources/provider-router.ts +323 -93
- package/src/sources/yahoo-finance.test.ts +37 -0
- package/src/sources/yahoo-finance.ts +48 -2
- package/src/state/app-bootstrap.test.ts +50 -23
- package/src/state/app-bootstrap.ts +29 -13
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/state/session-persistence.test.ts +5 -3
- package/src/state/use-inline-tickers.ts +3 -0
- package/src/state/use-quote-streaming.ts +11 -0
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +1 -0
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/utils/api-client.test.ts +248 -0
- package/src/utils/api-client.ts +178 -26
- package/src/utils/exchanges.test.ts +16 -0
- package/src/utils/exchanges.ts +33 -0
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/options.ts +1 -1
- package/src/utils/price-history.test.ts +28 -1
- package/src/utils/price-history.ts +16 -0
- package/src/utils/quote-freshness.test.ts +150 -0
- package/src/utils/quote-freshness.ts +42 -1
- package/src/utils/quote-resolution.ts +2 -0
- package/src/utils/quote-time.test.ts +34 -0
- package/src/utils/quote-time.ts +18 -5
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/digest-store.ts +0 -44
|
@@ -3,7 +3,7 @@ import { memo, useState, useEffect, useRef, useCallback, useMemo } from "react";
|
|
|
3
3
|
import { useShortcut } from "../../react/input";
|
|
4
4
|
import { TextAttributes, type ScrollBoxRenderable, type TextareaRenderable } from "../../ui";
|
|
5
5
|
import type { GloomPlugin, PaneProps } from "../../types/plugin";
|
|
6
|
-
import { useAppDispatch, useAppSelector, useAppStateRef, usePaneInstance, usePaneInstanceId } from "../../state/app-context";
|
|
6
|
+
import { syncConfigActiveLayoutState, useAppDispatch, useAppSelector, useAppStateRef, usePaneInstance, usePaneInstanceId } from "../../state/app-context";
|
|
7
7
|
import { useInlineTickers, type InlineTickerCatalogEntry } from "../../state/use-inline-tickers";
|
|
8
8
|
import { ExternalLinkText, getMessageComposerBlockHeight, MessageComposer } from "../../components/ui";
|
|
9
9
|
import { TickerBadgeText } from "../../components/ticker-badge-text";
|
|
@@ -12,6 +12,7 @@ import { blendHex, colors, hoverBg } from "../../theme/colors";
|
|
|
12
12
|
import { apiClient, type ChatChannel, type ChatMessage } from "../../utils/api-client";
|
|
13
13
|
import { formatTimeAgo } from "../../utils/format";
|
|
14
14
|
import { tokenizeInlineContent } from "../../utils/inline-content-tokenizer";
|
|
15
|
+
import { isPlainKey } from "../../utils/keyboard";
|
|
15
16
|
import { getSharedRegistry } from "../../plugins/registry";
|
|
16
17
|
import { usePluginAppActions } from "../../plugins/plugin-runtime";
|
|
17
18
|
import { setPaneSetting } from "../../pane-settings";
|
|
@@ -34,6 +35,8 @@ interface ChatContentProps {
|
|
|
34
35
|
| "attachChannelView"
|
|
35
36
|
| "getSnapshot"
|
|
36
37
|
| "refreshChannels"
|
|
38
|
+
| "refreshChatState"
|
|
39
|
+
| "refreshPresence"
|
|
37
40
|
| "loadOlderMessages"
|
|
38
41
|
| "loadOlderChannelMessages"
|
|
39
42
|
| "refreshMessages"
|
|
@@ -43,6 +46,7 @@ interface ChatContentProps {
|
|
|
43
46
|
| "sendToChannel"
|
|
44
47
|
| "setDraft"
|
|
45
48
|
| "setChannelDraft"
|
|
49
|
+
| "setChannelNotificationsEnabled"
|
|
46
50
|
| "setReplyToId"
|
|
47
51
|
| "setChannelReplyToId"
|
|
48
52
|
| "subscribe"
|
|
@@ -61,10 +65,17 @@ const MESSAGE_SELECTION_BOTTOM_INSET = 1;
|
|
|
61
65
|
const DESKTOP_MESSAGE_RIGHT_PADDING = 2;
|
|
62
66
|
const CHANNEL_SIDEBAR_MIN_WIDTH = 18;
|
|
63
67
|
const CHANNEL_SIDEBAR_MAX_WIDTH = 24;
|
|
68
|
+
const DESKTOP_CHANNEL_SIDEBAR_MIN_WIDTH = 14;
|
|
69
|
+
const DESKTOP_CHANNEL_SIDEBAR_MAX_WIDTH = 19;
|
|
70
|
+
const DESKTOP_CHANNEL_SIDEBAR_WIDTH_RATIO = 0.192;
|
|
64
71
|
const CHANNEL_SIDEBAR_BREAKPOINT = 72;
|
|
72
|
+
const DESKTOP_NOTIFICATION_ICON_WIDTH = 3;
|
|
73
|
+
const DESKTOP_ONLINE_COUNT_PADDING_X = 1;
|
|
74
|
+
const DESKTOP_CHAT_INPUT_TOP_MARGIN_PX = 6;
|
|
65
75
|
const DEFAULT_CHAT_CHANNEL_ID = "everyone";
|
|
66
76
|
const LAST_VISITED_CHAT_CHANNEL_KEY = "lastChatChannelId";
|
|
67
77
|
const CHAT_CHANNEL_MOUSE_HANDLED = "__gloomberbChatChannelHandled";
|
|
78
|
+
const SCROLL_BOTTOM_THRESHOLD_PX = 2;
|
|
68
79
|
|
|
69
80
|
function isGroupedWithPrevious(messages: ChatMessage[], index: number) {
|
|
70
81
|
if (index === 0) return false;
|
|
@@ -170,6 +181,15 @@ function scrollToBottom(scrollBox: ScrollBoxRenderable | null, preferPixels = fa
|
|
|
170
181
|
);
|
|
171
182
|
}
|
|
172
183
|
|
|
184
|
+
function isScrolledToBottom(scrollBox: ScrollBoxRenderable | null, preferPixels = false) {
|
|
185
|
+
if (!scrollBox) return true;
|
|
186
|
+
const exactPixels = preferPixels && hasPixelScrollMetrics(scrollBox);
|
|
187
|
+
const scrollTop = getScrollTop(scrollBox, exactPixels);
|
|
188
|
+
const maxScrollTop = Math.max(0, getScrollHeight(scrollBox, exactPixels) - getViewportHeight(scrollBox, exactPixels));
|
|
189
|
+
const threshold = exactPixels ? SCROLL_BOTTOM_THRESHOLD_PX : 0;
|
|
190
|
+
return maxScrollTop - scrollTop <= threshold;
|
|
191
|
+
}
|
|
192
|
+
|
|
173
193
|
function runAfterLayout(callback: () => void) {
|
|
174
194
|
if (typeof requestAnimationFrame === "function") {
|
|
175
195
|
requestAnimationFrame(callback);
|
|
@@ -311,6 +331,73 @@ function CloudStatusIcon() {
|
|
|
311
331
|
);
|
|
312
332
|
}
|
|
313
333
|
|
|
334
|
+
function ChannelNotificationIcon({
|
|
335
|
+
enabled,
|
|
336
|
+
onMouseDown,
|
|
337
|
+
}: {
|
|
338
|
+
enabled: boolean;
|
|
339
|
+
onMouseDown?: (event: any) => void;
|
|
340
|
+
}) {
|
|
341
|
+
const { nativePaneChrome } = useUiCapabilities();
|
|
342
|
+
const iconColor = enabled ? colors.positive : colors.textMuted;
|
|
343
|
+
|
|
344
|
+
if (!nativePaneChrome) {
|
|
345
|
+
return (
|
|
346
|
+
<Text fg={iconColor} selectable={false} onMouseDown={onMouseDown}>
|
|
347
|
+
{enabled ? "◖)" : "◖·"}
|
|
348
|
+
</Text>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<Span
|
|
354
|
+
fg={iconColor}
|
|
355
|
+
onMouseDown={onMouseDown}
|
|
356
|
+
style={{
|
|
357
|
+
color: iconColor,
|
|
358
|
+
display: "inline-flex",
|
|
359
|
+
alignItems: "center",
|
|
360
|
+
justifyContent: "center",
|
|
361
|
+
width: 16,
|
|
362
|
+
height: 16,
|
|
363
|
+
}}
|
|
364
|
+
>
|
|
365
|
+
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" aria-hidden="true">
|
|
366
|
+
<path
|
|
367
|
+
d="M4.5 9.5v5h3.2l4.8 4v-13l-4.8 4H4.5Z"
|
|
368
|
+
stroke="currentColor"
|
|
369
|
+
strokeWidth="1.8"
|
|
370
|
+
strokeLinecap="round"
|
|
371
|
+
strokeLinejoin="round"
|
|
372
|
+
/>
|
|
373
|
+
{enabled ? (
|
|
374
|
+
<>
|
|
375
|
+
<path
|
|
376
|
+
d="M16 8.5a5 5 0 0 1 0 7"
|
|
377
|
+
stroke="currentColor"
|
|
378
|
+
strokeWidth="1.8"
|
|
379
|
+
strokeLinecap="round"
|
|
380
|
+
/>
|
|
381
|
+
<path
|
|
382
|
+
d="M18.8 5.8a9 9 0 0 1 0 12.4"
|
|
383
|
+
stroke="currentColor"
|
|
384
|
+
strokeWidth="1.8"
|
|
385
|
+
strokeLinecap="round"
|
|
386
|
+
/>
|
|
387
|
+
</>
|
|
388
|
+
) : (
|
|
389
|
+
<path
|
|
390
|
+
d="M19 5 5 19"
|
|
391
|
+
stroke="currentColor"
|
|
392
|
+
strokeWidth="1.8"
|
|
393
|
+
strokeLinecap="round"
|
|
394
|
+
/>
|
|
395
|
+
)}
|
|
396
|
+
</svg>
|
|
397
|
+
</Span>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
314
401
|
function ChatActionChip({
|
|
315
402
|
label,
|
|
316
403
|
width,
|
|
@@ -752,35 +839,46 @@ const DesktopChatMessage = memo(function DesktopChatMessage({
|
|
|
752
839
|
|
|
753
840
|
function ChannelSidebar({
|
|
754
841
|
channels,
|
|
842
|
+
channelStates,
|
|
755
843
|
activeChannelId,
|
|
844
|
+
onlineCount,
|
|
756
845
|
width,
|
|
757
846
|
height,
|
|
758
847
|
focused,
|
|
759
848
|
keyboardFocused,
|
|
760
849
|
loading,
|
|
850
|
+
canManageNotifications,
|
|
761
851
|
onSelect,
|
|
762
852
|
onFocusRequest,
|
|
853
|
+
onToggleNotifications,
|
|
763
854
|
}: {
|
|
764
855
|
channels: ChatChannel[];
|
|
856
|
+
channelStates: ReturnType<ChatController["getSnapshot"]>["channelStates"];
|
|
765
857
|
activeChannelId: string;
|
|
858
|
+
onlineCount: number;
|
|
766
859
|
width: number;
|
|
767
860
|
height: number;
|
|
768
861
|
focused: boolean;
|
|
769
862
|
keyboardFocused: boolean;
|
|
770
863
|
loading: boolean;
|
|
864
|
+
canManageNotifications: boolean;
|
|
771
865
|
onSelect?: (channelId: string) => void;
|
|
772
866
|
onFocusRequest?: () => void;
|
|
867
|
+
onToggleNotifications?: (channelId: string, enabled: boolean) => void;
|
|
773
868
|
}) {
|
|
774
869
|
const { nativePaneChrome } = useUiCapabilities();
|
|
775
870
|
const borderWidth = nativePaneChrome ? 0 : width > 1 ? 1 : 0;
|
|
776
871
|
const listWidth = Math.max(width - borderWidth, 1);
|
|
777
|
-
const
|
|
872
|
+
const notificationWidth = canManageNotifications ? (nativePaneChrome ? DESKTOP_NOTIFICATION_ICON_WIDTH : 2) : 0;
|
|
873
|
+
const onlineCountPaddingX = nativePaneChrome ? DESKTOP_ONLINE_COUNT_PADDING_X : 0;
|
|
874
|
+
const labelWidth = Math.max(listWidth - 3 - notificationWidth, 1);
|
|
778
875
|
const dividerColor = focused ? colors.borderFocused : colors.border;
|
|
779
876
|
const sidebarBg = keyboardFocused ? blendHex(colors.panel, colors.borderFocused, 0.18) : colors.panel;
|
|
780
877
|
const activeBg = keyboardFocused
|
|
781
878
|
? blendHex(colors.selected, colors.borderFocused, 0.32)
|
|
782
879
|
: blendHex(colors.panel, colors.selected, 0.35);
|
|
783
880
|
const [hoveredChannelId, setHoveredChannelId] = useState<string | null>(null);
|
|
881
|
+
const channelStateById = useMemo(() => new Map(channelStates.map((state) => [state.channelId, state])), [channelStates]);
|
|
784
882
|
const sidebarLayoutHeight = nativePaneChrome ? "100%" : height;
|
|
785
883
|
const nativeFillStyle = nativePaneChrome ? { minHeight: 0 } : undefined;
|
|
786
884
|
const sidebarBorder = borderWidth > 0
|
|
@@ -810,6 +908,9 @@ function ChannelSidebar({
|
|
|
810
908
|
>
|
|
811
909
|
{channels.map((channel) => {
|
|
812
910
|
const active = channel.id === activeChannelId;
|
|
911
|
+
const channelState = channelStateById.get(channel.id);
|
|
912
|
+
const notificationsEnabled = channelState?.notificationsEnabled === true;
|
|
913
|
+
const unread = (channelState?.unreadCount ?? 0) > 0;
|
|
813
914
|
const hovered = hoveredChannelId === channel.id && !active;
|
|
814
915
|
const fg = active ? colors.selectedText : keyboardFocused ? colors.text : colors.textDim;
|
|
815
916
|
const bg = active ? activeBg : hovered ? hoverBg() : sidebarBg;
|
|
@@ -824,6 +925,14 @@ function ChannelSidebar({
|
|
|
824
925
|
onFocusRequest?.();
|
|
825
926
|
onSelect?.(channel.id);
|
|
826
927
|
};
|
|
928
|
+
const toggleNotifications = (event: any) => {
|
|
929
|
+
event?.preventDefault?.();
|
|
930
|
+
event?.stopPropagation?.();
|
|
931
|
+
if (event) {
|
|
932
|
+
event[CHAT_CHANNEL_MOUSE_HANDLED] = true;
|
|
933
|
+
}
|
|
934
|
+
onToggleNotifications?.(channel.id, !notificationsEnabled);
|
|
935
|
+
};
|
|
827
936
|
return (
|
|
828
937
|
<Box
|
|
829
938
|
key={channel.id}
|
|
@@ -837,9 +946,21 @@ function ChannelSidebar({
|
|
|
837
946
|
style={{ cursor: "pointer" }}
|
|
838
947
|
>
|
|
839
948
|
<Text fg={fg} selectable={false} onMouseDown={selectChannel}> </Text>
|
|
840
|
-
<Text fg={fg} selectable={false} onMouseDown={selectChannel}>{active ? "#" : " "}</Text>
|
|
841
|
-
<Text fg={fg} selectable={false} onMouseDown={selectChannel}>{truncateChannelLabel(label, labelWidth)}</Text>
|
|
949
|
+
<Text fg={fg} attributes={unread ? TextAttributes.BOLD : 0} selectable={false} onMouseDown={selectChannel}>{active ? "#" : " "}</Text>
|
|
950
|
+
<Text fg={fg} attributes={unread ? TextAttributes.BOLD : 0} selectable={false} onMouseDown={selectChannel}>{truncateChannelLabel(label, labelWidth)}</Text>
|
|
842
951
|
<Box flexGrow={1} onMouseDown={selectChannel} />
|
|
952
|
+
{canManageNotifications && (
|
|
953
|
+
<Box
|
|
954
|
+
width={notificationWidth}
|
|
955
|
+
height={1}
|
|
956
|
+
alignItems="center"
|
|
957
|
+
justifyContent="center"
|
|
958
|
+
onMouseDown={toggleNotifications}
|
|
959
|
+
style={{ cursor: "pointer" }}
|
|
960
|
+
>
|
|
961
|
+
<ChannelNotificationIcon enabled={notificationsEnabled} onMouseDown={toggleNotifications} />
|
|
962
|
+
</Box>
|
|
963
|
+
)}
|
|
843
964
|
</Box>
|
|
844
965
|
);
|
|
845
966
|
})}
|
|
@@ -849,6 +970,12 @@ function ChannelSidebar({
|
|
|
849
970
|
<Text fg={colors.textDim}> syncing</Text>
|
|
850
971
|
</Box>
|
|
851
972
|
)}
|
|
973
|
+
<Box height={1} width={listWidth} flexDirection="row" paddingX={onlineCountPaddingX}>
|
|
974
|
+
<Text fg={colors.positive}>●</Text>
|
|
975
|
+
<Text fg={colors.textDim}>
|
|
976
|
+
{` ${truncateChannelLabel(`${onlineCount} online`, Math.max(listWidth - 2 - onlineCountPaddingX * 2, 1))}`}
|
|
977
|
+
</Text>
|
|
978
|
+
</Box>
|
|
852
979
|
</Box>
|
|
853
980
|
{sidebarBorder}
|
|
854
981
|
{nativePaneChrome && (
|
|
@@ -888,16 +1015,20 @@ export function ChatContent({
|
|
|
888
1015
|
const [channels, setChannels] = useState<ChatChannel[]>(initialSnapshot.channels);
|
|
889
1016
|
const [channelsLoading, setChannelsLoading] = useState(initialSnapshot.channelsLoading);
|
|
890
1017
|
const showChannelSidebar = channels.length > 1 && width >= CHANNEL_SIDEBAR_BREAKPOINT && height >= 8;
|
|
1018
|
+
const sidebarMinWidth = nativePaneChrome ? DESKTOP_CHANNEL_SIDEBAR_MIN_WIDTH : CHANNEL_SIDEBAR_MIN_WIDTH;
|
|
1019
|
+
const sidebarMaxWidth = nativePaneChrome ? DESKTOP_CHANNEL_SIDEBAR_MAX_WIDTH : CHANNEL_SIDEBAR_MAX_WIDTH;
|
|
1020
|
+
const sidebarWidthRatio = nativePaneChrome ? DESKTOP_CHANNEL_SIDEBAR_WIDTH_RATIO : 0.24;
|
|
891
1021
|
const channelSidebarWidth = showChannelSidebar
|
|
892
|
-
? Math.min(
|
|
1022
|
+
? Math.min(sidebarMaxWidth, Math.max(sidebarMinWidth, Math.floor(width * sidebarWidthRatio)))
|
|
893
1023
|
: 0;
|
|
894
|
-
const compactChannelHeaderHeight = nativePaneChrome && !showChannelSidebar && channels.length > 1 ? 1 : 0;
|
|
895
1024
|
const chatWidth = Math.max(width - channelSidebarWidth, 1);
|
|
896
1025
|
const contentWidth = Math.max(chatWidth - 2, 1);
|
|
897
1026
|
const composerPrefixWidth = nativePaneChrome ? 0 : 3;
|
|
898
1027
|
const composerTextWidth = Math.max(contentWidth - composerPrefixWidth, 1);
|
|
899
1028
|
const inputValueRef = useRef(initialSnapshot.draft);
|
|
900
1029
|
const [messages, setMessages] = useState<ChatMessage[]>(initialSnapshot.messages);
|
|
1030
|
+
const [channelStates, setChannelStates] = useState(initialSnapshot.channelStates);
|
|
1031
|
+
const [onlineCount, setOnlineCount] = useState(initialSnapshot.onlineCount);
|
|
901
1032
|
const [hasSavedSession, setHasSavedSession] = useState(initialSnapshot.hasSavedSession);
|
|
902
1033
|
const [user, setUser] = useState<{ id: string; username: string; emailVerified: boolean } | null>(initialSnapshot.user);
|
|
903
1034
|
const [loading, setLoading] = useState(initialSnapshot.loading);
|
|
@@ -937,6 +1068,7 @@ export function ChatContent({
|
|
|
937
1068
|
: 0;
|
|
938
1069
|
const selectionActive = selectedIdx >= 0 && selectedIdx < messages.length;
|
|
939
1070
|
const stickyTranscript = followMessages && !selectionActive;
|
|
1071
|
+
const latestMessageId = messages[messages.length - 1]?.id ?? null;
|
|
940
1072
|
const updateComposerRows = useCallback((draft: string) => {
|
|
941
1073
|
const nextRows = estimateComposerHeight(draft, composerTextWidth);
|
|
942
1074
|
setComposerRows((current) => (current === nextRows ? current : nextRows));
|
|
@@ -952,6 +1084,7 @@ export function ChatContent({
|
|
|
952
1084
|
|
|
953
1085
|
useEffect(() => {
|
|
954
1086
|
void controller.refreshChannels().catch(() => {});
|
|
1087
|
+
void controller.refreshPresence().catch(() => {});
|
|
955
1088
|
void controller.refreshSession().catch(() => {});
|
|
956
1089
|
}, [controller]);
|
|
957
1090
|
|
|
@@ -962,6 +1095,8 @@ export function ChatContent({
|
|
|
962
1095
|
const snapshot = controller.getSnapshot(channelId);
|
|
963
1096
|
setMessages(snapshot.messages);
|
|
964
1097
|
setChannels(snapshot.channels);
|
|
1098
|
+
setChannelStates(snapshot.channelStates);
|
|
1099
|
+
setOnlineCount(snapshot.onlineCount);
|
|
965
1100
|
setChannelsLoading(snapshot.channelsLoading);
|
|
966
1101
|
setHasSavedSession(snapshot.hasSavedSession);
|
|
967
1102
|
setUser(snapshot.user);
|
|
@@ -987,6 +1122,8 @@ export function ChatContent({
|
|
|
987
1122
|
function handleSnapshot(snapshot: ReturnType<ChatController["getSnapshot"]>) {
|
|
988
1123
|
setMessages(snapshot.messages);
|
|
989
1124
|
setChannels(snapshot.channels);
|
|
1125
|
+
setChannelStates(snapshot.channelStates);
|
|
1126
|
+
setOnlineCount(snapshot.onlineCount);
|
|
990
1127
|
setChannelsLoading(snapshot.channelsLoading);
|
|
991
1128
|
setHasSavedSession(snapshot.hasSavedSession);
|
|
992
1129
|
setUser(snapshot.user);
|
|
@@ -1111,17 +1248,31 @@ export function ChatContent({
|
|
|
1111
1248
|
return textarea.visualCursor.visualRow >= visualLineCount - 1;
|
|
1112
1249
|
}, []);
|
|
1113
1250
|
|
|
1251
|
+
const clearLocalComposer = useCallback(() => {
|
|
1252
|
+
inputValueRef.current = "";
|
|
1253
|
+
updateComposerRows("");
|
|
1254
|
+
const textarea = inputRef.current;
|
|
1255
|
+
if (textarea && textarea.editBuffer.getText() !== "") {
|
|
1256
|
+
applyingExternalDraftRef.current = true;
|
|
1257
|
+
try {
|
|
1258
|
+
textarea.setText("");
|
|
1259
|
+
} finally {
|
|
1260
|
+
applyingExternalDraftRef.current = false;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}, [updateComposerRows]);
|
|
1264
|
+
|
|
1114
1265
|
const sendMessage = useCallback(() => {
|
|
1115
1266
|
const content = inputValueRef.current.trim();
|
|
1116
1267
|
if (!content) return;
|
|
1117
|
-
|
|
1118
|
-
controller.send(content, replyToRef.current?.id)
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1268
|
+
const accepted = useDefaultControllerChannel
|
|
1269
|
+
? controller.send(content, replyToRef.current?.id)
|
|
1270
|
+
: controller.sendToChannel(channelId, content, replyToRef.current?.id);
|
|
1271
|
+
if (!accepted) return;
|
|
1272
|
+
clearLocalComposer();
|
|
1122
1273
|
setSelectedIdx(-1);
|
|
1123
1274
|
setFollowMessages(true);
|
|
1124
|
-
}, [channelId, controller, useDefaultControllerChannel]);
|
|
1275
|
+
}, [channelId, clearLocalComposer, controller, useDefaultControllerChannel]);
|
|
1125
1276
|
|
|
1126
1277
|
const requestOlderMessages = useCallback(() => {
|
|
1127
1278
|
const scrollBox = scrollRef.current;
|
|
@@ -1132,7 +1283,7 @@ export function ChatContent({
|
|
|
1132
1283
|
oldestMessageId: messages[0]?.id ?? null,
|
|
1133
1284
|
scrollHeight: getScrollHeight(scrollBox, exactPixels),
|
|
1134
1285
|
scrollTop: getScrollTop(scrollBox, exactPixels),
|
|
1135
|
-
selectedMessageId: selectedIdx >= 0 ? messages[selectedIdx]?.id ?? null : null,
|
|
1286
|
+
selectedMessageId: selectedIdx >= 0 ? messages[selectedIdx]?.id ?? null : messages[0]?.id ?? null,
|
|
1136
1287
|
};
|
|
1137
1288
|
setFollowMessages(false);
|
|
1138
1289
|
const request = useDefaultControllerChannel
|
|
@@ -1149,6 +1300,23 @@ export function ChatContent({
|
|
|
1149
1300
|
requestOlderMessages();
|
|
1150
1301
|
}, [requestOlderMessages]);
|
|
1151
1302
|
|
|
1303
|
+
const handleTranscriptScrollActivity = useCallback((event?: { scroll?: { direction?: "up" | "down" | "left" | "right" } }) => {
|
|
1304
|
+
const direction = event?.scroll?.direction;
|
|
1305
|
+
runAfterLayout(() => {
|
|
1306
|
+
requestOlderMessagesIfNeeded();
|
|
1307
|
+
|
|
1308
|
+
const scrollBox = scrollRef.current;
|
|
1309
|
+
if (!scrollBox) return;
|
|
1310
|
+
const atBottom = isScrolledToBottom(scrollBox, nativePaneChrome);
|
|
1311
|
+
if (direction === "up" && !atBottom) {
|
|
1312
|
+
setFollowMessages(false);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
setFollowMessages(atBottom);
|
|
1317
|
+
});
|
|
1318
|
+
}, [nativePaneChrome, requestOlderMessagesIfNeeded]);
|
|
1319
|
+
|
|
1152
1320
|
const scrollToLoadedMessage = useCallback((messageId: string) => {
|
|
1153
1321
|
const targetIndex = messages.findIndex((message) => message.id === messageId);
|
|
1154
1322
|
if (targetIndex < 0) return false;
|
|
@@ -1307,20 +1475,20 @@ export function ChatContent({
|
|
|
1307
1475
|
const isEnterKey = event.name === "return" || event.name === "enter";
|
|
1308
1476
|
|
|
1309
1477
|
if (sidebarFocusedRef.current && showChannelSidebar) {
|
|
1310
|
-
if (event
|
|
1478
|
+
if (isPlainKey(event, "left")) {
|
|
1311
1479
|
event.preventDefault?.();
|
|
1312
1480
|
event.stopPropagation?.();
|
|
1313
1481
|
return;
|
|
1314
1482
|
}
|
|
1315
1483
|
|
|
1316
|
-
if (event
|
|
1484
|
+
if (isPlainKey(event, "right")) {
|
|
1317
1485
|
event.preventDefault?.();
|
|
1318
1486
|
event.stopPropagation?.();
|
|
1319
1487
|
focusChatContent();
|
|
1320
1488
|
return;
|
|
1321
1489
|
}
|
|
1322
1490
|
|
|
1323
|
-
if (event
|
|
1491
|
+
if (isPlainKey(event, "up", "down")) {
|
|
1324
1492
|
event.preventDefault?.();
|
|
1325
1493
|
event.stopPropagation?.();
|
|
1326
1494
|
moveSidebarChannelSelection(event.name);
|
|
@@ -1329,7 +1497,7 @@ export function ChatContent({
|
|
|
1329
1497
|
}
|
|
1330
1498
|
|
|
1331
1499
|
if (
|
|
1332
|
-
event
|
|
1500
|
+
isPlainKey(event, "left") &&
|
|
1333
1501
|
showChannelSidebar &&
|
|
1334
1502
|
(!inputFocused || inputValueRef.current.length === 0) &&
|
|
1335
1503
|
focusChannelSidebar()
|
|
@@ -1351,7 +1519,7 @@ export function ChatContent({
|
|
|
1351
1519
|
return;
|
|
1352
1520
|
}
|
|
1353
1521
|
|
|
1354
|
-
if ((event
|
|
1522
|
+
if (isPlainKey(event, "up", "down") && shouldLeaveComposerForSelection(event.name)) {
|
|
1355
1523
|
const moved = moveMessageSelection(event.name);
|
|
1356
1524
|
if (moved) {
|
|
1357
1525
|
event.preventDefault?.();
|
|
@@ -1364,12 +1532,12 @@ export function ChatContent({
|
|
|
1364
1532
|
return;
|
|
1365
1533
|
}
|
|
1366
1534
|
|
|
1367
|
-
if ((event
|
|
1535
|
+
if (isPlainKey(event, "]") && cycleChannel(1)) {
|
|
1368
1536
|
event.preventDefault?.();
|
|
1369
1537
|
event.stopPropagation?.();
|
|
1370
1538
|
return;
|
|
1371
1539
|
}
|
|
1372
|
-
if ((event
|
|
1540
|
+
if (isPlainKey(event, "[") && cycleChannel(-1)) {
|
|
1373
1541
|
event.preventDefault?.();
|
|
1374
1542
|
event.stopPropagation?.();
|
|
1375
1543
|
return;
|
|
@@ -1382,7 +1550,7 @@ export function ChatContent({
|
|
|
1382
1550
|
return;
|
|
1383
1551
|
}
|
|
1384
1552
|
|
|
1385
|
-
if ((isEnterKey || event
|
|
1553
|
+
if ((isEnterKey || isPlainKey(event, "i")) && canSend) {
|
|
1386
1554
|
event.preventDefault?.();
|
|
1387
1555
|
event.stopPropagation?.();
|
|
1388
1556
|
queueMicrotask(() => focusComposer());
|
|
@@ -1399,7 +1567,7 @@ export function ChatContent({
|
|
|
1399
1567
|
return;
|
|
1400
1568
|
}
|
|
1401
1569
|
|
|
1402
|
-
if (event
|
|
1570
|
+
if (isPlainKey(event, "j", "down")) {
|
|
1403
1571
|
event.preventDefault?.();
|
|
1404
1572
|
event.stopPropagation?.();
|
|
1405
1573
|
if (selectedIdx === messages.length - 1) {
|
|
@@ -1409,7 +1577,7 @@ export function ChatContent({
|
|
|
1409
1577
|
moveMessageSelection("down");
|
|
1410
1578
|
return;
|
|
1411
1579
|
}
|
|
1412
|
-
if (event
|
|
1580
|
+
if (isPlainKey(event, "k", "up")) {
|
|
1413
1581
|
event.preventDefault?.();
|
|
1414
1582
|
event.stopPropagation?.();
|
|
1415
1583
|
if (selectedIdx === 0 && hasOlderMessages && !loadingOlderMessages) {
|
|
@@ -1420,14 +1588,14 @@ export function ChatContent({
|
|
|
1420
1588
|
return;
|
|
1421
1589
|
}
|
|
1422
1590
|
|
|
1423
|
-
if (canSend && event
|
|
1591
|
+
if (canSend && isPlainKey(event, "r") && selectedIdx >= 0 && selectedIdx < messages.length) {
|
|
1424
1592
|
event.preventDefault?.();
|
|
1425
1593
|
event.stopPropagation?.();
|
|
1426
1594
|
beginReplyTo(selectedIdx, { deferFocus: true });
|
|
1427
1595
|
return;
|
|
1428
1596
|
}
|
|
1429
1597
|
|
|
1430
|
-
if (event
|
|
1598
|
+
if (isPlainKey(event, "g")) {
|
|
1431
1599
|
event.preventDefault?.();
|
|
1432
1600
|
event.stopPropagation?.();
|
|
1433
1601
|
setSelectedIdx(0);
|
|
@@ -1453,7 +1621,7 @@ export function ChatContent({
|
|
|
1453
1621
|
const inputAreaHeight = canSend ? composerBlockHeight + inputMetaHeight : 2;
|
|
1454
1622
|
const topSeparatorHeight = nativePaneChrome ? 0 : 1;
|
|
1455
1623
|
const footerSeparatorHeight = !nativePaneChrome && !canSend ? 1 : 0;
|
|
1456
|
-
const messageAreaHeight = Math.max(1, height -
|
|
1624
|
+
const messageAreaHeight = Math.max(1, height - topSeparatorHeight - footerSeparatorHeight - inputAreaHeight);
|
|
1457
1625
|
const composerWidth = nativePaneChrome ? chatWidth : contentWidth;
|
|
1458
1626
|
|
|
1459
1627
|
useEffect(() => {
|
|
@@ -1464,36 +1632,38 @@ export function ChatContent({
|
|
|
1464
1632
|
useEffect(() => {
|
|
1465
1633
|
if (!stickyTranscript) return;
|
|
1466
1634
|
queueMicrotask(() => scrollToBottom(scrollRef.current, nativePaneChrome));
|
|
1467
|
-
}, [contentWidth, height,
|
|
1635
|
+
}, [channelId, contentWidth, height, latestMessageId, messageAreaHeight, nativePaneChrome, stickyTranscript]);
|
|
1468
1636
|
|
|
1469
1637
|
useEffect(() => {
|
|
1470
1638
|
const anchor = prependAnchorRef.current;
|
|
1471
1639
|
if (!anchor || loadingOlderMessages) return;
|
|
1472
1640
|
|
|
1641
|
+
const currentAnchor = anchor;
|
|
1642
|
+
const scrollBox = scrollRef.current;
|
|
1643
|
+
if (!scrollBox) return;
|
|
1644
|
+
if (pendingJumpMessageIdRef.current) {
|
|
1645
|
+
prependAnchorRef.current = null;
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
const previousOldestIndex = currentAnchor.oldestMessageId
|
|
1650
|
+
? messages.findIndex((message) => message.id === currentAnchor.oldestMessageId)
|
|
1651
|
+
: -1;
|
|
1652
|
+
const exactPixels = nativePaneChrome === true && hasPixelScrollMetrics(scrollBox);
|
|
1653
|
+
const addedRows = !exactPixels && previousOldestIndex > 0
|
|
1654
|
+
? getMessageTopOffset(messages, previousOldestIndex, contentWidth)
|
|
1655
|
+
: Math.max(0, getScrollHeight(scrollBox, exactPixels) - currentAnchor.scrollHeight);
|
|
1656
|
+
scrollToPosition(scrollBox, currentAnchor.scrollTop + addedRows, exactPixels);
|
|
1657
|
+
if (currentAnchor.selectedMessageId) {
|
|
1658
|
+
const selectedMessageIndex = messages.findIndex((message) => message.id === currentAnchor.selectedMessageId);
|
|
1659
|
+
if (selectedMessageIndex >= 0) {
|
|
1660
|
+
setSelectedIdx(selectedMessageIndex);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1473
1663
|
queueMicrotask(() => {
|
|
1474
|
-
|
|
1475
|
-
const scrollBox = scrollRef.current;
|
|
1476
|
-
if (!currentAnchor || !scrollBox) return;
|
|
1477
|
-
if (pendingJumpMessageIdRef.current) {
|
|
1664
|
+
if (prependAnchorRef.current === currentAnchor) {
|
|
1478
1665
|
prependAnchorRef.current = null;
|
|
1479
|
-
return;
|
|
1480
1666
|
}
|
|
1481
|
-
|
|
1482
|
-
const previousOldestIndex = currentAnchor.oldestMessageId
|
|
1483
|
-
? messages.findIndex((message) => message.id === currentAnchor.oldestMessageId)
|
|
1484
|
-
: -1;
|
|
1485
|
-
const exactPixels = nativePaneChrome === true && hasPixelScrollMetrics(scrollBox);
|
|
1486
|
-
const addedRows = !exactPixels && previousOldestIndex > 0
|
|
1487
|
-
? getMessageTopOffset(messages, previousOldestIndex, contentWidth)
|
|
1488
|
-
: Math.max(0, getScrollHeight(scrollBox, exactPixels) - currentAnchor.scrollHeight);
|
|
1489
|
-
scrollToPosition(scrollBox, currentAnchor.scrollTop + addedRows, exactPixels);
|
|
1490
|
-
if (currentAnchor.selectedMessageId) {
|
|
1491
|
-
const selectedMessageIndex = messages.findIndex((message) => message.id === currentAnchor.selectedMessageId);
|
|
1492
|
-
if (selectedMessageIndex >= 0) {
|
|
1493
|
-
setSelectedIdx(selectedMessageIndex);
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
prependAnchorRef.current = null;
|
|
1497
1667
|
});
|
|
1498
1668
|
}, [contentWidth, loadingOlderMessages, messages, nativePaneChrome]);
|
|
1499
1669
|
|
|
@@ -1506,6 +1676,7 @@ export function ChatContent({
|
|
|
1506
1676
|
const sb = scrollRef.current;
|
|
1507
1677
|
if (!sb) return;
|
|
1508
1678
|
if (!selectionActive) return;
|
|
1679
|
+
if (prependAnchorRef.current) return;
|
|
1509
1680
|
if (nativePaneChrome) {
|
|
1510
1681
|
const selectedMessageId = messages[selectedIdx]?.id ?? "";
|
|
1511
1682
|
runAfterLayout(() => scrollElementIntoScrollBoxView(scrollRef.current, messageElementsRef.current.get(selectedMessageId)));
|
|
@@ -1531,8 +1702,6 @@ export function ChatContent({
|
|
|
1531
1702
|
)
|
|
1532
1703
|
: "";
|
|
1533
1704
|
const inputPlaceholder = replyTo ? `Reply to @${replyTo.user.username}...` : "Type a message...";
|
|
1534
|
-
const activeChannel = channels.find((channel) => channel.id === channelId);
|
|
1535
|
-
const activeChannelLabel = formatChannelLabel(activeChannel, channelId);
|
|
1536
1705
|
const chatContentBg = focused && showChannelSidebar && !sidebarFocused
|
|
1537
1706
|
? blendHex(colors.bg, colors.borderFocused, 0.08)
|
|
1538
1707
|
: undefined;
|
|
@@ -1550,14 +1719,20 @@ export function ChatContent({
|
|
|
1550
1719
|
{showChannelSidebar && (
|
|
1551
1720
|
<ChannelSidebar
|
|
1552
1721
|
channels={channels}
|
|
1722
|
+
channelStates={channelStates}
|
|
1553
1723
|
activeChannelId={channelId}
|
|
1724
|
+
onlineCount={onlineCount}
|
|
1554
1725
|
width={channelSidebarWidth}
|
|
1555
1726
|
height={height}
|
|
1556
1727
|
focused={focused}
|
|
1557
1728
|
keyboardFocused={sidebarFocused}
|
|
1558
1729
|
loading={channelsLoading}
|
|
1730
|
+
canManageNotifications={!!user?.emailVerified}
|
|
1559
1731
|
onSelect={changeChannel}
|
|
1560
1732
|
onFocusRequest={() => setSidebarFocused(true)}
|
|
1733
|
+
onToggleNotifications={(nextChannelId, enabled) => {
|
|
1734
|
+
controller.setChannelNotificationsEnabled(nextChannelId, enabled);
|
|
1735
|
+
}}
|
|
1561
1736
|
/>
|
|
1562
1737
|
)}
|
|
1563
1738
|
|
|
@@ -1570,15 +1745,6 @@ export function ChatContent({
|
|
|
1570
1745
|
onMouseDown={() => setSidebarFocused(false)}
|
|
1571
1746
|
style={nativeFillStyle}
|
|
1572
1747
|
>
|
|
1573
|
-
{compactChannelHeaderHeight > 0 && (
|
|
1574
|
-
<Box height={1} width={contentWidth} flexDirection="row">
|
|
1575
|
-
<Text fg={colors.textDim}>#</Text>
|
|
1576
|
-
<Text fg={colors.positive} attributes={TextAttributes.BOLD}>
|
|
1577
|
-
{truncateChannelLabel(activeChannelLabel, Math.max(contentWidth - 1, 1))}
|
|
1578
|
-
</Text>
|
|
1579
|
-
</Box>
|
|
1580
|
-
)}
|
|
1581
|
-
|
|
1582
1748
|
{!nativePaneChrome && (
|
|
1583
1749
|
<Box height={1} width={contentWidth}>
|
|
1584
1750
|
<Text fg={colors.border}>{"-".repeat(contentWidth)}</Text>
|
|
@@ -1593,7 +1759,7 @@ export function ChatContent({
|
|
|
1593
1759
|
focusable={false}
|
|
1594
1760
|
stickyScroll={stickyTranscript}
|
|
1595
1761
|
stickyStart="bottom"
|
|
1596
|
-
onMouseScroll={
|
|
1762
|
+
onMouseScroll={handleTranscriptScrollActivity}
|
|
1597
1763
|
style={nativePaneChrome ? { minHeight: 0 } : undefined}
|
|
1598
1764
|
>
|
|
1599
1765
|
{loadingOlderMessages && (
|
|
@@ -1655,6 +1821,17 @@ export function ChatContent({
|
|
|
1655
1821
|
|
|
1656
1822
|
{canSend ? (
|
|
1657
1823
|
<>
|
|
1824
|
+
{nativePaneChrome && (
|
|
1825
|
+
<Box
|
|
1826
|
+
width={composerWidth}
|
|
1827
|
+
style={{
|
|
1828
|
+
flex: `0 0 ${DESKTOP_CHAT_INPUT_TOP_MARGIN_PX}px`,
|
|
1829
|
+
height: DESKTOP_CHAT_INPUT_TOP_MARGIN_PX,
|
|
1830
|
+
minHeight: DESKTOP_CHAT_INPUT_TOP_MARGIN_PX,
|
|
1831
|
+
}}
|
|
1832
|
+
/>
|
|
1833
|
+
)}
|
|
1834
|
+
|
|
1658
1835
|
{replyTo && (
|
|
1659
1836
|
<Box height={1} width={contentWidth} flexDirection="row">
|
|
1660
1837
|
<Text fg={colors.textMuted}> replying to </Text>
|
|
@@ -1735,9 +1912,6 @@ function ChatPane({ focused, width, height, close }: PaneProps) {
|
|
|
1735
1912
|
const persistChannelId = useCallback((nextChannelId: string) => {
|
|
1736
1913
|
const currentState = stateRef.current;
|
|
1737
1914
|
const layout = setPaneSetting(currentState.config.layout, paneId, "channelId", nextChannelId);
|
|
1738
|
-
const layouts = currentState.config.layouts.map((savedLayout, index) => (
|
|
1739
|
-
index === currentState.config.activeLayoutIndex ? { ...savedLayout, layout } : savedLayout
|
|
1740
|
-
));
|
|
1741
1915
|
const pluginConfig = {
|
|
1742
1916
|
...currentState.config.pluginConfig,
|
|
1743
1917
|
"gloomberb-cloud": {
|
|
@@ -1748,11 +1922,16 @@ function ChatPane({ focused, width, height, close }: PaneProps) {
|
|
|
1748
1922
|
const nextConfig = {
|
|
1749
1923
|
...currentState.config,
|
|
1750
1924
|
layout,
|
|
1751
|
-
layouts,
|
|
1752
1925
|
pluginConfig,
|
|
1753
1926
|
};
|
|
1754
|
-
|
|
1755
|
-
|
|
1927
|
+
const syncedConfig = syncConfigActiveLayoutState(
|
|
1928
|
+
nextConfig,
|
|
1929
|
+
currentState.paneState,
|
|
1930
|
+
currentState.focusedPaneId,
|
|
1931
|
+
currentState.activePanel,
|
|
1932
|
+
);
|
|
1933
|
+
dispatch({ type: "SET_CONFIG", config: syncedConfig });
|
|
1934
|
+
scheduleConfigSave(syncedConfig);
|
|
1756
1935
|
}, [dispatch, paneId, stateRef]);
|
|
1757
1936
|
const [channelId, setLocalChannelId] = useState(initialChannelIdRef.current);
|
|
1758
1937
|
const pendingChannelIdRef = useRef<string | null>(null);
|