diff options
| author | MohamedBassem <me@mbassem.com> | 2024-11-24 10:30:42 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-11-24 10:30:42 +0000 |
| commit | 2e5cd5993b317489e109b219ace8dec9acfb249d (patch) | |
| tree | 7521e62435db7c6df5a00efa31cf65d90f327b28 /apps/mobile | |
| parent | dd80f91ef8cc029c5a451d0fc41d58f9614d7df0 (diff) | |
| download | karakeep-2e5cd5993b317489e109b219ace8dec9acfb249d.tar.zst | |
feature(mobile): Add support for creating tags from the manage tags page
Diffstat (limited to 'apps/mobile')
| -rw-r--r-- | apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx | 99 |
1 files changed, 86 insertions, 13 deletions
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx index 2f670c9d..8429e4ba 100644 --- a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx @@ -6,11 +6,13 @@ import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView"; import FullPageSpinner from "@/components/ui/FullPageSpinner"; import { Input } from "@/components/ui/Input"; import { useToast } from "@/components/ui/Toast"; -import { Check } from "lucide-react-native"; +import { Check, Plus } from "lucide-react-native"; import { useUpdateBookmarkTags } from "@hoarder/shared-react/hooks/bookmarks"; import { api } from "@hoarder/shared-react/trpc"; +const NEW_TAG_ID = "new-tag"; + const ListPickerPage = () => { const { slug: bookmarkId } = useLocalSearchParams(); @@ -27,27 +29,54 @@ const ListPickerPage = () => { showProgress: false, }); }; - const { data: allTags, isPending: isAllTagsPending } = - api.tags.list.useQuery(); + + const { data: allTags, isPending: isAllTagsPending } = api.tags.list.useQuery( + undefined, + { + select: React.useCallback( + (data: { tags: { id: string; name: string }[] }) => { + return data.tags + .map((t) => ({ + id: t.id, + name: t.name, + lowered: t.name.toLowerCase(), + })) + .sort((a, b) => a.lowered.localeCompare(b.lowered)); + }, + [], + ), + }, + ); const { data: existingTags } = api.bookmarks.getBookmark.useQuery( { bookmarkId, }, { - select: (data) => data.tags.map((t) => ({ id: t.id, name: t.name })), + select: React.useCallback( + (data: { tags: { id: string; name: string }[] }) => + data.tags.map((t) => ({ + id: t.id, + name: t.name, + lowered: t.name.toLowerCase(), + })), + [], + ), }, ); const [optimisticTags, setOptimisticTags] = React.useState( existingTags ?? [], ); + React.useEffect(() => { + setOptimisticTags(existingTags ?? []); + }, [existingTags]); const { mutate: updateTags } = useUpdateBookmarkTags({ onMutate: (req) => { req.attach.forEach((t) => setOptimisticTags((prev) => [ ...prev, - { id: t.tagId!, name: t.tagName! }, + { id: t.tagId!, name: t.tagName!, lowered: t.tagName!.toLowerCase() }, ]), ); req.detach.forEach((t) => @@ -62,12 +91,36 @@ const ListPickerPage = () => { }, [optimisticTags]); const filteredTags = useMemo(() => { - return allTags?.tags.filter( + const loweredSearch = search.toLowerCase(); + let filteredAllTags = allTags?.filter( (t) => - t.name.toLowerCase().startsWith(search.toLowerCase()) && + t.lowered.startsWith(loweredSearch) && !optimisticExistingTagIds.has(t.id), ); - }, [search, allTags, optimisticExistingTagIds]); + let addCreateTag = false; + if (allTags && search) { + const exactMatchExists = + allTags.some((t) => t.lowered == loweredSearch) || + optimisticTags.some((t) => t.lowered == loweredSearch); + addCreateTag = !exactMatchExists; + } + if (filteredAllTags && addCreateTag) { + filteredAllTags = [ + { + id: NEW_TAG_ID, + name: search, + lowered: loweredSearch, + }, + ...filteredAllTags, + ]; + } + + const filteredOptimisticTags = optimisticTags.filter((t) => + t.lowered.startsWith(loweredSearch), + ); + + return { filteredAllTags, filteredOptimisticTags }; + }, [search, allTags, optimisticTags, optimisticExistingTagIds]); if (isAllTagsPending) { return <FullPageSpinner />; @@ -94,11 +147,11 @@ const ListPickerPage = () => { sections={[ { title: "Existing Tags", - data: optimisticTags ?? [], + data: filteredTags.filteredOptimisticTags ?? [], }, { title: "All Tags", - data: filteredTags ?? [], + data: filteredTags.filteredAllTags ?? [], }, ]} renderItem={(t) => ( @@ -109,11 +162,23 @@ const ListPickerPage = () => { bookmarkId, detach: t.section.title == "Existing Tags" - ? [{ tagId: t.item.id, tagName: t.item.name }] + ? [ + { + tagId: + t.item.id == NEW_TAG_ID ? undefined : t.item.id, + tagName: t.item.name, + }, + ] : [], attach: t.section.title == "All Tags" - ? [{ tagId: t.item.id, tagName: t.item.name }] + ? [ + { + tagId: + t.item.id == NEW_TAG_ID ? undefined : t.item.id, + tagName: t.item.name, + }, + ] : [], }) } @@ -125,8 +190,16 @@ const ListPickerPage = () => { comp={(s) => <Check color={s?.color} />} /> )} + {t.section.title == "All Tags" && t.item.id == NEW_TAG_ID && ( + <TailwindResolver + className="text-accent-foreground" + comp={(s) => <Plus color={s?.color} />} + /> + )} <Text className="text-center text-lg text-accent-foreground"> - {t.item.name} + {t.item.id == NEW_TAG_ID + ? `Create new tag '${t.item.name}'` + : t.item.name} </Text> </View> </Pressable> |
