aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile/components/bookmarks
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--apps/mobile/components/bookmarks/BookmarkAssetImage.tsx15
-rw-r--r--apps/mobile/components/bookmarks/BookmarkAssetView.tsx2
-rw-r--r--apps/mobile/components/bookmarks/BookmarkCard.tsx115
-rw-r--r--apps/mobile/components/bookmarks/BookmarkLinkPreview.tsx85
-rw-r--r--apps/mobile/components/bookmarks/BookmarkLinkTypeSelector.tsx21
-rw-r--r--apps/mobile/components/bookmarks/BookmarkLinkView.tsx3
-rw-r--r--apps/mobile/components/bookmarks/BookmarkList.tsx1
-rw-r--r--apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx24
8 files changed, 186 insertions, 80 deletions
diff --git a/apps/mobile/components/bookmarks/BookmarkAssetImage.tsx b/apps/mobile/components/bookmarks/BookmarkAssetImage.tsx
index 8fa88c8b..35726e4b 100644
--- a/apps/mobile/components/bookmarks/BookmarkAssetImage.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkAssetImage.tsx
@@ -1,14 +1,25 @@
-import { Image } from "react-native";
+import { View } from "react-native";
+import { Image, ImageContentFit } from "expo-image";
import { useAssetUrl } from "@/lib/hooks";
export default function BookmarkAssetImage({
assetId,
className,
+ contentFit = "cover",
}: {
assetId: string;
className: string;
+ contentFit?: ImageContentFit;
}) {
const assetSource = useAssetUrl(assetId);
- return <Image source={assetSource} className={className} />;
+ return (
+ <View className={className}>
+ <Image
+ source={assetSource}
+ style={{ width: "100%", height: "100%" }}
+ contentFit={contentFit}
+ />
+ </View>
+ );
}
diff --git a/apps/mobile/components/bookmarks/BookmarkAssetView.tsx b/apps/mobile/components/bookmarks/BookmarkAssetView.tsx
index 5fe2f470..e009a027 100644
--- a/apps/mobile/components/bookmarks/BookmarkAssetView.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkAssetView.tsx
@@ -48,7 +48,7 @@ export default function BookmarkAssetView({
<Pressable onPress={() => setImageZoom(true)}>
<BookmarkAssetImage
assetId={bookmark.content.assetId}
- className="h-56 min-h-56 w-full object-cover"
+ className="h-56 min-h-56 w-full"
/>
</Pressable>
</View>
diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx
index 922951e5..060aada9 100644
--- a/apps/mobile/components/bookmarks/BookmarkCard.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx
@@ -1,7 +1,6 @@
import {
ActivityIndicator,
Alert,
- Image,
Platform,
Pressable,
ScrollView,
@@ -9,14 +8,16 @@ import {
View,
} from "react-native";
import * as Clipboard from "expo-clipboard";
-import * as FileSystem from "expo-file-system";
+import * as FileSystem from "expo-file-system/legacy";
import * as Haptics from "expo-haptics";
+import { Image } from "expo-image";
import { router, useRouter } from "expo-router";
import * as Sharing from "expo-sharing";
import { Text } from "@/components/ui/Text";
import useAppSettings from "@/lib/settings";
-import { api } from "@/lib/trpc";
+import { buildApiHeaders } from "@/lib/utils";
import { MenuView } from "@react-native-menu/menu";
+import { useQuery } from "@tanstack/react-query";
import { Ellipsis, ShareIcon, Star } from "lucide-react-native";
import type { ZBookmark } from "@karakeep/shared/types/bookmarks";
@@ -25,6 +26,7 @@ import {
useUpdateBookmark,
} from "@karakeep/shared-react/hooks/bookmarks";
import { useWhoAmI } from "@karakeep/shared-react/hooks/users";
+import { useTRPC } from "@karakeep/shared-react/trpc";
import { BookmarkTypes } from "@karakeep/shared/types/bookmarks";
import {
getBookmarkLinkImageUrl,
@@ -124,9 +126,10 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) {
assetUrl,
fileUri,
{
- headers: {
- Authorization: `Bearer ${settings.apiKey}`,
- },
+ headers: buildApiHeaders(
+ settings.apiKey,
+ settings.customHeaders,
+ ),
},
);
@@ -314,29 +317,36 @@ function LinkCard({
let imageComp;
if (imageUrl) {
imageComp = (
- <Image
- source={
- imageUrl.localAsset
- ? {
- uri: `${settings.address}${imageUrl.url}`,
- headers: {
- Authorization: `Bearer ${settings.apiKey}`,
- },
- }
- : {
- uri: imageUrl.url,
- }
- }
- className="h-56 min-h-56 w-full object-cover"
- />
+ <View className="h-56 min-h-56 w-full">
+ <Image
+ source={
+ imageUrl.localAsset
+ ? {
+ uri: `${settings.address}${imageUrl.url}`,
+ headers: buildApiHeaders(
+ settings.apiKey,
+ settings.customHeaders,
+ ),
+ }
+ : {
+ uri: imageUrl.url,
+ }
+ }
+ style={{ width: "100%", height: "100%" }}
+ contentFit="cover"
+ />
+ </View>
);
} else {
imageComp = (
- <Image
- // oxlint-disable-next-line no-require-imports
- source={require("@/assets/blur.jpeg")}
- className="h-56 w-full rounded-t-lg"
- />
+ <View className="h-56 w-full overflow-hidden rounded-t-lg">
+ <Image
+ // oxlint-disable-next-line no-require-imports
+ source={require("@/assets/blur.jpeg")}
+ style={{ width: "100%", height: "100%" }}
+ contentFit="cover"
+ />
+ </View>
);
}
@@ -345,7 +355,8 @@ function LinkCard({
<Pressable onPress={onOpenBookmark}>{imageComp}</Pressable>
<View className="flex gap-2 p-2">
<Text
- className="line-clamp-2 text-xl font-bold text-foreground"
+ className="text-xl font-bold text-foreground"
+ numberOfLines={2}
onPress={onOpenBookmark}
>
{bookmark.title ?? bookmark.content.title ?? parsedUrl.host}
@@ -360,7 +371,9 @@ function LinkCard({
<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">
- <Text className="my-auto line-clamp-1">{parsedUrl.host}</Text>
+ <Text className="my-auto" numberOfLines={1}>
+ {parsedUrl.host}
+ </Text>
<ActionBar bookmark={bookmark} />
</View>
</View>
@@ -388,7 +401,7 @@ function TextCard({
<View className="flex max-h-96 gap-2 p-2">
<Pressable onPress={onOpenBookmark}>
{bookmark.title && (
- <Text className="line-clamp-2 text-xl font-bold">
+ <Text className="text-xl font-bold" numberOfLines={2}>
{bookmark.title}
</Text>
)}
@@ -437,13 +450,15 @@ function AssetCard({
<Pressable onPress={onOpenBookmark}>
<BookmarkAssetImage
assetId={assetImage}
- className="h-56 min-h-56 w-full object-cover"
+ className="h-56 min-h-56 w-full"
/>
</Pressable>
<View className="flex gap-2 p-2">
<Pressable onPress={onOpenBookmark}>
{title && (
- <Text className="line-clamp-2 text-xl font-bold">{title}</Text>
+ <Text numberOfLines={2} className="text-xl font-bold">
+ {title}
+ </Text>
)}
</Pressable>
{note && (
@@ -469,20 +484,23 @@ export default function BookmarkCard({
}: {
bookmark: ZBookmark;
}) {
- const { data: bookmark } = api.bookmarks.getBookmark.useQuery(
- {
- bookmarkId: initialData.id,
- },
- {
- initialData,
- refetchInterval: (query) => {
- const data = query.state.data;
- if (!data) {
- return false;
- }
- return getBookmarkRefreshInterval(data);
+ const api = useTRPC();
+ const { data: bookmark } = useQuery(
+ api.bookmarks.getBookmark.queryOptions(
+ {
+ bookmarkId: initialData.id,
+ },
+ {
+ initialData,
+ refetchInterval: (query) => {
+ const data = query.state.data;
+ if (!data) {
+ return false;
+ }
+ return getBookmarkRefreshInterval(data);
+ },
},
- },
+ ),
);
const router = useRouter();
@@ -521,5 +539,12 @@ export default function BookmarkCard({
break;
}
- return <View className="overflow-hidden rounded-xl bg-card">{comp}</View>;
+ return (
+ <View
+ className="overflow-hidden rounded-xl bg-card"
+ style={{ borderCurve: "continuous" }}
+ >
+ {comp}
+ </View>
+ );
}
diff --git a/apps/mobile/components/bookmarks/BookmarkLinkPreview.tsx b/apps/mobile/components/bookmarks/BookmarkLinkPreview.tsx
index 730bcd08..57e00c24 100644
--- a/apps/mobile/components/bookmarks/BookmarkLinkPreview.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkLinkPreview.tsx
@@ -5,14 +5,17 @@ import WebView from "react-native-webview";
import { WebViewSourceUri } from "react-native-webview/lib/WebViewTypes";
import { Text } from "@/components/ui/Text";
import { useAssetUrl } from "@/lib/hooks";
-import { api } from "@/lib/trpc";
+import { useReaderSettings, WEBVIEW_FONT_FAMILIES } from "@/lib/readerSettings";
import { useColorScheme } from "@/lib/useColorScheme";
+import { useQuery } from "@tanstack/react-query";
+import { useTRPC } from "@karakeep/shared-react/trpc";
import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
import FullPageError from "../FullPageError";
import FullPageSpinner from "../ui/FullPageSpinner";
import BookmarkAssetImage from "./BookmarkAssetImage";
+import { PDFViewer } from "./PDFViewer";
export function BookmarkLinkBrowserPreview({
bookmark,
@@ -32,22 +35,50 @@ export function BookmarkLinkBrowserPreview({
);
}
+export function BookmarkLinkPdfPreview({ bookmark }: { bookmark: ZBookmark }) {
+ if (bookmark.content.type !== BookmarkTypes.LINK) {
+ throw new Error("Wrong content type rendered");
+ }
+
+ const asset = bookmark.assets.find((r) => r.assetType == "pdf");
+
+ const assetSource = useAssetUrl(asset?.id ?? "");
+
+ if (!asset) {
+ return (
+ <View className="flex-1 bg-background">
+ <Text>Asset has no PDF</Text>
+ </View>
+ );
+ }
+
+ return (
+ <View className="flex flex-1">
+ <PDFViewer source={assetSource.uri ?? ""} headers={assetSource.headers} />
+ </View>
+ );
+}
+
export function BookmarkLinkReaderPreview({
bookmark,
}: {
bookmark: ZBookmark;
}) {
const { isDarkColorScheme: isDark } = useColorScheme();
+ const { settings: readerSettings } = useReaderSettings();
+ const api = useTRPC();
const {
data: bookmarkWithContent,
error,
isLoading,
refetch,
- } = api.bookmarks.getBookmark.useQuery({
- bookmarkId: bookmark.id,
- includeContent: true,
- });
+ } = useQuery(
+ api.bookmarks.getBookmark.queryOptions({
+ bookmarkId: bookmark.id,
+ includeContent: true,
+ }),
+ );
if (isLoading) {
return <FullPageSpinner />;
@@ -61,6 +92,10 @@ export function BookmarkLinkReaderPreview({
throw new Error("Wrong content type rendered");
}
+ const fontFamily = WEBVIEW_FONT_FAMILIES[readerSettings.fontFamily];
+ const fontSize = readerSettings.fontSize;
+ const lineHeight = readerSettings.lineHeight;
+
return (
<View className="flex-1 bg-background">
<WebView
@@ -73,8 +108,9 @@ export function BookmarkLinkReaderPreview({
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
- line-height: 1.6;
+ font-family: ${fontFamily};
+ font-size: ${fontSize}px;
+ line-height: ${lineHeight};
color: ${isDark ? "#e5e7eb" : "#374151"};
margin: 0;
padding: 16px;
@@ -85,17 +121,29 @@ export function BookmarkLinkReaderPreview({
img { max-width: 100%; height: auto; border-radius: 8px; }
a { color: #3b82f6; text-decoration: none; }
a:hover { text-decoration: underline; }
- blockquote {
- border-left: 4px solid ${isDark ? "#374151" : "#e5e7eb"};
- margin: 1em 0;
- padding-left: 1em;
- color: ${isDark ? "#9ca3af" : "#6b7280"};
+ blockquote {
+ border-left: 4px solid ${isDark ? "#374151" : "#e5e7eb"};
+ margin: 1em 0;
+ padding-left: 1em;
+ color: ${isDark ? "#9ca3af" : "#6b7280"};
+ }
+ pre, code {
+ font-family: ui-monospace, Menlo, Monaco, 'Courier New', monospace;
+ background: ${isDark ? "#1f2937" : "#f3f4f6"};
+ }
+ pre {
+ padding: 1em;
+ border-radius: 6px;
+ overflow-x: auto;
+ }
+ code {
+ padding: 0.2em 0.4em;
+ border-radius: 3px;
+ font-size: 0.9em;
}
- pre {
- background: ${isDark ? "#1f2937" : "#f3f4f6"};
- padding: 1em;
- border-radius: 6px;
- overflow-x: auto;
+ pre code {
+ padding: 0;
+ background: none;
}
</style>
</head>
@@ -180,7 +228,8 @@ export function BookmarkLinkScreenshotPreview({
<Pressable onPress={() => setImageZoom(true)}>
<BookmarkAssetImage
assetId={asset.id}
- className="h-full w-full object-contain"
+ className="h-full w-full"
+ contentFit="contain"
/>
</Pressable>
</View>
diff --git a/apps/mobile/components/bookmarks/BookmarkLinkTypeSelector.tsx b/apps/mobile/components/bookmarks/BookmarkLinkTypeSelector.tsx
index 58cbcc8d..5c9955bd 100644
--- a/apps/mobile/components/bookmarks/BookmarkLinkTypeSelector.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkLinkTypeSelector.tsx
@@ -4,7 +4,12 @@ import { ChevronDown } from "lucide-react-native";
import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
-export type BookmarkLinkType = "browser" | "reader" | "screenshot" | "archive";
+export type BookmarkLinkType =
+ | "browser"
+ | "reader"
+ | "screenshot"
+ | "archive"
+ | "pdf";
function getAvailableViewTypes(bookmark: ZBookmark): BookmarkLinkType[] {
if (bookmark.content.type !== BookmarkTypes.LINK) {
@@ -26,6 +31,9 @@ function getAvailableViewTypes(bookmark: ZBookmark): BookmarkLinkType[] {
) {
availableTypes.push("archive");
}
+ if (bookmark.assets.some((asset) => asset.assetType === "pdf")) {
+ availableTypes.push("pdf");
+ }
return availableTypes;
}
@@ -43,7 +51,7 @@ export default function BookmarkLinkTypeSelector({
}: BookmarkLinkTypeSelectorProps) {
const availableTypes = getAvailableViewTypes(bookmark);
- const allActions = [
+ const viewActions = [
{
id: "reader" as const,
title: "Reader View",
@@ -64,9 +72,14 @@ export default function BookmarkLinkTypeSelector({
title: "Archived Page",
state: type === "archive" ? ("on" as const) : undefined,
},
+ {
+ id: "pdf" as const,
+ title: "PDF",
+ state: type === "pdf" ? ("on" as const) : undefined,
+ },
];
- const availableActions = allActions.filter((action) =>
+ const availableViewActions = viewActions.filter((action) =>
availableTypes.includes(action.id),
);
@@ -76,7 +89,7 @@ export default function BookmarkLinkTypeSelector({
Haptics.selectionAsync();
onChange(nativeEvent.event as BookmarkLinkType);
}}
- actions={availableActions}
+ actions={availableViewActions}
shouldOpenOnLongPress={false}
>
<ChevronDown onPress={() => Haptics.selectionAsync()} color="gray" />
diff --git a/apps/mobile/components/bookmarks/BookmarkLinkView.tsx b/apps/mobile/components/bookmarks/BookmarkLinkView.tsx
index e8a78029..ba4d5b0c 100644
--- a/apps/mobile/components/bookmarks/BookmarkLinkView.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkLinkView.tsx
@@ -1,6 +1,7 @@
import {
BookmarkLinkArchivePreview,
BookmarkLinkBrowserPreview,
+ BookmarkLinkPdfPreview,
BookmarkLinkReaderPreview,
BookmarkLinkScreenshotPreview,
} from "@/components/bookmarks/BookmarkLinkPreview";
@@ -31,5 +32,7 @@ export default function BookmarkLinkView({
return <BookmarkLinkScreenshotPreview bookmark={bookmark} />;
case "archive":
return <BookmarkLinkArchivePreview bookmark={bookmark} />;
+ case "pdf":
+ return <BookmarkLinkPdfPreview bookmark={bookmark} />;
}
}
diff --git a/apps/mobile/components/bookmarks/BookmarkList.tsx b/apps/mobile/components/bookmarks/BookmarkList.tsx
index adcf12e0..b3ac13e0 100644
--- a/apps/mobile/components/bookmarks/BookmarkList.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkList.tsx
@@ -30,6 +30,7 @@ export default function BookmarkList({
<Animated.FlatList
ref={flatListRef}
itemLayoutAnimation={LinearTransition}
+ contentInsetAdjustmentBehavior="automatic"
ListHeaderComponent={header}
contentContainerStyle={{
gap: 15,
diff --git a/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx b/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx
index e627ee16..25be7c2d 100644
--- a/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx
+++ b/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx
@@ -1,6 +1,7 @@
-import { api } from "@/lib/trpc";
+import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
import type { ZGetBookmarksRequest } from "@karakeep/shared/types/bookmarks";
+import { useTRPC } from "@karakeep/shared-react/trpc";
import { BookmarkTypes } from "@karakeep/shared/types/bookmarks";
import FullPageError from "../FullPageError";
@@ -14,7 +15,8 @@ export default function UpdatingBookmarkList({
query: Omit<ZGetBookmarksRequest, "sortOrder" | "includeContent">; // Sort order is not supported in mobile yet
header?: React.ReactElement;
}) {
- const apiUtils = api.useUtils();
+ const api = useTRPC();
+ const queryClient = useQueryClient();
const {
data,
isPending,
@@ -23,12 +25,14 @@ export default function UpdatingBookmarkList({
fetchNextPage,
isFetchingNextPage,
refetch,
- } = api.bookmarks.getBookmarks.useInfiniteQuery(
- { ...query, useCursorV2: true, includeContent: false },
- {
- initialCursor: null,
- getNextPageParam: (lastPage) => lastPage.nextCursor,
- },
+ } = useInfiniteQuery(
+ api.bookmarks.getBookmarks.infiniteQueryOptions(
+ { ...query, useCursorV2: true, includeContent: false },
+ {
+ initialCursor: null,
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
+ },
+ ),
);
if (error) {
@@ -40,8 +44,8 @@ export default function UpdatingBookmarkList({
}
const onRefresh = () => {
- apiUtils.bookmarks.getBookmarks.invalidate();
- apiUtils.bookmarks.getBookmark.invalidate();
+ queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter());
+ queryClient.invalidateQueries(api.bookmarks.getBookmark.pathFilter());
};
return (