Skip to content

Commit 0a76219

Browse files
Add Markdown File Viewer (#1384)
* add markdown viewer * fix "</body>" showing as markdown content for a few seconds in markdown viewer * fix indentation * replace markdown library 'uiw/react-markdown-preview' with 'marked' * update marked * add comment to explain removal of anchor links * fix formatting with pre-commit --------- Co-authored-by: Pamela Fox <[email protected]>
1 parent 4ce59be commit 0a76219

File tree

6 files changed

+164
-5
lines changed

6 files changed

+164
-5
lines changed

app/frontend/package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@fluentui/react-components": "^9.37.3",
1919
"@fluentui/react-icons": "^2.0.221",
2020
"@react-spring/web": "^9.7.3",
21+
"marked": "^9.1.6",
2122
"dompurify": "^3.0.6",
2223
"react": "^18.2.0",
2324
"react-dom": "^18.2.0",

app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SupportingContent } from "../SupportingContent";
66
import { ChatAppResponse } from "../../api";
77
import { AnalysisPanelTabs } from "./AnalysisPanelTabs";
88
import { ThoughtProcess } from "./ThoughtProcess";
9+
import { MarkdownViewer } from "../MarkdownViewer";
910
import { useMsal } from "@azure/msal-react";
1011
import { getHeaders } from "../../api";
1112
import { useLogin, getToken } from "../../authConfig";
@@ -53,6 +54,22 @@ export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeigh
5354
fetchCitation();
5455
}, []);
5556

57+
const renderFileViewer = () => {
58+
if (!activeCitation) {
59+
return null;
60+
}
61+
62+
const fileExtension = activeCitation.split(".").pop()?.toLowerCase();
63+
switch (fileExtension) {
64+
case "png":
65+
return <img src={citation} className={styles.citationImg} alt="Citation Image" />;
66+
case "md":
67+
return <MarkdownViewer src={activeCitation} />;
68+
default:
69+
return <iframe title="Citation" src={citation} width="100%" height={citationHeight} />;
70+
}
71+
};
72+
5673
return (
5774
<Pivot
5875
className={className}
@@ -78,11 +95,7 @@ export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeigh
7895
headerText="Citation"
7996
headerButtonProps={isDisabledCitationTab ? pivotItemDisabledStyle : undefined}
8097
>
81-
{activeCitation?.endsWith(".png") ? (
82-
<img src={citation} className={styles.citationImg} />
83-
) : (
84-
<iframe title="Citation" src={citation} width="100%" height={citationHeight} />
85-
)}
98+
{renderFileViewer()}
8699
</PivotItem>
87100
</Pivot>
88101
);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
.downloadButton {
2+
position: relative;
3+
float: right;
4+
}
5+
6+
.markdownViewer {
7+
border-radius: 8px;
8+
box-shadow:
9+
#0000000d 0 0 0 1px,
10+
#0000001a 0 2px 3px;
11+
background-color: white;
12+
margin: 20px 0;
13+
}
14+
15+
.loading {
16+
padding: 100px;
17+
height: 100vh;
18+
background-color: white;
19+
}
20+
21+
.error {
22+
height: 100vh;
23+
background-color: white;
24+
}
25+
26+
.markdown {
27+
padding: 30px;
28+
}
29+
30+
table {
31+
border-collapse: collapse;
32+
}
33+
34+
th,
35+
td {
36+
border: 1px solid #ddd;
37+
padding: 8px;
38+
}
39+
40+
tr:nth-child(even) {
41+
background-color: #f6f8fa;
42+
}
43+
44+
code {
45+
display: block;
46+
font-family: monospace;
47+
padding: 10px;
48+
background-color: #f6f8fa;
49+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import React, { useState, useEffect } from "react";
2+
import { marked } from "marked";
3+
import styles from "./MarkdownViewer.module.css";
4+
import { Spinner, SpinnerSize, MessageBar, MessageBarType, Link, IconButton } from "@fluentui/react";
5+
6+
interface MarkdownViewerProps {
7+
src: string;
8+
}
9+
10+
export const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ src }) => {
11+
const [content, setContent] = useState<string>("");
12+
const [isLoading, setIsLoading] = useState<boolean>(true);
13+
const [error, setError] = useState<Error | null>(null);
14+
15+
/**
16+
* Anchor links are not handled well by 'marked' and result in HTTP 404 errors as the URL they point to does not exist.
17+
* This function removes them from the resulted HTML.
18+
*/
19+
const removeAnchorLinks = (html: string) => {
20+
const ancorLinksRegex = /<a\s+(?:[^>]*?\s+)?href=['"](#[^"']*?)['"][^>]*?>/g;
21+
return html.replace(ancorLinksRegex, "");
22+
};
23+
24+
useEffect(() => {
25+
const fetchMarkdown = async () => {
26+
try {
27+
const response = await fetch(src);
28+
29+
if (!response.ok) {
30+
throw new Error("Failed loading markdown file.");
31+
}
32+
33+
const markdownText = await response.text();
34+
const parsedHtml = await marked.parse(markdownText);
35+
const cleanedHtml = removeAnchorLinks(parsedHtml);
36+
setContent(cleanedHtml);
37+
} catch (error: any) {
38+
setError(error);
39+
} finally {
40+
setIsLoading(false);
41+
}
42+
};
43+
44+
fetchMarkdown();
45+
}, [src]);
46+
47+
return (
48+
<div>
49+
{isLoading ? (
50+
<div className={`${styles.loading} ${styles.markdownViewer}`}>
51+
<Spinner size={SpinnerSize.large} label="Loading file" />
52+
</div>
53+
) : error ? (
54+
<div className={`${styles.error} ${styles.markdownViewer}`}>
55+
<MessageBar messageBarType={MessageBarType.error} isMultiline={false}>
56+
{error.message}
57+
<Link href={src} download>
58+
Download the file
59+
</Link>
60+
</MessageBar>
61+
</div>
62+
) : (
63+
<div>
64+
<IconButton
65+
className={styles.downloadButton}
66+
style={{ color: "black" }}
67+
iconProps={{ iconName: "Save" }}
68+
title="Save"
69+
ariaLabel="Save"
70+
href={src}
71+
download
72+
/>
73+
<div className={`${styles.markdown} ${styles.markdownViewer}`} dangerouslySetInnerHTML={{ __html: content }} />
74+
</div>
75+
)}
76+
</div>
77+
);
78+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./MarkdownViewer";

0 commit comments

Comments
 (0)