diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue index ae779a39..67626fb4 100644 --- a/app/components/AppHeader.vue +++ b/app/components/AppHeader.vue @@ -72,11 +72,14 @@ onKeyStroke(',', e => { - +
+ + + +const props = defineProps<{ + packageName: string +}>() + +const { useIsBookmarked, toggleBookmark } = useBookmarks() + +const isBookmarked = useIsBookmarked(() => props.packageName) + +function handleClick() { + toggleBookmark(props.packageName) +} + + + diff --git a/app/components/HeaderBookmarksDropdown.vue b/app/components/HeaderBookmarksDropdown.vue new file mode 100644 index 00000000..644152eb --- /dev/null +++ b/app/components/HeaderBookmarksDropdown.vue @@ -0,0 +1,112 @@ + + + diff --git a/app/composables/useBookmarks.ts b/app/composables/useBookmarks.ts new file mode 100644 index 00000000..71b2797e --- /dev/null +++ b/app/composables/useBookmarks.ts @@ -0,0 +1,92 @@ +import type { RemovableRef } from '@vueuse/core' +import { useLocalStorage } from '@vueuse/core' + +/** + * Bookmark entry with package name and timestamp + */ +export interface Bookmark { + packageName: string + addedAt: number +} + +const STORAGE_KEY = 'npmx-bookmarks' + +// Shared bookmarks instance (singleton per app) +let bookmarksRef: RemovableRef | null = null + +/** + * Composable for managing package bookmarks with localStorage persistence. + * Bookmarks are shared across all components that use this composable. + */ +export function useBookmarks() { + if (!bookmarksRef) { + bookmarksRef = useLocalStorage(STORAGE_KEY, [], { + mergeDefaults: true, + }) + } + + const bookmarks = bookmarksRef + + /** + * Check if a package is bookmarked + */ + function isBookmarked(packageName: string): boolean { + return bookmarks.value.some(b => b.packageName === packageName) + } + + /** + * Reactive computed to check if a specific package is bookmarked + */ + function useIsBookmarked(packageName: MaybeRefOrGetter) { + return computed(() => isBookmarked(toValue(packageName))) + } + + /** + * Add a package to bookmarks + */ + function addBookmark(packageName: string): void { + if (!isBookmarked(packageName)) { + bookmarks.value = [{ packageName, addedAt: Date.now() }, ...bookmarks.value] + } + } + + /** + * Remove a package from bookmarks + */ + function removeBookmark(packageName: string): void { + bookmarks.value = bookmarks.value.filter(b => b.packageName !== packageName) + } + + /** + * Toggle bookmark status for a package + */ + function toggleBookmark(packageName: string): void { + if (isBookmarked(packageName)) { + removeBookmark(packageName) + } else { + addBookmark(packageName) + } + } + + /** + * Clear all bookmarks + */ + function clearBookmarks(): void { + bookmarks.value = [] + } + + const bookmarkCount = computed(() => bookmarks.value.length) + const hasBookmarks = computed(() => bookmarks.value.length > 0) + + return { + bookmarks, + bookmarkCount, + hasBookmarks, + isBookmarked, + useIsBookmarked, + addBookmark, + removeBookmark, + toggleBookmark, + clearBookmarks, + } +} diff --git a/app/pages/[...package].vue b/app/pages/[...package].vue index b32e1bba..e172f116 100644 --- a/app/pages/[...package].vue +++ b/app/pages/[...package].vue @@ -574,12 +574,15 @@ function handleClick(event: MouseEvent) { - +