@contractspec/lib.example-shared-ui 6.0.6 → 6.0.7
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/.turbo/turbo-build.log +90 -84
- package/AGENTS.md +43 -25
- package/README.md +63 -35
- package/dist/EvolutionDashboard.js +9 -9
- package/dist/EvolutionSidebar.js +15 -15
- package/dist/LocalDataIndicator.js +3 -3
- package/dist/MarkdownView.d.ts +0 -7
- package/dist/MarkdownView.js +76 -172
- package/dist/PersonalizationInsights.js +12 -12
- package/dist/SaveToStudioButton.js +2 -2
- package/dist/SpecDrivenTemplateShell.d.ts +1 -1
- package/dist/SpecDrivenTemplateShell.js +10 -10
- package/dist/SpecEditorPanel.js +3 -3
- package/dist/TemplateShell.js +10 -10
- package/dist/browser/EvolutionDashboard.js +9 -9
- package/dist/browser/EvolutionSidebar.js +15 -15
- package/dist/browser/LocalDataIndicator.js +3 -3
- package/dist/browser/MarkdownView.js +76 -172
- package/dist/browser/PersonalizationInsights.js +12 -12
- package/dist/browser/SaveToStudioButton.js +2 -2
- package/dist/browser/SpecDrivenTemplateShell.js +10 -10
- package/dist/browser/SpecEditorPanel.js +3 -3
- package/dist/browser/TemplateShell.js +10 -10
- package/dist/browser/hooks/index.js +29 -29
- package/dist/browser/index.js +193 -286
- package/dist/browser/lib/component-registry.js +1 -1
- package/dist/browser/markdown/formatPresentationName.js +9 -0
- package/dist/browser/markdown/useMarkdownPresentation.js +65 -0
- package/dist/hooks/index.d.ts +3 -3
- package/dist/hooks/index.js +29 -29
- package/dist/index.d.ts +12 -11
- package/dist/index.js +193 -286
- package/dist/lib/component-registry.js +1 -1
- package/dist/markdown/formatPresentationName.d.ts +1 -0
- package/dist/markdown/formatPresentationName.js +10 -0
- package/dist/markdown/useMarkdownPresentation.d.ts +21 -0
- package/dist/markdown/useMarkdownPresentation.js +66 -0
- package/dist/node/EvolutionDashboard.js +9 -9
- package/dist/node/EvolutionSidebar.js +15 -15
- package/dist/node/LocalDataIndicator.js +3 -3
- package/dist/node/MarkdownView.js +76 -172
- package/dist/node/PersonalizationInsights.js +12 -12
- package/dist/node/SaveToStudioButton.js +2 -2
- package/dist/node/SpecDrivenTemplateShell.js +10 -10
- package/dist/node/SpecEditorPanel.js +3 -3
- package/dist/node/TemplateShell.js +10 -10
- package/dist/node/hooks/index.js +29 -29
- package/dist/node/index.js +193 -286
- package/dist/node/lib/component-registry.js +1 -1
- package/dist/node/markdown/formatPresentationName.js +9 -0
- package/dist/node/markdown/useMarkdownPresentation.js +65 -0
- package/dist/utils/index.d.ts +1 -1
- package/package.json +38 -11
- package/src/EvolutionDashboard.tsx +415 -415
- package/src/EvolutionSidebar.tsx +245 -245
- package/src/LocalDataIndicator.tsx +28 -28
- package/src/MarkdownView.tsx +119 -372
- package/src/OverlayContextProvider.tsx +272 -272
- package/src/PersonalizationInsights.tsx +232 -232
- package/src/SaveToStudioButton.tsx +51 -51
- package/src/SpecDrivenTemplateShell.tsx +59 -59
- package/src/SpecEditorPanel.tsx +138 -138
- package/src/TemplateShell.tsx +50 -50
- package/src/bundles/ExampleTemplateBundle.ts +78 -78
- package/src/hooks/index.ts +3 -3
- package/src/hooks/useBehaviorTracking.ts +252 -252
- package/src/hooks/useEvolution.ts +437 -437
- package/src/hooks/useRegistryTemplates.ts +42 -42
- package/src/hooks/useSpecContent.ts +214 -214
- package/src/hooks/useWorkflowComposer.ts +567 -567
- package/src/index.ts +12 -11
- package/src/lib/component-registry.tsx +40 -40
- package/src/lib/runtime-context.tsx +31 -31
- package/src/lib/types.ts +57 -57
- package/src/markdown/formatPresentationName.ts +9 -0
- package/src/markdown/useMarkdownPresentation.ts +107 -0
- package/src/overlay-types.ts +15 -15
- package/src/utils/fetchPresentationData.ts +13 -13
- package/src/utils/generateSpecFromTemplate.ts +29 -29
- package/src/utils/index.ts +1 -1
- package/tsconfig.json +8 -8
package/src/MarkdownView.tsx
CHANGED
|
@@ -1,29 +1,24 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
4
3
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Button,
|
|
5
|
+
ErrorState,
|
|
6
|
+
LoaderBlock,
|
|
7
|
+
MarkdownRenderer,
|
|
8
8
|
} from '@contractspec/lib.design-system';
|
|
9
|
-
import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
|
|
10
9
|
import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import type { TemplateId } from './lib/types';
|
|
14
|
-
|
|
10
|
+
import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
|
|
11
|
+
import { useCallback } from 'react';
|
|
15
12
|
import { useTemplateRuntime } from './lib/runtime-context';
|
|
13
|
+
import type { TemplateId } from './lib/types';
|
|
14
|
+
import { formatPresentationName } from './markdown/formatPresentationName';
|
|
15
|
+
import { useMarkdownPresentation } from './markdown/useMarkdownPresentation';
|
|
16
16
|
|
|
17
17
|
export interface MarkdownViewProps {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface MarkdownOutput {
|
|
25
|
-
mimeType: string;
|
|
26
|
-
body: string;
|
|
18
|
+
/** Optional override, otherwise comes from context */
|
|
19
|
+
templateId?: TemplateId;
|
|
20
|
+
presentationId?: string;
|
|
21
|
+
className?: string;
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
/**
|
|
@@ -31,359 +26,111 @@ interface MarkdownOutput {
|
|
|
31
26
|
* It allows switching between available presentations for the template.
|
|
32
27
|
*/
|
|
33
28
|
export function MarkdownView({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
templateId: propTemplateId,
|
|
30
|
+
presentationId,
|
|
31
|
+
className,
|
|
37
32
|
}: MarkdownViewProps) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<Button
|
|
142
|
-
key={name}
|
|
143
|
-
variant={selectedPresentation === name ? 'default' : 'outline'}
|
|
144
|
-
size="sm"
|
|
145
|
-
onPress={() => setSelectedPresentation(name)}
|
|
146
|
-
>
|
|
147
|
-
{formatPresentationName(name)}
|
|
148
|
-
</Button>
|
|
149
|
-
))}
|
|
150
|
-
<div className="ml-auto flex items-center gap-2">
|
|
151
|
-
<Badge variant="secondary">LLM-friendly</Badge>
|
|
152
|
-
<Button
|
|
153
|
-
variant="outline"
|
|
154
|
-
size="sm"
|
|
155
|
-
onPress={handleCopy}
|
|
156
|
-
disabled={!markdownContent || loading}
|
|
157
|
-
>
|
|
158
|
-
Copy
|
|
159
|
-
</Button>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
{/* Content Area */}
|
|
164
|
-
<Card className="overflow-hidden">
|
|
165
|
-
{loading && <LoaderBlock label="Rendering markdown..." />}
|
|
166
|
-
|
|
167
|
-
{error && (
|
|
168
|
-
<ErrorState
|
|
169
|
-
title="Render failed"
|
|
170
|
-
description={error.message}
|
|
171
|
-
onRetry={renderMarkdown}
|
|
172
|
-
retryLabel="Retry"
|
|
173
|
-
/>
|
|
174
|
-
)}
|
|
175
|
-
|
|
176
|
-
{!loading && !error && markdownContent && (
|
|
177
|
-
<div className="p-6">
|
|
178
|
-
<MarkdownRenderer content={markdownContent} />
|
|
179
|
-
</div>
|
|
180
|
-
)}
|
|
181
|
-
|
|
182
|
-
{!loading && !error && !markdownContent && (
|
|
183
|
-
<div className="p-6 text-center">
|
|
184
|
-
<p className="text-muted-foreground">
|
|
185
|
-
Select a presentation to view its markdown output.
|
|
186
|
-
</p>
|
|
187
|
-
</div>
|
|
188
|
-
)}
|
|
189
|
-
</Card>
|
|
190
|
-
</div>
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Simple markdown renderer using pre-formatted display
|
|
196
|
-
* For production, consider using react-markdown or similar
|
|
197
|
-
*/
|
|
198
|
-
export function MarkdownRenderer({ content }: { content: string }) {
|
|
199
|
-
const lines = content.split('\n');
|
|
200
|
-
const rendered: React.ReactNode[] = [];
|
|
201
|
-
let i = 0;
|
|
202
|
-
|
|
203
|
-
while (i < lines.length) {
|
|
204
|
-
const line = lines[i] ?? '';
|
|
205
|
-
|
|
206
|
-
// Check for table (starts with | and next line is separator)
|
|
207
|
-
if (line.startsWith('|') && lines[i + 1]?.match(/^\|[\s-|]+\|$/)) {
|
|
208
|
-
const tableLines: string[] = [line];
|
|
209
|
-
i++;
|
|
210
|
-
// Collect all table lines
|
|
211
|
-
while (i < lines.length && (lines[i]?.startsWith('|') ?? false)) {
|
|
212
|
-
tableLines.push(lines[i] ?? '');
|
|
213
|
-
i++;
|
|
214
|
-
}
|
|
215
|
-
rendered.push(renderTable(tableLines, rendered.length));
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Headers
|
|
220
|
-
if (line.startsWith('# ')) {
|
|
221
|
-
rendered.push(
|
|
222
|
-
<h1 key={i} className="mb-4 text-2xl font-bold">
|
|
223
|
-
{line.slice(2)}
|
|
224
|
-
</h1>
|
|
225
|
-
);
|
|
226
|
-
} else if (line.startsWith('## ')) {
|
|
227
|
-
rendered.push(
|
|
228
|
-
<h2 key={i} className="mt-6 mb-3 text-xl font-semibold">
|
|
229
|
-
{line.slice(3)}
|
|
230
|
-
</h2>
|
|
231
|
-
);
|
|
232
|
-
} else if (line.startsWith('### ')) {
|
|
233
|
-
rendered.push(
|
|
234
|
-
<h3 key={i} className="mt-4 mb-2 text-lg font-medium">
|
|
235
|
-
{line.slice(4)}
|
|
236
|
-
</h3>
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
// Blockquotes
|
|
240
|
-
else if (line.startsWith('> ')) {
|
|
241
|
-
rendered.push(
|
|
242
|
-
<blockquote
|
|
243
|
-
key={i}
|
|
244
|
-
className="text-muted-foreground my-2 border-l-4 border-violet-500/50 pl-4 italic"
|
|
245
|
-
>
|
|
246
|
-
{line.slice(2)}
|
|
247
|
-
</blockquote>
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
// List items
|
|
251
|
-
else if (line.startsWith('- ')) {
|
|
252
|
-
rendered.push(
|
|
253
|
-
<li key={i} className="ml-4 list-disc">
|
|
254
|
-
{formatInlineMarkdown(line.slice(2))}
|
|
255
|
-
</li>
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
// Bold text (lines starting with **)
|
|
259
|
-
else if (line.startsWith('**') && line.includes(':**')) {
|
|
260
|
-
const [label, ...rest] = line.split(':**');
|
|
261
|
-
rendered.push(
|
|
262
|
-
<p key={i} className="my-1">
|
|
263
|
-
<strong>{label?.slice(2)}:</strong>
|
|
264
|
-
{rest.join(':**')}
|
|
265
|
-
</p>
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
// Italic text (lines starting with _)
|
|
269
|
-
else if (line.startsWith('_') && line.endsWith('_')) {
|
|
270
|
-
rendered.push(
|
|
271
|
-
<p key={i} className="text-muted-foreground my-1 italic">
|
|
272
|
-
{line.slice(1, -1)}
|
|
273
|
-
</p>
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
// Empty lines
|
|
277
|
-
else if (!line.trim()) {
|
|
278
|
-
rendered.push(<div key={i} className="h-2" />);
|
|
279
|
-
}
|
|
280
|
-
// Regular text
|
|
281
|
-
else {
|
|
282
|
-
rendered.push(
|
|
283
|
-
<p key={i} className="my-1">
|
|
284
|
-
{formatInlineMarkdown(line)}
|
|
285
|
-
</p>
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
i++;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return (
|
|
293
|
-
<div className="prose prose-sm dark:prose-invert max-w-none">
|
|
294
|
-
{rendered}
|
|
295
|
-
</div>
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Render a markdown table
|
|
301
|
-
*/
|
|
302
|
-
function renderTable(lines: string[], keyPrefix: number): React.ReactNode {
|
|
303
|
-
if (lines.length < 2) return null;
|
|
304
|
-
|
|
305
|
-
const parseRow = (row: string) =>
|
|
306
|
-
row
|
|
307
|
-
.split('|')
|
|
308
|
-
.slice(1, -1)
|
|
309
|
-
.map((cell) => cell.trim());
|
|
310
|
-
|
|
311
|
-
const headers = parseRow(lines[0] ?? '');
|
|
312
|
-
// Skip separator line (index 1)
|
|
313
|
-
const dataRows = lines.slice(2).map(parseRow);
|
|
314
|
-
|
|
315
|
-
return (
|
|
316
|
-
<div key={`table-${keyPrefix}`} className="my-4 overflow-x-auto">
|
|
317
|
-
<table className="border-border min-w-full border-collapse border text-sm">
|
|
318
|
-
<thead>
|
|
319
|
-
<tr className="bg-muted/50">
|
|
320
|
-
{headers.map((header, idx) => (
|
|
321
|
-
<th
|
|
322
|
-
key={idx}
|
|
323
|
-
className="border-border border px-3 py-2 text-left font-semibold"
|
|
324
|
-
>
|
|
325
|
-
{header}
|
|
326
|
-
</th>
|
|
327
|
-
))}
|
|
328
|
-
</tr>
|
|
329
|
-
</thead>
|
|
330
|
-
<tbody>
|
|
331
|
-
{dataRows.map((row, rowIdx) => (
|
|
332
|
-
<tr key={rowIdx} className="hover:bg-muted/30">
|
|
333
|
-
{row.map((cell, cellIdx) => (
|
|
334
|
-
<td key={cellIdx} className="border-border border px-3 py-2">
|
|
335
|
-
{formatInlineMarkdown(cell)}
|
|
336
|
-
</td>
|
|
337
|
-
))}
|
|
338
|
-
</tr>
|
|
339
|
-
))}
|
|
340
|
-
</tbody>
|
|
341
|
-
</table>
|
|
342
|
-
</div>
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Format inline markdown (bold, code)
|
|
348
|
-
*/
|
|
349
|
-
function formatInlineMarkdown(text: string): React.ReactNode {
|
|
350
|
-
// Handle **bold** text
|
|
351
|
-
const parts = text.split(/(\*\*[^*]+\*\*)/g);
|
|
352
|
-
return parts.map((part, i) => {
|
|
353
|
-
if (part.startsWith('**') && part.endsWith('**')) {
|
|
354
|
-
return <strong key={i}>{part.slice(2, -2)}</strong>;
|
|
355
|
-
}
|
|
356
|
-
// Handle `code` text
|
|
357
|
-
if (part.includes('`')) {
|
|
358
|
-
const codeParts = part.split(/(`[^`]+`)/g);
|
|
359
|
-
return codeParts.map((cp, j) => {
|
|
360
|
-
if (cp.startsWith('`') && cp.endsWith('`')) {
|
|
361
|
-
return (
|
|
362
|
-
<code
|
|
363
|
-
key={`${i}-${j}`}
|
|
364
|
-
className="rounded bg-violet-500/10 px-1.5 py-0.5 font-mono text-sm"
|
|
365
|
-
>
|
|
366
|
-
{cp.slice(1, -1)}
|
|
367
|
-
</code>
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
return cp;
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
return part;
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Format presentation name for display
|
|
379
|
-
*/
|
|
380
|
-
function formatPresentationName(name: string): string {
|
|
381
|
-
// Extract the last part after the last dot
|
|
382
|
-
const parts = name.split('.');
|
|
383
|
-
const lastPart = parts[parts.length - 1] ?? name;
|
|
384
|
-
// Convert kebab-case to Title Case
|
|
385
|
-
return lastPart
|
|
386
|
-
.split('-')
|
|
387
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
388
|
-
.join(' ');
|
|
33
|
+
const {
|
|
34
|
+
engine,
|
|
35
|
+
template,
|
|
36
|
+
templateId: contextTemplateId,
|
|
37
|
+
resolvePresentation,
|
|
38
|
+
fetchData,
|
|
39
|
+
} = useTemplateRuntime();
|
|
40
|
+
|
|
41
|
+
const templateId = propTemplateId ?? contextTemplateId;
|
|
42
|
+
const presentations = (template?.presentations as string[]) ?? [];
|
|
43
|
+
const {
|
|
44
|
+
error,
|
|
45
|
+
loading,
|
|
46
|
+
markdownContent,
|
|
47
|
+
renderMarkdown,
|
|
48
|
+
selectedPresentation,
|
|
49
|
+
setSelectedPresentation,
|
|
50
|
+
} = useMarkdownPresentation({
|
|
51
|
+
engine,
|
|
52
|
+
fetchData,
|
|
53
|
+
presentationId,
|
|
54
|
+
presentations,
|
|
55
|
+
resolvePresentation,
|
|
56
|
+
templateId,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!presentations.length) {
|
|
60
|
+
return (
|
|
61
|
+
<Card className={className}>
|
|
62
|
+
<div className="p-6 text-center">
|
|
63
|
+
<p className="text-muted-foreground">
|
|
64
|
+
No presentations available for this template.
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
</Card>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleCopy = useCallback(() => {
|
|
72
|
+
if (markdownContent) {
|
|
73
|
+
void navigator.clipboard.writeText(markdownContent);
|
|
74
|
+
}
|
|
75
|
+
}, [markdownContent]);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className={className}>
|
|
79
|
+
{/* Presentation Selector */}
|
|
80
|
+
<div className="mb-4 flex flex-wrap items-center gap-2">
|
|
81
|
+
<span className="font-medium text-muted-foreground text-sm">
|
|
82
|
+
Presentation:
|
|
83
|
+
</span>
|
|
84
|
+
{presentations.map((name) => (
|
|
85
|
+
<Button
|
|
86
|
+
key={name}
|
|
87
|
+
variant={selectedPresentation === name ? 'default' : 'outline'}
|
|
88
|
+
size="sm"
|
|
89
|
+
onPress={() => setSelectedPresentation(name)}
|
|
90
|
+
>
|
|
91
|
+
{formatPresentationName(name)}
|
|
92
|
+
</Button>
|
|
93
|
+
))}
|
|
94
|
+
<div className="ml-auto flex items-center gap-2">
|
|
95
|
+
<Badge variant="secondary">LLM-friendly</Badge>
|
|
96
|
+
<Button
|
|
97
|
+
variant="outline"
|
|
98
|
+
size="sm"
|
|
99
|
+
onPress={handleCopy}
|
|
100
|
+
disabled={!markdownContent || loading}
|
|
101
|
+
>
|
|
102
|
+
Copy
|
|
103
|
+
</Button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Content Area */}
|
|
108
|
+
<Card className="overflow-hidden">
|
|
109
|
+
{loading && <LoaderBlock label="Rendering markdown..." />}
|
|
110
|
+
|
|
111
|
+
{error && (
|
|
112
|
+
<ErrorState
|
|
113
|
+
title="Render failed"
|
|
114
|
+
description={error.message}
|
|
115
|
+
onRetry={renderMarkdown}
|
|
116
|
+
retryLabel="Retry"
|
|
117
|
+
/>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{!loading && !error && markdownContent && (
|
|
121
|
+
<div className="p-6">
|
|
122
|
+
<MarkdownRenderer content={markdownContent} />
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{!loading && !error && !markdownContent && (
|
|
127
|
+
<div className="p-6 text-center">
|
|
128
|
+
<p className="text-muted-foreground">
|
|
129
|
+
Select a presentation to view its markdown output.
|
|
130
|
+
</p>
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
</Card>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
389
136
|
}
|