newpr 1.0.25 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newpr",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "description": "AI-powered large PR review tool - understand PRs with 1000+ lines of changes",
5
5
  "module": "src/cli/index.ts",
6
6
  "type": "module",
@@ -75,6 +75,7 @@
75
75
  "katex": "^0.16.28",
76
76
  "lucide-react": "^0.567.0",
77
77
  "meriyah": "^7.1.0",
78
+ "mermaid": "^11.12.3",
78
79
  "react": "19.1.0",
79
80
  "react-dom": "19.1.0",
80
81
  "react-markdown": "^10.1.0",
@@ -1,4 +1,4 @@
1
- import { memo, useState, useEffect, useMemo } from "react";
1
+ import { memo, useState, useEffect, useMemo, isValidElement } from "react";
2
2
  import ReactMarkdown from "react-markdown";
3
3
  import remarkGfm from "remark-gfm";
4
4
  import remarkMath from "remark-math";
@@ -211,6 +211,75 @@ function preprocess(text: string): string {
211
211
  return processed.replace(/\x00MATH_BLOCK_(\d+)\x00/g, (_, idx) => mathBlocks[Number(idx)]!);
212
212
  }
213
213
 
214
+ let mermaidCounter = 0;
215
+ let mermaidModule: typeof import("mermaid") | null = null;
216
+ let mermaidLoading: Promise<typeof import("mermaid")> | null = null;
217
+
218
+ function loadMermaid(): Promise<typeof import("mermaid")> {
219
+ if (mermaidModule) return Promise.resolve(mermaidModule);
220
+ if (!mermaidLoading) {
221
+ mermaidLoading = import("mermaid").then((m) => {
222
+ mermaidModule = m;
223
+ return m;
224
+ });
225
+ }
226
+ return mermaidLoading;
227
+ }
228
+
229
+ function MermaidBlock({ code, dark }: { code: string; dark: boolean }) {
230
+ const [svg, setSvg] = useState<string>("");
231
+ const [error, setError] = useState(false);
232
+
233
+ useEffect(() => {
234
+ let cancelled = false;
235
+ const id = `mermaid-${++mermaidCounter}`;
236
+
237
+ loadMermaid()
238
+ .then(({ default: mermaid }) => {
239
+ if (cancelled) return;
240
+ mermaid.initialize({
241
+ startOnLoad: false,
242
+ theme: dark ? "dark" : "default",
243
+ securityLevel: "loose",
244
+ fontFamily: "inherit",
245
+ });
246
+ return mermaid.render(id, code);
247
+ })
248
+ .then((result) => {
249
+ if (!cancelled && result) {
250
+ setSvg(result.svg);
251
+ setError(false);
252
+ }
253
+ })
254
+ .catch(() => {
255
+ if (!cancelled) setError(true);
256
+ });
257
+
258
+ return () => {
259
+ cancelled = true;
260
+ };
261
+ }, [code, dark]);
262
+
263
+ if (error) {
264
+ return <code className="text-xs font-mono whitespace-pre-wrap">{code}</code>;
265
+ }
266
+
267
+ if (!svg) {
268
+ return (
269
+ <div className="flex items-center justify-center py-6 text-xs text-muted-foreground/40">
270
+ Rendering diagram…
271
+ </div>
272
+ );
273
+ }
274
+
275
+ return (
276
+ <div
277
+ className="my-2 overflow-x-auto [&>svg]:mx-auto [&>svg]:max-w-full"
278
+ dangerouslySetInnerHTML={{ __html: svg }}
279
+ />
280
+ );
281
+ }
282
+
214
283
  export const Markdown = memo(function Markdown({ children, onAnchorClick, activeId, streaming = false }: MarkdownProps) {
215
284
  const processed = useMemo(() => preprocess(children), [children]);
216
285
  const hl = useHighlighter();
@@ -234,6 +303,10 @@ export const Markdown = memo(function Markdown({ children, onAnchorClick, active
234
303
  }
235
304
  return <code className="px-1.5 py-0.5 rounded bg-muted text-xs font-mono">{children}</code>;
236
305
  }
306
+ if (className?.includes("language-mermaid")) {
307
+ const raw = String(children).replace(/\n$/, "");
308
+ return <MermaidBlock code={raw} dark={dark} />;
309
+ }
237
310
  const lang = langFromClassName(className);
238
311
  if (lang && hl) {
239
312
  const code = String(children).replace(/\n$/, "");
@@ -250,9 +323,14 @@ export const Markdown = memo(function Markdown({ children, onAnchorClick, active
250
323
  }
251
324
  return <code className="px-1.5 py-0.5 rounded bg-muted text-xs font-mono">{children}</code>;
252
325
  },
253
- pre: ({ children }) => (
254
- <pre className="bg-muted rounded-lg p-4 overflow-x-auto mb-3 whitespace-pre text-xs font-mono [&>span>pre]:!bg-transparent [&>span>pre]:!p-0 [&>span>pre]:!m-0">{children}</pre>
255
- ),
326
+ pre: ({ children }) => {
327
+ if (isValidElement(children) && children.type === MermaidBlock) {
328
+ return <div className="rounded-lg border border-border/50 bg-muted/30 p-4 mb-3 overflow-x-auto">{children}</div>;
329
+ }
330
+ return (
331
+ <pre className="bg-muted rounded-lg p-4 overflow-x-auto mb-3 whitespace-pre text-xs font-mono [&>span>pre]:!bg-transparent [&>span>pre]:!p-0 [&>span>pre]:!m-0">{children}</pre>
332
+ );
333
+ },
256
334
  span: ({ children, ...props }) => {
257
335
  const allProps = props as Record<string, unknown>;
258
336
  const lineRef = allProps["data-line-ref"] as string | undefined;