mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-17 11:20:37 +09:00
feat: polish issue thread markdown and references
This commit is contained in:
parent
548721248e
commit
958c11699e
16 changed files with 659 additions and 44 deletions
|
|
@ -1,14 +1,22 @@
|
|||
import { isValidElement, useEffect, useId, useState, type ReactNode } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import Markdown, { type Components } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { cn } from "../lib/utils";
|
||||
import { useTheme } from "../context/ThemeContext";
|
||||
import { mentionChipInlineStyle, parseMentionChipHref } from "../lib/mention-chips";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { Link } from "@/lib/router";
|
||||
import { parseIssueReferenceFromHref, remarkLinkIssueReferences } from "../lib/issue-reference";
|
||||
import { remarkSoftBreaks } from "../lib/remark-soft-breaks";
|
||||
import { StatusIcon } from "./StatusIcon";
|
||||
|
||||
interface MarkdownBodyProps {
|
||||
children: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
softBreaks?: boolean;
|
||||
/** Optional resolver for relative image paths (e.g. within export packages) */
|
||||
resolveImageSrc?: (src: string) => string | null;
|
||||
/** Called when a user clicks an inline image */
|
||||
|
|
@ -17,6 +25,29 @@ interface MarkdownBodyProps {
|
|||
|
||||
let mermaidLoaderPromise: Promise<typeof import("mermaid").default> | null = null;
|
||||
|
||||
function MarkdownIssueLink({
|
||||
issuePathId,
|
||||
href,
|
||||
children,
|
||||
}: {
|
||||
issuePathId: string;
|
||||
href: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const { data } = useQuery({
|
||||
queryKey: queryKeys.issues.detail(issuePathId),
|
||||
queryFn: () => issuesApi.get(issuePathId),
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
return (
|
||||
<Link to={href} className="inline-flex items-center gap-1.5 align-baseline">
|
||||
{data ? <StatusIcon status={data.status} className="h-3.5 w-3.5" /> : null}
|
||||
<span>{children}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function loadMermaid() {
|
||||
if (!mermaidLoaderPromise) {
|
||||
mermaidLoaderPromise = import("mermaid").then((module) => module.default);
|
||||
|
|
@ -94,8 +125,11 @@ function MermaidDiagramBlock({ source, darkMode }: { source: string; darkMode: b
|
|||
);
|
||||
}
|
||||
|
||||
export function MarkdownBody({ children, className, style, resolveImageSrc, onImageClick }: MarkdownBodyProps) {
|
||||
export function MarkdownBody({ children, className, style, softBreaks = true, resolveImageSrc, onImageClick }: MarkdownBodyProps) {
|
||||
const { theme } = useTheme();
|
||||
const remarkPlugins = softBreaks
|
||||
? [remarkGfm, remarkLinkIssueReferences, remarkSoftBreaks]
|
||||
: [remarkGfm, remarkLinkIssueReferences];
|
||||
const components: Components = {
|
||||
pre: ({ node: _node, children: preChildren, ...preProps }) => {
|
||||
const mermaidSource = extractMermaidSource(preChildren);
|
||||
|
|
@ -105,6 +139,15 @@ export function MarkdownBody({ children, className, style, resolveImageSrc, onIm
|
|||
return <pre {...preProps}>{preChildren}</pre>;
|
||||
},
|
||||
a: ({ href, children: linkChildren }) => {
|
||||
const issueRef = parseIssueReferenceFromHref(href);
|
||||
if (issueRef) {
|
||||
return (
|
||||
<MarkdownIssueLink issuePathId={issueRef.issuePathId} href={issueRef.href}>
|
||||
{linkChildren}
|
||||
</MarkdownIssueLink>
|
||||
);
|
||||
}
|
||||
|
||||
const parsed = href ? parseMentionChipHref(href) : null;
|
||||
if (parsed) {
|
||||
const targetHref = parsed.kind === "project"
|
||||
|
|
@ -159,7 +202,7 @@ export function MarkdownBody({ children, className, style, resolveImageSrc, onIm
|
|||
)}
|
||||
style={style}
|
||||
>
|
||||
<Markdown remarkPlugins={[remarkGfm]} components={components} urlTransform={(url) => url}>
|
||||
<Markdown remarkPlugins={remarkPlugins} components={components} urlTransform={(url) => url}>
|
||||
{children}
|
||||
</Markdown>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue