@bendyline/squisq-react 1.0.2 → 1.0.3
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/dist/CaptionOverlay.d.ts +16 -7
- package/dist/CaptionOverlay.d.ts.map +1 -1
- package/dist/CaptionOverlay.js +7 -3
- package/dist/CaptionOverlay.js.map +1 -1
- package/dist/DocControlsBottom.d.ts.map +1 -1
- package/dist/DocControlsBottom.js +5 -1
- package/dist/DocControlsBottom.js.map +1 -1
- package/dist/DocControlsOverlay.d.ts.map +1 -1
- package/dist/DocControlsOverlay.js +9 -4
- package/dist/DocControlsOverlay.js.map +1 -1
- package/dist/DocControlsSidebar.d.ts.map +1 -1
- package/dist/DocControlsSidebar.js +5 -1
- package/dist/DocControlsSidebar.js.map +1 -1
- package/dist/DocPlayer.d.ts +5 -2
- package/dist/DocPlayer.d.ts.map +1 -1
- package/dist/DocPlayer.js +36 -7
- package/dist/DocPlayer.js.map +1 -1
- package/dist/DocPlayerWithSidebar.d.ts.map +1 -1
- package/dist/DocPlayerWithSidebar.js +1 -0
- package/dist/DocPlayerWithSidebar.js.map +1 -1
- package/dist/DocProgressBar.js +2 -2
- package/dist/LinearDocView.d.ts.map +1 -1
- package/dist/LinearDocView.js +88 -6
- package/dist/LinearDocView.js.map +1 -1
- package/dist/SocialCaptionOverlay.d.ts +27 -0
- package/dist/SocialCaptionOverlay.d.ts.map +1 -0
- package/dist/SocialCaptionOverlay.js +184 -0
- package/dist/SocialCaptionOverlay.js.map +1 -0
- package/dist/__tests__/DocControlsSlideshow.test.js +1 -0
- package/dist/__tests__/DocControlsSlideshow.test.js.map +1 -1
- package/dist/hooks/useAudioSync.d.ts.map +1 -1
- package/dist/hooks/useAudioSync.js +4 -2
- package/dist/hooks/useAudioSync.js.map +1 -1
- package/dist/hooks/useDocPlayback.d.ts.map +1 -1
- package/dist/hooks/useDocPlayback.js +7 -4
- package/dist/hooks/useDocPlayback.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/layers/ShapeLayer.d.ts.map +1 -1
- package/dist/layers/ShapeLayer.js +7 -3
- package/dist/layers/ShapeLayer.js.map +1 -1
- package/dist/layers/TextLayer.js +1 -1
- package/dist/layers/TextLayer.js.map +1 -1
- package/dist/squisq-player.global.js +70 -5
- package/dist/squisq-player.global.js.map +1 -1
- package/dist/standalone-entry.d.ts +8 -0
- package/dist/standalone-entry.d.ts.map +1 -1
- package/dist/standalone-entry.js +6 -3
- package/dist/standalone-entry.js.map +1 -1
- package/dist/standalone-source.js +1 -1
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/CaptionOverlay.tsx +33 -8
- package/src/DocControlsBottom.tsx +11 -5
- package/src/DocControlsOverlay.tsx +18 -6
- package/src/DocControlsSidebar.tsx +11 -5
- package/src/DocPlayer.tsx +56 -9
- package/src/DocPlayerWithSidebar.tsx +1 -0
- package/src/DocProgressBar.tsx +2 -2
- package/src/LinearDocView.tsx +103 -11
- package/src/SocialCaptionOverlay.tsx +254 -0
- package/src/__tests__/DocControlsSlideshow.test.tsx +1 -0
- package/src/hooks/useAudioSync.ts +5 -2
- package/src/hooks/useDocPlayback.ts +8 -3
- package/src/index.ts +3 -0
- package/src/layers/ShapeLayer.tsx +8 -3
- package/src/layers/TextLayer.tsx +1 -1
- package/src/standalone-entry.tsx +23 -3
- package/src/types.ts +21 -0
package/dist/types.d.ts
CHANGED
|
@@ -28,6 +28,21 @@ export type ControlsLayout = 'overlay' | 'sidebar' | 'bottom';
|
|
|
28
28
|
* No audio, no timeline, no controls — just a scrollable page.
|
|
29
29
|
*/
|
|
30
30
|
export type DisplayMode = 'video' | 'slideshow' | 'linear';
|
|
31
|
+
/**
|
|
32
|
+
* Caption display style.
|
|
33
|
+
*
|
|
34
|
+
* - `'standard'` — Traditional broadcast-style captions: small white text
|
|
35
|
+
* on a semi-transparent black badge at the top of the player.
|
|
36
|
+
* - `'social'` — Social media-style (Instagram/TikTok): large centered words
|
|
37
|
+
* showing 3-5 words at a time with the active word highlighted in the
|
|
38
|
+
* theme's primary color. Font and colors pulled from the active theme.
|
|
39
|
+
*/
|
|
40
|
+
export type CaptionStyle = 'standard' | 'social';
|
|
41
|
+
/**
|
|
42
|
+
* Caption display mode — combines enable/disable with style selection.
|
|
43
|
+
* The CC button cycles through: off → standard → social → off.
|
|
44
|
+
*/
|
|
45
|
+
export type CaptionMode = 'off' | 'standard' | 'social';
|
|
31
46
|
/** Slide navigation actions for slideshow display mode */
|
|
32
47
|
export interface SlideNavActions {
|
|
33
48
|
/** Navigate to the next slide */
|
|
@@ -47,6 +62,8 @@ export interface PlaybackState {
|
|
|
47
62
|
docProgress: number;
|
|
48
63
|
hasCaptions: boolean;
|
|
49
64
|
captionsEnabled: boolean;
|
|
65
|
+
/** Current caption display mode (off, standard, social). */
|
|
66
|
+
captionMode: CaptionMode;
|
|
50
67
|
isFullscreen?: boolean;
|
|
51
68
|
/** Current audio segment index (0-based) */
|
|
52
69
|
currentSegmentIndex: number;
|
|
@@ -61,6 +78,8 @@ export interface PlaybackActions {
|
|
|
61
78
|
restart: () => void;
|
|
62
79
|
seekTo: (time: number) => void;
|
|
63
80
|
setCaptionsEnabled: (enabled: boolean) => void;
|
|
81
|
+
/** Cycle caption mode: off → standard → social → off */
|
|
82
|
+
cycleCaptionMode: () => void;
|
|
64
83
|
toggleFullscreen?: () => void;
|
|
65
84
|
}
|
|
66
85
|
/** Block marker data for the progress bar */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0JH,6CAA6C;AAC7C,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACtC,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACvD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "React component library for doc playback, block rendering, and media layers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@bendyline/squisq": "1.1.
|
|
55
|
+
"@bendyline/squisq": "1.1.1"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/react": "^18.0.0",
|
package/src/CaptionOverlay.tsx
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
* CaptionOverlay Component
|
|
3
3
|
*
|
|
4
4
|
* Displays closed captions synchronized with audio playback.
|
|
5
|
-
*
|
|
6
|
-
* persistent element (no mount/unmount flicker).
|
|
5
|
+
* Supports two styles:
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* - `'standard'` (default): Traditional broadcast captions — small white text
|
|
8
|
+
* on a semi-transparent black badge at the top of the player.
|
|
9
|
+
* - `'social'`: Social media-style (Instagram/TikTok) — large centered words
|
|
10
|
+
* with the active word highlighted in the theme's primary color.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
|
-
import type { CaptionTrack } from '@bendyline/squisq/schemas';
|
|
13
|
+
import type { CaptionTrack, ViewportConfig } from '@bendyline/squisq/schemas';
|
|
13
14
|
import { getCaptionAtTime } from '@bendyline/squisq/schemas';
|
|
15
|
+
import type { Theme } from '@bendyline/squisq/schemas';
|
|
16
|
+
import type { CaptionStyle } from './types';
|
|
17
|
+
import { SocialCaptionOverlay } from './SocialCaptionOverlay';
|
|
14
18
|
|
|
15
19
|
interface CaptionOverlayProps {
|
|
16
20
|
/** Caption track with timestamped phrases */
|
|
@@ -19,8 +23,14 @@ interface CaptionOverlayProps {
|
|
|
19
23
|
currentTime: number;
|
|
20
24
|
/** Whether captions are enabled */
|
|
21
25
|
enabled?: boolean;
|
|
22
|
-
/** Font size in pixels (default: 16) */
|
|
26
|
+
/** Font size in pixels for standard style (default: 16) */
|
|
23
27
|
fontSize?: number;
|
|
28
|
+
/** Caption display style (default: 'standard') */
|
|
29
|
+
captionStyle?: CaptionStyle;
|
|
30
|
+
/** Theme for social-style caption colors and fonts */
|
|
31
|
+
theme?: Theme;
|
|
32
|
+
/** Viewport config for social-style font scaling */
|
|
33
|
+
viewport?: ViewportConfig;
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
export function CaptionOverlay({
|
|
@@ -28,9 +38,24 @@ export function CaptionOverlay({
|
|
|
28
38
|
currentTime,
|
|
29
39
|
enabled = true,
|
|
30
40
|
fontSize = 16,
|
|
41
|
+
captionStyle = 'standard',
|
|
42
|
+
theme,
|
|
43
|
+
viewport,
|
|
31
44
|
}: CaptionOverlayProps) {
|
|
32
|
-
//
|
|
33
|
-
|
|
45
|
+
// Delegate to social overlay when that style is active
|
|
46
|
+
if (captionStyle === 'social') {
|
|
47
|
+
return (
|
|
48
|
+
<SocialCaptionOverlay
|
|
49
|
+
captions={captions}
|
|
50
|
+
currentTime={currentTime}
|
|
51
|
+
enabled={enabled}
|
|
52
|
+
theme={theme}
|
|
53
|
+
viewport={viewport}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Standard caption rendering
|
|
34
59
|
const phrase = enabled && captions ? getCaptionAtTime(captions, currentTime) : null;
|
|
35
60
|
const captionText = phrase?.text ?? null;
|
|
36
61
|
|
|
@@ -85,13 +85,19 @@ export function DocControlsBottom({
|
|
|
85
85
|
{state.currentBlockIndex + 1}/{state.totalBlocks}
|
|
86
86
|
</span>
|
|
87
87
|
|
|
88
|
-
{/* Caption
|
|
88
|
+
{/* Caption mode cycle */}
|
|
89
89
|
{state.hasCaptions && (
|
|
90
90
|
<button
|
|
91
|
-
className={`bottom-ctrl-btn ${state.
|
|
92
|
-
onClick={() => actions.
|
|
93
|
-
title={
|
|
94
|
-
|
|
91
|
+
className={`bottom-ctrl-btn ${state.captionMode !== 'off' ? 'bottom-ctrl-btn--active' : ''}`}
|
|
92
|
+
onClick={() => actions.cycleCaptionMode()}
|
|
93
|
+
title={
|
|
94
|
+
state.captionMode === 'off'
|
|
95
|
+
? 'Captions: Off'
|
|
96
|
+
: state.captionMode === 'standard'
|
|
97
|
+
? 'Captions: Standard'
|
|
98
|
+
: 'Captions: Social'
|
|
99
|
+
}
|
|
100
|
+
aria-label="Cycle caption style"
|
|
95
101
|
>
|
|
96
102
|
<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
|
|
97
103
|
<path d="M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" />
|
|
@@ -121,27 +121,39 @@ export function DocControlsOverlay({
|
|
|
121
121
|
{state.currentBlockIndex + 1}/{state.totalBlocks}
|
|
122
122
|
</span>
|
|
123
123
|
|
|
124
|
-
{/* Caption
|
|
124
|
+
{/* Caption mode cycle button: off → standard → social → off */}
|
|
125
125
|
{state.hasCaptions && (
|
|
126
126
|
<button
|
|
127
|
-
onClick={() => actions.
|
|
127
|
+
onClick={() => actions.cycleCaptionMode()}
|
|
128
128
|
style={{
|
|
129
|
-
background: state.
|
|
129
|
+
background: state.captionMode !== 'off' ? 'rgba(255,255,255,0.2)' : 'none',
|
|
130
130
|
border: 'none',
|
|
131
|
-
color: state.
|
|
131
|
+
color: state.captionMode !== 'off' ? 'white' : 'rgba(255,255,255,0.5)',
|
|
132
132
|
cursor: 'pointer',
|
|
133
133
|
padding: '8px',
|
|
134
134
|
fontSize: '12px',
|
|
135
135
|
display: 'flex',
|
|
136
136
|
alignItems: 'center',
|
|
137
|
+
gap: '4px',
|
|
137
138
|
borderRadius: '4px',
|
|
138
139
|
}}
|
|
139
|
-
title={
|
|
140
|
-
|
|
140
|
+
title={
|
|
141
|
+
state.captionMode === 'off'
|
|
142
|
+
? 'Captions: Off (click for Standard)'
|
|
143
|
+
: state.captionMode === 'standard'
|
|
144
|
+
? 'Captions: Standard (click for Social)'
|
|
145
|
+
: 'Captions: Social (click to turn off)'
|
|
146
|
+
}
|
|
147
|
+
aria-label="Cycle caption style"
|
|
141
148
|
>
|
|
142
149
|
<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
|
|
143
150
|
<path d="M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" />
|
|
144
151
|
</svg>
|
|
152
|
+
{state.captionMode !== 'off' && (
|
|
153
|
+
<span style={{ fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
|
|
154
|
+
{state.captionMode === 'standard' ? 'CC' : 'SM'}
|
|
155
|
+
</span>
|
|
156
|
+
)}
|
|
145
157
|
</button>
|
|
146
158
|
)}
|
|
147
159
|
|
|
@@ -69,13 +69,19 @@ export function DocControlsSidebar({ state, actions }: DocControlsSidebarProps)
|
|
|
69
69
|
{state.currentBlockIndex + 1}/{state.totalBlocks}
|
|
70
70
|
</div>
|
|
71
71
|
|
|
72
|
-
{/* Caption
|
|
72
|
+
{/* Caption mode cycle */}
|
|
73
73
|
{state.hasCaptions && (
|
|
74
74
|
<button
|
|
75
|
-
className={`sidebar-ctrl-btn ${state.
|
|
76
|
-
onClick={() => actions.
|
|
77
|
-
title={
|
|
78
|
-
|
|
75
|
+
className={`sidebar-ctrl-btn ${state.captionMode !== 'off' ? 'sidebar-ctrl-btn--active' : ''}`}
|
|
76
|
+
onClick={() => actions.cycleCaptionMode()}
|
|
77
|
+
title={
|
|
78
|
+
state.captionMode === 'off'
|
|
79
|
+
? 'Captions: Off'
|
|
80
|
+
: state.captionMode === 'standard'
|
|
81
|
+
? 'Captions: Standard'
|
|
82
|
+
: 'Captions: Social'
|
|
83
|
+
}
|
|
84
|
+
aria-label="Cycle caption style"
|
|
79
85
|
>
|
|
80
86
|
<svg viewBox="0 0 24 24" fill="currentColor" width="20" height="20">
|
|
81
87
|
<path d="M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" />
|
package/src/DocPlayer.tsx
CHANGED
|
@@ -48,6 +48,8 @@ import type {
|
|
|
48
48
|
PlaybackActions,
|
|
49
49
|
BlockMarker,
|
|
50
50
|
DisplayMode,
|
|
51
|
+
CaptionStyle,
|
|
52
|
+
CaptionMode,
|
|
51
53
|
SlideNavActions,
|
|
52
54
|
SquisqWindow,
|
|
53
55
|
} from './types';
|
|
@@ -169,6 +171,9 @@ interface DocPlayerProps {
|
|
|
169
171
|
* template-annotated sections as inline SVG cards. No audio, no timeline.
|
|
170
172
|
*/
|
|
171
173
|
displayMode?: DisplayMode;
|
|
174
|
+
/** Caption display style (default: 'standard').
|
|
175
|
+
* 'social' shows large centered words with the active word highlighted. */
|
|
176
|
+
captionStyle?: CaptionStyle;
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
export function DocPlayer({
|
|
@@ -192,6 +197,7 @@ export function DocPlayer({
|
|
|
192
197
|
forceViewport,
|
|
193
198
|
displayMode = 'video',
|
|
194
199
|
theme,
|
|
200
|
+
captionStyle = 'standard',
|
|
195
201
|
}: DocPlayerProps) {
|
|
196
202
|
const isSlideshowMode = displayMode === 'slideshow';
|
|
197
203
|
const isLinearMode = displayMode === 'linear';
|
|
@@ -433,7 +439,16 @@ export function DocPlayer({
|
|
|
433
439
|
});
|
|
434
440
|
});
|
|
435
441
|
};
|
|
436
|
-
w.getDuration = () =>
|
|
442
|
+
w.getDuration = () => {
|
|
443
|
+
// When audio is present totalDuration comes from audio segments.
|
|
444
|
+
// For audio-less docs, compute from block timings instead.
|
|
445
|
+
if (totalDuration > 0) return totalDuration;
|
|
446
|
+
if (expandedBlocks.length > 0) {
|
|
447
|
+
const last = expandedBlocks[expandedBlocks.length - 1];
|
|
448
|
+
return last.startTime + last.duration;
|
|
449
|
+
}
|
|
450
|
+
return 0;
|
|
451
|
+
};
|
|
437
452
|
// Expose block metadata for testing -- allows tests to find specific templates
|
|
438
453
|
w.getBlocks = () =>
|
|
439
454
|
expandedBlocks.map((s: Block) => ({
|
|
@@ -494,14 +509,36 @@ export function DocPlayer({
|
|
|
494
509
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- script is a stable prop; re-registering on every script change is unnecessary
|
|
495
510
|
}, [renderMode, isDebugMode, seekTo, totalDuration, expandedBlocks, coverBlock]);
|
|
496
511
|
|
|
497
|
-
//
|
|
498
|
-
|
|
512
|
+
// Caption mode state: cycles through off → standard → social → off
|
|
513
|
+
// The captionStyle prop sets the default active style; captionsEnabledProp
|
|
514
|
+
// can override the initial on/off state.
|
|
515
|
+
const defaultMode: CaptionMode =
|
|
516
|
+
captionsEnabledProp === false ? 'off' : captionStyle || 'standard';
|
|
517
|
+
const [captionMode, setCaptionMode] = useState<CaptionMode>(defaultMode);
|
|
518
|
+
|
|
519
|
+
// Derive captionsEnabled and active style from the mode
|
|
520
|
+
const captionsEnabled = captionMode !== 'off';
|
|
521
|
+
const activeCaptionStyle: CaptionStyle = captionMode === 'social' ? 'social' : 'standard';
|
|
522
|
+
|
|
499
523
|
const setCaptionsEnabled = useCallback(
|
|
500
524
|
(enabled: boolean) => {
|
|
525
|
+
// When re-enabling, restore the prop-specified style rather than
|
|
526
|
+
// always defaulting to 'standard'
|
|
527
|
+
setCaptionMode(enabled ? captionStyle || 'standard' : 'off');
|
|
501
528
|
onCaptionsToggle?.(enabled);
|
|
502
529
|
},
|
|
503
|
-
[onCaptionsToggle],
|
|
530
|
+
[onCaptionsToggle, captionStyle],
|
|
504
531
|
);
|
|
532
|
+
|
|
533
|
+
const cycleCaptionMode = useCallback(() => {
|
|
534
|
+
setCaptionMode((prev) => {
|
|
535
|
+
const next: CaptionMode =
|
|
536
|
+
prev === 'off' ? 'standard' : prev === 'standard' ? 'social' : 'off';
|
|
537
|
+
onCaptionsToggle?.(next !== 'off');
|
|
538
|
+
return next;
|
|
539
|
+
});
|
|
540
|
+
}, [onCaptionsToggle]);
|
|
541
|
+
|
|
505
542
|
const hasCaptions = script.captions && script.captions.phrases.length > 0;
|
|
506
543
|
|
|
507
544
|
// Map segment indices to human-readable titles (from sectionHeader blocks)
|
|
@@ -518,6 +555,7 @@ export function DocPlayer({
|
|
|
518
555
|
docProgress,
|
|
519
556
|
hasCaptions: !!hasCaptions,
|
|
520
557
|
captionsEnabled,
|
|
558
|
+
captionMode,
|
|
521
559
|
isFullscreen,
|
|
522
560
|
currentSegmentIndex: currentSegment,
|
|
523
561
|
currentSegmentName:
|
|
@@ -534,6 +572,7 @@ export function DocPlayer({
|
|
|
534
572
|
docProgress,
|
|
535
573
|
hasCaptions,
|
|
536
574
|
captionsEnabled,
|
|
575
|
+
captionMode,
|
|
537
576
|
isFullscreen,
|
|
538
577
|
currentSegment,
|
|
539
578
|
segmentTitleMap,
|
|
@@ -548,9 +587,10 @@ export function DocPlayer({
|
|
|
548
587
|
restart,
|
|
549
588
|
seekTo,
|
|
550
589
|
setCaptionsEnabled,
|
|
590
|
+
cycleCaptionMode,
|
|
551
591
|
toggleFullscreen: onFullscreenToggle,
|
|
552
592
|
}),
|
|
553
|
-
[toggle, restart, seekTo, setCaptionsEnabled, onFullscreenToggle],
|
|
593
|
+
[toggle, restart, seekTo, setCaptionsEnabled, cycleCaptionMode, onFullscreenToggle],
|
|
554
594
|
);
|
|
555
595
|
|
|
556
596
|
// Slide navigation actions for slideshow mode
|
|
@@ -803,13 +843,16 @@ export function DocPlayer({
|
|
|
803
843
|
</div>
|
|
804
844
|
)}
|
|
805
845
|
|
|
806
|
-
{/* Caption overlay --
|
|
807
|
-
{hasCaptions &&
|
|
846
|
+
{/* Caption overlay -- shown during playback and in render mode when captions are enabled */}
|
|
847
|
+
{hasCaptions && (renderMode ? captionsEnabled : true) && (
|
|
808
848
|
<CaptionOverlay
|
|
809
849
|
captions={script.captions}
|
|
810
850
|
currentTime={currentTime}
|
|
811
|
-
enabled={captionsEnabled && (isPlaying || currentTime > 0)}
|
|
851
|
+
enabled={captionsEnabled && (renderMode || isPlaying || currentTime > 0)}
|
|
812
852
|
fontSize={16}
|
|
853
|
+
captionStyle={activeCaptionStyle}
|
|
854
|
+
theme={theme}
|
|
855
|
+
viewport={activeViewport}
|
|
813
856
|
/>
|
|
814
857
|
)}
|
|
815
858
|
|
|
@@ -850,7 +893,11 @@ export function DocPlayer({
|
|
|
850
893
|
</div>
|
|
851
894
|
<div>
|
|
852
895
|
<span style={{ color: '#888' }}>time:</span> {currentTime.toFixed(2)}s /{' '}
|
|
853
|
-
{totalDuration.toFixed(1)}s
|
|
896
|
+
{totalDuration.toFixed(1)}s{' '}
|
|
897
|
+
<span style={{ color: '#666' }}>
|
|
898
|
+
(progress: {(docProgress * 100).toFixed(1)}%, scriptDur:{' '}
|
|
899
|
+
{script.duration.toFixed(1)})
|
|
900
|
+
</span>
|
|
854
901
|
</div>
|
|
855
902
|
<div>
|
|
856
903
|
<span style={{ color: '#888' }}>blockTime:</span> {blockTime.toFixed(2)}s /{' '}
|
package/src/DocProgressBar.tsx
CHANGED
|
@@ -106,7 +106,7 @@ export function DocProgressBar({
|
|
|
106
106
|
left: 0,
|
|
107
107
|
width: `${state.docProgress * 100}%`,
|
|
108
108
|
height: '6px',
|
|
109
|
-
background: '#
|
|
109
|
+
background: '#5b9bd5',
|
|
110
110
|
borderRadius: '3px',
|
|
111
111
|
transition: 'width 0.1s',
|
|
112
112
|
}}
|
|
@@ -125,7 +125,7 @@ export function DocProgressBar({
|
|
|
125
125
|
borderRadius: '50%',
|
|
126
126
|
background:
|
|
127
127
|
marker.index === state.currentBlockIndex ? '#ffffff' : 'rgba(255,255,255,0.5)',
|
|
128
|
-
border: '2px solid #
|
|
128
|
+
border: '2px solid #5b9bd5',
|
|
129
129
|
cursor: 'pointer',
|
|
130
130
|
zIndex: 2,
|
|
131
131
|
transition: 'transform 0.15s, background 0.15s',
|
package/src/LinearDocView.tsx
CHANGED
|
@@ -55,7 +55,7 @@ export interface LinearDocViewProps {
|
|
|
55
55
|
function isAnnotatedBlock(block: Block): boolean {
|
|
56
56
|
const annotation = block.sourceHeading?.templateAnnotation;
|
|
57
57
|
if (!annotation) return false;
|
|
58
|
-
return hasTemplate(annotation.template);
|
|
58
|
+
return !!annotation.template && hasTemplate(annotation.template);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -103,7 +103,12 @@ function BlockSection({ block, basePath, viewport, renderContext, blockIndex }:
|
|
|
103
103
|
duration: 1,
|
|
104
104
|
audioSegment: 0,
|
|
105
105
|
title: headingText,
|
|
106
|
-
...getTemplateDefaults(
|
|
106
|
+
...getTemplateDefaults(
|
|
107
|
+
annotation.template ?? 'sectionHeader',
|
|
108
|
+
headingText,
|
|
109
|
+
bodyText,
|
|
110
|
+
block.contents,
|
|
111
|
+
),
|
|
107
112
|
...annotation.params,
|
|
108
113
|
...block.templateOverrides,
|
|
109
114
|
};
|
|
@@ -276,6 +281,15 @@ export function LinearDocView({
|
|
|
276
281
|
[activeViewport, totalBlocks, theme],
|
|
277
282
|
);
|
|
278
283
|
|
|
284
|
+
const activeTheme = theme ?? DEFAULT_THEME;
|
|
285
|
+
const bgColor = activeTheme.colors.background;
|
|
286
|
+
const textColor = activeTheme.colors.text;
|
|
287
|
+
const mutedColor = activeTheme.colors.textMuted;
|
|
288
|
+
const primaryColor = activeTheme.colors.primary;
|
|
289
|
+
const bodyFont = activeTheme.typography.bodyFontFamily;
|
|
290
|
+
const titleFont = activeTheme.typography.titleFontFamily;
|
|
291
|
+
const lineHt = activeTheme.typography.lineHeight ?? 1.7;
|
|
292
|
+
|
|
279
293
|
return (
|
|
280
294
|
<div
|
|
281
295
|
className={`squisq-linear ${className || ''}`}
|
|
@@ -284,19 +298,97 @@ export function LinearDocView({
|
|
|
284
298
|
height: '100%',
|
|
285
299
|
overflowY: 'auto',
|
|
286
300
|
overflowX: 'hidden',
|
|
301
|
+
background: bgColor,
|
|
287
302
|
}}
|
|
288
303
|
>
|
|
289
304
|
<div
|
|
290
|
-
className="squisq-linear-content"
|
|
291
|
-
style={
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
305
|
+
className="squisq-linear-content squisq-md"
|
|
306
|
+
style={
|
|
307
|
+
{
|
|
308
|
+
maxWidth: '720px',
|
|
309
|
+
margin: '0 auto',
|
|
310
|
+
padding: '24px 16px',
|
|
311
|
+
lineHeight: lineHt,
|
|
312
|
+
fontSize: '16px',
|
|
313
|
+
fontFamily: bodyFont,
|
|
314
|
+
color: textColor,
|
|
315
|
+
// CSS custom properties for MarkdownRenderer / nested elements
|
|
316
|
+
'--squisq-linear-title-font': titleFont,
|
|
317
|
+
'--squisq-linear-body-font': bodyFont,
|
|
318
|
+
'--squisq-linear-text': textColor,
|
|
319
|
+
'--squisq-linear-muted': mutedColor,
|
|
320
|
+
'--squisq-linear-primary': primaryColor,
|
|
321
|
+
'--squisq-linear-bg': bgColor,
|
|
322
|
+
} as React.CSSProperties
|
|
323
|
+
}
|
|
299
324
|
>
|
|
325
|
+
{/* Theme-aware typography and layout for document mode */}
|
|
326
|
+
<style>{`
|
|
327
|
+
.squisq-linear-content h1,
|
|
328
|
+
.squisq-linear-content h2,
|
|
329
|
+
.squisq-linear-content h3,
|
|
330
|
+
.squisq-linear-content h4,
|
|
331
|
+
.squisq-linear-content h5,
|
|
332
|
+
.squisq-linear-content h6 {
|
|
333
|
+
font-family: var(--squisq-linear-title-font);
|
|
334
|
+
color: var(--squisq-linear-text);
|
|
335
|
+
margin-top: 1.2em;
|
|
336
|
+
margin-bottom: 0.4em;
|
|
337
|
+
}
|
|
338
|
+
.squisq-linear-content h1 { font-size: 2em; }
|
|
339
|
+
.squisq-linear-content h2 { font-size: 1.5em; }
|
|
340
|
+
.squisq-linear-content h3 { font-size: 1.25em; }
|
|
341
|
+
.squisq-linear-content p {
|
|
342
|
+
margin-bottom: 0.75em;
|
|
343
|
+
}
|
|
344
|
+
.squisq-linear-content ul,
|
|
345
|
+
.squisq-linear-content ol {
|
|
346
|
+
padding-left: 2em;
|
|
347
|
+
margin-bottom: 0.75em;
|
|
348
|
+
}
|
|
349
|
+
.squisq-linear-content li {
|
|
350
|
+
margin-bottom: 0.3em;
|
|
351
|
+
}
|
|
352
|
+
.squisq-linear-content a {
|
|
353
|
+
color: var(--squisq-linear-primary);
|
|
354
|
+
}
|
|
355
|
+
.squisq-linear-content code {
|
|
356
|
+
color: var(--squisq-linear-primary);
|
|
357
|
+
font-size: 0.9em;
|
|
358
|
+
padding: 0.15em 0.3em;
|
|
359
|
+
border-radius: 3px;
|
|
360
|
+
background: rgba(128, 128, 128, 0.15);
|
|
361
|
+
}
|
|
362
|
+
.squisq-linear-content pre {
|
|
363
|
+
padding: 1em;
|
|
364
|
+
border-radius: 6px;
|
|
365
|
+
background: rgba(0, 0, 0, 0.2);
|
|
366
|
+
overflow-x: auto;
|
|
367
|
+
margin-bottom: 0.75em;
|
|
368
|
+
}
|
|
369
|
+
.squisq-linear-content pre code {
|
|
370
|
+
padding: 0;
|
|
371
|
+
background: none;
|
|
372
|
+
}
|
|
373
|
+
.squisq-linear-content blockquote {
|
|
374
|
+
border-left: 3px solid var(--squisq-linear-muted);
|
|
375
|
+
color: var(--squisq-linear-muted);
|
|
376
|
+
padding-left: 1em;
|
|
377
|
+
margin-left: 0;
|
|
378
|
+
margin-bottom: 0.75em;
|
|
379
|
+
}
|
|
380
|
+
.squisq-linear-content hr {
|
|
381
|
+
border: none;
|
|
382
|
+
border-top: 1px solid var(--squisq-linear-muted);
|
|
383
|
+
margin: 1.5em 0;
|
|
384
|
+
}
|
|
385
|
+
.squisq-linear-content strong {
|
|
386
|
+
font-weight: 700;
|
|
387
|
+
}
|
|
388
|
+
.squisq-linear-content em {
|
|
389
|
+
font-style: italic;
|
|
390
|
+
}
|
|
391
|
+
`}</style>
|
|
300
392
|
{doc.blocks.map((block, i) => (
|
|
301
393
|
<BlockSection
|
|
302
394
|
key={block.id}
|