import { splitPullRequestBody, type PullRequestBodySegment, } from "@t3tools/client-runtime/pull-request-markdown"; import type { ReactNode } from "react"; import { View } from "react-native"; import { useProject } from "../../state/entities"; import { pullRequestTarget, type PullRequestParams } from "./usePullRequestDiff"; import { PullRequestMedia, PullRequestMediaContext, usePullRequestMediaUrl, } from "./PullRequestMedia"; import { videoMimeType } from "@t3tools/shared/video"; import { createContext, useContext, useMemo, useState } from "react"; import { changeRequestReferenceAutolinker } from "@t3tools/client-runtime/pull-request-markdown"; import type { ReferenceAutolink } from "@t3tools/client-runtime/source-control-clients"; import { linkReferenceAutolinks } from "./pullRequestAutolinks"; import { useOpenPullRequest } from "../../native/useOpenPullRequest"; import { SelectableMarkdownText } from "../../native/SelectableMarkdownText"; import { useMarkdownPreviewStyles } from "../files/FileMarkdownPreview"; import { markdownImageSources } from "../../lib/markdownImageSources"; import { FilePreviewGallery, useFilePreviewGallery } from "../../components/FilePreviewGallery"; import { pullRequestImageSource } from "./pullRequestMediaSource"; import { FilePreviewModal, type FilePreviewSource, type FilePreviewItem, } from "../../components/FilePreviewModal"; import { ThreadMarkdownImageView } from "../threads/ThreadMarkdownImage"; import type { MarkdownImageRenderer } from "../../native/SelectableMarkdownText"; const AutolinksContext = createContext<((text: string) => ReadonlyArray) | null>( null, ); export function PullRequestMarkdownRepository(props: { /** The change request the markdown belongs to, whose host decides what its text links. */ readonly changeRequest: { readonly provider: string; readonly url: string } | null | undefined; readonly params: PullRequestParams; readonly children: ReactNode; }) { const provider = props.changeRequest?.provider; const url = props.changeRequest?.url; const autolinks = provider === undefined || url === undefined ? null : changeRequestReferenceAutolinker({ provider, url }); const target = pullRequestTarget(props.params); const project = useProject({ environmentId: target.environmentId, projectId: target.input.projectId, }); const cwd = project?.workspaceRoot; const media = cwd ? { environmentId: target.environmentId, cwd } : null; return ( {props.children} ); } function MarkdownImage(props: { readonly href: string; readonly alt: string | null; readonly onPreview: (source: FilePreviewSource) => void; }) { const media = usePullRequestMediaUrl(props.href); const context = useContext(PullRequestMediaContext); const withGallery = useFilePreviewGallery(); return ( props.onPreview( withGallery({ ...pullRequestImageSource(props.href, context), name: source.name, sourceIdentifier: source.sourceIdentifier, }), ) } /> ); } export function PullRequestMarkdown(props: { readonly markdown: string; readonly imageGroupMarkdown?: string; }) { const segments = useMemo(() => splitPullRequestBody(props.markdown), [props.markdown]); const imageGroupMarkdown = props.imageGroupMarkdown ?? props.markdown; return ( {segments.map((segment) => ( ))} ); } function PullRequestBodySegmentView(props: { readonly segment: PullRequestBodySegment; readonly imageGroupMarkdown: string; }) { const { segment } = props; if (segment.kind === "markdown") { return ( ); } if (segment.kind === "attachment") { return ; } return ( {segment.rows.map((cells, rowIndex) => ( // oxlint-disable-next-line react/no-array-index-key -- rows have no identity besides position {cells.map((cell, cellIndex) => ( // oxlint-disable-next-line react/no-array-index-key -- cells have no identity besides position {cell.segments.map((cellSegment) => ( ))} ))} ))} ); } function PullRequestMarkdownContent(props: { readonly markdown: string; readonly imageGroupMarkdown?: string; }) { const autolinks = useContext(AutolinksContext); const mediaContext = useContext(PullRequestMediaContext); const markdown = linkReferenceAutolinks(props.markdown, autolinks); const openPullRequest = useOpenPullRequest(); const openLink = (href: string) => { void openPullRequest(href); }; const gallery: FilePreviewItem[] = markdownImageSources( props.imageGroupMarkdown ?? props.markdown, ) .filter( (uri) => /^https?:\/\//i.test(uri) && videoMimeType({ name: uri.split(/[?#]/)[0] ?? uri, mimeType: "" }) === null, ) .map((uri) => pullRequestImageSource(uri, mediaContext)); const [preview, setPreview] = useState(null); const renderImage: MarkdownImageRenderer = (image) => ( ); const styles = useMarkdownPreviewStyles(); return ( setPreview(pullRequestImageSource(href, mediaContext))} /> setPreview(null)} /> ); }