more-compute 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
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.
- frontend/.DS_Store +0 -0
- frontend/.gitignore +41 -0
- frontend/README.md +36 -0
- frontend/__init__.py +1 -0
- frontend/app/favicon.ico +0 -0
- frontend/app/globals.css +1537 -0
- frontend/app/layout.tsx +173 -0
- frontend/app/page.tsx +11 -0
- frontend/components/AddCellButton.tsx +42 -0
- frontend/components/Cell.tsx +244 -0
- frontend/components/CellButton.tsx +58 -0
- frontend/components/CellOutput.tsx +70 -0
- frontend/components/ErrorDisplay.tsx +208 -0
- frontend/components/ErrorModal.tsx +154 -0
- frontend/components/MarkdownRenderer.tsx +84 -0
- frontend/components/Notebook.tsx +520 -0
- frontend/components/Sidebar.tsx +46 -0
- frontend/components/popups/ComputePopup.tsx +879 -0
- frontend/components/popups/FilterPopup.tsx +427 -0
- frontend/components/popups/FolderPopup.tsx +171 -0
- frontend/components/popups/MetricsPopup.tsx +168 -0
- frontend/components/popups/PackagesPopup.tsx +112 -0
- frontend/components/popups/PythonPopup.tsx +292 -0
- frontend/components/popups/SettingsPopup.tsx +68 -0
- frontend/eslint.config.mjs +25 -0
- frontend/lib/api.ts +469 -0
- frontend/lib/settings.ts +87 -0
- frontend/lib/websocket-native.ts +202 -0
- frontend/lib/websocket.ts +134 -0
- frontend/next-env.d.ts +6 -0
- frontend/next.config.mjs +17 -0
- frontend/next.config.ts +7 -0
- frontend/package-lock.json +5676 -0
- frontend/package.json +41 -0
- frontend/postcss.config.mjs +5 -0
- frontend/public/assets/icons/add.svg +1 -0
- frontend/public/assets/icons/check.svg +1 -0
- frontend/public/assets/icons/copy.svg +1 -0
- frontend/public/assets/icons/folder.svg +1 -0
- frontend/public/assets/icons/metric.svg +1 -0
- frontend/public/assets/icons/packages.svg +1 -0
- frontend/public/assets/icons/play.svg +1 -0
- frontend/public/assets/icons/python.svg +265 -0
- frontend/public/assets/icons/setting.svg +1 -0
- frontend/public/assets/icons/stop.svg +1 -0
- frontend/public/assets/icons/trash.svg +1 -0
- frontend/public/assets/icons/up-down.svg +1 -0
- frontend/public/assets/icons/x.svg +1 -0
- frontend/public/file.svg +1 -0
- frontend/public/fonts/Fira.ttf +0 -0
- frontend/public/fonts/Tiempos.woff2 +0 -0
- frontend/public/fonts/VeraMono.ttf +0 -0
- frontend/public/globe.svg +1 -0
- frontend/public/next.svg +1 -0
- frontend/public/vercel.svg +1 -0
- frontend/public/window.svg +1 -0
- frontend/tailwind.config.ts +29 -0
- frontend/tsconfig.json +27 -0
- frontend/types/notebook.ts +58 -0
- kernel_run.py +6 -0
- {more_compute-0.1.2.dist-info → more_compute-0.1.3.dist-info}/METADATA +1 -1
- more_compute-0.1.3.dist-info/RECORD +85 -0
- {more_compute-0.1.2.dist-info → more_compute-0.1.3.dist-info}/top_level.txt +1 -0
- more_compute-0.1.2.dist-info/RECORD +0 -26
- {more_compute-0.1.2.dist-info → more_compute-0.1.3.dist-info}/WHEEL +0 -0
- {more_compute-0.1.2.dist-info → more_compute-0.1.3.dist-info}/entry_points.txt +0 -0
- {more_compute-0.1.2.dist-info → more_compute-0.1.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { FC, useState } from 'react';
|
|
4
|
+
import { Copy, Check } from 'lucide-react';
|
|
5
|
+
import { Output, ErrorOutput } from '@/types/notebook';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
I would like custom error handling for most general errors
|
|
9
|
+
|
|
10
|
+
imagine like user does not know pip is ran with !pip install, and just runs pip install,
|
|
11
|
+
|
|
12
|
+
it would be nice just to have a custom error message pointing user to use !pip rather than just fail, etc
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
interface ErrorDisplayProps {
|
|
17
|
+
error: Output;
|
|
18
|
+
maxLines?: number;
|
|
19
|
+
onFixIndentation?: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const TypedErrorDisplay: FC<{ error: ErrorOutput; onFixIndentation?: () => void }> = ({ error, onFixIndentation }) => {
|
|
23
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
24
|
+
const isIndentationError = error.ename === 'IndentationError';
|
|
25
|
+
|
|
26
|
+
const copyToClipboard = () => {
|
|
27
|
+
const errorDetails = `Error: ${error.ename}: ${error.evalue}\n\nTraceback:\n${error.traceback.join('\n')}`;
|
|
28
|
+
navigator.clipboard.writeText(errorDetails).then(() => {
|
|
29
|
+
setIsCopied(true);
|
|
30
|
+
setTimeout(() => setIsCopied(false), 2000);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getErrorIcon = (errorType?: string) => {
|
|
35
|
+
switch (errorType) {
|
|
36
|
+
case 'pip_error':
|
|
37
|
+
return {
|
|
38
|
+
text: 'Use !pip install instead of pip install',
|
|
39
|
+
style: {
|
|
40
|
+
background: '#fef3c7',
|
|
41
|
+
color: '#d97706',
|
|
42
|
+
border: '1px solid #fbbf24'
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
case 'import_error':
|
|
46
|
+
return {
|
|
47
|
+
text: 'Import Error',
|
|
48
|
+
style: {
|
|
49
|
+
background: '#fee2e2',
|
|
50
|
+
color: '#dc2626',
|
|
51
|
+
border: '1px solid #f87171'
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
case 'file_error':
|
|
55
|
+
return {
|
|
56
|
+
text: 'File Error',
|
|
57
|
+
style: {
|
|
58
|
+
background: '#fdf4ff',
|
|
59
|
+
color: '#c026d3',
|
|
60
|
+
border: '1px solid #e879f9'
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
default:
|
|
64
|
+
return {
|
|
65
|
+
text: 'Error',
|
|
66
|
+
style: {
|
|
67
|
+
background: '#f3f4f6',
|
|
68
|
+
color: '#6b7280',
|
|
69
|
+
border: '1px solid #d1d5db'
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const indicator = getErrorIcon(error.error_type);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="error-output-container">
|
|
79
|
+
{/* Error Type Indicator */}
|
|
80
|
+
{error.error_type && (
|
|
81
|
+
<div
|
|
82
|
+
style={{
|
|
83
|
+
padding: '8px 12px',
|
|
84
|
+
marginBottom: '8px',
|
|
85
|
+
borderRadius: '4px',
|
|
86
|
+
fontSize: '12px',
|
|
87
|
+
fontWeight: 600,
|
|
88
|
+
letterSpacing: '0.3px',
|
|
89
|
+
...indicator.style
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{indicator.text}
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{/* Traceback Section */}
|
|
97
|
+
<div
|
|
98
|
+
style={{
|
|
99
|
+
position: 'relative',
|
|
100
|
+
background: '#fef2f2',
|
|
101
|
+
border: '1px solid #fca5a5',
|
|
102
|
+
borderRadius: '6px',
|
|
103
|
+
marginTop: '8px'
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
{/* Action Buttons */}
|
|
107
|
+
<div style={{
|
|
108
|
+
position: 'absolute',
|
|
109
|
+
top: '8px',
|
|
110
|
+
right: '8px',
|
|
111
|
+
zIndex: 10,
|
|
112
|
+
display: 'flex',
|
|
113
|
+
gap: '4px'
|
|
114
|
+
}}>
|
|
115
|
+
{/* Fix Indentation Button */}
|
|
116
|
+
{isIndentationError && onFixIndentation && (
|
|
117
|
+
<button
|
|
118
|
+
onClick={onFixIndentation}
|
|
119
|
+
style={{
|
|
120
|
+
background: 'rgba(59, 130, 246, 0.1)',
|
|
121
|
+
border: '1px solid #3b82f6',
|
|
122
|
+
borderRadius: '4px',
|
|
123
|
+
padding: '6px 10px',
|
|
124
|
+
cursor: 'pointer',
|
|
125
|
+
display: 'flex',
|
|
126
|
+
alignItems: 'center',
|
|
127
|
+
justifyContent: 'center',
|
|
128
|
+
transition: 'all 0.2s ease',
|
|
129
|
+
fontSize: '11px',
|
|
130
|
+
fontWeight: 500,
|
|
131
|
+
color: '#3b82f6'
|
|
132
|
+
}}
|
|
133
|
+
title="Auto-fix indentation"
|
|
134
|
+
>
|
|
135
|
+
Fix Indent
|
|
136
|
+
</button>
|
|
137
|
+
)}
|
|
138
|
+
{/* Copy Button */}
|
|
139
|
+
<button
|
|
140
|
+
onClick={copyToClipboard}
|
|
141
|
+
style={{
|
|
142
|
+
background: 'rgba(255, 255, 255, 0.9)',
|
|
143
|
+
border: '1px solid #d1d5db',
|
|
144
|
+
borderRadius: '4px',
|
|
145
|
+
padding: '6px',
|
|
146
|
+
cursor: 'pointer',
|
|
147
|
+
display: 'flex',
|
|
148
|
+
alignItems: 'center',
|
|
149
|
+
justifyContent: 'center',
|
|
150
|
+
transition: 'all 0.2s ease'
|
|
151
|
+
}}
|
|
152
|
+
title="Copy error to clipboard"
|
|
153
|
+
>
|
|
154
|
+
{isCopied ? <Check size={14} color="#10b981" /> : <Copy size={14} />}
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{/* Truncation Indicator */}
|
|
159
|
+
{/* The original code had this, but the new TypedErrorDisplay doesn't have it.
|
|
160
|
+
Assuming it's not needed for the new TypedErrorDisplay or that it's handled differently.
|
|
161
|
+
For now, removing it as it's not in the new_code. */}
|
|
162
|
+
{/*
|
|
163
|
+
{isLimited && (
|
|
164
|
+
<div
|
|
165
|
+
style={{
|
|
166
|
+
padding: '8px 12px',
|
|
167
|
+
background: '#fee2e2',
|
|
168
|
+
color: '#b91c1c',
|
|
169
|
+
fontSize: '11px',
|
|
170
|
+
borderBottom: '1px solid #fca5a5',
|
|
171
|
+
fontStyle: 'italic'
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
... (showing last {maxLines} lines of {tracebackLines.length} total lines - scroll up to see more)
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
*/}
|
|
178
|
+
|
|
179
|
+
{/* Error Content */}
|
|
180
|
+
<div
|
|
181
|
+
style={{
|
|
182
|
+
padding: '12px',
|
|
183
|
+
fontFamily: "'SF Mono', Monaco, Consolas, monospace",
|
|
184
|
+
fontSize: '12px',
|
|
185
|
+
lineHeight: 1.4,
|
|
186
|
+
color: '#b91c1c',
|
|
187
|
+
background: 'transparent',
|
|
188
|
+
whiteSpace: 'pre-wrap',
|
|
189
|
+
overflowX: 'auto',
|
|
190
|
+
margin: 0
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
{error.traceback?.join('\n') || ''}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, onFixIndentation }) => {
|
|
201
|
+
// Type guard to ensure we have an ErrorOutput
|
|
202
|
+
if (error.output_type !== 'error') {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return <TypedErrorDisplay error={error} onFixIndentation={onFixIndentation} />;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export default ErrorDisplay;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ExternalLink, X } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
interface ErrorModalProps {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
title: string;
|
|
8
|
+
message: string;
|
|
9
|
+
actionLabel?: string;
|
|
10
|
+
actionUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ErrorModal: React.FC<ErrorModalProps> = ({
|
|
14
|
+
isOpen,
|
|
15
|
+
onClose,
|
|
16
|
+
title,
|
|
17
|
+
message,
|
|
18
|
+
actionLabel,
|
|
19
|
+
actionUrl,
|
|
20
|
+
}) => {
|
|
21
|
+
if (!isOpen) return null;
|
|
22
|
+
|
|
23
|
+
const handleAction = () => {
|
|
24
|
+
if (actionUrl) {
|
|
25
|
+
window.open(actionUrl, "_blank");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
style={{
|
|
32
|
+
position: "fixed",
|
|
33
|
+
top: 0,
|
|
34
|
+
left: 0,
|
|
35
|
+
right: 0,
|
|
36
|
+
bottom: 0,
|
|
37
|
+
backgroundColor: "rgba(0, 0, 0, 0.4)",
|
|
38
|
+
display: "flex",
|
|
39
|
+
alignItems: "center",
|
|
40
|
+
justifyContent: "center",
|
|
41
|
+
zIndex: 10000,
|
|
42
|
+
}}
|
|
43
|
+
onClick={onClose}
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
style={{
|
|
47
|
+
backgroundColor: "#1a1a1a",
|
|
48
|
+
borderRadius: "8px",
|
|
49
|
+
padding: "24px",
|
|
50
|
+
maxWidth: "500px",
|
|
51
|
+
width: "90%",
|
|
52
|
+
maxHeight: "80vh",
|
|
53
|
+
overflow: "auto",
|
|
54
|
+
border: "2px solid #444",
|
|
55
|
+
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.8)",
|
|
56
|
+
}}
|
|
57
|
+
onClick={(e) => e.stopPropagation()}
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
style={{
|
|
61
|
+
display: "flex",
|
|
62
|
+
justifyContent: "space-between",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
marginBottom: "16px",
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<h2
|
|
68
|
+
style={{
|
|
69
|
+
margin: 0,
|
|
70
|
+
fontSize: "18px",
|
|
71
|
+
fontWeight: 600,
|
|
72
|
+
color: "#ff6b6b",
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{title}
|
|
76
|
+
</h2>
|
|
77
|
+
<button
|
|
78
|
+
onClick={onClose}
|
|
79
|
+
style={{
|
|
80
|
+
background: "none",
|
|
81
|
+
border: "none",
|
|
82
|
+
cursor: "pointer",
|
|
83
|
+
padding: "4px",
|
|
84
|
+
display: "flex",
|
|
85
|
+
alignItems: "center",
|
|
86
|
+
color: "#999",
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<X size={20} />
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div
|
|
94
|
+
style={{
|
|
95
|
+
marginBottom: "20px",
|
|
96
|
+
fontSize: "14px",
|
|
97
|
+
lineHeight: "1.6",
|
|
98
|
+
whiteSpace: "pre-line",
|
|
99
|
+
color: "#e0e0e0",
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
{message}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div
|
|
106
|
+
style={{
|
|
107
|
+
display: "flex",
|
|
108
|
+
gap: "8px",
|
|
109
|
+
justifyContent: "flex-end",
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
{actionUrl && actionLabel && (
|
|
113
|
+
<button
|
|
114
|
+
onClick={handleAction}
|
|
115
|
+
style={{
|
|
116
|
+
padding: "10px 20px",
|
|
117
|
+
backgroundColor: "#4a9eff",
|
|
118
|
+
color: "white",
|
|
119
|
+
border: "none",
|
|
120
|
+
borderRadius: "4px",
|
|
121
|
+
cursor: "pointer",
|
|
122
|
+
fontSize: "14px",
|
|
123
|
+
fontWeight: 500,
|
|
124
|
+
display: "flex",
|
|
125
|
+
alignItems: "center",
|
|
126
|
+
gap: "6px",
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
{actionLabel}
|
|
130
|
+
<ExternalLink size={16} />
|
|
131
|
+
</button>
|
|
132
|
+
)}
|
|
133
|
+
<button
|
|
134
|
+
onClick={onClose}
|
|
135
|
+
style={{
|
|
136
|
+
padding: "10px 20px",
|
|
137
|
+
backgroundColor: "#2a2a2a",
|
|
138
|
+
color: "#e0e0e0",
|
|
139
|
+
border: "1px solid #444",
|
|
140
|
+
borderRadius: "4px",
|
|
141
|
+
cursor: "pointer",
|
|
142
|
+
fontSize: "14px",
|
|
143
|
+
fontWeight: 500,
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
Close
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export default ErrorModal;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
interface MarkdownRendererProps {
|
|
6
|
+
source: string;
|
|
7
|
+
onClick?: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ source, onClick }) => {
|
|
11
|
+
const escapeHtml = (text: string) => {
|
|
12
|
+
const div = document.createElement('div');
|
|
13
|
+
div.textContent = text;
|
|
14
|
+
return div.innerHTML;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const renderMarkdown = (text: string) => {
|
|
18
|
+
let html = text;
|
|
19
|
+
|
|
20
|
+
// Code blocks (must be processed first)
|
|
21
|
+
html = html.replace(/```([\s\S]*?)```/g, (match, code) => {
|
|
22
|
+
return `<pre><code>${escapeHtml(code.trim())}</code></pre>`;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Language-specific code blocks
|
|
26
|
+
html = html.replace(/```(\w+)\n([\s\S]*?)```/g, (match, lang, code) => {
|
|
27
|
+
return `<pre><code class="language-${lang}">${escapeHtml(code.trim())}</code></pre>`;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Headers
|
|
31
|
+
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
|
|
32
|
+
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
|
|
33
|
+
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
|
|
34
|
+
|
|
35
|
+
// Bold and Italic
|
|
36
|
+
html = html.replace(/\*\*\*([^*]+)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
37
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
38
|
+
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
39
|
+
|
|
40
|
+
// Strikethrough
|
|
41
|
+
html = html.replace(/~~([^~]+)~~/g, '<del>$1</del>');
|
|
42
|
+
|
|
43
|
+
// Links
|
|
44
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
45
|
+
|
|
46
|
+
// Unordered lists
|
|
47
|
+
html = html.replace(/^\s*[-*+] (.+)$/gim, (match, item) => `<li>${item}</li>`);
|
|
48
|
+
html = html.replace(/(<li>[\s\S]*?<\/li>)/g, '<ul>$1</ul>').replace(/<\/ul>\s*<ul>/g, '');
|
|
49
|
+
|
|
50
|
+
// Ordered lists
|
|
51
|
+
html = html.replace(/^\s*\d+\. (.+)$/gim, (match, item) => `<ol><li>${item}</li></ol>`).replace(/<\/ol>\s*<ol>/g, '');
|
|
52
|
+
|
|
53
|
+
// Inline code
|
|
54
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
55
|
+
|
|
56
|
+
// Blockquotes
|
|
57
|
+
html = html.replace(/^> (.+)$/gim, '<blockquote>$1</blockquote>');
|
|
58
|
+
|
|
59
|
+
// Handle HTML br tags first - convert them to proper line breaks
|
|
60
|
+
html = html.replace(/<br\s*\/?>/gi, '\n');
|
|
61
|
+
|
|
62
|
+
// Line breaks and paragraphs - handle paragraph separation properly
|
|
63
|
+
// Split by double newlines to create paragraphs, but preserve single line breaks within paragraphs
|
|
64
|
+
html = html.replace(/\n\s*\n/g, '</p><p>'); // Paragraph breaks (double newlines with optional whitespace)
|
|
65
|
+
html = html.replace(/\n(?!<\/p>)/g, '<br>'); // Line breaks within paragraphs (but not paragraph breaks)
|
|
66
|
+
|
|
67
|
+
// Ensure content is wrapped in paragraph tags if not already in a block element
|
|
68
|
+
if (!html.match(/^<(h[1-6]|ul|ol|pre|blockquote|hr|p)/) && html.trim()) {
|
|
69
|
+
html = '<p>' + html + '</p>';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return html;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
className="markdown-rendered"
|
|
78
|
+
onClick={onClick}
|
|
79
|
+
dangerouslySetInnerHTML={{ __html: renderMarkdown(source) }}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default MarkdownRenderer;
|