Deduplicate buildKindOptions, AVAILABLE_FONTS, and sidebar editing logic into shared modules
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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('/'); };
|
||||
|
||||
@@ -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,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(
|
||||
|
||||
@@ -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,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,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;
|
||||
|
||||
Reference in New Issue
Block a user