diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-11-23 12:25:56 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-23 12:25:56 +0000 |
| commit | c5c71ba9507f1c739773cf2677c53f83d29300bc (patch) | |
| tree | 6c106e52a6bfda1d9289a738a00dacfc5934f4e3 /apps/mobile/components/bookmarks/BookmarkCard.tsx | |
| parent | 8a5a109cdf14b6503b6bd07aa667788924a12fe6 (diff) | |
| download | karakeep-c5c71ba9507f1c739773cf2677c53f83d29300bc.tar.zst | |
feat(mobile): proper handling for shared list permissions (#2165)
* feat(mobile): Restrict bookmark editing in shared lists
Apply the same ownership-based restrictions that exist in the web app
to the mobile app. Users can now only edit, delete, and manage their
own bookmarks, even when viewing them in shared lists.
Changes:
- BottomActions: Hide edit actions (lists, tags, info, delete) for non-owners
- BookmarkCard: Hide favorite button and action menu for non-owners
- Info page: Make title, notes, tags, and lists read-only for non-owners
- NotePreview: Hide "Edit Notes" button for non-owners
All restrictions are based on comparing the current user ID (from useWhoAmI)
with the bookmark's userId field.
* some fixes
* make tags non clickable for collaborators
* add leave list
---------
Co-authored-by: Claude <noreply@anthropic.com>
Diffstat (limited to 'apps/mobile/components/bookmarks/BookmarkCard.tsx')
| -rw-r--r-- | apps/mobile/components/bookmarks/BookmarkCard.tsx | 202 |
1 files changed, 121 insertions, 81 deletions
diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx index 98d8d3e2..922951e5 100644 --- a/apps/mobile/components/bookmarks/BookmarkCard.tsx +++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx @@ -24,6 +24,7 @@ import { useDeleteBookmark, useUpdateBookmark, } from "@karakeep/shared-react/hooks/bookmarks"; +import { useWhoAmI } from "@karakeep/shared-react/hooks/users"; import { BookmarkTypes } from "@karakeep/shared/types/bookmarks"; import { getBookmarkLinkImageUrl, @@ -42,6 +43,10 @@ import TagPill from "./TagPill"; function ActionBar({ bookmark }: { bookmark: ZBookmark }) { const { toast } = useToast(); const { settings } = useAppSettings(); + const { data: currentUser } = useWhoAmI(); + + // Check if the current user owns this bookmark + const isOwner = currentUser?.id === bookmark.userId; const onError = () => { toast({ @@ -156,24 +161,71 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) { } }; + // Build actions array based on ownership + const menuActions = []; + if (isOwner) { + menuActions.push( + { + id: "edit", + title: "Edit", + image: Platform.select({ + ios: "pencil", + }), + }, + { + id: "manage_list", + title: "Manage Lists", + image: Platform.select({ + ios: "list.bullet", + }), + }, + { + id: "manage_tags", + title: "Manage Tags", + image: Platform.select({ + ios: "tag", + }), + }, + { + id: "archive", + title: bookmark.archived ? "Un-archive" : "Archive", + image: Platform.select({ + ios: "folder", + }), + }, + { + id: "delete", + title: "Delete", + attributes: { + destructive: true, + }, + image: Platform.select({ + ios: "trash", + }), + }, + ); + } + return ( <View className="flex flex-row gap-4"> {(isArchivePending || isDeletionPending) && <ActivityIndicator />} - <Pressable - onPress={() => { - Haptics.selectionAsync(); - favouriteBookmark({ - bookmarkId: bookmark.id, - favourited: !bookmark.favourited, - }); - }} - > - {(variables ? variables.favourited : bookmark.favourited) ? ( - <Star fill="#ebb434" color="#ebb434" /> - ) : ( - <Star color="gray" /> - )} - </Pressable> + {isOwner && ( + <Pressable + onPress={() => { + Haptics.selectionAsync(); + favouriteBookmark({ + bookmarkId: bookmark.id, + favourited: !bookmark.favourited, + }); + }} + > + {(variables ? variables.favourited : bookmark.favourited) ? ( + <Star fill="#ebb434" color="#ebb434" /> + ) : ( + <Star color="gray" /> + )} + </Pressable> + )} <Pressable onPress={() => { @@ -184,74 +236,39 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) { <ShareIcon color="gray" /> </Pressable> - <MenuView - onPressAction={({ nativeEvent }) => { - Haptics.selectionAsync(); - if (nativeEvent.event === "delete") { - deleteBookmarkAlert(); - } else if (nativeEvent.event === "archive") { - archiveBookmark({ - bookmarkId: bookmark.id, - archived: !bookmark.archived, - }); - } else if (nativeEvent.event === "manage_list") { - router.push(`/dashboard/bookmarks/${bookmark.id}/manage_lists`); - } else if (nativeEvent.event === "manage_tags") { - router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`); - } else if (nativeEvent.event === "edit") { - router.push(`/dashboard/bookmarks/${bookmark.id}/info`); - } - }} - actions={[ - { - id: "edit", - title: "Edit", - image: Platform.select({ - ios: "pencil", - }), - }, - { - id: "manage_list", - title: "Manage Lists", - image: Platform.select({ - ios: "list.bullet", - }), - }, - { - id: "manage_tags", - title: "Manage Tags", - image: Platform.select({ - ios: "tag", - }), - }, - { - id: "archive", - title: bookmark.archived ? "Un-archive" : "Archive", - image: Platform.select({ - ios: "folder", - }), - }, - { - id: "delete", - title: "Delete", - attributes: { - destructive: true, - }, - image: Platform.select({ - ios: "trash", - }), - }, - ]} - shouldOpenOnLongPress={false} - > - <Ellipsis onPress={() => Haptics.selectionAsync()} color="gray" /> - </MenuView> + {isOwner && menuActions.length > 0 && ( + <MenuView + onPressAction={({ nativeEvent }) => { + Haptics.selectionAsync(); + if (nativeEvent.event === "delete") { + deleteBookmarkAlert(); + } else if (nativeEvent.event === "archive") { + archiveBookmark({ + bookmarkId: bookmark.id, + archived: !bookmark.archived, + }); + } else if (nativeEvent.event === "manage_list") { + router.push(`/dashboard/bookmarks/${bookmark.id}/manage_lists`); + } else if (nativeEvent.event === "manage_tags") { + router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`); + } else if (nativeEvent.event === "edit") { + router.push(`/dashboard/bookmarks/${bookmark.id}/info`); + } + }} + actions={menuActions} + shouldOpenOnLongPress={false} + > + <Ellipsis onPress={() => Haptics.selectionAsync()} color="gray" /> + </MenuView> + )} </View> ); } function TagList({ bookmark }: { bookmark: ZBookmark }) { const tags = bookmark.tags; + const { data: currentUser } = useWhoAmI(); + const isOwner = currentUser?.id === bookmark.userId; if (isBookmarkStillTagging(bookmark)) { return ( @@ -266,7 +283,7 @@ function TagList({ bookmark }: { bookmark: ZBookmark }) { <ScrollView horizontal showsHorizontalScrollIndicator={false}> <View className="flex flex-row gap-2"> {tags.map((t) => ( - <TagPill key={t.id} tag={t} /> + <TagPill key={t.id} tag={t} clickable={isOwner} /> ))} </View> </ScrollView> @@ -281,6 +298,9 @@ function LinkCard({ onOpenBookmark: () => void; }) { const { settings } = useAppSettings(); + const { data: currentUser } = useWhoAmI(); + const isOwner = currentUser?.id === bookmark.userId; + if (bookmark.content.type !== BookmarkTypes.LINK) { throw new Error("Wrong content type rendered"); } @@ -330,7 +350,13 @@ function LinkCard({ > {bookmark.title ?? bookmark.content.title ?? parsedUrl.host} </Text> - {note && <NotePreview note={note} bookmarkId={bookmark.id} />} + {note && ( + <NotePreview + note={note} + bookmarkId={bookmark.id} + readOnly={!isOwner} + /> + )} <TagList bookmark={bookmark} /> <Divider orientation="vertical" className="mt-2 h-0.5 w-full" /> <View className="mt-2 flex flex-row justify-between px-2 pb-2"> @@ -350,6 +376,9 @@ function TextCard({ onOpenBookmark: () => void; }) { const { settings } = useAppSettings(); + const { data: currentUser } = useWhoAmI(); + const isOwner = currentUser?.id === bookmark.userId; + if (bookmark.content.type !== BookmarkTypes.TEXT) { throw new Error("Wrong content type rendered"); } @@ -369,7 +398,9 @@ function TextCard({ <BookmarkTextMarkdown text={content} /> </Pressable> </View> - {note && <NotePreview note={note} bookmarkId={bookmark.id} />} + {note && ( + <NotePreview note={note} bookmarkId={bookmark.id} readOnly={!isOwner} /> + )} <TagList bookmark={bookmark} /> <Divider orientation="vertical" className="mt-2 h-0.5 w-full" /> <View className="flex flex-row justify-between p-2"> @@ -388,6 +419,9 @@ function AssetCard({ onOpenBookmark: () => void; }) { const { settings } = useAppSettings(); + const { data: currentUser } = useWhoAmI(); + const isOwner = currentUser?.id === bookmark.userId; + if (bookmark.content.type !== BookmarkTypes.ASSET) { throw new Error("Wrong content type rendered"); } @@ -412,7 +446,13 @@ function AssetCard({ <Text className="line-clamp-2 text-xl font-bold">{title}</Text> )} </Pressable> - {note && <NotePreview note={note} bookmarkId={bookmark.id} />} + {note && ( + <NotePreview + note={note} + bookmarkId={bookmark.id} + readOnly={!isOwner} + /> + )} <TagList bookmark={bookmark} /> <Divider orientation="vertical" className="mt-2 h-0.5 w-full" /> <View className="mt-2 flex flex-row justify-between px-2 pb-2"> |
