@bendyline/squisq-editor-react 1.3.0 → 1.5.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.
Files changed (461) hide show
  1. package/dist/DocumentSettingsDialog.d.ts +26 -0
  2. package/dist/DocumentSettingsDialog.d.ts.map +1 -0
  3. package/dist/DocumentSettingsDialog.js +115 -0
  4. package/dist/DocumentSettingsDialog.js.map +1 -0
  5. package/dist/EditorContext.d.ts +248 -4
  6. package/dist/EditorContext.d.ts.map +1 -1
  7. package/dist/EditorContext.js +248 -10
  8. package/dist/EditorContext.js.map +1 -1
  9. package/dist/EditorShell.d.ts +184 -4
  10. package/dist/EditorShell.d.ts.map +1 -1
  11. package/dist/EditorShell.js +184 -12
  12. package/dist/EditorShell.js.map +1 -1
  13. package/dist/EmojiPicker.d.ts +50 -0
  14. package/dist/EmojiPicker.d.ts.map +1 -0
  15. package/dist/EmojiPicker.js +182 -0
  16. package/dist/EmojiPicker.js.map +1 -0
  17. package/dist/ImageEditor.d.ts +68 -0
  18. package/dist/ImageEditor.d.ts.map +1 -0
  19. package/dist/ImageEditor.js +166 -0
  20. package/dist/ImageEditor.js.map +1 -0
  21. package/dist/ImageNodeView.d.ts +13 -1
  22. package/dist/ImageNodeView.d.ts.map +1 -1
  23. package/dist/ImageNodeView.js +172 -19
  24. package/dist/ImageNodeView.js.map +1 -1
  25. package/dist/ImageViewer.d.ts +26 -0
  26. package/dist/ImageViewer.d.ts.map +1 -0
  27. package/dist/ImageViewer.js +119 -0
  28. package/dist/ImageViewer.js.map +1 -0
  29. package/dist/InlineIcon.d.ts +17 -0
  30. package/dist/InlineIcon.d.ts.map +1 -0
  31. package/dist/InlineIcon.js +72 -0
  32. package/dist/InlineIcon.js.map +1 -0
  33. package/dist/InlinePreviewGutter.d.ts +52 -0
  34. package/dist/InlinePreviewGutter.d.ts.map +1 -0
  35. package/dist/InlinePreviewGutter.js +397 -0
  36. package/dist/InlinePreviewGutter.js.map +1 -0
  37. package/dist/LinkDialog.d.ts +43 -0
  38. package/dist/LinkDialog.d.ts.map +1 -0
  39. package/dist/LinkDialog.js +102 -0
  40. package/dist/LinkDialog.js.map +1 -0
  41. package/dist/MediaBin.d.ts +12 -1
  42. package/dist/MediaBin.d.ts.map +1 -1
  43. package/dist/MediaBin.js +13 -3
  44. package/dist/MediaBin.js.map +1 -1
  45. package/dist/MentionExtension.js +10 -7
  46. package/dist/MentionExtension.js.map +1 -1
  47. package/dist/OutlinePanel.d.ts +17 -0
  48. package/dist/OutlinePanel.d.ts.map +1 -0
  49. package/dist/OutlinePanel.js +167 -0
  50. package/dist/OutlinePanel.js.map +1 -0
  51. package/dist/PlainHtmlPreview.d.ts +50 -0
  52. package/dist/PlainHtmlPreview.d.ts.map +1 -0
  53. package/dist/PlainHtmlPreview.js +155 -0
  54. package/dist/PlainHtmlPreview.js.map +1 -0
  55. package/dist/PreviewControls.d.ts +15 -1
  56. package/dist/PreviewControls.d.ts.map +1 -1
  57. package/dist/PreviewControls.js +75 -18
  58. package/dist/PreviewControls.js.map +1 -1
  59. package/dist/PreviewPanel.d.ts +11 -10
  60. package/dist/PreviewPanel.d.ts.map +1 -1
  61. package/dist/PreviewPanel.js +20 -17
  62. package/dist/PreviewPanel.js.map +1 -1
  63. package/dist/RawEditor.d.ts.map +1 -1
  64. package/dist/RawEditor.js +198 -4
  65. package/dist/RawEditor.js.map +1 -1
  66. package/dist/RecorderEntry.d.ts +24 -0
  67. package/dist/RecorderEntry.d.ts.map +1 -0
  68. package/dist/RecorderEntry.js +139 -0
  69. package/dist/RecorderEntry.js.map +1 -0
  70. package/dist/TemplateAnnotation.d.ts.map +1 -1
  71. package/dist/TemplateAnnotation.js +32 -6
  72. package/dist/TemplateAnnotation.js.map +1 -1
  73. package/dist/TemplatePicker.d.ts +53 -0
  74. package/dist/TemplatePicker.d.ts.map +1 -0
  75. package/dist/TemplatePicker.js +388 -0
  76. package/dist/TemplatePicker.js.map +1 -0
  77. package/dist/ThemeCustomizerPanel.d.ts +32 -0
  78. package/dist/ThemeCustomizerPanel.d.ts.map +1 -0
  79. package/dist/ThemeCustomizerPanel.js +256 -0
  80. package/dist/ThemeCustomizerPanel.js.map +1 -0
  81. package/dist/ThemePicker.d.ts +33 -0
  82. package/dist/ThemePicker.d.ts.map +1 -0
  83. package/dist/ThemePicker.js +148 -0
  84. package/dist/ThemePicker.js.map +1 -0
  85. package/dist/Toolbar.d.ts.map +1 -1
  86. package/dist/Toolbar.js +508 -33
  87. package/dist/Toolbar.js.map +1 -1
  88. package/dist/VersionHistoryPanel.d.ts +14 -0
  89. package/dist/VersionHistoryPanel.d.ts.map +1 -0
  90. package/dist/VersionHistoryPanel.js +147 -0
  91. package/dist/VersionHistoryPanel.js.map +1 -0
  92. package/dist/ViewMenuPanel.d.ts +13 -0
  93. package/dist/ViewMenuPanel.d.ts.map +1 -0
  94. package/dist/ViewMenuPanel.js +58 -0
  95. package/dist/ViewMenuPanel.js.map +1 -0
  96. package/dist/WysiwygEditor.d.ts.map +1 -1
  97. package/dist/WysiwygEditor.js +198 -9
  98. package/dist/WysiwygEditor.js.map +1 -1
  99. package/dist/__tests__/detectMarkdown.test.js +0 -14
  100. package/dist/__tests__/detectMarkdown.test.js.map +1 -1
  101. package/dist/__tests__/documentSettingsDialog.test.d.ts +2 -0
  102. package/dist/__tests__/documentSettingsDialog.test.d.ts.map +1 -0
  103. package/dist/__tests__/documentSettingsDialog.test.js +132 -0
  104. package/dist/__tests__/documentSettingsDialog.test.js.map +1 -0
  105. package/dist/__tests__/emojiPicker.test.d.ts +2 -0
  106. package/dist/__tests__/emojiPicker.test.d.ts.map +1 -0
  107. package/dist/__tests__/emojiPicker.test.js +111 -0
  108. package/dist/__tests__/emojiPicker.test.js.map +1 -0
  109. package/dist/__tests__/fileKind.test.js +13 -0
  110. package/dist/__tests__/fileKind.test.js.map +1 -1
  111. package/dist/__tests__/imageEditAffordance.test.d.ts +2 -0
  112. package/dist/__tests__/imageEditAffordance.test.d.ts.map +1 -0
  113. package/dist/__tests__/imageEditAffordance.test.js +188 -0
  114. package/dist/__tests__/imageEditAffordance.test.js.map +1 -0
  115. package/dist/__tests__/imageEditorShell.test.d.ts +2 -0
  116. package/dist/__tests__/imageEditorShell.test.d.ts.map +1 -0
  117. package/dist/__tests__/imageEditorShell.test.js +52 -0
  118. package/dist/__tests__/imageEditorShell.test.js.map +1 -0
  119. package/dist/__tests__/imageEditorState.test.d.ts +3 -0
  120. package/dist/__tests__/imageEditorState.test.d.ts.map +1 -0
  121. package/dist/__tests__/imageEditorState.test.js +148 -0
  122. package/dist/__tests__/imageEditorState.test.js.map +1 -0
  123. package/dist/__tests__/inlinePreviewGutter.test.d.ts +2 -0
  124. package/dist/__tests__/inlinePreviewGutter.test.d.ts.map +1 -0
  125. package/dist/__tests__/inlinePreviewGutter.test.js +51 -0
  126. package/dist/__tests__/inlinePreviewGutter.test.js.map +1 -0
  127. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts +2 -0
  128. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts.map +1 -0
  129. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js +63 -0
  130. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js.map +1 -0
  131. package/dist/__tests__/jsonEditor.test.d.ts +2 -0
  132. package/dist/__tests__/jsonEditor.test.d.ts.map +1 -0
  133. package/dist/__tests__/jsonEditor.test.js +134 -0
  134. package/dist/__tests__/jsonEditor.test.js.map +1 -0
  135. package/dist/__tests__/layersPanel.test.d.ts +2 -0
  136. package/dist/__tests__/layersPanel.test.d.ts.map +1 -0
  137. package/dist/__tests__/layersPanel.test.js +84 -0
  138. package/dist/__tests__/layersPanel.test.js.map +1 -0
  139. package/dist/__tests__/linkDialogDocPicker.test.d.ts +7 -0
  140. package/dist/__tests__/linkDialogDocPicker.test.d.ts.map +1 -0
  141. package/dist/__tests__/linkDialogDocPicker.test.js +75 -0
  142. package/dist/__tests__/linkDialogDocPicker.test.js.map +1 -0
  143. package/dist/__tests__/mediaAttachmentFlow.test.d.ts +2 -0
  144. package/dist/__tests__/mediaAttachmentFlow.test.d.ts.map +1 -0
  145. package/dist/__tests__/mediaAttachmentFlow.test.js +99 -0
  146. package/dist/__tests__/mediaAttachmentFlow.test.js.map +1 -0
  147. package/dist/__tests__/outlinePanel.test.d.ts +2 -0
  148. package/dist/__tests__/outlinePanel.test.d.ts.map +1 -0
  149. package/dist/__tests__/outlinePanel.test.js +68 -0
  150. package/dist/__tests__/outlinePanel.test.js.map +1 -0
  151. package/dist/__tests__/plainHtmlPreview.test.d.ts +2 -0
  152. package/dist/__tests__/plainHtmlPreview.test.d.ts.map +1 -0
  153. package/dist/__tests__/plainHtmlPreview.test.js +87 -0
  154. package/dist/__tests__/plainHtmlPreview.test.js.map +1 -0
  155. package/dist/__tests__/propertiesPanel.test.d.ts +2 -0
  156. package/dist/__tests__/propertiesPanel.test.d.ts.map +1 -0
  157. package/dist/__tests__/propertiesPanel.test.js +64 -0
  158. package/dist/__tests__/propertiesPanel.test.js.map +1 -0
  159. package/dist/__tests__/recorderFormats.test.d.ts +2 -0
  160. package/dist/__tests__/recorderFormats.test.d.ts.map +1 -0
  161. package/dist/__tests__/recorderFormats.test.js +121 -0
  162. package/dist/__tests__/recorderFormats.test.js.map +1 -0
  163. package/dist/__tests__/recorderTimingJson.test.d.ts +2 -0
  164. package/dist/__tests__/recorderTimingJson.test.d.ts.map +1 -0
  165. package/dist/__tests__/recorderTimingJson.test.js +37 -0
  166. package/dist/__tests__/recorderTimingJson.test.js.map +1 -0
  167. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts +2 -0
  168. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts.map +1 -0
  169. package/dist/__tests__/templateAnnotationRoundTrip.test.js +31 -0
  170. package/dist/__tests__/templateAnnotationRoundTrip.test.js.map +1 -0
  171. package/dist/__tests__/tiptapBridge.test.js +26 -0
  172. package/dist/__tests__/tiptapBridge.test.js.map +1 -1
  173. package/dist/__tests__/tiptapImageRoundTrip.test.d.ts +2 -0
  174. package/dist/__tests__/tiptapImageRoundTrip.test.d.ts.map +1 -0
  175. package/dist/__tests__/tiptapImageRoundTrip.test.js +68 -0
  176. package/dist/__tests__/tiptapImageRoundTrip.test.js.map +1 -0
  177. package/dist/__tests__/useImageEditor.test.d.ts +2 -0
  178. package/dist/__tests__/useImageEditor.test.d.ts.map +1 -0
  179. package/dist/__tests__/useImageEditor.test.js +131 -0
  180. package/dist/__tests__/useImageEditor.test.js.map +1 -0
  181. package/dist/__tests__/useMediaRecorder.test.d.ts +2 -0
  182. package/dist/__tests__/useMediaRecorder.test.d.ts.map +1 -0
  183. package/dist/__tests__/useMediaRecorder.test.js +153 -0
  184. package/dist/__tests__/useMediaRecorder.test.js.map +1 -0
  185. package/dist/__tests__/versionHistory.test.d.ts +2 -0
  186. package/dist/__tests__/versionHistory.test.d.ts.map +1 -0
  187. package/dist/__tests__/versionHistory.test.js +124 -0
  188. package/dist/__tests__/versionHistory.test.js.map +1 -0
  189. package/dist/blockSlice.d.ts +24 -0
  190. package/dist/blockSlice.d.ts.map +1 -0
  191. package/dist/blockSlice.js +63 -0
  192. package/dist/blockSlice.js.map +1 -0
  193. package/dist/buildPreviewDoc.d.ts.map +1 -1
  194. package/dist/buildPreviewDoc.js +52 -2
  195. package/dist/buildPreviewDoc.js.map +1 -1
  196. package/dist/emojiData.d.ts +81 -0
  197. package/dist/emojiData.d.ts.map +1 -0
  198. package/dist/emojiData.js +1283 -0
  199. package/dist/emojiData.js.map +1 -0
  200. package/dist/fileKind.d.ts +6 -2
  201. package/dist/fileKind.d.ts.map +1 -1
  202. package/dist/fileKind.js +25 -4
  203. package/dist/fileKind.js.map +1 -1
  204. package/dist/hooks/useFileDrop.d.ts.map +1 -1
  205. package/dist/hooks/useFileDrop.js +40 -4
  206. package/dist/hooks/useFileDrop.js.map +1 -1
  207. package/dist/imageEditor/CanvasSurface.d.ts +31 -0
  208. package/dist/imageEditor/CanvasSurface.d.ts.map +1 -0
  209. package/dist/imageEditor/CanvasSurface.js +264 -0
  210. package/dist/imageEditor/CanvasSurface.js.map +1 -0
  211. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts +39 -0
  212. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts.map +1 -0
  213. package/dist/imageEditor/ImageVersionHistoryDropdown.js +283 -0
  214. package/dist/imageEditor/ImageVersionHistoryDropdown.js.map +1 -0
  215. package/dist/imageEditor/LayersPanel.d.ts +14 -0
  216. package/dist/imageEditor/LayersPanel.d.ts.map +1 -0
  217. package/dist/imageEditor/LayersPanel.js +43 -0
  218. package/dist/imageEditor/LayersPanel.js.map +1 -0
  219. package/dist/imageEditor/PropertiesPanel.d.ts +14 -0
  220. package/dist/imageEditor/PropertiesPanel.d.ts.map +1 -0
  221. package/dist/imageEditor/PropertiesPanel.js +97 -0
  222. package/dist/imageEditor/PropertiesPanel.js.map +1 -0
  223. package/dist/imageEditor/Toolbar.d.ts +30 -0
  224. package/dist/imageEditor/Toolbar.d.ts.map +1 -0
  225. package/dist/imageEditor/Toolbar.js +108 -0
  226. package/dist/imageEditor/Toolbar.js.map +1 -0
  227. package/dist/imageEditor/icons.d.ts +24 -0
  228. package/dist/imageEditor/icons.d.ts.map +1 -0
  229. package/dist/imageEditor/icons.js +45 -0
  230. package/dist/imageEditor/icons.js.map +1 -0
  231. package/dist/imageEditor/layers/EditorImageLayer.d.ts +16 -0
  232. package/dist/imageEditor/layers/EditorImageLayer.d.ts.map +1 -0
  233. package/dist/imageEditor/layers/EditorImageLayer.js +37 -0
  234. package/dist/imageEditor/layers/EditorImageLayer.js.map +1 -0
  235. package/dist/imageEditor/layers/EditorShapeLayer.d.ts +15 -0
  236. package/dist/imageEditor/layers/EditorShapeLayer.d.ts.map +1 -0
  237. package/dist/imageEditor/layers/EditorShapeLayer.js +20 -0
  238. package/dist/imageEditor/layers/EditorShapeLayer.js.map +1 -0
  239. package/dist/imageEditor/layers/EditorTextLayer.d.ts +18 -0
  240. package/dist/imageEditor/layers/EditorTextLayer.d.ts.map +1 -0
  241. package/dist/imageEditor/layers/EditorTextLayer.js +13 -0
  242. package/dist/imageEditor/layers/EditorTextLayer.js.map +1 -0
  243. package/dist/imageEditor/layers/SelectionHandles.d.ts +17 -0
  244. package/dist/imageEditor/layers/SelectionHandles.d.ts.map +1 -0
  245. package/dist/imageEditor/layers/SelectionHandles.js +19 -0
  246. package/dist/imageEditor/layers/SelectionHandles.js.map +1 -0
  247. package/dist/imageEditor/state.d.ts +76 -0
  248. package/dist/imageEditor/state.d.ts.map +1 -0
  249. package/dist/imageEditor/state.js +87 -0
  250. package/dist/imageEditor/state.js.map +1 -0
  251. package/dist/imageEditor/useImageEditor.d.ts +53 -0
  252. package/dist/imageEditor/useImageEditor.d.ts.map +1 -0
  253. package/dist/imageEditor/useImageEditor.js +244 -0
  254. package/dist/imageEditor/useImageEditor.js.map +1 -0
  255. package/dist/imageEditor/useImageEditorTokens.d.ts +16 -0
  256. package/dist/imageEditor/useImageEditorTokens.d.ts.map +1 -0
  257. package/dist/imageEditor/useImageEditorTokens.js +45 -0
  258. package/dist/imageEditor/useImageEditorTokens.js.map +1 -0
  259. package/dist/index.d.ts +48 -1
  260. package/dist/index.d.ts.map +1 -1
  261. package/dist/index.js +36 -0
  262. package/dist/index.js.map +1 -1
  263. package/dist/jsonEditor/EmbeddedRichTextField.d.ts +15 -0
  264. package/dist/jsonEditor/EmbeddedRichTextField.d.ts.map +1 -0
  265. package/dist/jsonEditor/EmbeddedRichTextField.js +74 -0
  266. package/dist/jsonEditor/EmbeddedRichTextField.js.map +1 -0
  267. package/dist/jsonEditor/JsonEditor.d.ts +36 -0
  268. package/dist/jsonEditor/JsonEditor.d.ts.map +1 -0
  269. package/dist/jsonEditor/JsonEditor.js +15 -0
  270. package/dist/jsonEditor/JsonEditor.js.map +1 -0
  271. package/dist/jsonEditor/JsonEditorContext.d.ts +28 -0
  272. package/dist/jsonEditor/JsonEditorContext.d.ts.map +1 -0
  273. package/dist/jsonEditor/JsonEditorContext.js +41 -0
  274. package/dist/jsonEditor/JsonEditorContext.js.map +1 -0
  275. package/dist/jsonEditor/RenderNode.d.ts +16 -0
  276. package/dist/jsonEditor/RenderNode.d.ts.map +1 -0
  277. package/dist/jsonEditor/RenderNode.js +32 -0
  278. package/dist/jsonEditor/RenderNode.js.map +1 -0
  279. package/dist/jsonEditor/editors.d.ts +36 -0
  280. package/dist/jsonEditor/editors.d.ts.map +1 -0
  281. package/dist/jsonEditor/editors.js +347 -0
  282. package/dist/jsonEditor/editors.js.map +1 -0
  283. package/dist/jsonEditor/index.d.ts +3 -0
  284. package/dist/jsonEditor/index.d.ts.map +1 -0
  285. package/dist/jsonEditor/index.js +2 -0
  286. package/dist/jsonEditor/index.js.map +1 -0
  287. package/dist/jsonEditor/useJsonEditorTokens.d.ts +13 -0
  288. package/dist/jsonEditor/useJsonEditorTokens.d.ts.map +1 -0
  289. package/dist/jsonEditor/useJsonEditorTokens.js +38 -0
  290. package/dist/jsonEditor/useJsonEditorTokens.js.map +1 -0
  291. package/dist/recorder/RecorderButton.d.ts +31 -0
  292. package/dist/recorder/RecorderButton.d.ts.map +1 -0
  293. package/dist/recorder/RecorderButton.js +24 -0
  294. package/dist/recorder/RecorderButton.js.map +1 -0
  295. package/dist/recorder/RecorderModal.d.ts +59 -0
  296. package/dist/recorder/RecorderModal.d.ts.map +1 -0
  297. package/dist/recorder/RecorderModal.js +333 -0
  298. package/dist/recorder/RecorderModal.js.map +1 -0
  299. package/dist/recorder/RecorderPanel.d.ts +25 -0
  300. package/dist/recorder/RecorderPanel.d.ts.map +1 -0
  301. package/dist/recorder/RecorderPanel.js +30 -0
  302. package/dist/recorder/RecorderPanel.js.map +1 -0
  303. package/dist/recorder/formats.d.ts +51 -0
  304. package/dist/recorder/formats.d.ts.map +1 -0
  305. package/dist/recorder/formats.js +144 -0
  306. package/dist/recorder/formats.js.map +1 -0
  307. package/dist/recorder/hooks/useMediaRecorder.d.ts +90 -0
  308. package/dist/recorder/hooks/useMediaRecorder.d.ts.map +1 -0
  309. package/dist/recorder/hooks/useMediaRecorder.js +277 -0
  310. package/dist/recorder/hooks/useMediaRecorder.js.map +1 -0
  311. package/dist/recorder/hooks/useStreamPreview.d.ts +22 -0
  312. package/dist/recorder/hooks/useStreamPreview.d.ts.map +1 -0
  313. package/dist/recorder/hooks/useStreamPreview.js +44 -0
  314. package/dist/recorder/hooks/useStreamPreview.js.map +1 -0
  315. package/dist/recorder/sources/cameraStream.d.ts +22 -0
  316. package/dist/recorder/sources/cameraStream.d.ts.map +1 -0
  317. package/dist/recorder/sources/cameraStream.js +24 -0
  318. package/dist/recorder/sources/cameraStream.js.map +1 -0
  319. package/dist/recorder/sources/micStream.d.ts +15 -0
  320. package/dist/recorder/sources/micStream.d.ts.map +1 -0
  321. package/dist/recorder/sources/micStream.js +24 -0
  322. package/dist/recorder/sources/micStream.js.map +1 -0
  323. package/dist/recorder/sources/screenStream.d.ts +53 -0
  324. package/dist/recorder/sources/screenStream.d.ts.map +1 -0
  325. package/dist/recorder/sources/screenStream.js +114 -0
  326. package/dist/recorder/sources/screenStream.js.map +1 -0
  327. package/dist/recorder/timingJson.d.ts +51 -0
  328. package/dist/recorder/timingJson.d.ts.map +1 -0
  329. package/dist/recorder/timingJson.js +42 -0
  330. package/dist/recorder/timingJson.js.map +1 -0
  331. package/dist/tiptap/TiptapAudio.d.ts +26 -0
  332. package/dist/tiptap/TiptapAudio.d.ts.map +1 -0
  333. package/dist/tiptap/TiptapAudio.js +58 -0
  334. package/dist/tiptap/TiptapAudio.js.map +1 -0
  335. package/dist/tiptap/TiptapVideo.d.ts +30 -0
  336. package/dist/tiptap/TiptapVideo.d.ts.map +1 -0
  337. package/dist/tiptap/TiptapVideo.js +66 -0
  338. package/dist/tiptap/TiptapVideo.js.map +1 -0
  339. package/dist/tiptap/useResolvedMediaSrc.d.ts +2 -0
  340. package/dist/tiptap/useResolvedMediaSrc.d.ts.map +1 -0
  341. package/dist/tiptap/useResolvedMediaSrc.js +42 -0
  342. package/dist/tiptap/useResolvedMediaSrc.js.map +1 -0
  343. package/dist/tiptapBridge.d.ts.map +1 -1
  344. package/dist/tiptapBridge.js +210 -16
  345. package/dist/tiptapBridge.js.map +1 -1
  346. package/dist/useHeadingLayout.d.ts +54 -0
  347. package/dist/useHeadingLayout.d.ts.map +1 -0
  348. package/dist/useHeadingLayout.js +260 -0
  349. package/dist/useHeadingLayout.js.map +1 -0
  350. package/dist/utils/collectInlineFontAwesomeCss.d.ts +21 -0
  351. package/dist/utils/collectInlineFontAwesomeCss.d.ts.map +1 -0
  352. package/dist/utils/collectInlineFontAwesomeCss.js +68 -0
  353. package/dist/utils/collectInlineFontAwesomeCss.js.map +1 -0
  354. package/dist/utils/dropUtils.d.ts +21 -2
  355. package/dist/utils/dropUtils.d.ts.map +1 -1
  356. package/dist/utils/dropUtils.js +43 -4
  357. package/dist/utils/dropUtils.js.map +1 -1
  358. package/dist/utils/normalizeMalformedAssetUrl.d.ts +15 -0
  359. package/dist/utils/normalizeMalformedAssetUrl.d.ts.map +1 -0
  360. package/dist/utils/normalizeMalformedAssetUrl.js +27 -0
  361. package/dist/utils/normalizeMalformedAssetUrl.js.map +1 -0
  362. package/package.json +8 -5
  363. package/src/DocumentSettingsDialog.tsx +266 -0
  364. package/src/EditorContext.tsx +534 -10
  365. package/src/EditorShell.tsx +691 -63
  366. package/src/EmojiPicker.tsx +332 -0
  367. package/src/ImageEditor.tsx +327 -0
  368. package/src/ImageNodeView.tsx +222 -21
  369. package/src/ImageViewer.tsx +221 -0
  370. package/src/InlineIcon.ts +84 -0
  371. package/src/InlinePreviewGutter.tsx +582 -0
  372. package/src/LinkDialog.tsx +276 -0
  373. package/src/MediaBin.tsx +22 -3
  374. package/src/MentionExtension.tsx +10 -7
  375. package/src/OutlinePanel.tsx +295 -0
  376. package/src/PlainHtmlPreview.tsx +211 -0
  377. package/src/PreviewControls.tsx +130 -24
  378. package/src/PreviewPanel.tsx +38 -21
  379. package/src/RawEditor.tsx +215 -4
  380. package/src/RecorderEntry.tsx +164 -0
  381. package/src/TemplateAnnotation.ts +32 -6
  382. package/src/TemplatePicker.tsx +818 -0
  383. package/src/ThemeCustomizerPanel.tsx +595 -0
  384. package/src/ThemePicker.tsx +319 -0
  385. package/src/Toolbar.tsx +708 -111
  386. package/src/VersionHistoryPanel.tsx +329 -0
  387. package/src/ViewMenuPanel.tsx +188 -0
  388. package/src/WysiwygEditor.tsx +229 -9
  389. package/src/__tests__/detectMarkdown.test.ts +0 -15
  390. package/src/__tests__/documentSettingsDialog.test.tsx +147 -0
  391. package/src/__tests__/emojiPicker.test.tsx +133 -0
  392. package/src/__tests__/fileKind.test.ts +16 -0
  393. package/src/__tests__/imageEditAffordance.test.tsx +268 -0
  394. package/src/__tests__/imageEditorShell.test.tsx +57 -0
  395. package/src/__tests__/imageEditorState.test.ts +171 -0
  396. package/src/__tests__/inlinePreviewGutter.test.tsx +62 -0
  397. package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +103 -0
  398. package/src/__tests__/jsonEditor.test.tsx +168 -0
  399. package/src/__tests__/layersPanel.test.tsx +97 -0
  400. package/src/__tests__/linkDialogDocPicker.test.tsx +137 -0
  401. package/src/__tests__/mediaAttachmentFlow.test.ts +110 -0
  402. package/src/__tests__/outlinePanel.test.tsx +79 -0
  403. package/src/__tests__/plainHtmlPreview.test.tsx +107 -0
  404. package/src/__tests__/propertiesPanel.test.tsx +69 -0
  405. package/src/__tests__/recorderFormats.test.ts +146 -0
  406. package/src/__tests__/recorderTimingJson.test.ts +41 -0
  407. package/src/__tests__/templateAnnotationRoundTrip.test.ts +34 -0
  408. package/src/__tests__/tiptapBridge.test.ts +29 -0
  409. package/src/__tests__/tiptapImageRoundTrip.test.ts +73 -0
  410. package/src/__tests__/useImageEditor.test.tsx +159 -0
  411. package/src/__tests__/useMediaRecorder.test.ts +186 -0
  412. package/src/__tests__/versionHistory.test.tsx +197 -0
  413. package/src/blockSlice.ts +75 -0
  414. package/src/buildPreviewDoc.ts +61 -6
  415. package/src/emojiData.ts +1337 -0
  416. package/src/fileKind.ts +30 -6
  417. package/src/hooks/useFileDrop.ts +40 -4
  418. package/src/imageEditor/CanvasSurface.tsx +402 -0
  419. package/src/imageEditor/ImageVersionHistoryDropdown.tsx +396 -0
  420. package/src/imageEditor/LayersPanel.tsx +143 -0
  421. package/src/imageEditor/PropertiesPanel.tsx +428 -0
  422. package/src/imageEditor/Toolbar.tsx +242 -0
  423. package/src/imageEditor/icons.tsx +144 -0
  424. package/src/imageEditor/image-editor.css +450 -0
  425. package/src/imageEditor/layers/EditorImageLayer.tsx +45 -0
  426. package/src/imageEditor/layers/EditorShapeLayer.tsx +62 -0
  427. package/src/imageEditor/layers/EditorTextLayer.tsx +45 -0
  428. package/src/imageEditor/layers/SelectionHandles.tsx +86 -0
  429. package/src/imageEditor/state.ts +153 -0
  430. package/src/imageEditor/useImageEditor.ts +328 -0
  431. package/src/imageEditor/useImageEditorTokens.ts +70 -0
  432. package/src/index.ts +82 -0
  433. package/src/jsonEditor/EmbeddedRichTextField.tsx +81 -0
  434. package/src/jsonEditor/JsonEditor.tsx +81 -0
  435. package/src/jsonEditor/JsonEditorContext.tsx +75 -0
  436. package/src/jsonEditor/RenderNode.tsx +66 -0
  437. package/src/jsonEditor/editors.tsx +678 -0
  438. package/src/jsonEditor/index.ts +2 -0
  439. package/src/jsonEditor/json-editor.css +463 -0
  440. package/src/jsonEditor/useJsonEditorTokens.ts +63 -0
  441. package/src/recorder/RecorderButton.tsx +72 -0
  442. package/src/recorder/RecorderModal.tsx +596 -0
  443. package/src/recorder/RecorderPanel.tsx +93 -0
  444. package/src/recorder/formats.ts +159 -0
  445. package/src/recorder/hooks/useMediaRecorder.ts +378 -0
  446. package/src/recorder/hooks/useStreamPreview.ts +47 -0
  447. package/src/recorder/sources/cameraStream.ts +32 -0
  448. package/src/recorder/sources/micStream.ts +25 -0
  449. package/src/recorder/sources/screenStream.ts +162 -0
  450. package/src/recorder/timingJson.ts +66 -0
  451. package/src/styles/editor.css +2490 -51
  452. package/src/styles/image-edit-affordance.css +201 -0
  453. package/src/styles/index.css +10 -0
  454. package/src/tiptap/TiptapAudio.tsx +86 -0
  455. package/src/tiptap/TiptapVideo.tsx +119 -0
  456. package/src/tiptap/useResolvedMediaSrc.ts +47 -0
  457. package/src/tiptapBridge.ts +227 -22
  458. package/src/useHeadingLayout.ts +294 -0
  459. package/src/utils/collectInlineFontAwesomeCss.ts +69 -0
  460. package/src/utils/dropUtils.ts +54 -6
  461. package/src/utils/normalizeMalformedAssetUrl.ts +22 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * PlainHtmlPreview
