Thread focus: auto-expand collapsed replies, debounced scroll, visible highlight; persist script filter

This commit is contained in:
Jure
2026-03-31 08:35:37 +02:00
parent c35e734310
commit 3d6ab39bfe
4 changed files with 34 additions and 13 deletions

View File

@@ -55,7 +55,7 @@ export function NoteCard({ event, focused, onReplyInThread }: NoteCardProps) {
<article
ref={cardRef}
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) => {
// Don't navigate if clicking on interactive elements
const target = e.target as HTMLElement;

View File

@@ -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) {
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);
// Filter out muted children

View File

@@ -108,16 +108,18 @@ export function ThreadView() {
return () => { cancelled = true; };
}, [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(() => {
if (!loading && rootEvent && focusedEvent.id !== rootEvent.id) {
const timer = setTimeout(() => {
const el = document.querySelector(`[data-note-id="${focusedEvent.id}"]`);
el?.scrollIntoView({ behavior: "smooth", block: "center" });
}, 100);
return () => clearTimeout(timer);
}
}, [loading, rootEvent?.id, focusedEvent.id]);
if (focusedEvent.id === rootEvent?.id) return;
clearTimeout(scrollTimer.current);
scrollTimer.current = setTimeout(() => {
const el = document.querySelector(`[data-note-id="${focusedEvent.id}"]`);
el?.scrollIntoView({ behavior: "smooth", block: "center" });
}, 400);
return () => clearTimeout(scrollTimer.current);
}, [tree, focusedEvent.id, rootEvent?.id]);
// Called when any inline reply box publishes a reply
const handleReplyPublished = (reply: NDKEvent) => {

View File

@@ -53,6 +53,7 @@ interface UIState {
const SIDEBAR_KEY = "wrystr_sidebar_collapsed";
const FONT_SIZE_KEY = "wrystr_font_size";
const THEME_KEY = "wrystr_theme";
const SCRIPT_FILTER_KEY = "wrystr_script_filter";
export const useUIStore = create<UIState>((set, _get) => ({
currentView: "feed",
@@ -69,7 +70,7 @@ export const useUIStore = create<UIState>((set, _get) => ({
pendingHashtag: null,
showHelp: false,
showDebugPanel: false,
feedLanguageFilter: null,
feedLanguageFilter: localStorage.getItem(SCRIPT_FILTER_KEY) || null,
followsTab: "followers",
fontSize: parseInt(localStorage.getItem(FONT_SIZE_KEY) || "14", 10),
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: [] };
}),
setFeedLanguageFilter: (feedLanguageFilter) => set({ feedLanguageFilter }),
setFeedLanguageFilter: (feedLanguageFilter) => {
if (feedLanguageFilter) {
localStorage.setItem(SCRIPT_FILTER_KEY, feedLanguageFilter);
} else {
localStorage.removeItem(SCRIPT_FILTER_KEY);
}
set({ feedLanguageFilter });
},
setFontSize: (fontSize) => {
localStorage.setItem(FONT_SIZE_KEY, String(fontSize));
set({ fontSize });