@dfosco/storyboard-react 4.2.0-beta.0 → 4.2.0-beta.17
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 +5 -4
- package/src/AuthModal/AuthModal.jsx +6 -2
- package/src/BranchBar/BranchBar.jsx +17 -5
- package/src/BranchBar/BranchBar.module.css +11 -2
- package/src/CommandPalette/CommandPalette.jsx +267 -164
- package/src/CommandPalette/command-palette.css +130 -78
- package/src/Icon.jsx +112 -48
- package/src/Viewfinder.jsx +511 -61
- package/src/Viewfinder.module.css +414 -2
- package/src/canvas/CanvasPage.bridge.test.jsx +14 -6
- package/src/canvas/CanvasPage.dragdrop.test.jsx +10 -6
- package/src/canvas/CanvasPage.jsx +157 -174
- package/src/canvas/CanvasPage.module.css +0 -15
- package/src/canvas/CanvasPage.multiselect.test.jsx +10 -6
- package/src/canvas/ConnectorLayer.jsx +5 -5
- package/src/canvas/PageSelector.test.jsx +15 -6
- package/src/canvas/useCanvas.js +1 -1
- package/src/canvas/widgets/ActionWidget.jsx +200 -0
- package/src/canvas/widgets/ActionWidget.module.css +122 -0
- package/src/canvas/widgets/FigmaEmbed.jsx +97 -29
- package/src/canvas/widgets/FigmaEmbed.module.css +61 -0
- package/src/canvas/widgets/ImageWidget.jsx +1 -1
- package/src/canvas/widgets/LinkPreview.jsx +64 -5
- package/src/canvas/widgets/LinkPreview.module.css +127 -0
- package/src/canvas/widgets/MarkdownBlock.jsx +39 -17
- package/src/canvas/widgets/MarkdownBlock.module.css +123 -0
- package/src/canvas/widgets/PrototypeEmbed.jsx +183 -20
- package/src/canvas/widgets/PrototypeEmbed.module.css +117 -0
- package/src/canvas/widgets/PrototypeEmbed.test.jsx +2 -2
- package/src/canvas/widgets/SplitExpandModal.jsx +234 -0
- package/src/canvas/widgets/SplitExpandModal.module.css +335 -0
- package/src/canvas/widgets/SplitScreenTopBar.jsx +30 -0
- package/src/canvas/widgets/SplitScreenTopBar.module.css +58 -0
- package/src/canvas/widgets/StoryWidget.jsx +7 -4
- package/src/canvas/widgets/TerminalReadWidget.jsx +140 -0
- package/src/canvas/widgets/TerminalReadWidget.module.css +92 -0
- package/src/canvas/widgets/TerminalWidget.jsx +299 -49
- package/src/canvas/widgets/TerminalWidget.module.css +155 -1
- package/src/canvas/widgets/WidgetChrome.jsx +19 -14
- package/src/canvas/widgets/WidgetChrome.module.css +10 -0
- package/src/canvas/widgets/embedInteraction.test.jsx +24 -26
- package/src/canvas/widgets/expandUtils.js +188 -0
- package/src/canvas/widgets/index.js +5 -0
- package/src/canvas/widgets/snapshotDisplay.test.jsx +23 -71
- package/src/canvas/widgets/widgetConfig.js +19 -1
- package/src/hooks/useConfig.js +14 -0
- package/src/index.js +4 -0
- package/src/vite/data-plugin.js +264 -14
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from 'react'
|
|
2
2
|
import { remark } from 'remark'
|
|
3
3
|
import remarkGfm from 'remark-gfm'
|
|
4
4
|
import remarkHtml from 'remark-html'
|
|
@@ -6,6 +6,7 @@ import { MarkGithubIcon } from '@primer/octicons-react'
|
|
|
6
6
|
import WidgetWrapper from './WidgetWrapper.jsx'
|
|
7
7
|
import ResizeHandle from './ResizeHandle.jsx'
|
|
8
8
|
import { readProp, linkPreviewSchema } from './widgetProps.js'
|
|
9
|
+
import SplitExpandModal from './SplitExpandModal.jsx'
|
|
9
10
|
import styles from './LinkPreview.module.css'
|
|
10
11
|
|
|
11
12
|
const VIDEO_EXT_RE = /\.(mp4|mov|webm|ogg)(\?[^)]*)?$/i
|
|
@@ -106,7 +107,7 @@ function getCommentKindLabel(github) {
|
|
|
106
107
|
return 'Comment'
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
function GitHubIssueCard({ url, title, github, width, collapsed,
|
|
110
|
+
function GitHubIssueCard({ id, url, title, github, width, collapsed, expanded, onCloseExpand }) {
|
|
110
111
|
const authors = Array.isArray(github?.authors)
|
|
111
112
|
? github.authors.filter((a) => typeof a === 'string' && a.trim())
|
|
112
113
|
: []
|
|
@@ -146,6 +147,7 @@ function GitHubIssueCard({ url, title, github, width, collapsed, onUpdate }) {
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
return (
|
|
150
|
+
<>
|
|
149
151
|
<WidgetWrapper>
|
|
150
152
|
<div className={`${styles.issueCard} ${collapsed ? styles.issueCardCollapsed : ''}`} style={sizeStyle}>
|
|
151
153
|
<div className={styles.typeBar}>
|
|
@@ -204,10 +206,38 @@ function GitHubIssueCard({ url, title, github, width, collapsed, onUpdate }) {
|
|
|
204
206
|
)}
|
|
205
207
|
</div>
|
|
206
208
|
</WidgetWrapper>
|
|
209
|
+
<SplitExpandModal
|
|
210
|
+
expanded={expanded}
|
|
211
|
+
onClose={onCloseExpand}
|
|
212
|
+
widgetId={id}
|
|
213
|
+
title={`${kindLabel}: ${titleText || url || 'GitHub'}`}
|
|
214
|
+
>
|
|
215
|
+
<div className={styles.expandedIssue}>
|
|
216
|
+
<header className={styles.expandedIssueHeader}>
|
|
217
|
+
<h2 className={styles.expandedIssueTitle}>
|
|
218
|
+
<a href={url || '#'} target="_blank" rel="noopener noreferrer">
|
|
219
|
+
{titleText || url}
|
|
220
|
+
{issueNumber && <span className={styles.expandedIssueNumber}> {issueNumber}</span>}
|
|
221
|
+
</a>
|
|
222
|
+
</h2>
|
|
223
|
+
<div className={styles.expandedByline}>
|
|
224
|
+
{primaryAuthor && (
|
|
225
|
+
<a href={`https://github.com/${primaryAuthor}`} target="_blank" rel="noopener noreferrer" className={styles.expandedAuthor}>
|
|
226
|
+
<img src={`https://github.com/${primaryAuthor}.png?size=40`} alt="" width="20" height="20" className={styles.avatar} loading="lazy" />
|
|
227
|
+
{primaryAuthor}
|
|
228
|
+
</a>
|
|
229
|
+
)}
|
|
230
|
+
{createdAgo && <span className={styles.expandedBylineText}>{primaryAuthor ? ` opened ${createdAgo}` : `Opened ${createdAgo}`}</span>}
|
|
231
|
+
</div>
|
|
232
|
+
</header>
|
|
233
|
+
{bodyHtml && <div className={styles.expandedIssueBody} dangerouslySetInnerHTML={{ __html: bodyHtml }} />}
|
|
234
|
+
</div>
|
|
235
|
+
</SplitExpandModal>
|
|
236
|
+
</>
|
|
207
237
|
)
|
|
208
238
|
}
|
|
209
239
|
|
|
210
|
-
export default function LinkPreview({ props, onUpdate, resizable }) {
|
|
240
|
+
export default forwardRef(function LinkPreview({ id, props, onUpdate, resizable }, ref) {
|
|
211
241
|
const url = readProp(props, 'url', linkPreviewSchema)
|
|
212
242
|
const title = readProp(props, 'title', linkPreviewSchema)
|
|
213
243
|
const github = props?.github && typeof props.github === 'object' ? props.github : null
|
|
@@ -222,6 +252,18 @@ export default function LinkPreview({ props, onUpdate, resizable }) {
|
|
|
222
252
|
const cardRef = useRef(null)
|
|
223
253
|
const inputRef = useRef(null)
|
|
224
254
|
const [editing, setEditing] = useState(false)
|
|
255
|
+
const [expanded, setExpanded] = useState(false)
|
|
256
|
+
|
|
257
|
+
useImperativeHandle(ref, () => ({
|
|
258
|
+
handleAction(actionId) {
|
|
259
|
+
if (actionId === 'expand' || actionId === 'split-screen') { setExpanded(true); return true }
|
|
260
|
+
if (actionId === 'open-external') {
|
|
261
|
+
if (url) window.open(url, '_blank', 'noopener')
|
|
262
|
+
return true
|
|
263
|
+
}
|
|
264
|
+
return false
|
|
265
|
+
},
|
|
266
|
+
}), [url])
|
|
225
267
|
|
|
226
268
|
const startEditing = useCallback(() => {
|
|
227
269
|
if (!canEdit) return
|
|
@@ -242,12 +284,14 @@ export default function LinkPreview({ props, onUpdate, resizable }) {
|
|
|
242
284
|
if (github) {
|
|
243
285
|
return (
|
|
244
286
|
<GitHubIssueCard
|
|
287
|
+
id={id}
|
|
245
288
|
url={url}
|
|
246
289
|
title={title}
|
|
247
290
|
github={github}
|
|
248
291
|
width={width}
|
|
249
292
|
collapsed={!!props?.collapsed}
|
|
250
|
-
|
|
293
|
+
expanded={expanded}
|
|
294
|
+
onCloseExpand={() => setExpanded(false)}
|
|
251
295
|
/>
|
|
252
296
|
)
|
|
253
297
|
}
|
|
@@ -262,6 +306,7 @@ export default function LinkPreview({ props, onUpdate, resizable }) {
|
|
|
262
306
|
const handleResize = (w, h) => onUpdate?.({ width: w, height: h })
|
|
263
307
|
|
|
264
308
|
return (
|
|
309
|
+
<>
|
|
265
310
|
<div className={styles.container}>
|
|
266
311
|
<div ref={cardRef} className={styles.card} style={sizeStyle}>
|
|
267
312
|
{ogImage && (
|
|
@@ -316,5 +361,19 @@ export default function LinkPreview({ props, onUpdate, resizable }) {
|
|
|
316
361
|
</div>
|
|
317
362
|
{resizable && <ResizeHandle targetRef={cardRef} width={width} height={height} onResize={handleResize} />}
|
|
318
363
|
</div>
|
|
364
|
+
<SplitExpandModal
|
|
365
|
+
expanded={expanded}
|
|
366
|
+
onClose={() => setExpanded(false)}
|
|
367
|
+
widgetId={id}
|
|
368
|
+
title={title || hostname || 'Link Preview'}
|
|
369
|
+
>
|
|
370
|
+
<div className={styles.expandedLink}>
|
|
371
|
+
{ogImage && <img className={styles.expandedOgImage} src={ogImage} alt="" loading="lazy" />}
|
|
372
|
+
<h2 className={styles.expandedTitle}>{title || hostname || url || 'Untitled'}</h2>
|
|
373
|
+
{description && <p className={styles.expandedDescription}>{description}</p>}
|
|
374
|
+
{url && <a href={url} target="_blank" rel="noopener noreferrer" className={styles.expandedUrl}>{url}</a>}
|
|
375
|
+
</div>
|
|
376
|
+
</SplitExpandModal>
|
|
377
|
+
</>
|
|
319
378
|
)
|
|
320
|
-
}
|
|
379
|
+
})
|
|
@@ -417,3 +417,130 @@
|
|
|
417
417
|
font-size: 12px;
|
|
418
418
|
color: var(--fgColor-danger, #cf222e);
|
|
419
419
|
}
|
|
420
|
+
|
|
421
|
+
/* ── Expanded issue view in modal ─────────────────────────────────── */
|
|
422
|
+
|
|
423
|
+
.expandedIssue {
|
|
424
|
+
height: 100%;
|
|
425
|
+
display: flex;
|
|
426
|
+
flex-direction: column;
|
|
427
|
+
background: var(--bgColor-default, #ffffff);
|
|
428
|
+
font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.expandedIssueHeader {
|
|
432
|
+
padding: 24px 40px 16px;
|
|
433
|
+
border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.expandedIssueTitle {
|
|
437
|
+
margin: 0 0 8px;
|
|
438
|
+
font-size: 28px;
|
|
439
|
+
font-weight: 400;
|
|
440
|
+
line-height: 1.25;
|
|
441
|
+
color: var(--fgColor-default, #1f2328);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.expandedIssueTitle a {
|
|
445
|
+
color: inherit;
|
|
446
|
+
text-decoration: none;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.expandedIssueTitle a:hover {
|
|
450
|
+
text-decoration: underline;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.expandedIssueNumber {
|
|
454
|
+
color: var(--fgColor-muted, #656d76);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.expandedByline {
|
|
458
|
+
display: flex;
|
|
459
|
+
align-items: center;
|
|
460
|
+
gap: 8px;
|
|
461
|
+
font-size: 13px;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.expandedAuthor {
|
|
465
|
+
display: flex;
|
|
466
|
+
align-items: center;
|
|
467
|
+
gap: 6px;
|
|
468
|
+
text-decoration: none;
|
|
469
|
+
color: var(--fgColor-default, #1f2328);
|
|
470
|
+
font-weight: 600;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.expandedAuthor:hover {
|
|
474
|
+
text-decoration: underline;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.expandedBylineText {
|
|
478
|
+
color: var(--fgColor-muted, #656d76);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.expandedIssueBody {
|
|
482
|
+
flex: 1;
|
|
483
|
+
overflow: auto;
|
|
484
|
+
padding: 24px 40px;
|
|
485
|
+
font-size: 15px;
|
|
486
|
+
line-height: 1.7;
|
|
487
|
+
color: var(--fgColor-default, #1f2328);
|
|
488
|
+
max-width: 800px;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.expandedIssueBody * { pointer-events: auto; }
|
|
492
|
+
.expandedIssueBody a { color: var(--fgColor-accent, #0969da); text-decoration: none; }
|
|
493
|
+
.expandedIssueBody a:hover { text-decoration: underline; }
|
|
494
|
+
.expandedIssueBody img { max-width: 100%; height: auto; border-radius: 6px; margin: 8px 0; display: block; }
|
|
495
|
+
.expandedIssueBody video { max-width: 100%; height: auto; border-radius: 6px; margin: 8px 0; display: block; }
|
|
496
|
+
.expandedIssueBody h1 { font-size: 20px; font-weight: 700; margin: 16px 0 8px; border-bottom: 1px solid var(--borderColor-muted, #d8dee4); padding-bottom: 4px; }
|
|
497
|
+
.expandedIssueBody h2 { font-size: 17px; font-weight: 600; margin: 14px 0 6px; }
|
|
498
|
+
.expandedIssueBody h3 { font-size: 15px; font-weight: 600; margin: 12px 0 4px; }
|
|
499
|
+
.expandedIssueBody p { margin: 0 0 12px; }
|
|
500
|
+
.expandedIssueBody code { background: var(--bgColor-neutral-muted, #afb8c133); padding: 2px 5px; border-radius: 4px; font-size: 13px; font-family: ui-monospace, SFMono-Regular, monospace; }
|
|
501
|
+
.expandedIssueBody pre { padding: 12px 16px; border-radius: 6px; border: 1px solid var(--borderColor-muted, #d8dee4); overflow-x: auto; margin: 12px 0; background: var(--bgColor-neutral-muted, #afb8c133); }
|
|
502
|
+
.expandedIssueBody pre code { background: none; padding: 0; }
|
|
503
|
+
.expandedIssueBody ul { margin: 0 0 12px; padding-left: 24px; list-style: disc; }
|
|
504
|
+
.expandedIssueBody ol { margin: 0 0 12px; padding-left: 24px; list-style: decimal; }
|
|
505
|
+
.expandedIssueBody li { margin: 0 0 4px; display: list-item; }
|
|
506
|
+
.expandedIssueBody blockquote { border-left: 4px solid var(--borderColor-default, #d0d7de); margin: 12px 0; padding: 4px 16px; color: var(--fgColor-muted, #656d76); }
|
|
507
|
+
|
|
508
|
+
/* ── Expanded plain link view ─────────────────────────────────────── */
|
|
509
|
+
|
|
510
|
+
.expandedLink {
|
|
511
|
+
padding: 32px 40px;
|
|
512
|
+
font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.expandedOgImage {
|
|
516
|
+
max-width: 100%;
|
|
517
|
+
max-height: 400px;
|
|
518
|
+
object-fit: cover;
|
|
519
|
+
border-radius: 8px;
|
|
520
|
+
margin-bottom: 16px;
|
|
521
|
+
display: block;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.expandedTitle {
|
|
525
|
+
margin: 0 0 8px;
|
|
526
|
+
font-size: 24px;
|
|
527
|
+
font-weight: 600;
|
|
528
|
+
color: var(--fgColor-default, #1f2328);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.expandedDescription {
|
|
532
|
+
margin: 0 0 12px;
|
|
533
|
+
font-size: 15px;
|
|
534
|
+
line-height: 1.5;
|
|
535
|
+
color: var(--fgColor-muted, #656d76);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.expandedUrl {
|
|
539
|
+
font-size: 14px;
|
|
540
|
+
color: var(--fgColor-accent, #0969da);
|
|
541
|
+
text-decoration: none;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.expandedUrl:hover {
|
|
545
|
+
text-decoration: underline;
|
|
546
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useRef, useEffect, useCallback, useMemo } from 'react'
|
|
1
|
+
import { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react'
|
|
2
2
|
import { remark } from 'remark'
|
|
3
3
|
import remarkGfm from 'remark-gfm'
|
|
4
4
|
import remarkHtml from 'remark-html'
|
|
@@ -6,6 +6,7 @@ import WidgetWrapper from './WidgetWrapper.jsx'
|
|
|
6
6
|
import ResizeHandle from './ResizeHandle.jsx'
|
|
7
7
|
import { readProp } from './widgetProps.js'
|
|
8
8
|
import { schemas } from './widgetConfig.js'
|
|
9
|
+
import SplitExpandModal from './SplitExpandModal.jsx'
|
|
9
10
|
import styles from './MarkdownBlock.module.css'
|
|
10
11
|
|
|
11
12
|
const markdownSchema = schemas['markdown']
|
|
@@ -64,18 +65,26 @@ async function highlightCodeBlocks(html) {
|
|
|
64
65
|
)
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
export default function MarkdownBlock({ props, onUpdate, resizable }) {
|
|
68
|
+
export default forwardRef(function MarkdownBlock({ id, props, onUpdate, resizable }, ref) {
|
|
68
69
|
const content = readProp(props, 'content', markdownSchema)
|
|
69
70
|
const width = readProp(props, 'width', markdownSchema)
|
|
70
71
|
const height = props?.height
|
|
71
72
|
const collapsed = !!props?.collapsed
|
|
72
73
|
const canEdit = typeof onUpdate === 'function'
|
|
73
74
|
const [editing, setEditing] = useState(false)
|
|
75
|
+
const [expanded, setExpanded] = useState(false)
|
|
74
76
|
const editingActive = canEdit && editing
|
|
75
77
|
const textareaRef = useRef(null)
|
|
76
78
|
const blockRef = useRef(null)
|
|
77
79
|
const [editHeight, setEditHeight] = useState(null)
|
|
78
80
|
|
|
81
|
+
useImperativeHandle(ref, () => ({
|
|
82
|
+
handleAction(actionId) {
|
|
83
|
+
if (actionId === 'expand' || actionId === 'split-screen') { setExpanded(true); return true }
|
|
84
|
+
return false
|
|
85
|
+
},
|
|
86
|
+
}), [])
|
|
87
|
+
|
|
79
88
|
const handleResize = useCallback((w, h) => {
|
|
80
89
|
onUpdate?.({ width: w, height: h })
|
|
81
90
|
}, [onUpdate])
|
|
@@ -115,12 +124,17 @@ export default function MarkdownBlock({ props, onUpdate, resizable }) {
|
|
|
115
124
|
}
|
|
116
125
|
}, [canEdit, content])
|
|
117
126
|
|
|
127
|
+
const startEditing = useCallback(() => {
|
|
128
|
+
// Capture the preview height BEFORE React swaps to the textarea
|
|
129
|
+
if (blockRef.current) {
|
|
130
|
+
setEditHeight(blockRef.current.offsetHeight)
|
|
131
|
+
blockRef.current.dataset.scrollTop = blockRef.current.scrollTop
|
|
132
|
+
}
|
|
133
|
+
setEditing(true)
|
|
134
|
+
}, [])
|
|
135
|
+
|
|
118
136
|
useEffect(() => {
|
|
119
137
|
if (editingActive) {
|
|
120
|
-
// Capture the preview height before switching to editor
|
|
121
|
-
if (blockRef.current && !editHeight) {
|
|
122
|
-
setEditHeight(blockRef.current.offsetHeight)
|
|
123
|
-
}
|
|
124
138
|
if (textareaRef.current) {
|
|
125
139
|
// Place cursor at end and prevent scroll jump to top
|
|
126
140
|
const len = textareaRef.current.value.length
|
|
@@ -134,9 +148,10 @@ export default function MarkdownBlock({ props, onUpdate, resizable }) {
|
|
|
134
148
|
} else {
|
|
135
149
|
setEditHeight(null)
|
|
136
150
|
}
|
|
137
|
-
}, [editingActive
|
|
151
|
+
}, [editingActive])
|
|
138
152
|
|
|
139
153
|
return (
|
|
154
|
+
<>
|
|
140
155
|
<WidgetWrapper>
|
|
141
156
|
<div
|
|
142
157
|
ref={blockRef}
|
|
@@ -170,18 +185,11 @@ export default function MarkdownBlock({ props, onUpdate, resizable }) {
|
|
|
170
185
|
data-canvas-allow-text-selection={!canEdit ? '' : undefined}
|
|
171
186
|
onClick={!canEdit ? (e) => e.stopPropagation() : undefined}
|
|
172
187
|
onCopy={!canEdit ? handleReadOnlyCopy : undefined}
|
|
173
|
-
onDoubleClick={canEdit ?
|
|
174
|
-
// Save scroll position before switching to editor
|
|
175
|
-
if (blockRef.current) blockRef.current.dataset.scrollTop = blockRef.current.scrollTop
|
|
176
|
-
setEditing(true)
|
|
177
|
-
} : undefined}
|
|
188
|
+
onDoubleClick={canEdit ? startEditing : undefined}
|
|
178
189
|
role={canEdit ? 'button' : undefined}
|
|
179
190
|
tabIndex={canEdit ? 0 : undefined}
|
|
180
191
|
onKeyDown={canEdit ? (e) => {
|
|
181
|
-
if (e.key === 'Enter')
|
|
182
|
-
if (blockRef.current) blockRef.current.dataset.scrollTop = blockRef.current.scrollTop
|
|
183
|
-
setEditing(true)
|
|
184
|
-
}
|
|
192
|
+
if (e.key === 'Enter') startEditing()
|
|
185
193
|
} : undefined}
|
|
186
194
|
dangerouslySetInnerHTML={{
|
|
187
195
|
__html: renderedHtml || (canEdit
|
|
@@ -200,5 +208,19 @@ export default function MarkdownBlock({ props, onUpdate, resizable }) {
|
|
|
200
208
|
)}
|
|
201
209
|
</div>
|
|
202
210
|
</WidgetWrapper>
|
|
211
|
+
<SplitExpandModal
|
|
212
|
+
expanded={expanded}
|
|
213
|
+
onClose={() => setExpanded(false)}
|
|
214
|
+
widgetId={id}
|
|
215
|
+
title="Markdown"
|
|
216
|
+
>
|
|
217
|
+
<div
|
|
218
|
+
className={styles.expandedPreview}
|
|
219
|
+
dangerouslySetInnerHTML={{
|
|
220
|
+
__html: renderedHtml || '<p>No content</p>',
|
|
221
|
+
}}
|
|
222
|
+
/>
|
|
223
|
+
</SplitExpandModal>
|
|
224
|
+
</>
|
|
203
225
|
)
|
|
204
|
-
}
|
|
226
|
+
})
|
|
@@ -227,3 +227,126 @@
|
|
|
227
227
|
color: var(--sb--markdown-fg);
|
|
228
228
|
resize: none;
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
/* ── Expanded preview in modal ──────────────────────────────────── */
|
|
232
|
+
|
|
233
|
+
.expandedPreview {
|
|
234
|
+
padding: 32px 40px;
|
|
235
|
+
font-size: 15px;
|
|
236
|
+
line-height: 1.7;
|
|
237
|
+
color: var(--sb--markdown-fg);
|
|
238
|
+
font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
|
|
239
|
+
max-width: 800px;
|
|
240
|
+
margin: 0 auto;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.expandedPreview * {
|
|
244
|
+
pointer-events: auto;
|
|
245
|
+
color: inherit;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.expandedPreview a {
|
|
249
|
+
color: var(--sb--markdown-accent);
|
|
250
|
+
text-decoration: none;
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.expandedPreview a:hover {
|
|
255
|
+
text-decoration: underline;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.expandedPreview img {
|
|
259
|
+
max-width: 100%;
|
|
260
|
+
height: auto;
|
|
261
|
+
border-radius: 6px;
|
|
262
|
+
border: 1px solid var(--borderColor-default, #d0d7de);
|
|
263
|
+
margin: 8px 0;
|
|
264
|
+
display: block;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.expandedPreview video {
|
|
268
|
+
max-width: 100%;
|
|
269
|
+
height: auto;
|
|
270
|
+
border-radius: 6px;
|
|
271
|
+
border: 1px solid var(--borderColor-default, #d0d7de);
|
|
272
|
+
margin: 8px 0;
|
|
273
|
+
display: block;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.expandedPreview h1 { font-size: 24px; font-weight: 700; margin: 0 0 12px; line-height: 1.3; }
|
|
277
|
+
.expandedPreview h2 { font-size: 20px; font-weight: 600; margin: 0 0 10px; line-height: 1.3; }
|
|
278
|
+
.expandedPreview h3 { font-size: 17px; font-weight: 600; margin: 0 0 6px; line-height: 1.3; }
|
|
279
|
+
.expandedPreview p { margin: 0 0 12px; }
|
|
280
|
+
|
|
281
|
+
.expandedPreview code {
|
|
282
|
+
background: var(--bgColor-neutral-muted, #afb8c133);
|
|
283
|
+
padding: 2px 5px;
|
|
284
|
+
border-radius: 4px;
|
|
285
|
+
font-size: 13px;
|
|
286
|
+
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.expandedPreview pre {
|
|
290
|
+
padding: 12px 16px;
|
|
291
|
+
border-radius: 6px;
|
|
292
|
+
border: 1px solid var(--borderColor-muted, #d8dee4);
|
|
293
|
+
overflow-x: auto;
|
|
294
|
+
margin: 8px 0;
|
|
295
|
+
background: var(--bgColor-neutral-muted, #afb8c133);
|
|
296
|
+
line-height: 1.4;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.expandedPreview pre code {
|
|
300
|
+
background: none;
|
|
301
|
+
padding: 0;
|
|
302
|
+
font-size: 13px;
|
|
303
|
+
white-space: pre;
|
|
304
|
+
display: block;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.expandedPreview ul { margin: 0 0 12px; padding-left: 24px; list-style: disc; }
|
|
308
|
+
.expandedPreview ol { margin: 0 0 12px; padding-left: 24px; list-style: decimal; }
|
|
309
|
+
.expandedPreview li { margin: 0 0 4px; display: list-item; }
|
|
310
|
+
|
|
311
|
+
.expandedPreview blockquote {
|
|
312
|
+
border-left: 4px solid var(--borderColor-default, #d0d7de);
|
|
313
|
+
margin: 12px 0;
|
|
314
|
+
padding: 4px 16px;
|
|
315
|
+
color: var(--sb--markdown-muted);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.expandedPreview table {
|
|
319
|
+
border-collapse: collapse;
|
|
320
|
+
margin: 12px 0;
|
|
321
|
+
width: 100%;
|
|
322
|
+
font-size: 14px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.expandedPreview th,
|
|
326
|
+
.expandedPreview td {
|
|
327
|
+
border: 1px solid var(--borderColor-default, #d0d7de);
|
|
328
|
+
padding: 6px 12px;
|
|
329
|
+
text-align: left;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.expandedPreview th {
|
|
333
|
+
background: var(--bgColor-muted, #f6f8fa);
|
|
334
|
+
font-weight: 600;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.expandedPreview hr {
|
|
338
|
+
border: none;
|
|
339
|
+
border-top: 1px solid var(--borderColor-default, #d0d7de);
|
|
340
|
+
margin: 16px 0;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.expandedPreview input[type="checkbox"] {
|
|
344
|
+
margin-right: 6px;
|
|
345
|
+
pointer-events: none;
|
|
346
|
+
accent-color: var(--sb--markdown-accent);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.expandedPreview li:has(input[type="checkbox"]) {
|
|
350
|
+
list-style: none;
|
|
351
|
+
margin-left: -24px;
|
|
352
|
+
}
|