3
+ *
4
+ * Live WYSIWYG preview of the plain-HTML export. Renders the result of
5
+ * `markdownDocToPlainHtml` inside a sandboxed `<iframe srcDoc>` so the
6
+ * exported document's inline `<style>` block can't leak into the host
7
+ * page — and so the preview looks identical to what users get when they
8
+ * open the downloaded `.html`.
9
+ *
10
+ * Image handling: relative `<img src>` references in the markdown can't
11
+ * load directly from inside the iframe (there's no real document
12
+ * origin). When a `mediaProvider` is supplied, this component walks the
13
+ * parsed markdown for image refs, resolves each through
14
+ * `mediaProvider.resolveUrl()` (which returns a cached blob URL), and
15
+ * passes the resolved map to the renderer so the iframe gets blob URLs
16
+ * it can fetch.
17
+ */
18
+
19
+ import { useEffect, useMemo, useState } from 'react';
20
+ import type { CSSProperties } from 'react';
21
+ import { parseMarkdown } from '@bendyline/squisq/markdown';
22
+ import type { MarkdownDocument, HtmlNode } from '@bendyline/squisq/markdown';
23
+ import type { MediaProvider, Theme } from '@bendyline/squisq/schemas';
24
+ import { markdownDocToPlainHtml } from '@bendyline/squisq-formats/html';
25
+ import { normalizeMalformedAssetUrl } from './utils/normalizeMalformedAssetUrl';
26
+ import { collectInlineFontAwesomeCss } from './utils/collectInlineFontAwesomeCss';
27
+
28
+ export interface PlainHtmlPreviewProps {
29
+ /** Raw markdown source. */
30
+ markdown: string;
31
+ /** Document title — populates the iframe's `<title>`. */
32
+ title?: string;
33
+ /**
34
+ * Pre-resolved image substitutions (export-time use). Takes precedence
35
+ * over live `mediaProvider` resolution for any URL it contains.
36
+ */
37
+ images?: Map<string, string>;
38
+ /**
39
+ * When passed, relative image URLs in the markdown are resolved live
40
+ * via this provider. Skip for static previews where `images` already
41
+ * contains everything.
42
+ */
43
+ mediaProvider?: MediaProvider | null;
44
+ /** Token that, when changed, forces re-resolution of media URLs.
45
+ * Mirrors the `mediaRevision` bump the editor uses after an image
46
+ * edit so saves show up in the preview without remount. */
47
+ mediaRevision?: number;
48
+ /**
49
+ * Squisq theme to apply. When set, the iframe loads any Google-
50
+ * hosted fonts the theme uses and the rendered HTML adopts the
51
+ * theme's colors and typography.
52
+ */
53
+ theme?: Theme;
54
+ className?: string;
55
+ style?: CSSProperties;
56
+ }
57
+
58
+ const IFRAME_STYLE: CSSProperties = {
59
+ width: '100%',
60
+ height: '100%',
61
+ border: 'none',
62
+ background: '#fff',
63
+ display: 'block',
64
+ };
65
+
66
+ export function PlainHtmlPreview({
67
+ markdown,
68
+ title,
69
+ images,
70
+ mediaProvider,
71
+ mediaRevision,
72
+ theme,
73
+ className,
74
+ style,
75
+ }: PlainHtmlPreviewProps) {
76
+ const mdDoc = useMemo<MarkdownDocument>(() => parseMarkdown(markdown), [markdown]);
77
+
78
+ // Resolve any relative image URLs the doc references. Blob URLs are
79
+ // cheap once cached, so re-resolving on every keystroke is fine —
80
+ // `resolveUrl` is memoized inside the provider.
81
+ const [resolvedImages, setResolvedImages] = useState<Map<string, string> | null>(null);
82
+
83
+ useEffect(() => {
84
+ if (!mediaProvider) {
85
+ setResolvedImages(null);
86
+ return;
87
+ }
88
+ let cancelled = false;
89
+ const refs = Array.from(collectImageRefs(mdDoc));
90
+ Promise.all(
91
+ refs.map(async (ref) => {
92
+ // Word-style imports may have `http://<doc>_files/foo.png` —
93
+ // recover the relative path so the workspace provider resolves
94
+ // it, otherwise the iframe tries to fetch a nonsense hostname.
95
+ const recovered = normalizeMalformedAssetUrl(ref);
96
+ if (!recovered && isExternal(ref)) return [ref, ref] as const;
97
+ const lookup = recovered ?? ref;
98
+ try {
99
+ const url = await mediaProvider.resolveUrl(lookup);
100
+ return [ref, url] as const;
101
+ } catch {
102
+ return [ref, ref] as const;
103
+ }
104
+ }),
105
+ ).then((pairs) => {
106
+ if (cancelled) return;
107
+ const next = new Map<string, string>(pairs);
108
+ setResolvedImages(next);
109
+ });
110
+ return () => {
111
+ cancelled = true;
112
+ };
113
+ }, [mdDoc, mediaProvider, mediaRevision]);
114
+
115
+ const mergedImages = useMemo(() => {
116
+ if (!resolvedImages && !images) return undefined;
117
+ const merged = new Map<string, string>();
118
+ if (resolvedImages) for (const [k, v] of resolvedImages) merged.set(k, v);
119
+ if (images) for (const [k, v] of images) merged.set(k, v);
120
+ return merged;
121
+ }, [resolvedImages, images]);
122
+
123
+ // Gather FontAwesome @font-face + utility rules from the host page's
124
+ // own stylesheets so the iframe doesn't have to depend on a cross-
125
+ // origin CDN fetch (which sandbox / tracking-prevention can silently
126
+ // drop, leaving the icons invisible). The host (editor-react) already
127
+ // bundles FA, so the rules are guaranteed to be present and the font
128
+ // URLs inside them resolve to same-origin assets the iframe can
129
+ // fetch under `allow-same-origin`.
130
+ const iconsCss = useMemo(() => collectInlineFontAwesomeCss(), []);
131
+
132
+ const html = useMemo(
133
+ () => markdownDocToPlainHtml(mdDoc, { title, images: mergedImages, theme, iconsCss }),
134
+ [mdDoc, title, mergedImages, theme, iconsCss],
135
+ );
136
+
137
+ return (
138
+ <iframe
139
+ className={className}
140
+ data-testid="plain-html-preview"
141
+ title={title ?? 'HTML preview'}
142
+ srcDoc={html}
143
+ // `allow-same-origin` is required so the iframe can fetch blob:
144
+ // URLs created by the host's media provider. We intentionally do
145
+ // NOT include `allow-scripts` — the rendered HTML is plain-output
146
+ // markup with no JS, and refusing scripts hardens against
147
+ // accidental `<script>` content in user markdown.
148
+ sandbox="allow-same-origin"
149
+ style={{ ...IFRAME_STYLE, ...style }}
150
+ />
151
+ );
152
+ }
153
+
154
+ // ── Image collection ───────────────────────────────────────────────
155
+
156
+ function isExternal(url: string): boolean {
157
+ return (
158
+ !url ||
159
+ url.startsWith('data:') ||
160
+ url.startsWith('blob:') ||
161
+ url.startsWith('http://') ||
162
+ url.startsWith('https://') ||
163
+ url.startsWith('//')
164
+ );
165
+ }
166
+
167
+ /**
168
+ * Collect every image URL referenced anywhere in the doc — markdown
169
+ * `image` nodes plus any `<img src>` inside raw HTML blocks/inlines
170
+ * (the WYSIWYG editor emits the HTML form for resized images).
171
+ */
172
+ function collectImageRefs(doc: MarkdownDocument): Set<string> {
173
+ const refs = new Set<string>();
174
+
175
+ function visitHtml(nodes: HtmlNode[]): void {
176
+ for (const n of nodes) {
177
+ if (n.type !== 'htmlElement') continue;
178
+ const tag = n.tagName.toLowerCase();
179
+ // <img>, <video>, and <audio> all reference media via `src`.
180
+ // The export pipeline rewrites whichever map entries exist, so
181
+ // collecting them under the same set is enough — `ctx.images`
182
+ // is generic media despite the historical name.
183
+ if (tag === 'img' || tag === 'video' || tag === 'audio' || tag === 'source') {
184
+ const src = n.attributes.src;
185
+ if (typeof src === 'string' && src) refs.add(src);
186
+ }
187
+ if (tag === 'video' || tag === 'audio') {
188
+ const poster = n.attributes.poster;
189
+ if (typeof poster === 'string' && poster) refs.add(poster);
190
+ }
191
+ visitHtml(n.children);
192
+ }
193
+ }
194
+
195
+ function visit(node: unknown): void {
196
+ if (!node || typeof node !== 'object') return;
197
+ const n = node as Record<string, unknown>;
198
+ if (n.type === 'image' && typeof n.url === 'string' && n.url) {
199
+ refs.add(n.url);
200
+ }
201
+ if ((n.type === 'htmlBlock' || n.type === 'htmlInline') && Array.isArray(n.htmlChildren)) {
202
+ visitHtml(n.htmlChildren as HtmlNode[]);
203
+ }
204
+ if (Array.isArray(n.children)) {
205
+ for (const child of n.children) visit(child);
206
+ }
207
+ }
208
+
209
+ for (const child of doc.children) visit(child);
210
+ return refs;
211
+ }
@@ -10,14 +10,25 @@
10
10
  * - PreviewPanel (the actual player, which reads the selected values)
