xfeed 0.1.3 → 0.1.5
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/dist/index.js +350 -195
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -439,7 +439,7 @@ import { cac } from "cac";
|
|
|
439
439
|
|
|
440
440
|
// src/app.tsx
|
|
441
441
|
import { useKeyboard as useKeyboard15, useRenderer as useRenderer2 } from "@opentui/react";
|
|
442
|
-
import { useCallback as
|
|
442
|
+
import { useCallback as useCallback15, useEffect as useEffect20, useRef as useRef12, useState as useState23 } from "react";
|
|
443
443
|
|
|
444
444
|
// src/components/Footer.tsx
|
|
445
445
|
import { useTerminalDimensions } from "@opentui/react";
|
|
@@ -456,7 +456,10 @@ var colors = {
|
|
|
456
456
|
mention: "#9B59B6",
|
|
457
457
|
quoted: "#3498DB",
|
|
458
458
|
reply: "#95A5A6",
|
|
459
|
-
handle: "#6B8A9E"
|
|
459
|
+
handle: "#6B8A9E",
|
|
460
|
+
liked: "#E0245E",
|
|
461
|
+
bookmarked: "#1DA1F2",
|
|
462
|
+
actionInactive: "#888888"
|
|
460
463
|
};
|
|
461
464
|
|
|
462
465
|
// src/components/Footer.tsx
|
|
@@ -466,8 +469,9 @@ var DEFAULT_BINDINGS = [
|
|
|
466
469
|
{ key: "l", label: "like" },
|
|
467
470
|
{ key: "b", label: "bookmark" },
|
|
468
471
|
{ key: "r", label: "refresh" },
|
|
469
|
-
{ key: "
|
|
470
|
-
{ key: "
|
|
472
|
+
{ key: "1", label: "home" },
|
|
473
|
+
{ key: "2", label: "bookmarks" },
|
|
474
|
+
{ key: "3", label: "notifs" },
|
|
471
475
|
{ key: "q", label: "quit" }
|
|
472
476
|
];
|
|
473
477
|
function KeybindingItem({ binding }) {
|
|
@@ -1916,7 +1920,7 @@ function useModal() {
|
|
|
1916
1920
|
|
|
1917
1921
|
// src/experiments/index.tsx
|
|
1918
1922
|
import { QueryClientProvider } from "@tanstack/react-query";
|
|
1919
|
-
import { useState as
|
|
1923
|
+
import { useState as useState16 } from "react";
|
|
1920
1924
|
|
|
1921
1925
|
// src/experiments/query-client.ts
|
|
1922
1926
|
import { QueryClient } from "@tanstack/react-query";
|
|
@@ -1998,7 +2002,7 @@ var queryKeys = {
|
|
|
1998
2002
|
|
|
1999
2003
|
// src/experiments/TimelineScreenExperimental.tsx
|
|
2000
2004
|
import { useKeyboard as useKeyboard9 } from "@opentui/react";
|
|
2001
|
-
import { useEffect as useEffect9, useRef as
|
|
2005
|
+
import { useEffect as useEffect9, useRef as useRef5 } from "react";
|
|
2002
2006
|
|
|
2003
2007
|
// src/components/ErrorBanner.tsx
|
|
2004
2008
|
import { useEffect as useEffect6 } from "react";
|
|
@@ -2230,7 +2234,10 @@ function ErrorBanner({
|
|
|
2230
2234
|
|
|
2231
2235
|
// src/components/PostList.tsx
|
|
2232
2236
|
import { useKeyboard as useKeyboard8 } from "@opentui/react";
|
|
2233
|
-
import { useEffect as useEffect7, useRef as
|
|
2237
|
+
import { useEffect as useEffect7, useRef as useRef3 } from "react";
|
|
2238
|
+
|
|
2239
|
+
// src/components/PostCard.tsx
|
|
2240
|
+
import { useRef as useRef2, useState as useState10 } from "react";
|
|
2234
2241
|
|
|
2235
2242
|
// src/components/QuotedPostCard.tsx
|
|
2236
2243
|
import { jsxDEV as jsxDEV11 } from "@opentui/react/jsx-dev-runtime";
|
|
@@ -2390,8 +2397,85 @@ function PostCard({
|
|
|
2390
2397
|
isJustLiked,
|
|
2391
2398
|
isJustBookmarked,
|
|
2392
2399
|
parentAuthorUsername,
|
|
2393
|
-
mainPostAuthorUsername
|
|
2400
|
+
mainPostAuthorUsername,
|
|
2401
|
+
onCardClick,
|
|
2402
|
+
onLikeClick,
|
|
2403
|
+
onBookmarkClick
|
|
2394
2404
|
}) {
|
|
2405
|
+
const cardDragState = useRef2({ isDragging: false, startX: 0, startY: 0 });
|
|
2406
|
+
const likeDragState = useRef2({ isDragging: false, startX: 0, startY: 0 });
|
|
2407
|
+
const bookmarkDragState = useRef2({ isDragging: false, startX: 0, startY: 0 });
|
|
2408
|
+
const DRAG_THRESHOLD = 3;
|
|
2409
|
+
const handleCardMouse = onCardClick ? (event) => {
|
|
2410
|
+
if (event.button !== 0)
|
|
2411
|
+
return;
|
|
2412
|
+
if (event.type === "down") {
|
|
2413
|
+
cardDragState.current = {
|
|
2414
|
+
isDragging: false,
|
|
2415
|
+
startX: event.x,
|
|
2416
|
+
startY: event.y
|
|
2417
|
+
};
|
|
2418
|
+
} else if (event.type === "drag") {
|
|
2419
|
+
const dx = Math.abs(event.x - cardDragState.current.startX);
|
|
2420
|
+
const dy = Math.abs(event.y - cardDragState.current.startY);
|
|
2421
|
+
if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
|
|
2422
|
+
cardDragState.current.isDragging = true;
|
|
2423
|
+
}
|
|
2424
|
+
} else if (event.type === "up") {
|
|
2425
|
+
if (!cardDragState.current.isDragging) {
|
|
2426
|
+
onCardClick();
|
|
2427
|
+
}
|
|
2428
|
+
cardDragState.current.isDragging = false;
|
|
2429
|
+
}
|
|
2430
|
+
} : undefined;
|
|
2431
|
+
const handleLikeMouse = onLikeClick ? (event) => {
|
|
2432
|
+
if (event.button !== 0)
|
|
2433
|
+
return;
|
|
2434
|
+
event.stopPropagation();
|
|
2435
|
+
if (event.type === "down") {
|
|
2436
|
+
likeDragState.current = {
|
|
2437
|
+
isDragging: false,
|
|
2438
|
+
startX: event.x,
|
|
2439
|
+
startY: event.y
|
|
2440
|
+
};
|
|
2441
|
+
} else if (event.type === "drag") {
|
|
2442
|
+
const dx = Math.abs(event.x - likeDragState.current.startX);
|
|
2443
|
+
const dy = Math.abs(event.y - likeDragState.current.startY);
|
|
2444
|
+
if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
|
|
2445
|
+
likeDragState.current.isDragging = true;
|
|
2446
|
+
}
|
|
2447
|
+
} else if (event.type === "up") {
|
|
2448
|
+
if (!likeDragState.current.isDragging) {
|
|
2449
|
+
onLikeClick();
|
|
2450
|
+
}
|
|
2451
|
+
likeDragState.current.isDragging = false;
|
|
2452
|
+
}
|
|
2453
|
+
} : undefined;
|
|
2454
|
+
const handleBookmarkMouse = onBookmarkClick ? (event) => {
|
|
2455
|
+
if (event.button !== 0)
|
|
2456
|
+
return;
|
|
2457
|
+
event.stopPropagation();
|
|
2458
|
+
if (event.type === "down") {
|
|
2459
|
+
bookmarkDragState.current = {
|
|
2460
|
+
isDragging: false,
|
|
2461
|
+
startX: event.x,
|
|
2462
|
+
startY: event.y
|
|
2463
|
+
};
|
|
2464
|
+
} else if (event.type === "drag") {
|
|
2465
|
+
const dx = Math.abs(event.x - bookmarkDragState.current.startX);
|
|
2466
|
+
const dy = Math.abs(event.y - bookmarkDragState.current.startY);
|
|
2467
|
+
if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
|
|
2468
|
+
bookmarkDragState.current.isDragging = true;
|
|
2469
|
+
}
|
|
2470
|
+
} else if (event.type === "up") {
|
|
2471
|
+
if (!bookmarkDragState.current.isDragging) {
|
|
2472
|
+
onBookmarkClick();
|
|
2473
|
+
}
|
|
2474
|
+
bookmarkDragState.current.isDragging = false;
|
|
2475
|
+
}
|
|
2476
|
+
} : undefined;
|
|
2477
|
+
const [isLikeHovered, setIsLikeHovered] = useState10(false);
|
|
2478
|
+
const [isBookmarkHovered, setIsBookmarkHovered] = useState10(false);
|
|
2395
2479
|
const textToDisplay = parentAuthorUsername ? stripLeadingMention(post.text, parentAuthorUsername) : post.text;
|
|
2396
2480
|
const displayText = truncateText(textToDisplay, MAX_TEXT_LINES3);
|
|
2397
2481
|
const showShowMore = isTruncated(textToDisplay, MAX_TEXT_LINES3);
|
|
@@ -2399,6 +2483,7 @@ function PostCard({
|
|
|
2399
2483
|
const hasMedia = post.media && post.media.length > 0;
|
|
2400
2484
|
return /* @__PURE__ */ jsxDEV13("box", {
|
|
2401
2485
|
id,
|
|
2486
|
+
onMouse: handleCardMouse,
|
|
2402
2487
|
style: {
|
|
2403
2488
|
flexDirection: "column",
|
|
2404
2489
|
marginBottom: 1,
|
|
@@ -2472,7 +2557,10 @@ function PostCard({
|
|
|
2472
2557
|
]
|
|
2473
2558
|
}, undefined, true, undefined, this),
|
|
2474
2559
|
/* @__PURE__ */ jsxDEV13("text", {
|
|
2475
|
-
|
|
2560
|
+
onMouse: handleLikeMouse,
|
|
2561
|
+
onMouseOver: () => setIsLikeHovered(true),
|
|
2562
|
+
onMouseOut: () => setIsLikeHovered(false),
|
|
2563
|
+
fg: isJustLiked ? colors.success : isLikeHovered ? colors.liked : isLiked ? colors.liked : colors.actionInactive,
|
|
2476
2564
|
children: [
|
|
2477
2565
|
" ",
|
|
2478
2566
|
isLiked ? HEART_FILLED : /* @__PURE__ */ jsxDEV13("b", {
|
|
@@ -2481,7 +2569,10 @@ function PostCard({
|
|
|
2481
2569
|
]
|
|
2482
2570
|
}, undefined, true, undefined, this),
|
|
2483
2571
|
/* @__PURE__ */ jsxDEV13("text", {
|
|
2484
|
-
|
|
2572
|
+
onMouse: handleBookmarkMouse,
|
|
2573
|
+
onMouseOver: () => setIsBookmarkHovered(true),
|
|
2574
|
+
onMouseOut: () => setIsBookmarkHovered(false),
|
|
2575
|
+
fg: isJustBookmarked ? colors.success : isBookmarkHovered ? colors.bookmarked : isBookmarked ? colors.bookmarked : colors.actionInactive,
|
|
2485
2576
|
children: [
|
|
2486
2577
|
" ",
|
|
2487
2578
|
isBookmarked ? FLAG_FILLED : /* @__PURE__ */ jsxDEV13("b", {
|
|
@@ -2540,9 +2631,9 @@ function PostList({
|
|
|
2540
2631
|
loadingMore = false,
|
|
2541
2632
|
hasMore = true
|
|
2542
2633
|
}) {
|
|
2543
|
-
const scrollRef =
|
|
2544
|
-
const savedScrollTop =
|
|
2545
|
-
const wasFocused =
|
|
2634
|
+
const scrollRef = useRef3(null);
|
|
2635
|
+
const savedScrollTop = useRef3(0);
|
|
2636
|
+
const wasFocused = useRef3(focused);
|
|
2546
2637
|
useEffect7(() => {
|
|
2547
2638
|
const scrollbox = scrollRef.current;
|
|
2548
2639
|
if (!scrollbox)
|
|
@@ -2650,7 +2741,10 @@ function PostList({
|
|
|
2650
2741
|
isLiked: actionState?.liked,
|
|
2651
2742
|
isBookmarked: actionState?.bookmarked,
|
|
2652
2743
|
isJustLiked: actionState?.justLiked,
|
|
2653
|
-
isJustBookmarked: actionState?.justBookmarked
|
|
2744
|
+
isJustBookmarked: actionState?.justBookmarked,
|
|
2745
|
+
onCardClick: () => onPostSelect?.(post),
|
|
2746
|
+
onLikeClick: () => onLike?.(post),
|
|
2747
|
+
onBookmarkClick: () => onBookmark?.(post)
|
|
2654
2748
|
}, post.id, false, undefined, this);
|
|
2655
2749
|
}),
|
|
2656
2750
|
loadingMore ? /* @__PURE__ */ jsxDEV14("box", {
|
|
@@ -2697,7 +2791,7 @@ function usePreferences() {
|
|
|
2697
2791
|
|
|
2698
2792
|
// src/experiments/use-timeline-query.ts
|
|
2699
2793
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
2700
|
-
import { useCallback as useCallback5, useEffect as useEffect8, useMemo, useRef as
|
|
2794
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useMemo, useRef as useRef4, useState as useState11 } from "react";
|
|
2701
2795
|
async function fetchTimelinePage(client, tab, cursor) {
|
|
2702
2796
|
const result = cursor ? tab === "for_you" ? await client.getHomeTimeline(30, cursor) : await client.getHomeLatestTimeline(30, cursor) : tab === "for_you" ? await client.getHomeTimelineV2(30) : await client.getHomeLatestTimelineV2(30);
|
|
2703
2797
|
if (!result.success) {
|
|
@@ -2728,9 +2822,9 @@ function useTimelineQuery({
|
|
|
2728
2822
|
initialTab = "for_you",
|
|
2729
2823
|
refreshBannerDelay = DEFAULT_REFRESH_BANNER_DELAY
|
|
2730
2824
|
}) {
|
|
2731
|
-
const [tab, setTabState] =
|
|
2732
|
-
const [showRefreshBanner, setShowRefreshBanner] =
|
|
2733
|
-
const bannerTimerRef =
|
|
2825
|
+
const [tab, setTabState] = useState11(initialTab);
|
|
2826
|
+
const [showRefreshBanner, setShowRefreshBanner] = useState11(false);
|
|
2827
|
+
const bannerTimerRef = useRef4(null);
|
|
2734
2828
|
const {
|
|
2735
2829
|
data,
|
|
2736
2830
|
isLoading,
|
|
@@ -2896,7 +2990,7 @@ function TimelineScreenExperimental({
|
|
|
2896
2990
|
useEffect9(() => {
|
|
2897
2991
|
onPostCountChange?.(posts.length);
|
|
2898
2992
|
}, [posts.length, onPostCountChange]);
|
|
2899
|
-
const hasCalledInitialLoadComplete =
|
|
2993
|
+
const hasCalledInitialLoadComplete = useRef5(false);
|
|
2900
2994
|
useEffect9(() => {
|
|
2901
2995
|
if (!isLoading && !hasCalledInitialLoadComplete.current) {
|
|
2902
2996
|
hasCalledInitialLoadComplete.current = true;
|
|
@@ -2913,6 +3007,9 @@ function TimelineScreenExperimental({
|
|
|
2913
3007
|
case "2":
|
|
2914
3008
|
setTab("following");
|
|
2915
3009
|
break;
|
|
3010
|
+
case "tab":
|
|
3011
|
+
setTab(tab === "for_you" ? "following" : "for_you");
|
|
3012
|
+
break;
|
|
2916
3013
|
case "r":
|
|
2917
3014
|
refresh();
|
|
2918
3015
|
break;
|
|
@@ -2995,7 +3092,105 @@ function TimelineScreenExperimental({
|
|
|
2995
3092
|
}
|
|
2996
3093
|
// src/experiments/use-profile-query.ts
|
|
2997
3094
|
import { useQuery } from "@tanstack/react-query";
|
|
2998
|
-
import { useCallback as useCallback6, useState as
|
|
3095
|
+
import { useCallback as useCallback6, useState as useState12 } from "react";
|
|
3096
|
+
function useProfileQuery({
|
|
3097
|
+
client,
|
|
3098
|
+
username,
|
|
3099
|
+
isSelf = false
|
|
3100
|
+
}) {
|
|
3101
|
+
const [likesEnabled, setLikesEnabled] = useState12(false);
|
|
3102
|
+
const {
|
|
3103
|
+
data: profileData,
|
|
3104
|
+
isLoading: isProfileLoading,
|
|
3105
|
+
error: profileError,
|
|
3106
|
+
refetch: refetchProfile,
|
|
3107
|
+
isRefetching: isProfileRefetching
|
|
3108
|
+
} = useQuery({
|
|
3109
|
+
queryKey: queryKeys.user.profile(username.toLowerCase()),
|
|
3110
|
+
queryFn: async () => {
|
|
3111
|
+
const result = await client.getUserByScreenName(username);
|
|
3112
|
+
if (!result.success) {
|
|
3113
|
+
throw new Error(result.error ?? "Failed to load profile");
|
|
3114
|
+
}
|
|
3115
|
+
if (!result.user) {
|
|
3116
|
+
throw new Error("User not found");
|
|
3117
|
+
}
|
|
3118
|
+
return result.user;
|
|
3119
|
+
}
|
|
3120
|
+
});
|
|
3121
|
+
const {
|
|
3122
|
+
data: tweetsData,
|
|
3123
|
+
isLoading: isTweetsLoading,
|
|
3124
|
+
refetch: refetchTweets,
|
|
3125
|
+
isRefetching: isTweetsRefetching
|
|
3126
|
+
} = useQuery({
|
|
3127
|
+
queryKey: queryKeys.user.tweets(profileData?.id ?? ""),
|
|
3128
|
+
queryFn: async () => {
|
|
3129
|
+
if (!profileData?.id)
|
|
3130
|
+
return [];
|
|
3131
|
+
const result = await client.getUserTweets(profileData.id, 20);
|
|
3132
|
+
if (!result.success) {
|
|
3133
|
+
return [];
|
|
3134
|
+
}
|
|
3135
|
+
return result.tweets ?? [];
|
|
3136
|
+
},
|
|
3137
|
+
enabled: !!profileData?.id
|
|
3138
|
+
});
|
|
3139
|
+
const {
|
|
3140
|
+
data: likesData,
|
|
3141
|
+
isLoading: isLikesLoading,
|
|
3142
|
+
error: likesError,
|
|
3143
|
+
refetch: refetchLikes,
|
|
3144
|
+
isFetched: likesFetched
|
|
3145
|
+
} = useQuery({
|
|
3146
|
+
queryKey: queryKeys.user.likes(),
|
|
3147
|
+
queryFn: async () => {
|
|
3148
|
+
const result = await client.getLikes(20);
|
|
3149
|
+
if (!result.success) {
|
|
3150
|
+
throw new Error(result.error ?? "Failed to load likes");
|
|
3151
|
+
}
|
|
3152
|
+
return result.tweets ?? [];
|
|
3153
|
+
},
|
|
3154
|
+
enabled: isSelf && likesEnabled
|
|
3155
|
+
});
|
|
3156
|
+
const fetchLikes = useCallback6(() => {
|
|
3157
|
+
if (isSelf && !likesEnabled) {
|
|
3158
|
+
setLikesEnabled(true);
|
|
3159
|
+
} else if (isSelf && likesEnabled) {
|
|
3160
|
+
refetchLikes();
|
|
3161
|
+
}
|
|
3162
|
+
}, [isSelf, likesEnabled, refetchLikes]);
|
|
3163
|
+
const refresh = useCallback6(() => {
|
|
3164
|
+
refetchProfile();
|
|
3165
|
+
if (profileData?.id) {
|
|
3166
|
+
refetchTweets();
|
|
3167
|
+
}
|
|
3168
|
+
if (isSelf && likesEnabled) {
|
|
3169
|
+
refetchLikes();
|
|
3170
|
+
}
|
|
3171
|
+
}, [
|
|
3172
|
+
refetchProfile,
|
|
3173
|
+
refetchTweets,
|
|
3174
|
+
refetchLikes,
|
|
3175
|
+
profileData?.id,
|
|
3176
|
+
isSelf,
|
|
3177
|
+
likesEnabled
|
|
3178
|
+
]);
|
|
3179
|
+
return {
|
|
3180
|
+
user: profileData ?? null,
|
|
3181
|
+
tweets: tweetsData ?? [],
|
|
3182
|
+
isLoading: isProfileLoading,
|
|
3183
|
+
isTweetsLoading,
|
|
3184
|
+
error: profileError instanceof Error ? profileError.message : null,
|
|
3185
|
+
refresh,
|
|
3186
|
+
likedTweets: likesData ?? [],
|
|
3187
|
+
isLikesLoading,
|
|
3188
|
+
likesError: likesError instanceof Error ? likesError.message : null,
|
|
3189
|
+
fetchLikes,
|
|
3190
|
+
likesFetched,
|
|
3191
|
+
isRefetching: isProfileRefetching || isTweetsRefetching
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
2999
3194
|
// src/experiments/use-post-detail-query.ts
|
|
3000
3195
|
import {
|
|
3001
3196
|
useInfiniteQuery as useInfiniteQuery2,
|
|
@@ -3183,7 +3378,7 @@ function useBookmarksQuery({
|
|
|
3183
3378
|
}
|
|
3184
3379
|
// src/experiments/use-bookmark-mutation.ts
|
|
3185
3380
|
import { useMutation, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
|
|
3186
|
-
import { useCallback as useCallback8, useRef as
|
|
3381
|
+
import { useCallback as useCallback8, useRef as useRef6, useState as useState13 } from "react";
|
|
3187
3382
|
var JUST_ACTED_DURATION = 600;
|
|
3188
3383
|
function useBookmarkMutation({
|
|
3189
3384
|
client,
|
|
@@ -3191,9 +3386,9 @@ function useBookmarkMutation({
|
|
|
3191
3386
|
onError
|
|
3192
3387
|
}) {
|
|
3193
3388
|
const queryClient = useQueryClient3();
|
|
3194
|
-
const [pendingTweets, setPendingTweets] =
|
|
3195
|
-
const [justBookmarkedTweets, setJustBookmarkedTweets] =
|
|
3196
|
-
const justBookmarkedTimeouts =
|
|
3389
|
+
const [pendingTweets, setPendingTweets] = useState13(new Set);
|
|
3390
|
+
const [justBookmarkedTweets, setJustBookmarkedTweets] = useState13(new Set);
|
|
3391
|
+
const justBookmarkedTimeouts = useRef6(new Map);
|
|
3197
3392
|
const addMutation = useMutation({
|
|
3198
3393
|
mutationFn: async (tweet) => {
|
|
3199
3394
|
const result = await client.createBookmark(tweet.id);
|
|
@@ -3388,7 +3583,7 @@ import { useEffect as useEffect12, useMemo as useMemo5 } from "react";
|
|
|
3388
3583
|
|
|
3389
3584
|
// src/components/ThreadView.prototype.tsx
|
|
3390
3585
|
import { useKeyboard as useKeyboard10 } from "@opentui/react";
|
|
3391
|
-
import { useState as
|
|
3586
|
+
import { useState as useState14, useRef as useRef7, useEffect as useEffect11, useMemo as useMemo4 } from "react";
|
|
3392
3587
|
import { jsxDEV as jsxDEV17 } from "@opentui/react/jsx-dev-runtime";
|
|
3393
3588
|
var SELECTED_BG = "#2a2a3e";
|
|
3394
3589
|
var TREE = {
|
|
@@ -3627,11 +3822,11 @@ function ThreadViewPrototype({
|
|
|
3627
3822
|
onSelectTweet,
|
|
3628
3823
|
showFooter = true
|
|
3629
3824
|
}) {
|
|
3630
|
-
const scrollRef =
|
|
3631
|
-
const savedScrollTop =
|
|
3632
|
-
const wasFocused =
|
|
3633
|
-
const [selectedIndex, setSelectedIndex] =
|
|
3634
|
-
const [treeState, setTreeState] =
|
|
3825
|
+
const scrollRef = useRef7(null);
|
|
3826
|
+
const savedScrollTop = useRef7(0);
|
|
3827
|
+
const wasFocused = useRef7(focused);
|
|
3828
|
+
const [selectedIndex, setSelectedIndex] = useState14(0);
|
|
3829
|
+
const [treeState, setTreeState] = useState14(replyTree);
|
|
3635
3830
|
useEffect11(() => {
|
|
3636
3831
|
const scrollbox = scrollRef.current;
|
|
3637
3832
|
if (!scrollbox)
|
|
@@ -3956,7 +4151,7 @@ function useThreadQuery({
|
|
|
3956
4151
|
}
|
|
3957
4152
|
// src/experiments/use-user-actions.ts
|
|
3958
4153
|
import { useMutation as useMutation2, useQueryClient as useQueryClient5 } from "@tanstack/react-query";
|
|
3959
|
-
import { useCallback as useCallback9, useState as
|
|
4154
|
+
import { useCallback as useCallback9, useState as useState15 } from "react";
|
|
3960
4155
|
function useUserActions({
|
|
3961
4156
|
client,
|
|
3962
4157
|
username,
|
|
@@ -3964,7 +4159,7 @@ function useUserActions({
|
|
|
3964
4159
|
onError
|
|
3965
4160
|
}) {
|
|
3966
4161
|
const queryClient = useQueryClient5();
|
|
3967
|
-
const [pendingAction, setPendingAction] =
|
|
4162
|
+
const [pendingAction, setPendingAction] = useState15(null);
|
|
3968
4163
|
const updateProfileCache = useCallback9((updates) => {
|
|
3969
4164
|
queryClient.setQueryData(queryKeys.user.profile(username), (old) => {
|
|
3970
4165
|
if (!old?.success || !old.user)
|
|
@@ -4172,7 +4367,7 @@ function getQueryClient() {
|
|
|
4172
4367
|
return queryClientInstance;
|
|
4173
4368
|
}
|
|
4174
4369
|
function QueryProvider({ children }) {
|
|
4175
|
-
const [queryClient] =
|
|
4370
|
+
const [queryClient] = useState16(() => getQueryClient());
|
|
4176
4371
|
return /* @__PURE__ */ jsxDEV18(QueryClientProvider, {
|
|
4177
4372
|
client: queryClient,
|
|
4178
4373
|
children
|
|
@@ -4180,7 +4375,7 @@ function QueryProvider({ children }) {
|
|
|
4180
4375
|
}
|
|
4181
4376
|
|
|
4182
4377
|
// src/hooks/useActions.ts
|
|
4183
|
-
import { useState as
|
|
4378
|
+
import { useState as useState17, useCallback as useCallback10, useRef as useRef8 } from "react";
|
|
4184
4379
|
var DEFAULT_STATE = {
|
|
4185
4380
|
liked: false,
|
|
4186
4381
|
bookmarked: false,
|
|
@@ -4198,9 +4393,9 @@ function useActions({
|
|
|
4198
4393
|
bookmarkMutation,
|
|
4199
4394
|
currentFolderId
|
|
4200
4395
|
}) {
|
|
4201
|
-
const [states, setStates] =
|
|
4202
|
-
const justLikedTimeouts =
|
|
4203
|
-
const justBookmarkedTimeouts =
|
|
4396
|
+
const [states, setStates] = useState17(new Map);
|
|
4397
|
+
const justLikedTimeouts = useRef8(new Map);
|
|
4398
|
+
const justBookmarkedTimeouts = useRef8(new Map);
|
|
4204
4399
|
const getState = useCallback10((tweetId) => {
|
|
4205
4400
|
const baseState = states.get(tweetId) ?? DEFAULT_STATE;
|
|
4206
4401
|
if (bookmarkMutation) {
|
|
@@ -4377,10 +4572,10 @@ function useActions({
|
|
|
4377
4572
|
}
|
|
4378
4573
|
|
|
4379
4574
|
// src/hooks/useNavigation.ts
|
|
4380
|
-
import { useState as
|
|
4575
|
+
import { useState as useState18, useCallback as useCallback11 } from "react";
|
|
4381
4576
|
function useNavigation(options) {
|
|
4382
4577
|
const { initialView, mainViews } = options;
|
|
4383
|
-
const [history, setHistory] =
|
|
4578
|
+
const [history, setHistory] = useState18([initialView]);
|
|
4384
4579
|
const currentView = history[history.length - 1];
|
|
4385
4580
|
const previousView = history.length > 1 ? history[history.length - 2] : null;
|
|
4386
4581
|
const canGoBack = history.length > 1;
|
|
@@ -4483,7 +4678,7 @@ function ScreenHeader({ folderName, inFolder }) {
|
|
|
4483
4678
|
fg: colors.dim,
|
|
4484
4679
|
children: [
|
|
4485
4680
|
" ",
|
|
4486
|
-
"(f folders, c new",
|
|
4681
|
+
"(Tab/f folders, c new",
|
|
4487
4682
|
inFolder ? ", e rename, D delete" : "",
|
|
4488
4683
|
")"
|
|
4489
4684
|
]
|
|
@@ -4528,7 +4723,7 @@ function BookmarksScreen({
|
|
|
4528
4723
|
if (key.name === "r") {
|
|
4529
4724
|
refresh();
|
|
4530
4725
|
}
|
|
4531
|
-
if (key.name === "f") {
|
|
4726
|
+
if (key.name === "f" || key.name === "tab") {
|
|
4532
4727
|
onFolderPickerOpen?.();
|
|
4533
4728
|
}
|
|
4534
4729
|
if (key.name === "c") {
|
|
@@ -4623,7 +4818,7 @@ import { useKeyboard as useKeyboard12 } from "@opentui/react";
|
|
|
4623
4818
|
import { useEffect as useEffect16 } from "react";
|
|
4624
4819
|
|
|
4625
4820
|
// src/components/NotificationList.tsx
|
|
4626
|
-
import { useEffect as useEffect14, useRef as
|
|
4821
|
+
import { useEffect as useEffect14, useRef as useRef9 } from "react";
|
|
4627
4822
|
|
|
4628
4823
|
// src/components/NotificationItem.tsx
|
|
4629
4824
|
import { jsxDEV as jsxDEV20 } from "@opentui/react/jsx-dev-runtime";
|
|
@@ -4721,9 +4916,9 @@ function NotificationList({
|
|
|
4721
4916
|
loadingMore = false,
|
|
4722
4917
|
hasMore = true
|
|
4723
4918
|
}) {
|
|
4724
|
-
const scrollRef =
|
|
4725
|
-
const savedScrollTop =
|
|
4726
|
-
const wasFocused =
|
|
4919
|
+
const scrollRef = useRef9(null);
|
|
4920
|
+
const savedScrollTop = useRef9(0);
|
|
4921
|
+
const wasFocused = useRef9(focused);
|
|
4727
4922
|
useEffect14(() => {
|
|
4728
4923
|
const scrollbox = scrollRef.current;
|
|
4729
4924
|
if (!scrollbox)
|
|
@@ -4828,7 +5023,7 @@ import { useInfiniteQuery as useInfiniteQuery4 } from "@tanstack/react-query";
|
|
|
4828
5023
|
import { appendFileSync } from "fs";
|
|
4829
5024
|
import { homedir as homedir2 } from "os";
|
|
4830
5025
|
import { join } from "path";
|
|
4831
|
-
import { useCallback as useCallback12, useEffect as useEffect15, useMemo as useMemo6, useRef as
|
|
5026
|
+
import { useCallback as useCallback12, useEffect as useEffect15, useMemo as useMemo6, useRef as useRef10, useState as useState19 } from "react";
|
|
4832
5027
|
var LOG_FILE = join(homedir2(), ".xfeed-notifications.log");
|
|
4833
5028
|
function log(message, data) {
|
|
4834
5029
|
const timestamp = new Date().toISOString();
|
|
@@ -4911,8 +5106,8 @@ function useNotificationsQuery({
|
|
|
4911
5106
|
client,
|
|
4912
5107
|
pollingInterval = DEFAULT_POLLING_INTERVAL
|
|
4913
5108
|
}) {
|
|
4914
|
-
const lastSeenNewestIdRef =
|
|
4915
|
-
const [newNotificationsCount, setNewNotificationsCount] =
|
|
5109
|
+
const lastSeenNewestIdRef = useRef10(null);
|
|
5110
|
+
const [newNotificationsCount, setNewNotificationsCount] = useState19(0);
|
|
4916
5111
|
log("useNotificationsQuery render", {
|
|
4917
5112
|
lastSeenNewestId: lastSeenNewestIdRef.current,
|
|
4918
5113
|
newNotificationsCount
|
|
@@ -4962,7 +5157,7 @@ function useNotificationsQuery({
|
|
|
4962
5157
|
lastSeenNewestIdRef.current = newestId ?? null;
|
|
4963
5158
|
}
|
|
4964
5159
|
}, [notifications]);
|
|
4965
|
-
const lastUnreadSortIndexRef =
|
|
5160
|
+
const lastUnreadSortIndexRef = useRef10(null);
|
|
4966
5161
|
useEffect15(() => {
|
|
4967
5162
|
if (lastUnreadSortIndexRef.current === null && data?.pages?.[0]) {
|
|
4968
5163
|
const sortIndex = data.pages[0].unreadSortIndex;
|
|
@@ -5228,7 +5423,7 @@ function NotificationsScreen({
|
|
|
5228
5423
|
|
|
5229
5424
|
// src/screens/PostDetailScreen.tsx
|
|
5230
5425
|
import { useKeyboard as useKeyboard13 } from "@opentui/react";
|
|
5231
|
-
import { useState as
|
|
5426
|
+
import { useState as useState20, useRef as useRef11, useEffect as useEffect17, useCallback as useCallback13 } from "react";
|
|
5232
5427
|
init_media();
|
|
5233
5428
|
|
|
5234
5429
|
// src/lib/text.tsx
|
|
@@ -5356,16 +5551,16 @@ function PostDetailScreen({
|
|
|
5356
5551
|
hasMoreReplies,
|
|
5357
5552
|
loadMoreReplies
|
|
5358
5553
|
} = usePostDetailQuery({ client, tweet });
|
|
5359
|
-
const [isExpanded, setIsExpanded] =
|
|
5360
|
-
const [statusMessage, setStatusMessage] =
|
|
5361
|
-
const [linkIndex, setLinkIndex] =
|
|
5362
|
-
const [mentionIndex, setMentionIndex] =
|
|
5363
|
-
const [linkMetadata, setLinkMetadata] =
|
|
5364
|
-
const [isLoadingMetadata, setIsLoadingMetadata] =
|
|
5365
|
-
const [repliesMode, setRepliesMode] =
|
|
5366
|
-
const [mentionsMode, setMentionsMode] =
|
|
5367
|
-
const [linkMode, setLinkMode] =
|
|
5368
|
-
const scrollRef =
|
|
5554
|
+
const [isExpanded, setIsExpanded] = useState20(false);
|
|
5555
|
+
const [statusMessage, setStatusMessage] = useState20(null);
|
|
5556
|
+
const [linkIndex, setLinkIndex] = useState20(0);
|
|
5557
|
+
const [mentionIndex, setMentionIndex] = useState20(0);
|
|
5558
|
+
const [linkMetadata, setLinkMetadata] = useState20(null);
|
|
5559
|
+
const [isLoadingMetadata, setIsLoadingMetadata] = useState20(false);
|
|
5560
|
+
const [repliesMode, setRepliesMode] = useState20(false);
|
|
5561
|
+
const [mentionsMode, setMentionsMode] = useState20(false);
|
|
5562
|
+
const [linkMode, setLinkMode] = useState20(false);
|
|
5563
|
+
const scrollRef = useRef11(null);
|
|
5369
5564
|
useEffect17(() => {
|
|
5370
5565
|
setRepliesMode(false);
|
|
5371
5566
|
setMentionsMode(false);
|
|
@@ -5622,7 +5817,10 @@ function PostDetailScreen({
|
|
|
5622
5817
|
setMentionsMode(true);
|
|
5623
5818
|
setMentionIndex(0);
|
|
5624
5819
|
}
|
|
5625
|
-
}
|
|
5820
|
+
}
|
|
5821
|
+
break;
|
|
5822
|
+
case "f":
|
|
5823
|
+
if (isBookmarked) {
|
|
5626
5824
|
onMoveToFolder?.();
|
|
5627
5825
|
}
|
|
5628
5826
|
break;
|
|
@@ -5961,6 +6159,11 @@ function PostDetailScreen({
|
|
|
5961
6159
|
}, undefined, true, undefined, this)
|
|
5962
6160
|
}, undefined, false, undefined, this)
|
|
5963
6161
|
}, undefined, false, undefined, this) : null;
|
|
6162
|
+
const handleMentionRowClick = useCallback13((username) => (event) => {
|
|
6163
|
+
if (event.type === "up" && event.button === 0) {
|
|
6164
|
+
onProfileOpen?.(username);
|
|
6165
|
+
}
|
|
6166
|
+
}, [onProfileOpen]);
|
|
5964
6167
|
const firstMention = tweet.mentions?.[0];
|
|
5965
6168
|
const mentionsContent = hasMentions ? /* @__PURE__ */ jsxDEV24("box", {
|
|
5966
6169
|
style: { marginTop: 1, paddingLeft: 1, paddingRight: 1 },
|
|
@@ -5968,6 +6171,7 @@ function PostDetailScreen({
|
|
|
5968
6171
|
style: { flexDirection: "column" },
|
|
5969
6172
|
children: mentionCount === 1 ? /* @__PURE__ */ jsxDEV24("box", {
|
|
5970
6173
|
style: { flexDirection: "row" },
|
|
6174
|
+
onMouse: firstMention ? handleMentionRowClick(firstMention.username) : undefined,
|
|
5971
6175
|
children: [
|
|
5972
6176
|
/* @__PURE__ */ jsxDEV24("text", {
|
|
5973
6177
|
fg: colors.dim,
|
|
@@ -5997,7 +6201,7 @@ function PostDetailScreen({
|
|
|
5997
6201
|
}, undefined, false, undefined, this),
|
|
5998
6202
|
/* @__PURE__ */ jsxDEV24("text", {
|
|
5999
6203
|
fg: colors.dim,
|
|
6000
|
-
children: "
|
|
6204
|
+
children: "/click)"
|
|
6001
6205
|
}, undefined, false, undefined, this)
|
|
6002
6206
|
]
|
|
6003
6207
|
}, undefined, true, undefined, this) : mentionsMode ? /* @__PURE__ */ jsxDEV24(Fragment, {
|
|
@@ -6027,7 +6231,7 @@ function PostDetailScreen({
|
|
|
6027
6231
|
}, undefined, false, undefined, this),
|
|
6028
6232
|
/* @__PURE__ */ jsxDEV24("text", {
|
|
6029
6233
|
fg: colors.dim,
|
|
6030
|
-
children: " profile"
|
|
6234
|
+
children: "/click profile"
|
|
6031
6235
|
}, undefined, false, undefined, this)
|
|
6032
6236
|
]
|
|
6033
6237
|
}, undefined, true, undefined, this),
|
|
@@ -6035,6 +6239,7 @@ function PostDetailScreen({
|
|
|
6035
6239
|
const isSelected = idx === mentionIndex;
|
|
6036
6240
|
return /* @__PURE__ */ jsxDEV24("box", {
|
|
6037
6241
|
style: { flexDirection: "row", marginTop: idx === 0 ? 1 : 0 },
|
|
6242
|
+
onMouse: handleMentionRowClick(mention.username),
|
|
6038
6243
|
children: [
|
|
6039
6244
|
/* @__PURE__ */ jsxDEV24("text", {
|
|
6040
6245
|
fg: isSelected ? colors.mention : colors.muted,
|
|
@@ -6057,6 +6262,12 @@ function PostDetailScreen({
|
|
|
6057
6262
|
]
|
|
6058
6263
|
}, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV24("box", {
|
|
6059
6264
|
style: { flexDirection: "row" },
|
|
6265
|
+
onMouse: (event) => {
|
|
6266
|
+
if (event.type === "up" && event.button === 0) {
|
|
6267
|
+
setMentionsMode(true);
|
|
6268
|
+
setMentionIndex(0);
|
|
6269
|
+
}
|
|
6270
|
+
},
|
|
6060
6271
|
children: [
|
|
6061
6272
|
/* @__PURE__ */ jsxDEV24("text", {
|
|
6062
6273
|
fg: colors.dim,
|
|
@@ -6083,7 +6294,7 @@ function PostDetailScreen({
|
|
|
6083
6294
|
}, undefined, false, undefined, this),
|
|
6084
6295
|
/* @__PURE__ */ jsxDEV24("text", {
|
|
6085
6296
|
fg: colors.dim,
|
|
6086
|
-
children: "
|
|
6297
|
+
children: "/click)"
|
|
6087
6298
|
}, undefined, false, undefined, this)
|
|
6088
6299
|
]
|
|
6089
6300
|
}, undefined, true, undefined, this)
|
|
@@ -6187,13 +6398,13 @@ function PostDetailScreen({
|
|
|
6187
6398
|
{
|
|
6188
6399
|
key: "l",
|
|
6189
6400
|
label: isLiked ? HEART_FILLED2 : HEART_EMPTY2,
|
|
6190
|
-
activeColor: isJustLiked ? colors.success : isLiked ? colors.
|
|
6401
|
+
activeColor: isJustLiked ? colors.success : isLiked ? colors.liked : undefined,
|
|
6191
6402
|
isActive: isLiked || isJustLiked
|
|
6192
6403
|
},
|
|
6193
6404
|
{
|
|
6194
6405
|
key: "b",
|
|
6195
6406
|
label: isBookmarked ? FLAG_FILLED2 : FLAG_EMPTY2,
|
|
6196
|
-
activeColor: isJustBookmarked ? colors.success : isBookmarked ? colors.
|
|
6407
|
+
activeColor: isJustBookmarked ? colors.success : isBookmarked ? colors.bookmarked : undefined,
|
|
6197
6408
|
isActive: isBookmarked || isJustBookmarked
|
|
6198
6409
|
},
|
|
6199
6410
|
{ key: "p", label: "profile" },
|
|
@@ -6223,7 +6434,7 @@ function PostDetailScreen({
|
|
|
6223
6434
|
activeColor: isLoadingQuote ? colors.primary : undefined,
|
|
6224
6435
|
show: hasQuote
|
|
6225
6436
|
},
|
|
6226
|
-
{ key: "
|
|
6437
|
+
{ key: "f", label: "folder", show: isBookmarked }
|
|
6227
6438
|
];
|
|
6228
6439
|
return /* @__PURE__ */ jsxDEV24("box", {
|
|
6229
6440
|
style: { flexDirection: "column", height: "100%" },
|
|
@@ -6257,91 +6468,7 @@ function PostDetailScreen({
|
|
|
6257
6468
|
|
|
6258
6469
|
// src/screens/ProfileScreen.tsx
|
|
6259
6470
|
import { useKeyboard as useKeyboard14 } from "@opentui/react";
|
|
6260
|
-
import { useState as useState21, useCallback as
|
|
6261
|
-
|
|
6262
|
-
// src/hooks/useUserProfile.ts
|
|
6263
|
-
import { useState as useState20, useEffect as useEffect18, useCallback as useCallback14, useRef as useRef11 } from "react";
|
|
6264
|
-
function useUserProfile({
|
|
6265
|
-
client,
|
|
6266
|
-
username,
|
|
6267
|
-
isSelf = false
|
|
6268
|
-
}) {
|
|
6269
|
-
const [user, setUser] = useState20(null);
|
|
6270
|
-
const [tweets, setTweets] = useState20([]);
|
|
6271
|
-
const [loading, setLoading] = useState20(true);
|
|
6272
|
-
const [error, setError] = useState20(null);
|
|
6273
|
-
const [refreshCounter, setRefreshCounter] = useState20(0);
|
|
6274
|
-
const [likedTweets, setLikedTweets] = useState20([]);
|
|
6275
|
-
const [likesLoading, setLikesLoading] = useState20(false);
|
|
6276
|
-
const [likesError, setLikesError] = useState20(null);
|
|
6277
|
-
const [likesFetched, setLikesFetched] = useState20(false);
|
|
6278
|
-
const likesFetchInProgress = useRef11(false);
|
|
6279
|
-
const fetchProfile = useCallback14(async () => {
|
|
6280
|
-
setLoading(true);
|
|
6281
|
-
setError(null);
|
|
6282
|
-
const profileResult = await client.getUserByScreenName(username);
|
|
6283
|
-
if (!profileResult.success) {
|
|
6284
|
-
setError(profileResult.error ?? "Failed to load profile");
|
|
6285
|
-
setLoading(false);
|
|
6286
|
-
return;
|
|
6287
|
-
}
|
|
6288
|
-
const userProfile = profileResult.user;
|
|
6289
|
-
if (!userProfile) {
|
|
6290
|
-
setError("User not found");
|
|
6291
|
-
setLoading(false);
|
|
6292
|
-
return;
|
|
6293
|
-
}
|
|
6294
|
-
setUser(userProfile);
|
|
6295
|
-
const tweetsResult = await client.getUserTweets(userProfile.id, 20);
|
|
6296
|
-
if (tweetsResult.success) {
|
|
6297
|
-
setTweets(tweetsResult.tweets ?? []);
|
|
6298
|
-
} else {
|
|
6299
|
-
setTweets([]);
|
|
6300
|
-
}
|
|
6301
|
-
setLoading(false);
|
|
6302
|
-
}, [client, username]);
|
|
6303
|
-
const fetchLikes = useCallback14(async () => {
|
|
6304
|
-
if (!isSelf || likesFetchInProgress.current)
|
|
6305
|
-
return;
|
|
6306
|
-
likesFetchInProgress.current = true;
|
|
6307
|
-
setLikesLoading(true);
|
|
6308
|
-
setLikesError(null);
|
|
6309
|
-
const likesResult = await client.getLikes(20);
|
|
6310
|
-
if (likesResult.success) {
|
|
6311
|
-
setLikedTweets(likesResult.tweets ?? []);
|
|
6312
|
-
setLikesFetched(true);
|
|
6313
|
-
} else {
|
|
6314
|
-
setLikesError(likesResult.error ?? "Failed to load likes");
|
|
6315
|
-
}
|
|
6316
|
-
setLikesLoading(false);
|
|
6317
|
-
likesFetchInProgress.current = false;
|
|
6318
|
-
}, [client, isSelf]);
|
|
6319
|
-
useEffect18(() => {
|
|
6320
|
-
setUser(null);
|
|
6321
|
-
setTweets([]);
|
|
6322
|
-
setLikedTweets([]);
|
|
6323
|
-
setLikesFetched(false);
|
|
6324
|
-
setLikesError(null);
|
|
6325
|
-
fetchProfile();
|
|
6326
|
-
}, [fetchProfile, refreshCounter]);
|
|
6327
|
-
const refresh = useCallback14(() => {
|
|
6328
|
-
setRefreshCounter((prev) => prev + 1);
|
|
6329
|
-
}, []);
|
|
6330
|
-
return {
|
|
6331
|
-
user,
|
|
6332
|
-
tweets,
|
|
6333
|
-
loading,
|
|
6334
|
-
error,
|
|
6335
|
-
refresh,
|
|
6336
|
-
likedTweets,
|
|
6337
|
-
likesLoading,
|
|
6338
|
-
likesError,
|
|
6339
|
-
fetchLikes,
|
|
6340
|
-
likesFetched
|
|
6341
|
-
};
|
|
6342
|
-
}
|
|
6343
|
-
|
|
6344
|
-
// src/screens/ProfileScreen.tsx
|
|
6471
|
+
import { useState as useState21, useCallback as useCallback14, useEffect as useEffect18 } from "react";
|
|
6345
6472
|
init_media();
|
|
6346
6473
|
import { jsxDEV as jsxDEV25, Fragment as Fragment2 } from "@opentui/react/jsx-dev-runtime";
|
|
6347
6474
|
function formatJoinDate(createdAt) {
|
|
@@ -6386,15 +6513,15 @@ function ProfileScreen({
|
|
|
6386
6513
|
const {
|
|
6387
6514
|
user,
|
|
6388
6515
|
tweets,
|
|
6389
|
-
|
|
6516
|
+
isLoading,
|
|
6390
6517
|
error,
|
|
6391
6518
|
refresh,
|
|
6392
6519
|
likedTweets,
|
|
6393
|
-
|
|
6520
|
+
isLikesLoading,
|
|
6394
6521
|
likesError,
|
|
6395
6522
|
fetchLikes,
|
|
6396
6523
|
likesFetched
|
|
6397
|
-
} =
|
|
6524
|
+
} = useProfileQuery({
|
|
6398
6525
|
client,
|
|
6399
6526
|
username,
|
|
6400
6527
|
isSelf
|
|
@@ -6405,7 +6532,7 @@ function ProfileScreen({
|
|
|
6405
6532
|
});
|
|
6406
6533
|
const [isFollowing, setIsFollowing] = useState21(false);
|
|
6407
6534
|
const [isMuting, setIsMuting] = useState21(false);
|
|
6408
|
-
|
|
6535
|
+
useEffect18(() => {
|
|
6409
6536
|
if (user) {
|
|
6410
6537
|
setIsFollowing(user.following ?? false);
|
|
6411
6538
|
setIsMuting(user.muting ?? false);
|
|
@@ -6415,16 +6542,16 @@ function ProfileScreen({
|
|
|
6415
6542
|
const [isCollapsed, setIsCollapsed] = useState21(false);
|
|
6416
6543
|
const [mentionsMode, setMentionsMode] = useState21(false);
|
|
6417
6544
|
const [mentionIndex, setMentionIndex] = useState21(0);
|
|
6418
|
-
|
|
6419
|
-
if (isSelf && activeTab === "likes" && !likesFetched && !
|
|
6545
|
+
useEffect18(() => {
|
|
6546
|
+
if (isSelf && activeTab === "likes" && !likesFetched && !isLikesLoading) {
|
|
6420
6547
|
fetchLikes();
|
|
6421
6548
|
}
|
|
6422
|
-
}, [isSelf, activeTab, likesFetched,
|
|
6549
|
+
}, [isSelf, activeTab, likesFetched, isLikesLoading, fetchLikes]);
|
|
6423
6550
|
const bioMentions = user?.description ? extractMentions(user.description) : [];
|
|
6424
6551
|
const hasMentions = bioMentions.length > 0;
|
|
6425
6552
|
const mentionCount = bioMentions.length;
|
|
6426
6553
|
const currentMention = hasMentions ? bioMentions[mentionIndex] : undefined;
|
|
6427
|
-
const handleSelectedIndexChange =
|
|
6554
|
+
const handleSelectedIndexChange = useCallback14((index) => {
|
|
6428
6555
|
setIsCollapsed(index > 0);
|
|
6429
6556
|
}, []);
|
|
6430
6557
|
useKeyboard14((key) => {
|
|
@@ -6508,6 +6635,12 @@ function ProfileScreen({
|
|
|
6508
6635
|
setIsCollapsed(false);
|
|
6509
6636
|
}
|
|
6510
6637
|
break;
|
|
6638
|
+
case "tab":
|
|
6639
|
+
if (isSelf) {
|
|
6640
|
+
setActiveTab((prev) => prev === "tweets" ? "likes" : "tweets");
|
|
6641
|
+
setIsCollapsed(false);
|
|
6642
|
+
}
|
|
6643
|
+
break;
|
|
6511
6644
|
case "f":
|
|
6512
6645
|
if (!isSelf && user) {
|
|
6513
6646
|
const newFollowing = !isFollowing;
|
|
@@ -6897,7 +7030,7 @@ function ProfileScreen({
|
|
|
6897
7030
|
children: "[2] Likes"
|
|
6898
7031
|
}, undefined, false, undefined, this) : " 2 Likes"
|
|
6899
7032
|
}, undefined, false, undefined, this),
|
|
6900
|
-
activeTab === "likes" &&
|
|
7033
|
+
activeTab === "likes" && isLikesLoading && /* @__PURE__ */ jsxDEV25("text", {
|
|
6901
7034
|
fg: colors.muted,
|
|
6902
7035
|
children: " (loading...)"
|
|
6903
7036
|
}, undefined, false, undefined, this)
|
|
@@ -6946,7 +7079,7 @@ function ProfileScreen({
|
|
|
6946
7079
|
{ key: "x", label: "x.com" },
|
|
6947
7080
|
{ key: "r", label: "refresh" }
|
|
6948
7081
|
];
|
|
6949
|
-
if (
|
|
7082
|
+
if (isLoading) {
|
|
6950
7083
|
return /* @__PURE__ */ jsxDEV25("box", {
|
|
6951
7084
|
style: { flexDirection: "column", height: "100%" },
|
|
6952
7085
|
children: /* @__PURE__ */ jsxDEV25("box", {
|
|
@@ -7044,7 +7177,7 @@ function ProfileScreen({
|
|
|
7044
7177
|
}
|
|
7045
7178
|
|
|
7046
7179
|
// src/screens/SplashScreen.tsx
|
|
7047
|
-
import { useEffect as
|
|
7180
|
+
import { useEffect as useEffect19, useState as useState22 } from "react";
|
|
7048
7181
|
import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
|
|
7049
7182
|
var LOGO = [
|
|
7050
7183
|
" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
@@ -7058,7 +7191,7 @@ var SPINNER_FRAMES = ["\u25E2", "\u25E3", "\u25E4", "\u25E5"];
|
|
|
7058
7191
|
var SPINNER_INTERVAL_MS = 100;
|
|
7059
7192
|
function SplashScreen() {
|
|
7060
7193
|
const [spinnerIndex, setSpinnerIndex] = useState22(0);
|
|
7061
|
-
|
|
7194
|
+
useEffect19(() => {
|
|
7062
7195
|
const interval = setInterval(() => {
|
|
7063
7196
|
setSpinnerIndex((prev) => (prev + 1) % SPINNER_FRAMES.length);
|
|
7064
7197
|
}, SPINNER_INTERVAL_MS);
|
|
@@ -7194,7 +7327,7 @@ function App({ client, user }) {
|
|
|
7194
7327
|
}
|
|
7195
7328
|
function AppContent({ client, user }) {
|
|
7196
7329
|
const renderer = useRenderer2();
|
|
7197
|
-
const { currentView, navigate, goBack,
|
|
7330
|
+
const { currentView, navigate, goBack, isMainView } = useNavigation({
|
|
7198
7331
|
initialView: "timeline",
|
|
7199
7332
|
mainViews: MAIN_VIEWS
|
|
7200
7333
|
});
|
|
@@ -7206,7 +7339,7 @@ function AppContent({ client, user }) {
|
|
|
7206
7339
|
const [actionMessage, setActionMessage] = useState23(null);
|
|
7207
7340
|
const [showFooter, setShowFooter] = useState23(true);
|
|
7208
7341
|
const { openModal, closeModal, isModalOpen } = useModal();
|
|
7209
|
-
|
|
7342
|
+
useEffect20(() => {
|
|
7210
7343
|
client.setOnSessionExpired(() => {
|
|
7211
7344
|
openModal("session-expired", {});
|
|
7212
7345
|
});
|
|
@@ -7224,7 +7357,7 @@ function AppContent({ client, user }) {
|
|
|
7224
7357
|
bookmarkMutation,
|
|
7225
7358
|
currentFolderId: selectedBookmarkFolder?.id
|
|
7226
7359
|
});
|
|
7227
|
-
|
|
7360
|
+
useEffect20(() => {
|
|
7228
7361
|
if (actionMessage) {
|
|
7229
7362
|
const timer = setTimeout(() => setActionMessage(null), 3000);
|
|
7230
7363
|
return () => clearTimeout(timer);
|
|
@@ -7233,21 +7366,21 @@ function AppContent({ client, user }) {
|
|
|
7233
7366
|
const [showSplash, setShowSplash] = useState23(true);
|
|
7234
7367
|
const [minTimeElapsed, setMinTimeElapsed] = useState23(false);
|
|
7235
7368
|
const initialLoadComplete = useRef12(false);
|
|
7236
|
-
|
|
7369
|
+
useEffect20(() => {
|
|
7237
7370
|
const timer = setTimeout(() => {
|
|
7238
7371
|
setMinTimeElapsed(true);
|
|
7239
7372
|
}, SPLASH_MIN_DISPLAY_MS);
|
|
7240
7373
|
return () => clearTimeout(timer);
|
|
7241
7374
|
}, []);
|
|
7242
|
-
|
|
7375
|
+
useEffect20(() => {
|
|
7243
7376
|
if (minTimeElapsed && initialLoadComplete.current) {
|
|
7244
7377
|
setShowSplash(false);
|
|
7245
7378
|
}
|
|
7246
7379
|
}, [minTimeElapsed]);
|
|
7247
|
-
const handlePostCountChange =
|
|
7380
|
+
const handlePostCountChange = useCallback15((count) => {
|
|
7248
7381
|
setPostCount(count);
|
|
7249
7382
|
}, []);
|
|
7250
|
-
const handleInitialLoadComplete =
|
|
7383
|
+
const handleInitialLoadComplete = useCallback15(() => {
|
|
7251
7384
|
if (!initialLoadComplete.current) {
|
|
7252
7385
|
initialLoadComplete.current = true;
|
|
7253
7386
|
if (minTimeElapsed) {
|
|
@@ -7255,16 +7388,16 @@ function AppContent({ client, user }) {
|
|
|
7255
7388
|
}
|
|
7256
7389
|
}
|
|
7257
7390
|
}, [minTimeElapsed]);
|
|
7258
|
-
const handleBookmarkCountChange =
|
|
7391
|
+
const handleBookmarkCountChange = useCallback15((count) => {
|
|
7259
7392
|
setBookmarkCount(count);
|
|
7260
7393
|
}, []);
|
|
7261
|
-
const handleBookmarkHasMoreChange =
|
|
7394
|
+
const handleBookmarkHasMoreChange = useCallback15((hasMore) => {
|
|
7262
7395
|
setBookmarkHasMore(hasMore);
|
|
7263
7396
|
}, []);
|
|
7264
|
-
const handleNotificationCountChange =
|
|
7397
|
+
const handleNotificationCountChange = useCallback15((count) => {
|
|
7265
7398
|
setNotificationCount(count);
|
|
7266
7399
|
}, []);
|
|
7267
|
-
const handleUnreadCountChange =
|
|
7400
|
+
const handleUnreadCountChange = useCallback15((count) => {
|
|
7268
7401
|
setUnreadNotificationCount(count);
|
|
7269
7402
|
}, []);
|
|
7270
7403
|
const [postStack, setPostStack] = useState23([]);
|
|
@@ -7272,17 +7405,17 @@ function AppContent({ client, user }) {
|
|
|
7272
7405
|
const [isLoadingQuote, setIsLoadingQuote] = useState23(false);
|
|
7273
7406
|
const [isLoadingParent, setIsLoadingParent] = useState23(false);
|
|
7274
7407
|
const [threadRootTweet, setThreadRootTweet] = useState23(null);
|
|
7275
|
-
const handlePostSelect =
|
|
7408
|
+
const handlePostSelect = useCallback15((post) => {
|
|
7276
7409
|
setPostStack((prev) => [...prev, post]);
|
|
7277
7410
|
initState(post.id, post.favorited ?? false, post.bookmarked ?? false);
|
|
7278
7411
|
navigate("post-detail");
|
|
7279
7412
|
}, [navigate, initState]);
|
|
7280
|
-
const handlePostSelectFromThread =
|
|
7413
|
+
const handlePostSelectFromThread = useCallback15((post) => {
|
|
7281
7414
|
setPostStack((prev) => [...prev, post]);
|
|
7282
7415
|
initState(post.id, post.favorited ?? false, post.bookmarked ?? false);
|
|
7283
7416
|
navigate("post-detail");
|
|
7284
7417
|
}, [navigate, initState]);
|
|
7285
|
-
const handleQuoteSelect =
|
|
7418
|
+
const handleQuoteSelect = useCallback15(async (quotedTweet) => {
|
|
7286
7419
|
if (isLoadingQuote)
|
|
7287
7420
|
return;
|
|
7288
7421
|
if (postStack.some((p) => p.id === quotedTweet.id)) {
|
|
@@ -7303,7 +7436,7 @@ function AppContent({ client, user }) {
|
|
|
7303
7436
|
setIsLoadingQuote(false);
|
|
7304
7437
|
}
|
|
7305
7438
|
}, [client, initState, isLoadingQuote, navigate, postStack]);
|
|
7306
|
-
const handleParentSelect =
|
|
7439
|
+
const handleParentSelect = useCallback15(async (parentTweet) => {
|
|
7307
7440
|
if (isLoadingParent)
|
|
7308
7441
|
return;
|
|
7309
7442
|
const parentIndex = postStack.findIndex((p) => p.id === parentTweet.id);
|
|
@@ -7329,36 +7462,36 @@ function AppContent({ client, user }) {
|
|
|
7329
7462
|
setIsLoadingParent(false);
|
|
7330
7463
|
}
|
|
7331
7464
|
}, [client, goBack, initState, isLoadingParent, navigate, postStack]);
|
|
7332
|
-
const handleBackFromDetail =
|
|
7465
|
+
const handleBackFromDetail = useCallback15(() => {
|
|
7333
7466
|
goBack();
|
|
7334
7467
|
setPostStack((prev) => prev.slice(0, -1));
|
|
7335
7468
|
}, [goBack]);
|
|
7336
7469
|
const [profileStack, setProfileStack] = useState23([]);
|
|
7337
7470
|
const profileUsername = profileStack[profileStack.length - 1] ?? null;
|
|
7338
|
-
const handleProfileOpen =
|
|
7471
|
+
const handleProfileOpen = useCallback15((username) => {
|
|
7339
7472
|
setProfileStack((prev) => [...prev, username]);
|
|
7340
7473
|
navigate("profile");
|
|
7341
7474
|
}, [navigate]);
|
|
7342
|
-
const handleThreadView =
|
|
7475
|
+
const handleThreadView = useCallback15(() => {
|
|
7343
7476
|
if (selectedPost) {
|
|
7344
7477
|
setThreadRootTweet(selectedPost);
|
|
7345
7478
|
navigate("thread");
|
|
7346
7479
|
}
|
|
7347
7480
|
}, [navigate, selectedPost]);
|
|
7348
|
-
const handleBackFromThread =
|
|
7481
|
+
const handleBackFromThread = useCallback15(() => {
|
|
7349
7482
|
goBack();
|
|
7350
7483
|
setThreadRootTweet(null);
|
|
7351
7484
|
}, [goBack]);
|
|
7352
|
-
const handleBackFromProfile =
|
|
7485
|
+
const handleBackFromProfile = useCallback15(() => {
|
|
7353
7486
|
goBack();
|
|
7354
7487
|
setProfileStack((prev) => prev.slice(0, -1));
|
|
7355
7488
|
}, [goBack]);
|
|
7356
|
-
const handlePostSelectFromProfile =
|
|
7489
|
+
const handlePostSelectFromProfile = useCallback15((post) => {
|
|
7357
7490
|
setPostStack((prev) => [...prev, post]);
|
|
7358
7491
|
initState(post.id, post.favorited ?? false, post.bookmarked ?? false);
|
|
7359
7492
|
navigate("post-detail");
|
|
7360
7493
|
}, [navigate, initState]);
|
|
7361
|
-
const handleMoveToFolder =
|
|
7494
|
+
const handleMoveToFolder = useCallback15(() => {
|
|
7362
7495
|
if (!selectedPost)
|
|
7363
7496
|
return;
|
|
7364
7497
|
openModal("folder-picker", {
|
|
@@ -7376,7 +7509,7 @@ function AppContent({ client, user }) {
|
|
|
7376
7509
|
onClose: closeModal
|
|
7377
7510
|
});
|
|
7378
7511
|
}, [client, selectedPost, openModal, closeModal]);
|
|
7379
|
-
const handleBookmarkFolderSelectorOpen =
|
|
7512
|
+
const handleBookmarkFolderSelectorOpen = useCallback15(() => {
|
|
7380
7513
|
openModal("bookmark-folder-selector", {
|
|
7381
7514
|
client,
|
|
7382
7515
|
currentFolder: selectedBookmarkFolder,
|
|
@@ -7387,7 +7520,7 @@ function AppContent({ client, user }) {
|
|
|
7387
7520
|
onClose: closeModal
|
|
7388
7521
|
});
|
|
7389
7522
|
}, [client, selectedBookmarkFolder, openModal, closeModal]);
|
|
7390
|
-
const handleCreateBookmarkFolder =
|
|
7523
|
+
const handleCreateBookmarkFolder = useCallback15(() => {
|
|
7391
7524
|
openModal("folder-name-input", {
|
|
7392
7525
|
mode: "create",
|
|
7393
7526
|
onSubmit: async (name) => {
|
|
@@ -7402,7 +7535,7 @@ function AppContent({ client, user }) {
|
|
|
7402
7535
|
onClose: closeModal
|
|
7403
7536
|
});
|
|
7404
7537
|
}, [client, openModal, closeModal]);
|
|
7405
|
-
const handleEditBookmarkFolder =
|
|
7538
|
+
const handleEditBookmarkFolder = useCallback15(() => {
|
|
7406
7539
|
if (!selectedBookmarkFolder)
|
|
7407
7540
|
return;
|
|
7408
7541
|
openModal("folder-name-input", {
|
|
@@ -7421,7 +7554,7 @@ function AppContent({ client, user }) {
|
|
|
7421
7554
|
onClose: closeModal
|
|
7422
7555
|
});
|
|
7423
7556
|
}, [client, selectedBookmarkFolder, openModal, closeModal]);
|
|
7424
|
-
const handleDeleteBookmarkFolder =
|
|
7557
|
+
const handleDeleteBookmarkFolder = useCallback15(() => {
|
|
7425
7558
|
if (!selectedBookmarkFolder)
|
|
7426
7559
|
return;
|
|
7427
7560
|
openModal("delete-folder-confirm", {
|
|
@@ -7439,7 +7572,7 @@ function AppContent({ client, user }) {
|
|
|
7439
7572
|
onCancel: closeModal
|
|
7440
7573
|
});
|
|
7441
7574
|
}, [client, selectedBookmarkFolder, openModal, closeModal]);
|
|
7442
|
-
const handleNotificationSelect =
|
|
7575
|
+
const handleNotificationSelect = useCallback15((notification) => {
|
|
7443
7576
|
if (notification.targetTweet) {
|
|
7444
7577
|
setPostStack((prev) => [...prev, notification.targetTweet]);
|
|
7445
7578
|
initState(notification.targetTweet.id, notification.targetTweet.favorited ?? false, notification.targetTweet.bookmarked ?? false);
|
|
@@ -7526,11 +7659,32 @@ function AppContent({ client, user }) {
|
|
|
7526
7659
|
setShowFooter((prev) => !prev);
|
|
7527
7660
|
return;
|
|
7528
7661
|
}
|
|
7529
|
-
if (showSplash
|
|
7662
|
+
if (showSplash) {
|
|
7663
|
+
return;
|
|
7664
|
+
}
|
|
7665
|
+
if (key.name === "1") {
|
|
7666
|
+
setPostStack([]);
|
|
7667
|
+
setProfileStack([]);
|
|
7668
|
+
setThreadRootTweet(null);
|
|
7669
|
+
navigate("timeline");
|
|
7670
|
+
return;
|
|
7671
|
+
}
|
|
7672
|
+
if (key.name === "2") {
|
|
7673
|
+
setPostStack([]);
|
|
7674
|
+
setProfileStack([]);
|
|
7675
|
+
setThreadRootTweet(null);
|
|
7676
|
+
navigate("bookmarks");
|
|
7530
7677
|
return;
|
|
7531
7678
|
}
|
|
7532
|
-
if (key.name === "
|
|
7533
|
-
|
|
7679
|
+
if (key.name === "3") {
|
|
7680
|
+
setPostStack([]);
|
|
7681
|
+
setProfileStack([]);
|
|
7682
|
+
setThreadRootTweet(null);
|
|
7683
|
+
navigate("notifications");
|
|
7684
|
+
return;
|
|
7685
|
+
}
|
|
7686
|
+
if (!isMainView) {
|
|
7687
|
+
return;
|
|
7534
7688
|
}
|
|
7535
7689
|
if (key.name === "n") {
|
|
7536
7690
|
navigate("notifications");
|
|
@@ -9002,7 +9156,8 @@ ${body}`;
|
|
|
9002
9156
|
freedom_of_speech_not_reach_fetch_enabled: true,
|
|
9003
9157
|
standardized_nudges_misinfo: true,
|
|
9004
9158
|
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
|
|
9005
|
-
rweb_video_timestamps_enabled: true
|
|
9159
|
+
rweb_video_timestamps_enabled: true,
|
|
9160
|
+
responsive_web_grok_annotations_enabled: false
|
|
9006
9161
|
};
|
|
9007
9162
|
const params = new URLSearchParams({
|
|
9008
9163
|
variables: JSON.stringify(variables),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xfeed",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Terminal-based X/Twitter viewer with vim-style navigation. Install with: bun install -g xfeed",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"prepare": "lefthook install"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@opentui/core": "^0.1.
|
|
22
|
-
"@opentui/react": "^0.1.
|
|
21
|
+
"@opentui/core": "^0.1.69",
|
|
22
|
+
"@opentui/react": "^0.1.69",
|
|
23
23
|
"@steipete/sweet-cookie": "file:./vendor/sweet-cookie",
|
|
24
24
|
"@tanstack/react-query": "^5.90.16",
|
|
25
25
|
"@tanstack/react-router": "^1.144.0",
|