Deduplicate buildKindOptions, AVAILABLE_FONTS, and sidebar editing logic into shared modules

This commit is contained in:
Lemon
2026-04-11 22:57:24 -07:00
parent 645560077f
commit 2558743dae
7 changed files with 68 additions and 93 deletions
+6 -22
View File
@@ -1,4 +1,4 @@
import { useState, useCallback, useMemo } from 'react';
import { useState, useCallback } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import {
@@ -12,7 +12,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { DittoLogo } from '@/components/DittoLogo';
import { EmojifiedText } from '@/components/CustomEmoji';
import { ProfileSearchDropdown } from '@/components/ProfileSearchDropdown';
import { SidebarNavList, MORE_SEPARATOR_ID } from '@/components/SidebarNavItem';
import { SidebarNavList } from '@/components/SidebarNavItem';
import { SidebarMoreMenu } from '@/components/SidebarMoreMenu';
import LoginDialog from '@/components/auth/LoginDialog';
@@ -23,6 +23,7 @@ import { useLoggedInAccounts, type Account } from '@/hooks/useLoggedInAccounts';
import { useLoginActions } from '@/hooks/useLoginActions';
import { useFeedSettings } from '@/hooks/useFeedSettings';
import { useSidebarEditing } from '@/hooks/useSidebarEditing';
import { useAppContext } from '@/hooks/useAppContext';
import { useHasUnreadNotifications } from '@/hooks/useHasUnreadNotifications';
import { genUserName } from '@/lib/genUserName';
@@ -68,26 +69,9 @@ export function LeftSidebar() {
const homePage = config.homePage;
// In editing mode, build a combined list: visible + __more__ + hidden
const editingItems = useMemo(() => {
if (!editing) return [];
return [...orderedItems, MORE_SEPARATOR_ID, ...hiddenItems.map((h) => h.id)];
}, [editing, orderedItems, hiddenItems]);
const handleEditReorder = useCallback((newOrder: string[]) => {
const moreIdx = newOrder.indexOf(MORE_SEPARATOR_ID);
if (moreIdx === -1) return;
const newVisible = newOrder.slice(0, moreIdx);
updateSidebarOrder(newVisible);
}, [updateSidebarOrder]);
const handleEditRemove = useCallback((id: string, index?: number) => {
if (id === 'divider' && index !== undefined) {
removeFromSidebar(id, index);
} else {
removeFromSidebar(id);
}
}, [removeFromSidebar]);
const { editingItems, handleEditReorder, handleEditRemove } = useSidebarEditing({
editing, items: orderedItems, hiddenItems, updateSidebarOrder, removeFromSidebar,
});
const scrollToTopIfCurrent = useCallback((to: string) => (e: React.MouseEvent) => {
if (location.pathname === to) {
+6 -22
View File
@@ -1,10 +1,11 @@
import { useState, useId, useMemo, useCallback } from 'react';
import { useState, useId, useMemo } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { ChevronDown, ChevronUp, LogOut, UserPlus, Loader2, QrCode } from 'lucide-react';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { getAvatarShape } from '@/lib/avatarShape';
import { Sheet, SheetContent, SheetTitle } from '@/components/ui/sheet';
import { SidebarNavList, MORE_SEPARATOR_ID } from '@/components/SidebarNavItem';
import { SidebarNavList } from '@/components/SidebarNavItem';
import { useSidebarEditing } from '@/hooks/useSidebarEditing';
import { SidebarMoreMenu } from '@/components/SidebarMoreMenu';
import { LoginArea } from '@/components/auth/LoginArea';
@@ -98,26 +99,9 @@ export function MobileDrawer({ open, onOpenChange }: MobileDrawerProps) {
// In editing mode, build a combined list: visible + __more__ + hidden
const editingItems = useMemo(() => {
if (!editing) return [];
return [...visibleItems, MORE_SEPARATOR_ID, ...hiddenItems.map((h) => h.id)];
}, [editing, visibleItems, hiddenItems]);
const handleEditReorder = useCallback((newOrder: string[]) => {
const moreIdx = newOrder.indexOf(MORE_SEPARATOR_ID);
if (moreIdx === -1) return;
const newVisible = newOrder.slice(0, moreIdx);
updateSidebarOrder(newVisible);
}, [updateSidebarOrder]);
const handleEditRemove = useCallback((id: string, index?: number) => {
if (id === 'divider' && index !== undefined) {
removeFromSidebar(id, index);
} else {
removeFromSidebar(id);
}
}, [removeFromSidebar]);
const { editingItems, handleEditReorder, handleEditRemove } = useSidebarEditing({
editing, items: visibleItems, hiddenItems, updateSidebarOrder, removeFromSidebar,
});
const handleClose = () => { onOpenChange(false); };
const handleLogout = async () => { await logout(); handleClose(); navigate('/'); };
+4 -44
View File
@@ -14,56 +14,16 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrig
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { ProfileSearchDropdown } from '@/components/ProfileSearchDropdown';
import { useAuthor } from '@/hooks/useAuthor';
import { EXTRA_KINDS } from '@/lib/extraKinds';
import { CONTENT_KIND_ICONS } from '@/lib/sidebarItems';
import { cn } from '@/lib/utils';
import type { SearchProfile } from '@/hooks/useSearchProfiles';
import type { UserList } from '@/hooks/useUserLists';
import type { FollowPack } from '@/hooks/useFollowPacks';
import type { KindOption } from '@/lib/feedFilterUtils';
// ─── Types ───────────────────────────────────────────────────────────────────
type KindOption = {
value: string;
label: string;
description: string;
parentId: string;
icon: React.ComponentType<{ className?: string }> | undefined;
};
// ─── Kind options (built once) ───────────────────────────────────────────────
export function buildKindOptions(): KindOption[] {
const options: KindOption[] = [];
for (const def of EXTRA_KINDS) {
if (def.subKinds) {
for (const sub of def.subKinds) {
options.push({
value: String(sub.kind),
label: `${sub.label} (${sub.kind})`,
description: sub.description,
parentId: def.id,
icon: CONTENT_KIND_ICONS[def.id],
});
}
} else {
options.push({
value: String(def.kind),
label: `${def.label} (${def.kind})`,
description: def.description,
parentId: def.id,
icon: CONTENT_KIND_ICONS[def.id],
});
}
}
const seen = new Set<string>();
return options.filter((o) => {
if (seen.has(o.value)) return false;
seen.add(o.value);
return true;
});
}
// Re-export for consumers that were importing from this file
export { buildKindOptions } from '@/lib/feedFilterUtils';
export type { KindOption } from '@/lib/feedFilterUtils';
// ─── useScrollCarets ─────────────────────────────────────────────────────────
+1 -1
View File
@@ -1,7 +1,7 @@
import type { NostrEvent } from '@nostrify/nostrify';
import { Badge } from '@/components/ui/badge';
import { Clock, Globe, Image, Languages, MessageSquareOff, Radio, Search, SortDesc, Terminal, Users, Video, WandSparkles } from 'lucide-react';
import { buildKindOptions } from '@/components/SavedFeedFiltersEditor';
import { buildKindOptions } from '@/lib/feedFilterUtils';
/** Map from kind number string to friendly label like "Posts (1)". */
const KIND_LABEL_MAP: Map<string, string> = new Map(
+49
View File
@@ -0,0 +1,49 @@
import { useMemo, useCallback } from 'react';
import { MORE_SEPARATOR_ID } from '@/components/SidebarNavItem';
import type { HiddenSidebarItem } from '@/hooks/useFeedSettings';
/**
* Shared sidebar editing logic used by both LeftSidebar and MobileDrawer.
*
* Builds the combined editing list (visible + __more__ separator + hidden)
* and provides handlers for reordering and removing items.
*/
export function useSidebarEditing({
editing,
items,
hiddenItems,
updateSidebarOrder,
removeFromSidebar,
}: {
editing: boolean;
/** Visible sidebar item IDs (may be pre-filtered, e.g. MobileDrawer strips leading dividers). */
items: string[];
hiddenItems: HiddenSidebarItem[];
updateSidebarOrder: (newOrder: string[]) => void;
removeFromSidebar: (id: string, index?: number) => void;
}) {
/** Combined list for the drag-and-drop editor: visible items + separator + hidden items. */
const editingItems = useMemo(() => {
if (!editing) return [];
return [...items, MORE_SEPARATOR_ID, ...hiddenItems.map((h) => h.id)];
}, [editing, items, hiddenItems]);
/** Handle drag-and-drop reorder — extract items above the __more__ separator. */
const handleEditReorder = useCallback((newOrder: string[]) => {
const moreIdx = newOrder.indexOf(MORE_SEPARATOR_ID);
if (moreIdx === -1) return;
const newVisible = newOrder.slice(0, moreIdx);
updateSidebarOrder(newVisible);
}, [updateSidebarOrder]);
/** Remove a sidebar item; dividers require an index to identify which one. */
const handleEditRemove = useCallback((id: string, index?: number) => {
if (id === 'divider' && index !== undefined) {
removeFromSidebar(id, index);
} else {
removeFromSidebar(id);
}
}, [removeFromSidebar]);
return { editingItems, handleEditReorder, handleEditRemove };
}
+1 -3
View File
@@ -1,8 +1,6 @@
import { bundledFonts } from '@/lib/fonts';
import { AVAILABLE_FONTS } from '@/lib/aiChatTools';
import type { ChatMessage } from '@/hooks/useShakespeare';
const AVAILABLE_FONTS = bundledFonts.map((f) => f.family).join(', ');
/** Minimal profile fields injected into the system prompt so the AI knows who it's talking to. */
export interface UserIdentity {
/** The user's npub (bech32 public key). */
+1 -1
View File
@@ -1,7 +1,7 @@
import { EXTRA_KINDS } from '@/lib/extraKinds';
import { CONTENT_KIND_ICONS } from '@/lib/sidebarItems';
type KindOption = {
export type KindOption = {
value: string;
label: string;
description: string;