11
11
  */
12
12
 
13
- import { createContext, useContext, useState, useMemo, useEffect, useRef } from 'react';
13
+ import {
14
+ createContext,
15
+ useCallback,
16
+ useContext,
17
+ useState,
18
+ useMemo,
19
+ useEffect,
20
+ useRef,
21
+ } from 'react';
14
22
  import type { ReactNode } from 'react';
15
23
  import type { DisplayMode, CaptionStyle } from '@bendyline/squisq-react';
16
24
  import type { ViewportPreset, ViewportConfig } from '@bendyline/squisq/schemas';
17
25
  import { VIEWPORT_PRESETS, getThemeSummaries, resolveTheme } from '@bendyline/squisq/schemas';
18
26
  import type { Theme } from '@bendyline/squisq/schemas';
27
+ import { ThemePicker } from './ThemePicker';
19
28
  import { getTransformStyleSummaries } from '@bendyline/squisq/transform';
20
29
  import type { Doc } from '@bendyline/squisq/schemas';
30
+ import { setFrontmatterValues } from '@bendyline/squisq/markdown';
31
+ import { useEditorContext } from './EditorContext';
21
32
 
22
33
  // ── Context ──────────────────────────────────────────────────────
23
34
 
@@ -44,6 +55,16 @@ export function usePreviewSettings(): PreviewSettings {
44
55
  return ctx;
45
56
  }
