mirror of
https://github.com/hoornet/vega.git
synced 2026-05-11 06:39:10 -07:00
Thread focus: auto-expand collapsed replies, debounced scroll, visible highlight; persist script filter
This commit is contained in:
@@ -55,7 +55,7 @@ export function NoteCard({ event, focused, onReplyInThread }: NoteCardProps) {
|
|||||||
<article
|
<article
|
||||||
ref={cardRef}
|
ref={cardRef}
|
||||||
data-note-id={event.id}
|
data-note-id={event.id}
|
||||||
className={`border-b border-border px-4 py-3 hover:bg-bg-hover transition-colors duration-100 cursor-pointer group/card${focused ? " ring-1 ring-inset ring-accent/30" : ""}`}
|
className={`border-b border-border px-4 py-3 hover:bg-bg-hover transition-colors duration-100 cursor-pointer group/card${focused ? " bg-accent/10 border-l-2 border-l-accent" : ""}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
// Don't navigate if clicking on interactive elements
|
// Don't navigate if clicking on interactive elements
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
|
|||||||
@@ -108,8 +108,19 @@ function InlineThreadReply({ replyTo, rootEvent, onPublished }: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Check if any descendant of a node has the given event ID. */
|
||||||
|
function subtreeContains(node: ThreadNodeType, id: string): boolean {
|
||||||
|
for (const child of node.children) {
|
||||||
|
if (child.event.id === id) return true;
|
||||||
|
if (subtreeContains(child, id)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function ThreadNodeComponent({ node, rootEvent, onReplyPublished, focusedId, mutedPubkeys, contentMatchesMutedKeyword }: ThreadNodeProps) {
|
export function ThreadNodeComponent({ node, rootEvent, onReplyPublished, focusedId, mutedPubkeys, contentMatchesMutedKeyword }: ThreadNodeProps) {
|
||||||
const [expanded, setExpanded] = useState(false);
|
// Auto-expand if the focused note is hidden inside a collapsed section
|
||||||
|
const focusedInChildren = focusedId ? subtreeContains(node, focusedId) : false;
|
||||||
|
const [expanded, setExpanded] = useState(focusedInChildren);
|
||||||
const [showReplyBox, setShowReplyBox] = useState(false);
|
const [showReplyBox, setShowReplyBox] = useState(false);
|
||||||
|
|
||||||
// Filter out muted children
|
// Filter out muted children
|
||||||
|
|||||||
@@ -108,16 +108,18 @@ export function ThreadView() {
|
|||||||
return () => { cancelled = true; };
|
return () => { cancelled = true; };
|
||||||
}, [focusedEvent.id, retryCount]);
|
}, [focusedEvent.id, retryCount]);
|
||||||
|
|
||||||
// Scroll to focused note after tree renders (if not root)
|
// Scroll to focused note after tree fully loads.
|
||||||
|
// Use a short delay after each tree update; the last one wins.
|
||||||
|
const scrollTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading && rootEvent && focusedEvent.id !== rootEvent.id) {
|
if (focusedEvent.id === rootEvent?.id) return;
|
||||||
const timer = setTimeout(() => {
|
clearTimeout(scrollTimer.current);
|
||||||
const el = document.querySelector(`[data-note-id="${focusedEvent.id}"]`);
|
scrollTimer.current = setTimeout(() => {
|
||||||
el?.scrollIntoView({ behavior: "smooth", block: "center" });
|
const el = document.querySelector(`[data-note-id="${focusedEvent.id}"]`);
|
||||||
}, 100);
|
el?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
return () => clearTimeout(timer);
|
}, 400);
|
||||||
}
|
return () => clearTimeout(scrollTimer.current);
|
||||||
}, [loading, rootEvent?.id, focusedEvent.id]);
|
}, [tree, focusedEvent.id, rootEvent?.id]);
|
||||||
|
|
||||||
// Called when any inline reply box publishes a reply
|
// Called when any inline reply box publishes a reply
|
||||||
const handleReplyPublished = (reply: NDKEvent) => {
|
const handleReplyPublished = (reply: NDKEvent) => {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ interface UIState {
|
|||||||
const SIDEBAR_KEY = "wrystr_sidebar_collapsed";
|
const SIDEBAR_KEY = "wrystr_sidebar_collapsed";
|
||||||
const FONT_SIZE_KEY = "wrystr_font_size";
|
const FONT_SIZE_KEY = "wrystr_font_size";
|
||||||
const THEME_KEY = "wrystr_theme";
|
const THEME_KEY = "wrystr_theme";
|
||||||
|
const SCRIPT_FILTER_KEY = "wrystr_script_filter";
|
||||||
|
|
||||||
export const useUIStore = create<UIState>((set, _get) => ({
|
export const useUIStore = create<UIState>((set, _get) => ({
|
||||||
currentView: "feed",
|
currentView: "feed",
|
||||||
@@ -69,7 +70,7 @@ export const useUIStore = create<UIState>((set, _get) => ({
|
|||||||
pendingHashtag: null,
|
pendingHashtag: null,
|
||||||
showHelp: false,
|
showHelp: false,
|
||||||
showDebugPanel: false,
|
showDebugPanel: false,
|
||||||
feedLanguageFilter: null,
|
feedLanguageFilter: localStorage.getItem(SCRIPT_FILTER_KEY) || null,
|
||||||
followsTab: "followers",
|
followsTab: "followers",
|
||||||
fontSize: parseInt(localStorage.getItem(FONT_SIZE_KEY) || "14", 10),
|
fontSize: parseInt(localStorage.getItem(FONT_SIZE_KEY) || "14", 10),
|
||||||
themeId: localStorage.getItem(THEME_KEY) || "midnight",
|
themeId: localStorage.getItem(THEME_KEY) || "midnight",
|
||||||
@@ -102,7 +103,14 @@ export const useUIStore = create<UIState>((set, _get) => ({
|
|||||||
}
|
}
|
||||||
return { showHelp: false, currentView: "feed", selectedNote: null, viewStack: [] };
|
return { showHelp: false, currentView: "feed", selectedNote: null, viewStack: [] };
|
||||||
}),
|
}),
|
||||||
setFeedLanguageFilter: (feedLanguageFilter) => set({ feedLanguageFilter }),
|
setFeedLanguageFilter: (feedLanguageFilter) => {
|
||||||
|
if (feedLanguageFilter) {
|
||||||
|
localStorage.setItem(SCRIPT_FILTER_KEY, feedLanguageFilter);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(SCRIPT_FILTER_KEY);
|
||||||
|
}
|
||||||
|
set({ feedLanguageFilter });
|
||||||
|
},
|
||||||
setFontSize: (fontSize) => {
|
setFontSize: (fontSize) => {
|
||||||
localStorage.setItem(FONT_SIZE_KEY, String(fontSize));
|
localStorage.setItem(FONT_SIZE_KEY, String(fontSize));
|
||||||
set({ fontSize });
|
set({ fontSize });
|
||||||
|
|||||||
Reference in New Issue
Block a user