diff --git a/apps/mobile/app/project/[projectId]/viewer.tsx b/apps/mobile/app/project/[projectId]/viewer.tsx index bdcc65f..86b1b13 100644 --- a/apps/mobile/app/project/[projectId]/viewer.tsx +++ b/apps/mobile/app/project/[projectId]/viewer.tsx @@ -3,7 +3,10 @@ import { ActivityIndicator, ScrollView, View } from "react-native" import { useLocalSearchParams, useRouter } from "expo-router" import { useSafeAreaInsets } from "react-native-safe-area-context" import { useColorScheme } from "nativewind" +import * as Clipboard from "expo-clipboard" import ArrowLeftIcon from "lucide-react-native/dist/esm/icons/arrow-left" +import CheckIcon from "lucide-react-native/dist/esm/icons/check" +import CopyIcon from "lucide-react-native/dist/esm/icons/copy" import FileQuestionIcon from "lucide-react-native/dist/esm/icons/file-question" import { Button } from "@/components/ui/button" import { Text } from "@/components/ui/text" @@ -31,6 +34,14 @@ export default function ViewerPage() { const [truncated, setTruncated] = useState(false) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + const [copied, setCopied] = useState(false) + + const copyFile = useCallback(async () => { + if (!content) return + await Clipboard.setStringAsync(content) + setCopied(true) + setTimeout(() => setCopied(false), 1500) + }, [content]) const binary = useMemo(() => isBinaryPath(filePath), [filePath]) const language = useMemo(() => languageFromPath(filePath), [filePath]) @@ -83,9 +94,18 @@ export default function ViewerPage() { {directory || "/"} {!loading && content !== null && ( - - {language} - + )} @@ -114,7 +134,7 @@ export default function ViewerPage() { className="flex-1" contentContainerStyle={{ padding: 12, paddingBottom: insets.bottom + 20 }} > - + {truncated && ( Preview limited to the first {MAX_LINES.toLocaleString()} lines. diff --git a/apps/mobile/components/code-block.tsx b/apps/mobile/components/code-block.tsx index 3141a18..f2475cb 100644 --- a/apps/mobile/components/code-block.tsx +++ b/apps/mobile/components/code-block.tsx @@ -20,9 +20,11 @@ interface CodeBlockProps { text: string language?: string theme: "light" | "dark" + header?: boolean + lineNumbers?: boolean } -export function CodeBlock({ text, language, theme }: CodeBlockProps) { +export function CodeBlock({ text, language, theme, header = true, lineNumbers = false }: CodeBlockProps) { const [copied, setCopied] = useState(false) ensurePrismLanguages() const prismTheme = theme === "dark" ? themes.oneDark : themes.github @@ -36,6 +38,56 @@ export function CodeBlock({ text, language, theme }: CodeBlockProps) { setTimeout(() => setCopied(false), 1500) } + if (!header) { + return ( + + + {({ tokens, getTokenProps }) => ( + + {tokens.map((line, lineIndex) => ( + + {lineNumbers && ( + + {lineIndex + 1} + + )} + + {line.map((token, tokenIndex) => { + const tokenProps = getTokenProps({ token }) + return ( + + {tokenProps.children} + + ) + })} + + + ))} + + )} + + + ) + } + return ( MAX_CONTENT_BYTES ? text.slice(0, MAX_CONTENT_BYTES) : text }