46
57
 
58
+ /**
59
+ * Like {@link usePreviewSettings} but returns `null` when no provider is
60
+ * mounted. For consumers (e.g. WysiwygEditor) that want to react to the
61
+ * active theme when available without forcing every test harness to
62
+ * wrap them in a PreviewSettingsProvider.
63
+ */
64
+ export function usePreviewSettingsOptional(): PreviewSettings | null {
65
+ return useContext(PreviewSettingsContext);
66
+ }
67
+
47
68
  // ── Frontmatter resolvers ────────────────────────────────────────
48
69
 
49
70
  function resolveRenderAs(value: unknown): ViewportPreset | null {
@@ -68,9 +89,10 @@ function resolveRenderAs(value: unknown): ViewportPreset | null {
68
89
  function resolveDisplayMode(value: unknown): DisplayMode | null {
69
90
  if (typeof value !== 'string') return null;
70
91
  const v = value.trim().toLowerCase();
71
- if (v === 'video' || v === 'slideshow' || v === 'linear') return v;
92
+ if (v === 'video' || v === 'slideshow' || v === 'linear' || v === 'page') return v;
72
93
  if (v === 'slides' || v === 'presentation' || v === 'deck') return 'slideshow';
73
- if (v === 'document' || v === 'scroll' || v === 'page') return 'linear';
94
+ if (v === 'document' || v === 'scroll') return 'linear';
95
+ if (v === 'html' || v === 'plain' || v === 'reader') return 'page';
74
96
  return null;
75
97
  }
76
98
 
@@ -109,10 +131,50 @@ function resolveFrontmatterCaptionStyle(value: unknown): CaptionStyle | null {
109
131
  export interface PreviewSettingsProviderProps {
110
132
  doc: Doc | null;
111
133
  children: ReactNode;
134
+ /**
135
+ * Optional Theme to use for the preview, regardless of `Doc.themeId` or
136
+ * the user's theme dropdown selection. Used by the theme customizer to
137
+ * preview an in-progress theme without mutating the document. When
138
+ * present, `activeTheme` is this value and `activeThemeId` is its `id`.
139
+ */
140
+ themeOverride?: Theme | null;
141
+ }
142
+
143
+ /** Frontmatter keys we read/write for preview settings. The squisq-prefixed
144
+ * keys are the canonical names; the legacy keys are still read so existing
145
+ * documents keep working. Persistence (writes) uses only the squisq names. */
146
+ const FM_KEYS = {
147
+ theme: { canonical: 'squisq-theme', legacy: 'theme' as const },
148
+ transform: { canonical: 'squisq-transform', legacy: 'transform-style' as const },
149
+ captions: { canonical: 'squisq-captions', legacy: 'caption-style' as const },
150
+ } as const;
151
+
152
+ function readFrontmatterKey(
153
+ fm: Record<string, unknown> | undefined,
154
+ canonical: string,
155
+ legacy: string,
156
+ ): unknown {
157
+ if (!fm) return undefined;
158
+ return Object.prototype.hasOwnProperty.call(fm, canonical) ? fm[canonical] : fm[legacy];
112
159
  }
113
160
 
114
- export function PreviewSettingsProvider({ doc, children }: PreviewSettingsProviderProps) {
161
+ export function PreviewSettingsProvider({
162
+ doc,
163
+ children,
164
+ themeOverride,
165
+ }: PreviewSettingsProviderProps) {
115
166
  const frontmatter = doc?.frontmatter;
167
+ const { markdownSource, setMarkdownSource } = useEditorContext();
168
+
169
+ const persistFrontmatter = useCallback(
170
+ (updates: Record<string, string | null>) => {
171
+ const next = setFrontmatterValues(markdownSource, updates);
172
+ if (next !== markdownSource) {
173
+ setMarkdownSource(next);
174
+ }
175
+ },
176
+ [markdownSource, setMarkdownSource],
177
+ );
116
178
 
117
179
  // Viewport
118
180
  const fmPreset = useMemo(
@@ -130,30 +192,69 @@ export function PreviewSettingsProvider({ doc, children }: PreviewSettingsProvid
130
192
  useEffect(() => setSelectedDisplayMode(null), [fmMode]);
131
193
  const activeDisplayMode = selectedDisplayMode ?? fmMode ?? 'video';
132
194
 
133
- // Theme
134
- const fmTheme = useMemo(() => resolveFrontmatterTheme(frontmatter?.['theme']), [frontmatter]);
195
+ // Theme — persisted to `squisq-theme` (legacy `theme` still read for compat)
196
+ const fmTheme = useMemo(
197
+ () =>
198
+ resolveFrontmatterTheme(
199
+ readFrontmatterKey(frontmatter, FM_KEYS.theme.canonical, FM_KEYS.theme.legacy),
200
+ ),
201
+ [frontmatter],
202
+ );
135
203
  const [selectedThemeId, setSelectedThemeId] = useState<string | null>(null);
136
204
  useEffect(() => setSelectedThemeId(null), [fmTheme]);
137
- const activeThemeId = selectedThemeId ?? fmTheme ?? 'standard';
138
- const activeTheme = useMemo(() => resolveTheme(activeThemeId), [activeThemeId]);
205
+ const resolvedThemeId = selectedThemeId ?? fmTheme ?? 'standard';
206
+ const resolvedTheme = useMemo(() => resolveTheme(resolvedThemeId), [resolvedThemeId]);
207
+ // themeOverride wins over both dropdown selection and frontmatter
208
+ const activeThemeId = themeOverride?.id ?? resolvedThemeId;
209
+ const activeTheme = themeOverride ?? resolvedTheme;
210
+ const handleSetThemeId = useCallback(
211
+ (id: string | null) => {
212
+ setSelectedThemeId(id);
213
+ if (id !== null) persistFrontmatter({ [FM_KEYS.theme.canonical]: id });
214
+ },
215
+ [persistFrontmatter],
216
+ );
139
217
 
140
- // Transform
218
+ // Transform — persisted to `squisq-transform` (legacy `transform-style` read for compat)
141
219
  const fmTransform = useMemo(
142
- () => resolveFrontmatterTransform(frontmatter?.['transform-style']),
220
+ () =>
221
+ resolveFrontmatterTransform(
222
+ readFrontmatterKey(frontmatter, FM_KEYS.transform.canonical, FM_KEYS.transform.legacy),
223
+ ),
143
224
  [frontmatter],
144
225
  );
145
226
  const [selectedTransformStyle, setSelectedTransformStyle] = useState<string | null>(null);
146
227
  useEffect(() => setSelectedTransformStyle(null), [fmTransform]);
147
228
  const activeTransformStyle = selectedTransformStyle ?? fmTransform ?? '';
229
+ const handleSetTransformStyle = useCallback(
230
+ (id: string | null) => {
231
+ setSelectedTransformStyle(id);
232
+ if (id !== null) {
233
+ // Empty string = "None" — remove the key rather than writing a blank value.
234
+ persistFrontmatter({ [FM_KEYS.transform.canonical]: id === '' ? null : id });
235
+ }
236
+ },
237
+ [persistFrontmatter],
238
+ );
148
239
 
149
- // Caption style
240
+ // Caption style — persisted to `squisq-captions` (legacy `caption-style` read for compat)
150
241
  const fmCaption = useMemo(
151
- () => resolveFrontmatterCaptionStyle(frontmatter?.['caption-style']),
242
+ () =>
243
+ resolveFrontmatterCaptionStyle(
244
+ readFrontmatterKey(frontmatter, FM_KEYS.captions.canonical, FM_KEYS.captions.legacy),
245
+ ),
152
246
  [frontmatter],
153
247
  );
154
248
  const [selectedCaptionStyle, setSelectedCaptionStyle] = useState<CaptionStyle | null>(null);
155
249
  useEffect(() => setSelectedCaptionStyle(null), [fmCaption]);
156
250
  const activeCaptionStyle = selectedCaptionStyle ?? fmCaption ?? 'standard';
251
+ const handleSetCaptionStyle = useCallback(
252
+ (style: CaptionStyle | null) => {
253
+ setSelectedCaptionStyle(style);
254
+ if (style !== null) persistFrontmatter({ [FM_KEYS.captions.canonical]: style });
255
+ },
256
+ [persistFrontmatter],
257
+ );
157
258
 
158
259
  const value = useMemo<PreviewSettings>(
159
260
  () => ({
@@ -163,12 +264,12 @@ export function PreviewSettingsProvider({ doc, children }: PreviewSettingsProvid
163
264
  activeDisplayMode,
164
265
  setSelectedDisplayMode,
165
266
  activeThemeId,
166
- setSelectedThemeId,
267
+ setSelectedThemeId: handleSetThemeId,
167
268
  activeTheme,
168
269
  activeTransformStyle,
169
- setSelectedTransformStyle,
270
+ setSelectedTransformStyle: handleSetTransformStyle,
170
271
  activeCaptionStyle,
171
- setSelectedCaptionStyle,
272
+ setSelectedCaptionStyle: handleSetCaptionStyle,
172
273
  }),
173
274
  [
174
275
  activePreset,
@@ -178,6 +279,9 @@ export function PreviewSettingsProvider({ doc, children }: PreviewSettingsProvid
178
279
  activeTheme,
179
280
  activeTransformStyle,
180
281
  activeCaptionStyle,
282
+ handleSetThemeId,
283
+ handleSetTransformStyle,
284
+ handleSetCaptionStyle,
181
285
  ],
182
286
  );
183
287
 
@@ -199,10 +303,9 @@ const DISPLAY_MODE_OPTIONS: { key: DisplayMode; label: string }[] = [
199
303
  { key: 'video', label: 'Video' },
200
304
  { key: 'slideshow', label: 'Slideshow' },
201
305
  { key: 'linear', label: 'Document' },
306
+ { key: 'page', label: 'Page' },
202
307
  ];
203
308
 
204
- const THEME_OPTIONS = getThemeSummaries().map((s) => ({ key: s.id, label: s.name }));
205
-
206
309
  const TRANSFORM_STYLE_OPTIONS = [
207
310
  { key: '', label: 'None' },
208
311
  ...getTransformStyleSummaries().map((s) => ({ key: s.id, label: s.name })),
@@ -285,13 +388,16 @@ export function PreviewToolbarControls() {
285
388
  onChange={(v) => s.setSelectedDisplayMode(v as DisplayMode)}
286
389
  compact={isNarrow}
287
390
  />
288
- <PreviewSelect
289
- label="Theme"
290
- value={s.activeThemeId}
291
- options={THEME_OPTIONS}
292
- onChange={(v) => s.setSelectedThemeId(v)}
293
- compact={isNarrow}
294
- />
391
+ <div
392
+ className={`squisq-preview-control${isNarrow ? ' squisq-preview-control--compact' : ''}`}
393
+ >
394
+ <label style={labelStyle}>Theme:</label>
395
+ <ThemePicker
396
+ value={s.activeThemeId}
397
+ onChange={(v) => s.setSelectedThemeId(v)}
398
+ ariaLabel="Theme"
399
+ />
400
+ </div>
295
401
  <PreviewSelect
296
402
  label="Transform"
297
403
  value={s.activeTransformStyle}
@@ -2,17 +2,14 @@
2
2
  * PreviewPanel
3
3
  *
4
4
  * Renders a live preview of the current markdown document as a slideshow
5
- * using the DocPlayer component from @bendyline/squisq-react.
6
- *
7
- * The markdown-derived Doc (from markdownToDoc) contains hierarchical blocks
8
- * with template names, heading text, and body content — but no audio or
9
- * visual layers. This component bridges the gap by using buildPreviewDoc()
10
- * to flatten blocks, convert them to TemplateBlock slides with interleaved
11
- * images, and synthesize timing.
5
+ * using the DocPlayer component from @bendyline/squisq-react. The
6
+ * markdown → player-Doc conversion is delegated to the shared
7
+ * `buildPreviewDoc` helper so live preview and the export pipeline stay
8
+ * in sync.
12
9
  */
13
10
 
14
11
  import { useState, useEffect } from 'react';
15
- import { DocPlayer, LinearDocView } from '@bendyline/squisq-react';
12
+ import { DocPlayer, LinearDocView, useMediaProvider } from '@bendyline/squisq-react';
16
13
  import type { Doc } from '@bendyline/squisq/schemas';
17
14
  import { applyTransform } from '@bendyline/squisq/transform';
18
15
  import { resolveAudioMapping } from '@bendyline/squisq/doc';
@@ -20,14 +17,19 @@ import type { ContentContainer } from '@bendyline/squisq/storage';
20
17
  import { useEditorContext } from './EditorContext';
21
18
  import { usePreviewSettings } from './PreviewControls';
22
19
  import { buildPreviewDoc } from './buildPreviewDoc';
20
+ import { PlainHtmlPreview } from './PlainHtmlPreview';
23
21
 
24
22
  export interface PreviewPanelProps {
25
23
  /** Base path for resolving media URLs in DocPlayer */
26
24
  basePath?: string;
27
25
  /** Additional class name for the container */
28
26
  className?: string;
29
- /** Optional ContentContainer for audio mapping (MP3 discovery + timing.json) */
30
- container?: ContentContainer | null;
27
+ /**
28
+ * Workspace-scoped `ContentContainer` (the folder holding the doc and
29
+ * its siblings). Used here for audio mapping — MP3 discovery and
30
+ * `timing.json` reading.
31
+ */
32
+ workspaceContainer?: ContentContainer | null;
31
33
  }
32
34
 
33
35
  // ── Component ──────────────────────────────────────────────────────
@@ -37,8 +39,9 @@ export interface PreviewPanelProps {
37
39
  * or document view. Controls (viewport, mode, theme, transform, captions)
38
40
  * are rendered in the main toolbar via PreviewToolbarControls.
39
41
  */
40
- export function PreviewPanel({ basePath = '/', className, container }: PreviewPanelProps) {
41
- const { doc, parseError, isParsing } = useEditorContext();
42
+ export function PreviewPanel({ basePath = '/', className, workspaceContainer }: PreviewPanelProps) {
43
+ const { doc, parseError, isParsing, markdownSource, mediaRevision } = useEditorContext();
44
+ const mediaProvider = useMediaProvider();
42
45
  const {
43
46
  activeViewport,
44
47
  activeDisplayMode,
@@ -69,10 +72,10 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
69
72
  sourceDoc = result.doc;
70
73
  }
71
74
 
72
- // If we have a container, try to resolve audio mapping before building preview
73
- if (container) {
75
+ // If we have a workspace container, try to resolve audio mapping before building preview
76
+ if (workspaceContainer) {
74
77
  let cancelled = false;
75
- resolveAudioMapping(sourceDoc, container).then((audioDoc) => {
78
+ resolveAudioMapping(sourceDoc, workspaceContainer).then((audioDoc) => {
76
79
  if (!cancelled) {
77
80
  setPreviewDoc(buildPreviewDoc(audioDoc));
78
81
  }
@@ -85,7 +88,7 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
85
88
  }
86
89
 
87
90
  setPreviewDoc(buildPreviewDoc(sourceDoc));
88
- }, [doc, activeTransformStyle, container]);
91
+ }, [doc, activeTransformStyle, workspaceContainer]);
89
92
 
90
93
  // Status overlays for non-ready states
91
94
  if (isParsing) {
@@ -105,7 +108,10 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
105
108
  );
106
109
  }
107
110
 
108
- if (!previewDoc) {
111
+ // Page mode renders directly from markdown — it doesn't depend on the
112
+ // parsed Doc tree or the player preview build, so let it fall through
113
+ // even when those aren't ready yet.
114
+ if (!previewDoc && activeDisplayMode !== 'page') {
109
115
  return (
110
116
  <div className={`squisq-preview-status ${className || ''}`} data-testid="preview-panel">
111
117
  <p>No content to preview. Start typing in the editor.</p>
@@ -113,6 +119,9 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
113
119
  );
114
120
  }
115
121
 
122
+ const fillsContainer =
123
+ activeDisplayMode === 'linear' || activeDisplayMode === 'page' ? 'stretch' : 'center';
124
+
116
125
  return (
117
126
  <div
118
127
  className={`squisq-preview-container ${className || ''}`}
@@ -126,19 +135,27 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
126
135
  background: 'var(--squisq-bg, #f5f5f5)',
127
136
  }}
128
137
  >
129
- {/* Player / Document view */}
138
+ {/* Player / Document / Page view */}
130
139
  <div
131
140
  className="squisq-preview-player"
132
141
  style={{
133
142
  flex: 1,
134
143
  display: 'flex',
135
- alignItems: activeDisplayMode === 'linear' ? 'stretch' : 'center',
144
+ alignItems: fillsContainer,
136
145
  justifyContent: 'center',
137
146
  overflow: 'hidden',
138
147
  minHeight: 0,
139
148
  }}
140
149
  >
141
- {activeDisplayMode === 'linear' ? (
150
+ {activeDisplayMode === 'page' ? (
151
+ <PlainHtmlPreview
152
+ markdown={markdownSource}
153
+ title={(doc?.frontmatter?.title as string | undefined) ?? undefined}
154
+ mediaProvider={mediaProvider}
155
+ mediaRevision={mediaRevision}
156
+ theme={activeTheme}
157
+ />
158
+ ) : activeDisplayMode === 'linear' ? (
142
159
  <LinearDocView
143
160
  doc={doc!}
144
161
  basePath={basePath}
@@ -147,7 +164,7 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
147
164
  />
148
165
  ) : (
149
166
  <DocPlayer
150
- script={previewDoc}
167
+ script={previewDoc!}
151
168
  basePath={basePath}
152
169
  showControls
153
170
  muted