aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-11-23 12:25:56 +0000
committerGitHub <noreply@github.com>2025-11-23 12:25:56 +0000
commitc5c71ba9507f1c739773cf2677c53f83d29300bc (patch)
tree6c106e52a6bfda1d9289a738a00dacfc5934f4e3 /apps/mobile
parent8a5a109cdf14b6503b6bd07aa667788924a12fe6 (diff)
downloadkarakeep-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')
-rw-r--r--apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx110
-rw-r--r--apps/mobile/app/dashboard/lists/[slug].tsx49
-rw-r--r--apps/mobile/components/bookmarks/BookmarkCard.tsx202
-rw-r--r--apps/mobile/components/bookmarks/BottomActions.tsx11
-rw-r--r--apps/mobile/components/bookmarks/NotePreview.tsx33
-rw-r--r--apps/mobile/components/bookmarks/TagPill.tsx20
6 files changed, 279 insertions, 146 deletions
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx
index 15e2a082..c4b76aef 100644
--- a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx
+++ b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx
@@ -26,6 +26,7 @@ import {
useSummarizeBookmark,
useUpdateBookmark,
} from "@karakeep/shared-react/hooks/bookmarks";
+import { useWhoAmI } from "@karakeep/shared-react/hooks/users";
import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
import { isBookmarkStillTagging } from "@karakeep/shared/utils/bookmarkUtils";
@@ -41,7 +42,13 @@ function InfoSection({
);
}
-function TagList({ bookmark }: { bookmark: ZBookmark }) {
+function TagList({
+ bookmark,
+ readOnly,
+}: {
+ bookmark: ZBookmark;
+ readOnly: boolean;
+}) {
return (
<InfoSection>
{isBookmarkStillTagging(bookmark) ? (
@@ -54,24 +61,26 @@ function TagList({ bookmark }: { bookmark: ZBookmark }) {
<>
<View className="flex flex-row flex-wrap gap-2 rounded-lg p-2">
{bookmark.tags.map((t) => (
- <TagPill key={t.id} tag={t} />
+ <TagPill key={t.id} tag={t} clickable={!readOnly} />
))}
</View>
<Divider orientation="horizontal" />
</>
)
)}
- <View>
- <Pressable
- onPress={() =>
- router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`)
- }
- className="flex w-full flex-row justify-between gap-3"
- >
- <Text>Manage Tags</Text>
- <ChevronRight />
- </Pressable>
- </View>
+ {!readOnly && (
+ <View>
+ <Pressable
+ onPress={() =>
+ router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`)
+ }
+ className="flex w-full flex-row justify-between gap-3"
+ >
+ <Text>Manage Tags</Text>
+ <ChevronRight />
+ </Pressable>
+ </View>
+ )}
</InfoSection>
);
}
@@ -98,15 +107,17 @@ function TitleEditor({
title,
setTitle,
isPending,
+ disabled,
}: {
title: string | null | undefined;
setTitle: (title: string | null) => void;
isPending: boolean;
+ disabled?: boolean;
}) {
return (
<InfoSection>
<Input
- editable={!isPending}
+ editable={!isPending && !disabled}
multiline={false}
numberOfLines={1}
placeholder="Title"
@@ -121,15 +132,17 @@ function NotesEditor({
notes,
setNotes,
isPending,
+ disabled,
}: {
notes: string | null | undefined;
setNotes: (title: string | null) => void;
isPending: boolean;
+ disabled?: boolean;
}) {
return (
<InfoSection>
<Input
- editable={!isPending}
+ editable={!isPending && !disabled}
multiline={true}
placeholder="Notes"
inputClasses="h-24"
@@ -141,7 +154,13 @@ function NotesEditor({
);
}
-function AISummarySection({ bookmark }: { bookmark: ZBookmark }) {
+function AISummarySection({
+ bookmark,
+ readOnly,
+}: {
+ bookmark: ZBookmark;
+ readOnly: boolean;
+}) {
const { toast } = useToast();
const [isExpanded, setIsExpanded] = React.useState(false);
@@ -214,7 +233,7 @@ function AISummarySection({ bookmark }: { bookmark: ZBookmark }) {
</Text>
</Pressable>
)}
- {isExpanded && (
+ {isExpanded && !readOnly && (
<View className="mt-2 flex flex-row justify-end gap-2">
<Pressable
onPress={() => resummarize({ bookmarkId: bookmark.id })}
@@ -262,6 +281,9 @@ function AISummarySection({ bookmark }: { bookmark: ZBookmark }) {
}
// If no summary, show button to generate one
+ if (readOnly) {
+ return null;
+ }
return (
<InfoSection>
<Pressable
@@ -293,6 +315,7 @@ const ViewBookmarkPage = () => {
const insets = useSafeAreaInsets();
const { slug } = useLocalSearchParams();
const { toast } = useToast();
+ const { data: currentUser } = useWhoAmI();
if (typeof slug !== "string") {
throw new Error("Unexpected param type");
}
@@ -326,6 +349,9 @@ const ViewBookmarkPage = () => {
bookmarkId: slug,
});
+ // Check if the current user owns this bookmark
+ const isOwner = currentUser?.id === bookmark?.userId;
+
const [editedBookmark, setEditedBookmark] = React.useState<{
title?: string | null;
note?: string;
@@ -416,37 +442,41 @@ const ViewBookmarkPage = () => {
setEditedBookmark((prev) => ({ ...prev, title }))
}
isPending={isEditPending}
+ disabled={!isOwner}
/>
- <AISummarySection bookmark={bookmark} />
- <TagList bookmark={bookmark} />
- <ManageLists bookmark={bookmark} />
+ <AISummarySection bookmark={bookmark} readOnly={!isOwner} />
+ <TagList bookmark={bookmark} readOnly={!isOwner} />
+ {isOwner && <ManageLists bookmark={bookmark} />}
<NotesEditor
notes={bookmark.note}
setNotes={(note) =>
setEditedBookmark((prev) => ({ ...prev, note: note ?? "" }))
}
isPending={isEditPending}
+ disabled={!isOwner}
/>
- <View className="flex justify-between gap-3">
- <Button
- onPress={() =>
- editBookmark({
- bookmarkId: bookmark.id,
- ...editedBookmark,
- })
- }
- disabled={isEditPending}
- >
- <Text>Save</Text>
- </Button>
- <Button
- variant="destructive"
- onPress={handleDeleteBookmark}
- disabled={isDeletionPending}
- >
- <Text>Delete</Text>
- </Button>
- </View>
+ {isOwner && (
+ <View className="flex justify-between gap-3">
+ <Button
+ onPress={() =>
+ editBookmark({
+ bookmarkId: bookmark.id,
+ ...editedBookmark,
+ })
+ }
+ disabled={isEditPending}
+ >
+ <Text>Save</Text>
+ </Button>
+ <Button
+ variant="destructive"
+ onPress={handleDeleteBookmark}
+ disabled={isDeletionPending}
+ >
+ <Text>Delete</Text>
+ </Button>
+ </View>
+ )}
<View className="gap-2">
<Text className="items-center text-center">
Created {bookmark.createdAt.toLocaleString()}
diff --git a/apps/mobile/app/dashboard/lists/[slug].tsx b/apps/mobile/app/dashboard/lists/[slug].tsx
index f98dd6d3..e7aab443 100644
--- a/apps/mobile/app/dashboard/lists/[slug].tsx
+++ b/apps/mobile/app/dashboard/lists/[slug].tsx
@@ -9,6 +9,8 @@ import { api } from "@/lib/trpc";
import { MenuView } from "@react-native-menu/menu";
import { Ellipsis } from "lucide-react-native";
+import { ZBookmarkList } from "@karakeep/shared/types/lists";
+
export default function ListView() {
const { slug } = useLocalSearchParams();
if (typeof slug !== "string") {
@@ -27,7 +29,9 @@ export default function ListView() {
headerTitle: list ? `${list.icon} ${list.name}` : "",
headerBackTitle: "Back",
headerLargeTitle: true,
- headerRight: () => <ListActionsMenu listId={slug} />,
+ headerRight: () => (
+ <ListActionsMenu listId={slug} role={list?.userRole ?? "viewer"} />
+ ),
}}
/>
{error ? (
@@ -47,8 +51,20 @@ export default function ListView() {
);
}
-function ListActionsMenu({ listId }: { listId: string }) {
- const { mutate } = api.lists.delete.useMutation({
+function ListActionsMenu({
+ listId,
+ role,
+}: {
+ listId: string;
+ role: ZBookmarkList["userRole"];
+}) {
+ const { mutate: deleteList } = api.lists.delete.useMutation({
+ onSuccess: () => {
+ router.replace("/dashboard/lists");
+ },
+ });
+
+ const { mutate: leaveList } = api.lists.leaveList.useMutation({
onSuccess: () => {
router.replace("/dashboard/lists");
},
@@ -60,7 +76,20 @@ function ListActionsMenu({ listId }: { listId: string }) {
{
text: "Delete",
onPress: () => {
- mutate({ listId });
+ deleteList({ listId });
+ },
+ style: "destructive",
+ },
+ ]);
+ };
+
+ const handleLeave = () => {
+ Alert.alert("Leave List", "Are you sure you want to leave this list?", [
+ { text: "Cancel", style: "cancel" },
+ {
+ text: "Leave",
+ onPress: () => {
+ leaveList({ listId });
},
style: "destructive",
},
@@ -75,16 +104,28 @@ function ListActionsMenu({ listId }: { listId: string }) {
title: "Delete List",
attributes: {
destructive: true,
+ hidden: role !== "owner",
},
image: Platform.select({
ios: "trash",
}),
},
+ {
+ id: "leave",
+ title: "Leave List",
+ attributes: {
+ destructive: true,
+ hidden: role === "owner",
+ },
+ },
]}
onPressAction={({ nativeEvent }) => {
if (nativeEvent.event === "delete") {
handleDelete();
}
+ if (nativeEvent.event === "leave") {
+ handleLeave();
+ }
}}
shouldOpenOnLongPress={false}
>
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">
diff --git a/apps/mobile/components/bookmarks/BottomActions.tsx b/apps/mobile/components/bookmarks/BottomActions.tsx
index 8cfa27c9..64653779 100644
--- a/apps/mobile/components/bookmarks/BottomActions.tsx
+++ b/apps/mobile/components/bookmarks/BottomActions.tsx
@@ -5,6 +5,7 @@ import { useToast } from "@/components/ui/Toast";
import { ClipboardList, Globe, Info, Tag, Trash2 } from "lucide-react-native";
import { useDeleteBookmark } from "@karakeep/shared-react/hooks/bookmarks";
+import { useWhoAmI } from "@karakeep/shared-react/hooks/users";
import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
interface BottomActionsProps {
@@ -14,6 +15,10 @@ interface BottomActionsProps {
export default function BottomActions({ bookmark }: BottomActionsProps) {
const { toast } = useToast();
const router = useRouter();
+ const { data: currentUser } = useWhoAmI();
+
+ // Check if the current user owns this bookmark
+ const isOwner = currentUser?.id === bookmark.userId;
const { mutate: deleteBookmark, isPending: isDeletionPending } =
useDeleteBookmark({
@@ -56,7 +61,7 @@ export default function BottomActions({ bookmark }: BottomActionsProps) {
comp={(styles) => <ClipboardList color={styles?.color?.toString()} />}
/>
),
- shouldRender: true,
+ shouldRender: isOwner,
onClick: () =>
router.push(`/dashboard/bookmarks/${bookmark.id}/manage_lists`),
disabled: false,
@@ -69,7 +74,7 @@ export default function BottomActions({ bookmark }: BottomActionsProps) {
comp={(styles) => <Tag color={styles?.color?.toString()} />}
/>
),
- shouldRender: true,
+ shouldRender: isOwner,
onClick: () =>
router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`),
disabled: false,
@@ -94,7 +99,7 @@ export default function BottomActions({ bookmark }: BottomActionsProps) {
comp={(styles) => <Trash2 color={styles?.color?.toString()} />}
/>
),
- shouldRender: true,
+ shouldRender: isOwner,
onClick: deleteBookmarkAlert,
disabled: isDeletionPending,
},
diff --git a/apps/mobile/components/bookmarks/NotePreview.tsx b/apps/mobile/components/bookmarks/NotePreview.tsx
index d529d56e..0283d179 100644
--- a/apps/mobile/components/bookmarks/NotePreview.tsx
+++ b/apps/mobile/components/bookmarks/NotePreview.tsx
@@ -10,9 +10,14 @@ import { Text } from "../ui/Text";
interface NotePreviewProps {
note: string;
bookmarkId: string;
+ readOnly?: boolean;
}
-export function NotePreview({ note, bookmarkId }: NotePreviewProps) {
+export function NotePreview({
+ note,
+ bookmarkId,
+ readOnly = false,
+}: NotePreviewProps) {
const [isModalVisible, setIsModalVisible] = useState(false);
const { colorScheme } = useColorScheme();
const iconColor = colorScheme === "dark" ? "#9ca3af" : "#6b7280";
@@ -63,18 +68,20 @@ export function NotePreview({ note, bookmarkId }: NotePreviewProps) {
</ScrollView>
{/* Action Button */}
- <View className="flex flex-row justify-end border-t border-border pt-4">
- <Button
- variant="secondary"
- onPress={() => {
- setIsModalVisible(false);
- router.push(`/dashboard/bookmarks/${bookmarkId}/info`);
- }}
- >
- <Text className="text-sm">Edit Notes</Text>
- <ExternalLink size={14} color={modalIconColor} />
- </Button>
- </View>
+ {!readOnly && (
+ <View className="flex flex-row justify-end border-t border-border pt-4">
+ <Button
+ variant="secondary"
+ onPress={() => {
+ setIsModalVisible(false);
+ router.push(`/dashboard/bookmarks/${bookmarkId}/info`);
+ }}
+ >
+ <Text className="text-sm">Edit Notes</Text>
+ <ExternalLink size={14} color={modalIconColor} />
+ </Button>
+ </View>
+ )}
</View>
</View>
</Modal>
diff --git a/apps/mobile/components/bookmarks/TagPill.tsx b/apps/mobile/components/bookmarks/TagPill.tsx
index caf0f636..2097daab 100644
--- a/apps/mobile/components/bookmarks/TagPill.tsx
+++ b/apps/mobile/components/bookmarks/TagPill.tsx
@@ -1,17 +1,27 @@
-import { View } from "react-native";
+import { Text, View } from "react-native";
import { Link } from "expo-router";
import { ZBookmarkTags } from "@karakeep/shared/types/tags";
-export default function TagPill({ tag }: { tag: ZBookmarkTags }) {
+export default function TagPill({
+ tag,
+ clickable = true,
+}: {
+ tag: ZBookmarkTags;
+ clickable?: boolean;
+}) {
return (
<View
key={tag.id}
className="rounded-full border border-input px-2.5 py-0.5 text-xs font-semibold"
>
- <Link className="text-foreground" href={`dashboard/tags/${tag.id}`}>
- {tag.name}
- </Link>
+ {clickable ? (
+ <Link className="text-foreground" href={`dashboard/tags/${tag.id}`}>
+ {tag.name}
+ </Link>
+ ) : (
+ <Text className="text-foreground">{tag.name}</Text>
+ )}
</View>
);
}