-
Notifications
You must be signed in to change notification settings - Fork 0
[v0.6.6] - cmdk 검색 UX 개선 및 인기순 정렬 로직 최적화 #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c15c56c
caf1791
c8f04a8
d491fba
3f014ba
ab64121
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,14 +13,27 @@ export default function SortSelectClient({ value }: SortSelectClientProps) { | |||||||||||||||
| const router = useRouter(); | ||||||||||||||||
| const searchParams = useSearchParams(); | ||||||||||||||||
|
|
||||||||||||||||
| const handleChange = (next: PostSortValue) => { | ||||||||||||||||
| const buildUrl = (next: PostSortValue) => { | ||||||||||||||||
| const params = new URLSearchParams(searchParams.toString()); | ||||||||||||||||
|
|
||||||||||||||||
| params.set("sort", next); | ||||||||||||||||
| params.set("page", "1"); | ||||||||||||||||
| return `?${params.toString()}`; | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| const handleChange = (next: PostSortValue) => { | ||||||||||||||||
| router.push(buildUrl(next), { scroll: false }); | ||||||||||||||||
| }; | ||||||||||||||||
|
Comment on lines
+23
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
이전 코드에서 🛡️ 선택적 방어 코드 const handleChange = (next: PostSortValue) => {
+ if (next === value) return;
router.push(buildUrl(next), { scroll: false });
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
|
|
||||||||||||||||
| router.push(`?${params.toString()}`, { scroll: false }); | ||||||||||||||||
| const handlePrefetch = (next: PostSortValue) => { | ||||||||||||||||
| if (next === value) return; | ||||||||||||||||
| router.prefetch(buildUrl(next)); | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| return <SortSelect value={value} onChange={handleChange} />; | ||||||||||||||||
| return ( | ||||||||||||||||
| <SortSelect | ||||||||||||||||
| value={value} | ||||||||||||||||
| onChange={handleChange} | ||||||||||||||||
| onPrefetch={handlePrefetch} | ||||||||||||||||
| /> | ||||||||||||||||
| ); | ||||||||||||||||
| } | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,8 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; | |
| type UseCommandPaletteResult = { | ||
| open: boolean; | ||
| query: string; | ||
| items: CommandItem[]; | ||
| filteredItems: CommandItem[]; | ||
| displayItems: CommandItem[]; | ||
| openPalette: () => void; | ||
| closePalette: () => void; | ||
| togglePalette: () => void; | ||
|
|
@@ -38,6 +37,12 @@ const STATIC_ITEMS: CommandItem[] = [ | |
| }, | ||
| ]; | ||
|
|
||
| function isPostItem(item: CommandItem) { | ||
| return ( | ||
| item.id.startsWith("post-") || (item.href?.startsWith("/posts/") ?? false) | ||
| ); | ||
| } | ||
|
Comment on lines
+40
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
현재 동작에 문제는 없지만, 만약 나중에 🤖 Prompt for AI Agents |
||
|
|
||
| function buildPostItems(): CommandItem[] { | ||
| const posts = getAllPosts({ includeDrafts: false }); | ||
|
|
||
|
|
@@ -72,19 +77,53 @@ function buildPostItems(): CommandItem[] { | |
| }); | ||
| } | ||
|
|
||
| const RECENT_KEY = "b_log_cmdk_recent"; | ||
| const RECENT_LIMIT = 3; | ||
|
|
||
| function readRecent(): CommandItem[] { | ||
| if (typeof window === "undefined") return []; | ||
| try { | ||
| const raw = sessionStorage.getItem(RECENT_KEY); | ||
| const parsed = raw ? (JSON.parse(raw) as CommandItem[]) : []; | ||
| return Array.isArray(parsed) ? parsed.slice(0, RECENT_LIMIT) : []; | ||
| } catch { | ||
| return []; | ||
| } | ||
| } | ||
|
Comment on lines
+83
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
RECENT_LIMIT이 3이라 영향 범위는 작지만, 간단한 필터링을 추가하면 더 안전합니다. 🛡️ 간단한 shape 검증 예시 function readRecent(): CommandItem[] {
if (typeof window === "undefined") return [];
try {
const raw = sessionStorage.getItem(RECENT_KEY);
const parsed = raw ? (JSON.parse(raw) as CommandItem[]) : [];
- return Array.isArray(parsed) ? parsed.slice(0, RECENT_LIMIT) : [];
+ if (!Array.isArray(parsed)) return [];
+ return parsed
+ .filter(
+ (x): x is CommandItem =>
+ typeof x === "object" &&
+ x !== null &&
+ typeof x.id === "string" &&
+ typeof x.label === "string",
+ )
+ .slice(0, RECENT_LIMIT);
} catch {
return [];
}
}🤖 Prompt for AI Agents |
||
|
|
||
| function writeRecent(items: CommandItem[]) { | ||
| sessionStorage.setItem( | ||
| RECENT_KEY, | ||
| JSON.stringify(items.slice(0, RECENT_LIMIT)), | ||
| ); | ||
| } | ||
|
Comment on lines
+94
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
♻️ 저장 시 필요 필드만 추출 function writeRecent(items: CommandItem[]) {
+ const slim = items.slice(0, RECENT_LIMIT).map(({ id, label, hint, href }) => ({
+ id,
+ label,
+ hint,
+ href,
+ }));
sessionStorage.setItem(
RECENT_KEY,
- JSON.stringify(items.slice(0, RECENT_LIMIT)),
+ JSON.stringify(slim),
);
}🤖 Prompt for AI Agents |
||
|
|
||
| function pushRecent(item: CommandItem): CommandItem[] { | ||
| const current = readRecent(); | ||
| const next = [item, ...current.filter((x) => x.id !== item.id)].slice( | ||
| 0, | ||
| RECENT_LIMIT, | ||
| ); | ||
| writeRecent(next); | ||
| return next; | ||
| } | ||
|
Comment on lines
+83
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
현재 🛡️ 제안 function writeRecent(items: CommandItem[]) {
+ if (typeof window === "undefined") return;
sessionStorage.setItem(
RECENT_KEY,
JSON.stringify(items.slice(0, RECENT_LIMIT)),
);
}🤖 Prompt for AI Agents |
||
|
|
||
| export default function useCommandPaletteInternal(): UseCommandPaletteResult { | ||
| const router = useRouter(); | ||
| const [open, setOpen] = useState(false); | ||
| const [query, setQuery] = useState(""); | ||
| const [recentItems, setRecentItems] = useState<CommandItem[]>(() => | ||
| readRecent(), | ||
| ); | ||
|
|
||
| const items = useMemo<CommandItem[]>(() => { | ||
| const postItems = buildPostItems(); | ||
| return [...STATIC_ITEMS, ...postItems]; | ||
| }, []); | ||
|
|
||
| const filteredItems = useMemo(() => { | ||
| const searchResults = useMemo(() => { | ||
| const q = query.trim().toLowerCase(); | ||
| if (!q) return items; | ||
| if (!q) return []; | ||
|
|
||
| return items.filter((item) => { | ||
| const target = ( | ||
|
|
@@ -101,6 +140,14 @@ export default function useCommandPaletteInternal(): UseCommandPaletteResult { | |
| }); | ||
| }, [items, query]); | ||
|
|
||
| const displayItems = useMemo<CommandItem[]>(() => { | ||
| const q = query.trim(); | ||
| if (q.length > 0) return searchResults; | ||
|
|
||
| if (recentItems.length === 0) return STATIC_ITEMS; | ||
| return [...STATIC_ITEMS, ...recentItems]; | ||
| }, [query, recentItems, searchResults]); | ||
|
|
||
| const openPalette = useCallback(() => { | ||
| setQuery(""); | ||
| setOpen(true); | ||
|
|
@@ -123,13 +170,16 @@ export default function useCommandPaletteInternal(): UseCommandPaletteResult { | |
|
|
||
| const handleSelect = useCallback( | ||
| (item: CommandItem) => { | ||
| if (isPostItem(item)) { | ||
| setRecentItems(pushRecent(item)); | ||
| } | ||
| if (item.href) { | ||
| router.push(item.href); | ||
| } | ||
| setOpen(false); | ||
| setQuery(""); | ||
| }, | ||
| [router] | ||
| [router], | ||
| ); | ||
|
Comment on lines
171
to
183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
Line 174에서 당장 수정이 필요한 문제는 아니고, 현재 동작도 정확합니다. 다만 추후 로직이 복잡해질 경우 분리를 고려해 보시면 좋겠습니다. 🤖 Prompt for AI Agents |
||
|
|
||
| const handleOpenChange = useCallback((next: boolean) => { | ||
|
|
@@ -157,13 +207,12 @@ export default function useCommandPaletteInternal(): UseCommandPaletteResult { | |
| return { | ||
| open, | ||
| query, | ||
| items, | ||
| filteredItems, | ||
| displayItems, | ||
| openPalette, | ||
| closePalette, | ||
| togglePalette, | ||
| setQuery, | ||
| handleSelect, | ||
| handleOpenChange, | ||
| }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,7 @@ export function usePostLike(postId: string): UsePostLikeResult { | |
| const [liked, setLiked] = useState(false); | ||
| const [loading, setLoading] = useState(true); | ||
|
|
||
| const viewerIdRef = useRef<string | null>(null); | ||
| const viewerIdRef = useRef<string>(""); | ||
|
|
||
| useEffect(() => { | ||
| viewerIdRef.current = getOrCreateViewerId(); | ||
|
|
@@ -32,7 +32,7 @@ export function usePostLike(postId: string): UsePostLikeResult { | |
| if (!postId) return; | ||
|
|
||
| const run = async () => { | ||
| const viewerId = viewerIdRef.current ?? getOrCreateViewerId(); | ||
| const viewerId = viewerIdRef.current; | ||
| const { count, liked } = await fetchPostLikeState(postId, viewerId); | ||
|
|
||
| setCount(count); | ||
|
|
@@ -44,7 +44,7 @@ export function usePostLike(postId: string): UsePostLikeResult { | |
| }, [postId]); | ||
|
|
||
| const toggleLike = async () => { | ||
| const viewerId = viewerIdRef.current ?? getOrCreateViewerId(); | ||
| const viewerId = viewerIdRef.current; | ||
| if (!viewerId || !postId) return; | ||
|
|
||
| if (liked) { | ||
|
|
@@ -60,6 +60,9 @@ export function usePostLike(postId: string): UsePostLikeResult { | |
| } | ||
|
|
||
| const { error } = await addPostLike(postId, viewerId); | ||
| if (error) { | ||
| console.error("add like error:", error.code, error.message, error.details, error.hint); | ||
| } | ||
|
Comment on lines
+63
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중복 에러 체크 + 디버그 로깅이 남아 있습니다 Lines 63-65의
🧹 디버그 로깅 제거 및 기존 에러 핸들링에 통합 제안 const { error } = await addPostLike(postId, viewerId);
- if (error) {
- console.error("add like error:", error.code, error.message, error.details, error.hint);
-}
-
if (error) {
if (isDuplicateKeyError(error)) {
setLiked(true);
const refreshed = await fetchPostLikeCount(postId);
setCount(refreshed);
} else {
- console.error("좋아요 추가 실패:", error);
+ console.error("좋아요 추가 실패:", error.code, error.message);
}
return;
}🤖 Prompt for AI Agents |
||
|
|
||
| if (error) { | ||
| if (isDuplicateKeyError(error)) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Biome 정적 분석 경고는 설정 이슈입니다.
Biome가
@config디렉티브를 인식하지 못하고 있습니다. 이는 Tailwind CSS v4의 정상적인 문법이므로, Biome CSS 파서 옵션에서tailwindDirectives를 활성화하면 해결됩니다. 코드 자체에는 문제가 없습니다.🧰 Tools
🪛 Biome (2.3.13)
[error] 3-3: Tailwind-specific syntax is disabled.
Enable
tailwindDirectivesin the css parser options, or remove this if you are not using Tailwind CSS.(parse)
🤖 Prompt for AI Agents