@bendyline/squisq-react 2.5.0 → 2.6.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/README.md +19 -0
- package/dist/MarkdownRenderer-CerBKw-c.d.ts +53 -0
- package/dist/{chunk-ZKNX3EJI.js → chunk-GWD62KLD.js} +14 -5
- package/dist/{chunk-BOZJ655L.js → chunk-MVJQL2W2.js} +19 -12
- package/dist/{chunk-VMPQEUJH.js → chunk-TMCLQNLM.js} +71 -4
- package/dist/{chunk-THDXCSPC.js → chunk-U6P7HBUY.js} +51 -12
- package/dist/{chunk-DJRYQQXB.js → chunk-YKBVTYU4.js} +1 -1
- package/dist/hooks/index.d.ts +5 -1
- package/dist/hooks/index.js +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +5 -5
- package/dist/json-view/index.js +2 -2
- package/dist/markdown/index.d.ts +3 -31
- package/dist/markdown/index.js +1 -1
- package/dist/page/index.d.ts +11 -2
- package/dist/page/index.js +2 -2
- package/dist/player/index.d.ts +8 -1
- package/dist/player/index.js +4 -4
- package/dist/squisq-player.full.global.js +459 -432
- package/dist/squisq-player.global.js +87 -60
- package/dist/standalone-source.js +1 -1
- package/dist/styles/index.css +36 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -57,6 +57,25 @@ state rather than crashing.
|
|
|
57
57
|
| `MediaClipLayer` | Hidden `<audio>`/`<video>` elements for timed media clips |
|
|
58
58
|
| `JsonView` | Read-only viewer for JSON values bound to a Squisq-annotated schema |
|
|
59
59
|
|
|
60
|
+
### Fenced-code copy control
|
|
61
|
+
|
|
62
|
+
`MarkdownRenderer` and the linear document surfaces keep code-copy UI off by
|
|
63
|
+
default. Opt in with `showCodeCopyButton`. Web hosts can rely on
|
|
64
|
+
`navigator.clipboard`; Electron or native embeddings can provide their own
|
|
65
|
+
clipboard bridge:
|
|
66
|
+
|
|
67
|
+
````tsx
|
|
68
|
+
<LinearDocView
|
|
69
|
+
markdown={'```\n$ node packages/tooling/dist/cli.mjs components\n```'}
|
|
70
|
+
showCodeCopyButton
|
|
71
|
+
onCopyCode={(code, { language }) => hostClipboard.writeText(code)}
|
|
72
|
+
/>
|
|
73
|
+
````
|
|
74
|
+
|
|
75
|
+
The same two props are available on `DocPlayer` (for linear mode), and on the
|
|
76
|
+
standalone static `mount()` options. The callback receives the exact fence
|
|
77
|
+
contents, without the backtick delimiters.
|
|
78
|
+
|
|
60
79
|
## Layers
|
|
61
80
|
|
|
62
81
|
Blocks are composed of typed layers rendered as SVG:
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { Theme } from '@bendyline/squisq/schemas';
|
|
3
|
+
import { MarkdownBlockNode, HtmlPolicy } from '@bendyline/squisq/markdown';
|
|
4
|
+
|
|
5
|
+
interface CodeBlockCopyContext {
|
|
6
|
+
/** Authored fence language, when one was supplied. */
|
|
7
|
+
language?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Host-owned clipboard adapter for fenced code blocks.
|
|
11
|
+
*
|
|
12
|
+
* The callback runs directly from the button's click handler so Electron
|
|
13
|
+
* bridges and browser Clipboard APIs retain their user-gesture context.
|
|
14
|
+
*/
|
|
15
|
+
type CodeBlockCopyHandler = (code: string, context: CodeBlockCopyContext) => void | Promise<void>;
|
|
16
|
+
interface MarkdownRendererProps {
|
|
17
|
+
/** Block-level AST nodes to render */
|
|
18
|
+
nodes: MarkdownBlockNode[];
|
|
19
|
+
/** Optional CSS class for the wrapper element */
|
|
20
|
+
className?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Raw HTML policy. Defaults to `sanitize`, which removes unsafe tags,
|
|
23
|
+
* event handlers, and executable URL schemes before rendering.
|
|
24
|
+
*/
|
|
25
|
+
htmlPolicy?: HtmlPolicy;
|
|
26
|
+
/**
|
|
27
|
+
* Extra URL schemes to allow on links (e.g. a host app's internal
|
|
28
|
+
* navigation scheme it intercepts on click). Executable schemes are
|
|
29
|
+
* never allowed regardless. See {@link SanitizeUrlOptions}.
|
|
30
|
+
*/
|
|
31
|
+
linkSchemes?: readonly string[];
|
|
32
|
+
/** Resolved Squisq theme inherited by embedded Mermaid diagrams. */
|
|
33
|
+
theme?: Theme;
|
|
34
|
+
/** Show a subtle Copy button on ordinary fenced code blocks (default: false). */
|
|
35
|
+
showCodeCopyButton?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Optional host clipboard adapter. When omitted, enabled copy buttons use
|
|
38
|
+
* `navigator.clipboard.writeText`. Supply this for Electron, native shells,
|
|
39
|
+
* or hosts with their own clipboard permission/error handling.
|
|
40
|
+
*/
|
|
41
|
+
onCopyCode?: CodeBlockCopyHandler;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Renders MarkdownBlockNode[] AST as React HTML elements.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* <MarkdownRenderer nodes={block.contents} />
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare function MarkdownRenderer({ nodes, className, htmlPolicy, linkSchemes, theme, showCodeCopyButton, onCopyCode, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
52
|
+
|
|
53
|
+
export { type CodeBlockCopyHandler as C, MarkdownRenderer as M, type CodeBlockCopyContext as a, type MarkdownRendererProps as b };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BlockRenderer,
|
|
3
3
|
LinearDocView
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-U6P7HBUY.js";
|
|
5
5
|
import {
|
|
6
6
|
useAudioSync,
|
|
7
7
|
useDocPlayback,
|
|
8
8
|
useMediaSchedule,
|
|
9
9
|
useViewportOrientation
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MVJQL2W2.js";
|
|
11
11
|
import {
|
|
12
12
|
useAutoSurface
|
|
13
13
|
} from "./chunk-TT6ENR6T.js";
|
|
@@ -1859,7 +1859,9 @@ function DocPlayerContent({
|
|
|
1859
1859
|
pipShape,
|
|
1860
1860
|
pipPosition,
|
|
1861
1861
|
enableSwipe = true,
|
|
1862
|
-
globalKeyboardShortcuts = false
|
|
1862
|
+
globalKeyboardShortcuts = false,
|
|
1863
|
+
showCodeCopyButton = false,
|
|
1864
|
+
onCopyCode
|
|
1863
1865
|
}) {
|
|
1864
1866
|
const isSlideshowMode = displayMode === "slideshow";
|
|
1865
1867
|
const isLinearMode = displayMode === "linear";
|
|
@@ -1876,12 +1878,17 @@ function DocPlayerContent({
|
|
|
1876
1878
|
const params = new URLSearchParams(window.location.search);
|
|
1877
1879
|
return params.get("debug") === "true";
|
|
1878
1880
|
}, []);
|
|
1881
|
+
const syntheticDuration = useMemo3(
|
|
1882
|
+
() => audioMode === "synthetic" ? getDocPlaybackDuration(doc) : 0,
|
|
1883
|
+
[audioMode, doc]
|
|
1884
|
+
);
|
|
1879
1885
|
const internalAudio = useAudioSync(
|
|
1880
1886
|
audioRef,
|
|
1881
1887
|
doc.audio,
|
|
1882
1888
|
basePath,
|
|
1883
1889
|
!externalAudioController,
|
|
1884
|
-
audioMode
|
|
1890
|
+
audioMode,
|
|
1891
|
+
syntheticDuration
|
|
1885
1892
|
);
|
|
1886
1893
|
const audio = externalAudioController || internalAudio;
|
|
1887
1894
|
useEffect5(() => {
|
|
@@ -2660,7 +2667,9 @@ function DocPlayerContent({
|
|
|
2660
2667
|
viewport: activeViewport,
|
|
2661
2668
|
theme,
|
|
2662
2669
|
surface,
|
|
2663
|
-
animationsEnabled
|
|
2670
|
+
animationsEnabled,
|
|
2671
|
+
showCodeCopyButton,
|
|
2672
|
+
onCopyCode
|
|
2664
2673
|
}
|
|
2665
2674
|
)
|
|
2666
2675
|
}
|
|
@@ -16,7 +16,7 @@ function useMediaSchedule(schedule, currentTime) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// src/hooks/useAudioSync.ts
|
|
19
|
-
import { useState, useEffect, useRef, useCallback } from "react";
|
|
19
|
+
import { useState, useEffect, useMemo as useMemo2, useRef, useCallback } from "react";
|
|
20
20
|
import { fetchResourceBytes, isResourceUrlAllowed } from "@bendyline/squisq/markdown";
|
|
21
21
|
|
|
22
22
|
// src/hooks/AudioController.ts
|
|
@@ -61,7 +61,14 @@ function resolveAudioUrl(src, basePath) {
|
|
|
61
61
|
if (!basePath) return src;
|
|
62
62
|
return `${basePath.replace(/\/$/, "")}/${src.replace(/^\//, "")}`;
|
|
63
63
|
}
|
|
64
|
-
function useAudioSync(audioRef,
|
|
64
|
+
function useAudioSync(audioRef, track, basePath = "", enabled = true, mode = "media", syntheticDuration = 0) {
|
|
65
|
+
const audioTrack = useMemo2(() => {
|
|
66
|
+
if (mode !== "synthetic" || track?.segments?.length || syntheticDuration <= 0) return track;
|
|
67
|
+
return {
|
|
68
|
+
...track,
|
|
69
|
+
segments: [{ src: "", name: "synthetic", duration: syntheticDuration, startTime: 0 }]
|
|
70
|
+
};
|
|
71
|
+
}, [track, mode, syntheticDuration]);
|
|
65
72
|
const resourcePolicy = useResourcePolicy();
|
|
66
73
|
const [currentTime, setCurrentTime] = useState(0);
|
|
67
74
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
@@ -468,7 +475,7 @@ function useAudioSync(audioRef, audioTrack, basePath = "", enabled = true, mode
|
|
|
468
475
|
}
|
|
469
476
|
|
|
470
477
|
// src/hooks/useDocPlayback.ts
|
|
471
|
-
import { useMemo as
|
|
478
|
+
import { useMemo as useMemo3, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
472
479
|
import {
|
|
473
480
|
DEFAULT_THEME,
|
|
474
481
|
getBlockAtTime,
|
|
@@ -489,7 +496,7 @@ function useDocPlayback(script, currentTime, options = {}) {
|
|
|
489
496
|
onSeek,
|
|
490
497
|
useAudioSegmentTiming = true
|
|
491
498
|
} = options;
|
|
492
|
-
const blocks =
|
|
499
|
+
const blocks = useMemo3(() => {
|
|
493
500
|
if (!script?.blocks) {
|
|
494
501
|
return [];
|
|
495
502
|
}
|
|
@@ -532,20 +539,20 @@ function useDocPlayback(script, currentTime, options = {}) {
|
|
|
532
539
|
theme,
|
|
533
540
|
useAudioSegmentTiming
|
|
534
541
|
]);
|
|
535
|
-
const currentBlock =
|
|
536
|
-
const currentBlockIndex =
|
|
542
|
+
const currentBlock = useMemo3(() => getBlockAtTime(blocks, currentTime), [blocks, currentTime]);
|
|
543
|
+
const currentBlockIndex = useMemo3(
|
|
537
544
|
() => currentBlock ? blocks.indexOf(currentBlock) : -1,
|
|
538
545
|
[blocks, currentBlock]
|
|
539
546
|
);
|
|
540
|
-
const blockTime =
|
|
547
|
+
const blockTime = useMemo3(() => {
|
|
541
548
|
if (!currentBlock) return 0;
|
|
542
549
|
return Math.max(0, currentTime - currentBlock.startTime);
|
|
543
550
|
}, [currentBlock, currentTime]);
|
|
544
|
-
const blockProgress =
|
|
551
|
+
const blockProgress = useMemo3(() => {
|
|
545
552
|
if (!currentBlock || currentBlock.duration === 0) return 0;
|
|
546
553
|
return Math.min(1, blockTime / currentBlock.duration);
|
|
547
554
|
}, [currentBlock, blockTime]);
|
|
548
|
-
const docProgress =
|
|
555
|
+
const docProgress = useMemo3(() => {
|
|
549
556
|
if (!script || script.duration === 0) return 0;
|
|
550
557
|
return Math.min(1, currentTime / script.duration);
|
|
551
558
|
}, [script, currentTime]);
|
|
@@ -612,7 +619,7 @@ function useDocPlayback(script, currentTime, options = {}) {
|
|
|
612
619
|
}
|
|
613
620
|
|
|
614
621
|
// src/hooks/useViewportOrientation.ts
|
|
615
|
-
import { useState as useState2, useEffect as useEffect2, useMemo as
|
|
622
|
+
import { useState as useState2, useEffect as useEffect2, useMemo as useMemo4 } from "react";
|
|
616
623
|
import {
|
|
617
624
|
VIEWPORT_PRESETS as VIEWPORT_PRESETS2
|
|
618
625
|
} from "@bendyline/squisq/doc";
|
|
@@ -661,11 +668,11 @@ function useViewportOrientation() {
|
|
|
661
668
|
clearTimeout(timeoutId);
|
|
662
669
|
};
|
|
663
670
|
}, []);
|
|
664
|
-
const orientation =
|
|
671
|
+
const orientation = useMemo4(
|
|
665
672
|
() => getOrientationFromWindow(windowSize.width, windowSize.height),
|
|
666
673
|
[windowSize.width, windowSize.height]
|
|
667
674
|
);
|
|
668
|
-
const viewport =
|
|
675
|
+
const viewport = useMemo4(() => getViewportForOrientation(orientation), [orientation]);
|
|
669
676
|
return {
|
|
670
677
|
viewport,
|
|
671
678
|
orientation,
|
|
@@ -50,13 +50,72 @@ function InlineAudioPlayer({
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// src/MarkdownRenderer.tsx
|
|
53
|
-
import { Fragment } from "react";
|
|
53
|
+
import { Fragment, useEffect, useRef, useState } from "react";
|
|
54
54
|
import {
|
|
55
55
|
sanitizeHtmlNodes,
|
|
56
56
|
sanitizeUrl
|
|
57
57
|
} from "@bendyline/squisq/markdown";
|
|
58
58
|
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
59
59
|
var DEFAULT_CTX = { htmlPolicy: "sanitize" };
|
|
60
|
+
var CODE_COPY_LABELS = {
|
|
61
|
+
idle: "Copy",
|
|
62
|
+
copying: "Copying\u2026",
|
|
63
|
+
copied: "Copied",
|
|
64
|
+
failed: "Copy failed"
|
|
65
|
+
};
|
|
66
|
+
async function writeCodeToClipboard(code, language, onCopyCode) {
|
|
67
|
+
if (onCopyCode) {
|
|
68
|
+
await onCopyCode(code, language === void 0 ? {} : { language });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
|
|
72
|
+
throw new Error("Clipboard access is unavailable in this host");
|
|
73
|
+
}
|
|
74
|
+
await navigator.clipboard.writeText(code);
|
|
75
|
+
}
|
|
76
|
+
function CodeBlock({
|
|
77
|
+
value,
|
|
78
|
+
language,
|
|
79
|
+
ctx
|
|
80
|
+
}) {
|
|
81
|
+
const [status, setStatus] = useState("idle");
|
|
82
|
+
const resetTimer = useRef();
|
|
83
|
+
useEffect(
|
|
84
|
+
() => () => {
|
|
85
|
+
if (resetTimer.current !== void 0) clearTimeout(resetTimer.current);
|
|
86
|
+
},
|
|
87
|
+
[]
|
|
88
|
+
);
|
|
89
|
+
const pre = /* @__PURE__ */ jsx3("pre", { className: "squisq-md-code-block", children: /* @__PURE__ */ jsx3("code", { className: language ? `language-${language}` : void 0, children: value }) });
|
|
90
|
+
if (!ctx.showCodeCopyButton) return pre;
|
|
91
|
+
const handleCopy = async () => {
|
|
92
|
+
if (status === "copying") return;
|
|
93
|
+
setStatus("copying");
|
|
94
|
+
try {
|
|
95
|
+
await writeCodeToClipboard(value, language ?? void 0, ctx.onCopyCode);
|
|
96
|
+
setStatus("copied");
|
|
97
|
+
} catch {
|
|
98
|
+
setStatus("failed");
|
|
99
|
+
}
|
|
100
|
+
if (resetTimer.current !== void 0) clearTimeout(resetTimer.current);
|
|
101
|
+
resetTimer.current = setTimeout(() => setStatus("idle"), 1600);
|
|
102
|
+
};
|
|
103
|
+
return /* @__PURE__ */ jsxs("div", { className: "squisq-md-code-frame", children: [
|
|
104
|
+
pre,
|
|
105
|
+
/* @__PURE__ */ jsx3(
|
|
106
|
+
"button",
|
|
107
|
+
{
|
|
108
|
+
type: "button",
|
|
109
|
+
className: "squisq-md-code-copy",
|
|
110
|
+
"data-copy-state": status,
|
|
111
|
+
disabled: status === "copying",
|
|
112
|
+
onClick: () => void handleCopy(),
|
|
113
|
+
"aria-label": "Copy code to clipboard",
|
|
114
|
+
children: CODE_COPY_LABELS[status]
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
] });
|
|
118
|
+
}
|
|
60
119
|
function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
|
|
61
120
|
return nodes.map((node, i) => {
|
|
62
121
|
const key = `${keyPrefix}i${i}`;
|
|
@@ -181,7 +240,7 @@ function renderBlock(node, key, ctx = DEFAULT_CTX) {
|
|
|
181
240
|
key
|
|
182
241
|
);
|
|
183
242
|
}
|
|
184
|
-
return /* @__PURE__ */ jsx3(
|
|
243
|
+
return /* @__PURE__ */ jsx3(CodeBlock, { value: node.value, language: node.lang, ctx }, key);
|
|
185
244
|
case "thematicBreak":
|
|
186
245
|
return /* @__PURE__ */ jsx3("hr", { className: "squisq-md-hr" }, key);
|
|
187
246
|
case "table":
|
|
@@ -393,10 +452,18 @@ function MarkdownRenderer({
|
|
|
393
452
|
className,
|
|
394
453
|
htmlPolicy = "sanitize",
|
|
395
454
|
linkSchemes,
|
|
396
|
-
theme
|
|
455
|
+
theme,
|
|
456
|
+
showCodeCopyButton = false,
|
|
457
|
+
onCopyCode
|
|
397
458
|
}) {
|
|
398
459
|
if (!nodes || nodes.length === 0) return null;
|
|
399
|
-
return /* @__PURE__ */ jsx3("div", { className: `squisq-md ${className || ""}`, children: renderBlocks(nodes, "", {
|
|
460
|
+
return /* @__PURE__ */ jsx3("div", { className: `squisq-md ${className || ""}`, children: renderBlocks(nodes, "", {
|
|
461
|
+
htmlPolicy,
|
|
462
|
+
linkSchemes,
|
|
463
|
+
theme,
|
|
464
|
+
showCodeCopyButton,
|
|
465
|
+
onCopyCode
|
|
466
|
+
}) });
|
|
400
467
|
}
|
|
401
468
|
|
|
402
469
|
export {
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
InlineVideoPlayer,
|
|
18
18
|
MarkdownRenderer
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-TMCLQNLM.js";
|
|
20
20
|
import {
|
|
21
21
|
useMediaUrl
|
|
22
22
|
} from "./chunk-LR3AIGDD.js";
|
|
@@ -165,7 +165,8 @@ var defaultValue = {
|
|
|
165
165
|
viewport: VIEWPORT_PRESETS.landscape,
|
|
166
166
|
renderContext: { theme: DEFAULT_THEME },
|
|
167
167
|
animationsEnabled: true,
|
|
168
|
-
imageDisplayMode: "inline"
|
|
168
|
+
imageDisplayMode: "inline",
|
|
169
|
+
showCodeCopyButton: false
|
|
169
170
|
};
|
|
170
171
|
var PageViewContext = createContext(defaultValue);
|
|
171
172
|
function usePageView() {
|
|
@@ -438,11 +439,19 @@ function CardGridSection({ section }) {
|
|
|
438
439
|
] });
|
|
439
440
|
}
|
|
440
441
|
function ItemListSection({ section }) {
|
|
441
|
-
const { theme } = usePageView();
|
|
442
|
+
const { theme, showCodeCopyButton, onCopyCode } = usePageView();
|
|
442
443
|
const items = section.slots.items ?? [];
|
|
443
444
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
444
445
|
section.slots.title && /* @__PURE__ */ jsx3("h2", { className: "squisq-page-section-title squisq-page-items-title", children: section.slots.title }),
|
|
445
|
-
/* @__PURE__ */ jsx3("ol", { className: "squisq-page-items", children: items.map((item, i) => /* @__PURE__ */ jsx3("li", { children: item.markdown ? /* @__PURE__ */ jsx3(
|
|
446
|
+
/* @__PURE__ */ jsx3("ol", { className: "squisq-page-items", children: items.map((item, i) => /* @__PURE__ */ jsx3("li", { children: item.markdown ? /* @__PURE__ */ jsx3(
|
|
447
|
+
MarkdownRenderer,
|
|
448
|
+
{
|
|
449
|
+
nodes: item.markdown,
|
|
450
|
+
theme,
|
|
451
|
+
showCodeCopyButton,
|
|
452
|
+
onCopyCode
|
|
453
|
+
}
|
|
454
|
+
) : item.body }, i)) })
|
|
446
455
|
] });
|
|
447
456
|
}
|
|
448
457
|
function TimelineRailSection({ section }) {
|
|
@@ -470,12 +479,28 @@ function TableSection({ section }) {
|
|
|
470
479
|
] });
|
|
471
480
|
}
|
|
472
481
|
function ProseSection({ section, block }) {
|
|
473
|
-
const { theme } = usePageView();
|
|
474
|
-
const heading = section.slots.title !== void 0 && block?.sourceHeading ? /* @__PURE__ */ jsx3(
|
|
482
|
+
const { theme, showCodeCopyButton, onCopyCode } = usePageView();
|
|
483
|
+
const heading = section.slots.title !== void 0 && block?.sourceHeading ? /* @__PURE__ */ jsx3(
|
|
484
|
+
MarkdownRenderer,
|
|
485
|
+
{
|
|
486
|
+
nodes: [block.sourceHeading],
|
|
487
|
+
theme,
|
|
488
|
+
showCodeCopyButton,
|
|
489
|
+
onCopyCode
|
|
490
|
+
}
|
|
491
|
+
) : null;
|
|
475
492
|
const bodyNodes = section.slots.body?.markdown ?? block?.contents;
|
|
476
493
|
return /* @__PURE__ */ jsxs2("div", { className: "squisq-page-prose", children: [
|
|
477
494
|
heading,
|
|
478
|
-
bodyNodes && bodyNodes.length > 0 && /* @__PURE__ */ jsx3(
|
|
495
|
+
bodyNodes && bodyNodes.length > 0 && /* @__PURE__ */ jsx3(
|
|
496
|
+
MarkdownRenderer,
|
|
497
|
+
{
|
|
498
|
+
nodes: bodyNodes,
|
|
499
|
+
theme,
|
|
500
|
+
showCodeCopyButton,
|
|
501
|
+
onCopyCode
|
|
502
|
+
}
|
|
503
|
+
)
|
|
479
504
|
] });
|
|
480
505
|
}
|
|
481
506
|
function FooterSection({ section }) {
|
|
@@ -537,7 +562,7 @@ function sectionBody(entry, featureFlip) {
|
|
|
537
562
|
}
|
|
538
563
|
}
|
|
539
564
|
function PageSectionView({ entry, featureFlip, isLeadProse }) {
|
|
540
|
-
const { pageStyle, theme } = usePageView();
|
|
565
|
+
const { pageStyle, theme, showCodeCopyButton, onCopyCode } = usePageView();
|
|
541
566
|
const { section } = entry;
|
|
542
567
|
const richContent = section.slots.richContent?.markdown;
|
|
543
568
|
const classes = [
|
|
@@ -566,7 +591,15 @@ function PageSectionView({ entry, featureFlip, isLeadProse }) {
|
|
|
566
591
|
...hintAttrs,
|
|
567
592
|
children: /* @__PURE__ */ jsxs3("div", { className: "squisq-page-section-inner", children: [
|
|
568
593
|
sectionBody(entry, featureFlip),
|
|
569
|
-
richContent && richContent.length > 0 && /* @__PURE__ */ jsx4("div", { className: "squisq-page-rich-content", children: /* @__PURE__ */ jsx4(
|
|
594
|
+
richContent && richContent.length > 0 && /* @__PURE__ */ jsx4("div", { className: "squisq-page-rich-content", children: /* @__PURE__ */ jsx4(
|
|
595
|
+
MarkdownRenderer,
|
|
596
|
+
{
|
|
597
|
+
nodes: richContent,
|
|
598
|
+
theme,
|
|
599
|
+
showCodeCopyButton,
|
|
600
|
+
onCopyCode
|
|
601
|
+
}
|
|
602
|
+
) })
|
|
570
603
|
] })
|
|
571
604
|
}
|
|
572
605
|
);
|
|
@@ -603,7 +636,9 @@ function LinearDocView({
|
|
|
603
636
|
imageDisplayMode = "inline",
|
|
604
637
|
globalKeyboardShortcuts = false,
|
|
605
638
|
showCover = true,
|
|
606
|
-
transformPage
|
|
639
|
+
transformPage,
|
|
640
|
+
showCodeCopyButton = false,
|
|
641
|
+
onCopyCode
|
|
607
642
|
}) {
|
|
608
643
|
const scrollRef = useRef(null);
|
|
609
644
|
const activeViewport = viewport ?? VIEWPORT_PRESETS2.landscape;
|
|
@@ -650,7 +685,9 @@ function LinearDocView({
|
|
|
650
685
|
viewport: activeViewport,
|
|
651
686
|
renderContext,
|
|
652
687
|
animationsEnabled,
|
|
653
|
-
imageDisplayMode
|
|
688
|
+
imageDisplayMode,
|
|
689
|
+
showCodeCopyButton,
|
|
690
|
+
onCopyCode
|
|
654
691
|
}),
|
|
655
692
|
[
|
|
656
693
|
activeTheme,
|
|
@@ -659,7 +696,9 @@ function LinearDocView({
|
|
|
659
696
|
activeViewport,
|
|
660
697
|
renderContext,
|
|
661
698
|
animationsEnabled,
|
|
662
|
-
imageDisplayMode
|
|
699
|
+
imageDisplayMode,
|
|
700
|
+
showCodeCopyButton,
|
|
701
|
+
onCopyCode
|
|
663
702
|
]
|
|
664
703
|
);
|
|
665
704
|
const { featureOrdinals, leadProseIndex } = useMemo2(() => {
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -18,10 +18,14 @@ import { ResourcePolicy } from '@bendyline/squisq/markdown';
|
|
|
18
18
|
* This is the HTML5 Audio implementation of the AudioController interface.
|
|
19
19
|
* Hosts that drive audio through an external player (e.g. a native shell)
|
|
20
20
|
* can supply their own AudioController to DocPlayer instead of this hook.
|
|
21
|
+
*
|
|
22
|
+
* In `synthetic` mode there is no audio element to follow, so a caller that
|
|
23
|
+
* has no segments passes `syntheticDuration` — the document's own timeline
|
|
24
|
+
* length — and the same segment machinery drives a timer instead.
|
|
21
25
|
*/
|
|
22
26
|
|
|
23
27
|
type AudioSyncMode = 'media' | 'synthetic';
|
|
24
|
-
declare function useAudioSync(audioRef: RefObject<HTMLAudioElement>,
|
|
28
|
+
declare function useAudioSync(audioRef: RefObject<HTMLAudioElement>, track: AudioTrack | undefined, basePath?: string, enabled?: boolean, mode?: AudioSyncMode, syntheticDuration?: number): AudioController;
|
|
25
29
|
|
|
26
30
|
interface ModalDialogOptions {
|
|
27
31
|
/** The backdrop/portal root. Siblings of this branch are made inert. */
|
package/dist/hooks/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { SquisqRenderAPI, VideoPresentation as VideoPresentation$1, PipSize, PipShape, PipPosition } from './player/index.js';
|
|
2
2
|
export { BlockMarker, BlockRenderer, CaptionMode, CaptionOverlay, CaptionStyle, ControlsLayout, DisplayMode, DocControlsBottom, DocControlsOverlay, DocControlsSidebar, DocControlsSlideshow, DocPlayer, DocPlayerProps, DocPlayerWithSidebar, DocProgressBar, InlineAudioPlayer, InlineAudioPlayerProps, InlineVideoPlayer, InlineVideoPlayerProps, MediaClipLayer, MediaClipLayerProps, PlaybackActions, PlaybackState, RenderAudioSegmentInfo, RenderBlockInfo, RenderCaptionInfo, RenderChapterInfo, SlideNavActions, SocialCaptionOverlay, formatTime } from './player/index.js';
|
|
3
3
|
import { Theme, VideoPresentation, VideoPipSize, VideoPipShape, VideoPipPosition, Doc, ScheduledClip, MediaProvider } from '@bendyline/squisq/schemas';
|
|
4
|
-
|
|
4
|
+
import { C as CodeBlockCopyHandler } from './MarkdownRenderer-CerBKw-c.js';
|
|
5
|
+
export { a as CodeBlockCopyContext, M as MarkdownRenderer, b as MarkdownRendererProps } from './MarkdownRenderer-CerBKw-c.js';
|
|
6
|
+
export { MermaidDiagram, MermaidDiagramProps } from './markdown/index.js';
|
|
5
7
|
export { CanvasSection, CanvasSectionProps, ImageDisplayMode, LinearDocView, LinearDocViewProps, PageSectionView, PageSectionViewProps, PageViewContext, PageViewContextValue, usePageView } from './page/index.js';
|
|
6
8
|
export { ImageLayer, MapLayer, MermaidLayer, PathLayer, ShapeLayer, TableLayer, TextLayer, TreeLayer, VideoLayer } from './layers/index.js';
|
|
7
9
|
import { CoverSlideTemplate, CoverSlidePlayback } from '@bendyline/squisq/doc';
|
|
@@ -84,6 +86,10 @@ interface MountOptions {
|
|
|
84
86
|
animationsEnabled?: boolean;
|
|
85
87
|
/** Caption style: 'standard' or 'social'. Omit or set to undefined for no captions. */
|
|
86
88
|
captionStyle?: 'standard' | 'social';
|
|
89
|
+
/** Show a Copy button on fenced code blocks in static mode (default: false). */
|
|
90
|
+
showCodeCopyButton?: boolean;
|
|
91
|
+
/** Optional host clipboard adapter; otherwise the browser Clipboard API is used. */
|
|
92
|
+
onCopyCode?: CodeBlockCopyHandler;
|
|
87
93
|
}
|
|
88
94
|
/** Instance handle returned by {@link mount}. */
|
|
89
95
|
interface SquisqPlayerHandle {
|
|
@@ -147,4 +153,4 @@ declare function resolveDocPlayerAppearance(doc: Doc, overrides?: DocPlayerAppea
|
|
|
147
153
|
*/
|
|
148
154
|
declare function useMediaClipDurations(schedule: ScheduledClip[], basePath: string, mediaProviderOverride?: MediaProvider | null): Map<string, number>;
|
|
149
155
|
|
|
150
|
-
export { type DocPlayerAppearanceOverrides, type MountOptions, PipPosition, PipShape, PipSize, type ResolvedDocPlayerAppearance, type SquisqPlayerHandle, SquisqRenderAPI, VideoPresentation$1 as VideoPresentation, resolveDocPlayerAppearance, useMediaClipDurations };
|
|
156
|
+
export { CodeBlockCopyHandler, type DocPlayerAppearanceOverrides, type MountOptions, PipPosition, PipShape, PipSize, type ResolvedDocPlayerAppearance, type SquisqPlayerHandle, SquisqRenderAPI, VideoPresentation$1 as VideoPresentation, resolveDocPlayerAppearance, useMediaClipDurations };
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
formatTime,
|
|
13
13
|
resolveDocPlayerAppearance,
|
|
14
14
|
useMediaClipDurations
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-GWD62KLD.js";
|
|
16
16
|
import {
|
|
17
17
|
BlockRenderer,
|
|
18
18
|
CanvasSection,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
PageSectionView,
|
|
21
21
|
PageViewContext,
|
|
22
22
|
usePageView
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-U6P7HBUY.js";
|
|
24
24
|
import {
|
|
25
25
|
ImageLayer,
|
|
26
26
|
MapLayer,
|
|
@@ -42,10 +42,10 @@ import {
|
|
|
42
42
|
useDocPlayback,
|
|
43
43
|
useMediaSchedule,
|
|
44
44
|
useViewportOrientation
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-MVJQL2W2.js";
|
|
46
46
|
import {
|
|
47
47
|
JsonView
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-YKBVTYU4.js";
|
|
49
49
|
import {
|
|
50
50
|
useAutoSurface
|
|
51
51
|
} from "./chunk-TT6ENR6T.js";
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
InlineAudioPlayer,
|
|
54
54
|
InlineVideoPlayer,
|
|
55
55
|
MarkdownRenderer
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-TMCLQNLM.js";
|
|
57
57
|
import {
|
|
58
58
|
MermaidDiagram
|
|
59
59
|
} from "./chunk-WLUZTUNZ.js";
|
package/dist/json-view/index.js
CHANGED
package/dist/markdown/index.d.ts
CHANGED
|
@@ -1,35 +1,7 @@
|
|
|
1
|
+
export { a as CodeBlockCopyContext, C as CodeBlockCopyHandler, M as MarkdownRenderer, b as MarkdownRendererProps } from '../MarkdownRenderer-CerBKw-c.js';
|
|
1
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
3
|
import { Theme } from '@bendyline/squisq/schemas';
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
interface MarkdownRendererProps {
|
|
6
|
-
/** Block-level AST nodes to render */
|
|
7
|
-
nodes: MarkdownBlockNode[];
|
|
8
|
-
/** Optional CSS class for the wrapper element */
|
|
9
|
-
className?: string;
|
|
10
|
-
/**
|
|
11
|
-
* Raw HTML policy. Defaults to `sanitize`, which removes unsafe tags,
|
|
12
|
-
* event handlers, and executable URL schemes before rendering.
|
|
13
|
-
*/
|
|
14
|
-
htmlPolicy?: HtmlPolicy;
|
|
15
|
-
/**
|
|
16
|
-
* Extra URL schemes to allow on links (e.g. a host app's internal
|
|
17
|
-
* navigation scheme it intercepts on click). Executable schemes are
|
|
18
|
-
* never allowed regardless. See {@link SanitizeUrlOptions}.
|
|
19
|
-
*/
|
|
20
|
-
linkSchemes?: readonly string[];
|
|
21
|
-
/** Resolved Squisq theme inherited by embedded Mermaid diagrams. */
|
|
22
|
-
theme?: Theme;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Renders MarkdownBlockNode[] AST as React HTML elements.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```tsx
|
|
29
|
-
* <MarkdownRenderer nodes={block.contents} />
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
declare function MarkdownRenderer({ nodes, className, htmlPolicy, linkSchemes, theme, }: MarkdownRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
4
|
+
import '@bendyline/squisq/markdown';
|
|
33
5
|
|
|
34
6
|
interface MermaidDiagramProps {
|
|
35
7
|
source: string;
|
|
@@ -41,4 +13,4 @@ interface MermaidDiagramProps {
|
|
|
41
13
|
/** Read-only Mermaid rendering for page bodies and slide layers. */
|
|
42
14
|
declare function MermaidDiagram({ source, className, ariaLabel, theme, }: MermaidDiagramProps): react_jsx_runtime.JSX.Element;
|
|
43
15
|
|
|
44
|
-
export {
|
|
16
|
+
export { MermaidDiagram, type MermaidDiagramProps };
|