@dfosco/storyboard-react 4.0.0-beta.9 → 4.0.0
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 +6 -3
- package/src/AuthModal/AuthModal.jsx +134 -0
- package/src/AuthModal/AuthModal.module.css +221 -0
- package/src/BranchBar/BranchBar.jsx +56 -0
- package/src/BranchBar/BranchBar.module.css +230 -0
- package/src/BranchBar/useBranches.js +79 -0
- package/src/CommandPalette/CommandPalette.jsx +936 -0
- package/src/CommandPalette/CreateDialog.jsx +219 -0
- package/src/CommandPalette/command-palette.css +111 -0
- package/src/Icon.jsx +180 -0
- package/src/Viewfinder.jsx +1104 -57
- package/src/Viewfinder.module.css +1107 -149
- package/src/canvas/CanvasControls.jsx +51 -2
- package/src/canvas/CanvasControls.module.css +31 -0
- package/src/canvas/CanvasPage.bridge.test.jsx +142 -19
- package/src/canvas/CanvasPage.dragdrop.test.jsx +346 -0
- package/src/canvas/CanvasPage.jsx +807 -251
- package/src/canvas/CanvasPage.module.css +98 -50
- package/src/canvas/CanvasPage.multiselect.test.jsx +13 -11
- package/src/canvas/CanvasToolbar.jsx +2 -2
- package/src/canvas/MarqueeOverlay.jsx +20 -0
- package/src/canvas/PageSelector.jsx +239 -0
- package/src/canvas/PageSelector.module.css +165 -0
- package/src/canvas/PageSelector.test.jsx +104 -0
- package/src/canvas/canvasApi.js +22 -8
- package/src/canvas/canvasTheme.js +96 -52
- package/src/canvas/componentIsolate.jsx +33 -7
- package/src/canvas/useCanvas.js +9 -8
- package/src/canvas/useCanvas.test.js +4 -4
- package/src/canvas/useMarqueeSelect.js +187 -0
- package/src/canvas/useMarqueeSelect.test.js +78 -0
- package/src/canvas/widgets/CodePenEmbed.jsx +292 -0
- package/src/canvas/widgets/CodePenEmbed.module.css +161 -0
- package/src/canvas/widgets/ComponentWidget.jsx +42 -10
- package/src/canvas/widgets/ComponentWidget.module.css +6 -5
- package/src/canvas/widgets/FigmaEmbed.jsx +110 -24
- package/src/canvas/widgets/FigmaEmbed.module.css +21 -7
- package/src/canvas/widgets/LinkPreview.jsx +297 -11
- package/src/canvas/widgets/LinkPreview.module.css +386 -18
- package/src/canvas/widgets/LinkPreview.test.jsx +193 -0
- package/src/canvas/widgets/MarkdownBlock.jsx +86 -5
- package/src/canvas/widgets/MarkdownBlock.module.css +64 -15
- package/src/canvas/widgets/PrototypeEmbed.jsx +96 -145
- package/src/canvas/widgets/PrototypeEmbed.module.css +74 -4
- package/src/canvas/widgets/StickyNote.module.css +5 -0
- package/src/canvas/widgets/StickyNote.test.jsx +9 -9
- package/src/canvas/widgets/StoryWidget.jsx +277 -0
- package/src/canvas/widgets/StoryWidget.module.css +211 -0
- package/src/canvas/widgets/WidgetChrome.jsx +76 -20
- package/src/canvas/widgets/WidgetChrome.module.css +2 -6
- package/src/canvas/widgets/WidgetWrapper.module.css +2 -0
- package/src/canvas/widgets/codepenUrl.js +75 -0
- package/src/canvas/widgets/codepenUrl.test.js +76 -0
- package/src/canvas/widgets/embedInteraction.test.jsx +235 -0
- package/src/canvas/widgets/embedOverlay.module.css +35 -0
- package/src/canvas/widgets/embedTheme.js +138 -39
- package/src/canvas/widgets/githubUrl.js +82 -0
- package/src/canvas/widgets/githubUrl.test.js +74 -0
- package/src/canvas/widgets/iframeDevLogs.js +49 -0
- package/src/canvas/widgets/iframeDevLogs.test.jsx +81 -0
- package/src/canvas/widgets/index.js +4 -0
- package/src/canvas/widgets/pasteRules.js +295 -0
- package/src/canvas/widgets/pasteRules.test.js +474 -0
- package/src/canvas/widgets/snapshotDisplay.test.jsx +259 -0
- package/src/canvas/widgets/widgetConfig.js +16 -5
- package/src/canvas/widgets/widgetConfig.test.js +34 -12
- package/src/context.jsx +145 -16
- package/src/hooks/useSceneData.js +4 -2
- package/src/hooks/useThemeState.js +61 -0
- package/src/hooks/useThemeState.test.js +66 -0
- package/src/index.js +10 -0
- package/src/story/StoryPage.jsx +117 -0
- package/src/story/StoryPage.module.css +18 -0
- package/src/vite/data-plugin.js +348 -66
- package/src/vite/data-plugin.test.js +405 -5
|
@@ -3,9 +3,13 @@ import { remark } from 'remark'
|
|
|
3
3
|
import remarkGfm from 'remark-gfm'
|
|
4
4
|
import remarkHtml from 'remark-html'
|
|
5
5
|
import WidgetWrapper from './WidgetWrapper.jsx'
|
|
6
|
-
import
|
|
6
|
+
import ResizeHandle from './ResizeHandle.jsx'
|
|
7
|
+
import { readProp } from './widgetProps.js'
|
|
8
|
+
import { schemas } from './widgetConfig.js'
|
|
7
9
|
import styles from './MarkdownBlock.module.css'
|
|
8
10
|
|
|
11
|
+
const markdownSchema = schemas['markdown']
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* Renders markdown to HTML using remark with GitHub Flavored Markdown support.
|
|
11
15
|
*/
|
|
@@ -15,12 +19,55 @@ function renderMarkdown(text) {
|
|
|
15
19
|
.use(remarkGfm)
|
|
16
20
|
.use(remarkHtml, { sanitize: false })
|
|
17
21
|
.processSync(text)
|
|
18
|
-
|
|
22
|
+
// Open all links in new tabs
|
|
23
|
+
return String(result).replace(/<a\s/g, '<a target="_blank" rel="noopener noreferrer" ')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Post-process rendered HTML to syntax-highlight fenced code blocks.
|
|
28
|
+
* remark-html outputs <pre><code class="language-xxx">...</code></pre>.
|
|
29
|
+
* We replace the code content with highlight.js output.
|
|
30
|
+
*/
|
|
31
|
+
let hljsPromise = null
|
|
32
|
+
function getHljs() {
|
|
33
|
+
if (!hljsPromise) {
|
|
34
|
+
hljsPromise = import('@dfosco/storyboard-core/inspector/highlighter').then((mod) => mod)
|
|
35
|
+
}
|
|
36
|
+
return hljsPromise
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function highlightCodeBlocks(html) {
|
|
40
|
+
if (!html.includes('<code class="language-')) return html
|
|
41
|
+
const { createInspectorHighlighter } = await getHljs()
|
|
42
|
+
const hl = await createInspectorHighlighter()
|
|
43
|
+
return html.replace(
|
|
44
|
+
/<pre><code class="language-(\w+)">([\s\S]*?)<\/code><\/pre>/g,
|
|
45
|
+
(match, lang, code) => {
|
|
46
|
+
try {
|
|
47
|
+
// Decode all HTML entities that remark-html may produce
|
|
48
|
+
const decoded = code
|
|
49
|
+
.replace(/</gi, '<')
|
|
50
|
+
.replace(/>/gi, '>')
|
|
51
|
+
.replace(/&/gi, '&')
|
|
52
|
+
.replace(/"/gi, '"')
|
|
53
|
+
.replace(/'/gi, "'")
|
|
54
|
+
.replace(/</g, '<')
|
|
55
|
+
.replace(/>/g, '>')
|
|
56
|
+
.replace(/"/g, '"')
|
|
57
|
+
.replace(/&/g, '&')
|
|
58
|
+
// codeToHtml returns a full <pre style="bg;fg"><code>...</code></pre>
|
|
59
|
+
return hl.codeToHtml(decoded, { lang })
|
|
60
|
+
} catch {
|
|
61
|
+
return match
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
)
|
|
19
65
|
}
|
|
20
66
|
|
|
21
|
-
export default function MarkdownBlock({ props, onUpdate }) {
|
|
67
|
+
export default function MarkdownBlock({ props, onUpdate, resizable }) {
|
|
22
68
|
const content = readProp(props, 'content', markdownSchema)
|
|
23
69
|
const width = readProp(props, 'width', markdownSchema)
|
|
70
|
+
const height = props?.height
|
|
24
71
|
const canEdit = typeof onUpdate === 'function'
|
|
25
72
|
const [editing, setEditing] = useState(false)
|
|
26
73
|
const editingActive = canEdit && editing
|
|
@@ -28,6 +75,32 @@ export default function MarkdownBlock({ props, onUpdate }) {
|
|
|
28
75
|
const blockRef = useRef(null)
|
|
29
76
|
const [editHeight, setEditHeight] = useState(null)
|
|
30
77
|
|
|
78
|
+
const handleResize = useCallback((w, h) => {
|
|
79
|
+
onUpdate?.({ width: w, height: h })
|
|
80
|
+
}, [onUpdate])
|
|
81
|
+
|
|
82
|
+
const rawHtml = useMemo(() => renderMarkdown(content), [content])
|
|
83
|
+
const [renderedHtml, setRenderedHtml] = useState(rawHtml)
|
|
84
|
+
|
|
85
|
+
// Re-highlight when theme changes
|
|
86
|
+
const [themeKey, setThemeKey] = useState(0)
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
function onThemeChanged() { setThemeKey((k) => k + 1) }
|
|
89
|
+
document.addEventListener('storyboard:theme:changed', onThemeChanged)
|
|
90
|
+
return () => document.removeEventListener('storyboard:theme:changed', onThemeChanged)
|
|
91
|
+
}, [])
|
|
92
|
+
|
|
93
|
+
// Async-highlight code blocks after initial render or theme change
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
setRenderedHtml(rawHtml)
|
|
96
|
+
if (!rawHtml.includes('<code class="language-')) return
|
|
97
|
+
let cancelled = false
|
|
98
|
+
highlightCodeBlocks(rawHtml).then((highlighted) => {
|
|
99
|
+
if (!cancelled) setRenderedHtml(highlighted)
|
|
100
|
+
})
|
|
101
|
+
return () => { cancelled = true }
|
|
102
|
+
}, [rawHtml, themeKey])
|
|
103
|
+
|
|
31
104
|
const handleContentChange = useCallback((e) => {
|
|
32
105
|
onUpdate?.({ content: e.target.value })
|
|
33
106
|
}, [onUpdate])
|
|
@@ -60,7 +133,7 @@ export default function MarkdownBlock({ props, onUpdate }) {
|
|
|
60
133
|
<div
|
|
61
134
|
ref={blockRef}
|
|
62
135
|
className={styles.block}
|
|
63
|
-
style={{ width, minHeight: editHeight || undefined }}
|
|
136
|
+
style={{ width, ...(height ? { height, overflow: 'auto' } : {}), minHeight: editHeight || undefined }}
|
|
64
137
|
>
|
|
65
138
|
{editingActive ? (
|
|
66
139
|
<textarea
|
|
@@ -90,12 +163,20 @@ export default function MarkdownBlock({ props, onUpdate }) {
|
|
|
90
163
|
tabIndex={canEdit ? 0 : undefined}
|
|
91
164
|
onKeyDown={canEdit ? (e) => { if (e.key === 'Enter') setEditing(true) } : undefined}
|
|
92
165
|
dangerouslySetInnerHTML={{
|
|
93
|
-
__html:
|
|
166
|
+
__html: renderedHtml || (canEdit
|
|
94
167
|
? '<p class="placeholder">Double-click to edit…</p>'
|
|
95
168
|
: '<p class="placeholder">No content</p>'),
|
|
96
169
|
}}
|
|
97
170
|
/>
|
|
98
171
|
)}
|
|
172
|
+
{resizable && (
|
|
173
|
+
<ResizeHandle
|
|
174
|
+
targetRef={blockRef}
|
|
175
|
+
minWidth={200}
|
|
176
|
+
minHeight={60}
|
|
177
|
+
onResize={handleResize}
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
99
180
|
</div>
|
|
100
181
|
</WidgetWrapper>
|
|
101
182
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.block {
|
|
2
|
+
position: relative;
|
|
2
3
|
min-height: 80px;
|
|
3
4
|
--sb--markdown-bg: var(--bgColor-default, #ffffff);
|
|
4
5
|
--sb--markdown-fg: var(--fgColor-default, #1f2328);
|
|
@@ -23,6 +24,38 @@
|
|
|
23
24
|
pointer-events: none;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
.preview a {
|
|
28
|
+
color: var(--sb--markdown-accent);
|
|
29
|
+
text-decoration: none;
|
|
30
|
+
pointer-events: auto;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.preview a:hover {
|
|
35
|
+
text-decoration: underline;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.preview img {
|
|
39
|
+
max-width: 100%;
|
|
40
|
+
height: auto;
|
|
41
|
+
border-radius: 6px;
|
|
42
|
+
border: 1px solid var(--borderColor-default, #d0d7de);
|
|
43
|
+
margin: 8px 0;
|
|
44
|
+
display: block;
|
|
45
|
+
pointer-events: auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.preview video {
|
|
49
|
+
max-width: 100%;
|
|
50
|
+
height: auto;
|
|
51
|
+
border-radius: 6px;
|
|
52
|
+
border: 1px solid var(--borderColor-default, #d0d7de);
|
|
53
|
+
margin: 8px 0;
|
|
54
|
+
display: block;
|
|
55
|
+
pointer-events: auto;
|
|
56
|
+
background: var(--bgColor-muted, #f6f8fa);
|
|
57
|
+
}
|
|
58
|
+
|
|
26
59
|
.preview h1 {
|
|
27
60
|
font-size: 20px;
|
|
28
61
|
font-weight: 700;
|
|
@@ -52,28 +85,33 @@
|
|
|
52
85
|
background: var(--bgColor-neutral-muted, #afb8c133);
|
|
53
86
|
padding: 2px 5px;
|
|
54
87
|
border-radius: 4px;
|
|
55
|
-
font-size:
|
|
56
|
-
font-
|
|
88
|
+
font-size: 12px;
|
|
89
|
+
font-weight: 400;
|
|
90
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
57
91
|
}
|
|
58
92
|
|
|
59
93
|
.preview ul {
|
|
60
94
|
margin: 0 0 8px;
|
|
61
95
|
padding-left: 20px;
|
|
96
|
+
list-style-type: disc;
|
|
62
97
|
}
|
|
63
98
|
|
|
64
99
|
.preview li {
|
|
65
100
|
margin: 0 0 2px;
|
|
101
|
+
display: list-item;
|
|
66
102
|
}
|
|
67
103
|
|
|
68
104
|
.preview ol {
|
|
69
105
|
margin: 0 0 8px;
|
|
70
106
|
padding-left: 20px;
|
|
107
|
+
list-style-type: decimal;
|
|
71
108
|
}
|
|
72
109
|
|
|
73
|
-
/* GFM: Task lists */
|
|
110
|
+
/* GFM: Task lists — accent-colored checkboxes */
|
|
74
111
|
.preview input[type="checkbox"] {
|
|
75
112
|
margin-right: 6px;
|
|
76
113
|
pointer-events: none;
|
|
114
|
+
accent-color: var(--sb--markdown-accent);
|
|
77
115
|
}
|
|
78
116
|
|
|
79
117
|
.preview li:has(input[type="checkbox"]) {
|
|
@@ -107,21 +145,21 @@
|
|
|
107
145
|
font-weight: 600;
|
|
108
146
|
}
|
|
109
147
|
|
|
110
|
-
/* GFM: Autolinks */
|
|
111
|
-
.preview a {
|
|
112
|
-
color: var(--sb--markdown-accent);
|
|
113
|
-
text-decoration: none;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.preview a:hover {
|
|
117
|
-
text-decoration: underline;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
148
|
/* Code blocks */
|
|
121
149
|
.preview pre {
|
|
122
|
-
background: var(--bgColor-neutral-muted, #afb8c133);
|
|
123
150
|
padding: 12px 16px;
|
|
124
151
|
border-radius: 6px;
|
|
152
|
+
border: 1px solid var(--borderColor-muted, #d8dee4);
|
|
153
|
+
overflow-x: auto;
|
|
154
|
+
margin: 8px 0;
|
|
155
|
+
background: var(--bgColor-neutral-muted, #afb8c133);
|
|
156
|
+
line-height: 1.4;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* When inspector highlighter sets inline background, let it through */
|
|
160
|
+
.preview pre[style] {
|
|
161
|
+
border-radius: 6px;
|
|
162
|
+
border: 1px solid var(--borderColor-muted, #d8dee4);
|
|
125
163
|
overflow-x: auto;
|
|
126
164
|
margin: 8px 0;
|
|
127
165
|
}
|
|
@@ -129,7 +167,18 @@
|
|
|
129
167
|
.preview pre code {
|
|
130
168
|
background: none;
|
|
131
169
|
padding: 0;
|
|
132
|
-
font-size:
|
|
170
|
+
font-size: 12px;
|
|
171
|
+
font-weight: 400;
|
|
172
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
173
|
+
white-space: pre;
|
|
174
|
+
word-break: normal;
|
|
175
|
+
overflow-wrap: normal;
|
|
176
|
+
display: block;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Suppress line numbers from inspector highlighter's .line spans */
|
|
180
|
+
.preview :global(.line::before) {
|
|
181
|
+
display: none !important;
|
|
133
182
|
}
|
|
134
183
|
|
|
135
184
|
/* Blockquotes */
|