@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
@@ -10,7 +10,8 @@
10
10
  * and code blocks.
11
11
  */
12
12
 
13
- import { useEffect, useMemo, useRef } from 'react';
13
+ import { useEffect, useMemo, useRef, useState } from 'react';
14
+ import type { CSSProperties } from 'react';
14
15
  import { useEditor, EditorContent } from '@tiptap/react';
15
16
  import StarterKit from '@tiptap/starter-kit';
16
17
  import Table from '@tiptap/extension-table';
@@ -19,14 +20,23 @@ import TableCell from '@tiptap/extension-table-cell';
19
20
  import TableHeader from '@tiptap/extension-table-header';
20
21
  import TaskList from '@tiptap/extension-task-list';
21
22
  import TaskItem from '@tiptap/extension-task-item';
23
+ import Link from '@tiptap/extension-link';
22
24
  import Placeholder from '@tiptap/extension-placeholder';
25
+ import { resolveFontFamily, FONT_FALLBACKS } from '@bendyline/squisq/schemas';
23
26
  import { HeadingWithTemplate } from './TemplateAnnotation';
27
+ import { InlineIcon } from './InlineIcon';
24
28
  import { ImageWithMediaProvider } from './ImageNodeView';
29
+ import { TiptapVideo } from './tiptap/TiptapVideo';
30
+ import { TiptapAudio } from './tiptap/TiptapAudio';
31
+ import { TemplateBadgePopover, TEMPLATE_NAMES } from './TemplatePicker';
32
+ import { profileBlockContents, recommendTemplatesForBlock } from '@bendyline/squisq/recommend';
33
+ import { findBlockSliceByHeadingIndex } from './blockSlice';
25
34
  import { useEditorContext } from './EditorContext';
26
35
  import { buildMentionExtension } from './MentionExtension';
27
36
  import { markdownToTiptap, tiptapToMarkdown } from './tiptapBridge';
28
37
  import { looksLikeMarkdown } from './detectMarkdown';
29
38
  import { SQUISQ_MEDIA_MIME, parseSquisqMediaPayload } from './mediaDragMime';
39
+ import { usePreviewSettingsOptional } from './PreviewControls';
30
40
 
31
41
  // ── Frontmatter helpers ────────────────────────────────────────────
32
42
 
@@ -90,10 +100,17 @@ export function WysiwygEditor({
90
100
  submitOnEnter,
91
101
  readOnly = false,
92
102
  }: WysiwygEditorProps) {
93
- const { markdownSource, setMarkdownSource, setTiptapEditor, mediaProvider, mentionProvider } =
94
- useEditorContext();
103
+ const {
104
+ markdownSource,
105
+ setMarkdownSource,
106
+ setTiptapEditor,
107
+ mediaProvider,
108
+ mentionProvider,
109
+ blockTagsVisible,
110
+ themeInheritance,
111
+ } = useEditorContext();
95
112
  // Keep a ref so the mention extension — created once at editor mount —
96
- // always sees the latest provider. Swapping projects or gezels changes
113
+ // always sees the latest provider. Swapping projects changes
97
114
  // the provider without remounting the editor.
98
115
  const mentionProviderRef = useRef(mentionProvider);
99
116
  useEffect(() => {
@@ -136,9 +153,17 @@ export function WysiwygEditor({
136
153
  TableHeader,
137
154
  TaskList,
138
155
  TaskItem.configure({ nested: true }),
156
+ Link.configure({
157
+ openOnClick: false,
158
+ autolink: true,
159
+ HTMLAttributes: { rel: 'noopener noreferrer', target: '_blank' },
160
+ }),
139
161
  ImageWithMediaProvider.configure({ inline: false }),
162
+ TiptapVideo,
163
+ TiptapAudio,
140
164
  Placeholder.configure({ placeholder: resolvedPlaceholder }),
141
165
  buildMentionExtension(() => mentionProviderRef.current),
166
+ InlineIcon,
142
167
  ],
143
168
  content: markdownToTiptap(stripFrontmatter(markdownSource).body),
144
169
  onUpdate: ({ editor: ed }) => {
@@ -159,6 +184,18 @@ export function WysiwygEditor({
159
184
  // normal behavior (Enter = paragraph break, Shift+Enter = soft break).
160
185
  handleKeyDown: (view, event) => {
161
186
  if (event.key !== 'Enter' || !submitOnEnterRef.current) return false;
187
+ // Defer Enter to an open mention/suggestion popover so the user
188
+ // can pick the highlighted candidate. ProseMirror plugins fire
189
+ // AFTER editorProps.handleKeyDown, so without this short-circuit
190
+ // plain Enter submits the message and the popover closes
191
+ // without inserting the mention. The MentionExtension marks
192
+ // its container with `display: block` while items are showing
193
+ // and `display: none` when empty — only short-circuit when
194
+ // there's actually a candidate to pick.
195
+ const popover = document.querySelector<HTMLElement>('.squisq-mention-popover');
196
+ if (popover && popover.style.display !== 'none') {
197
+ return false;
198
+ }
162
199
  if (event.metaKey || event.ctrlKey) {
163
200
  // User wants a newline. Insert a hard-break and stop propagation so
164
201
  // we don't also create a new paragraph.
@@ -209,10 +246,20 @@ export function WysiwygEditor({
209
246
  // entries via a custom MIME type and skip the upload step.
210
247
  // Falls through to default handling for non-image drops or when no
211
248
  // MediaProvider is available.
212
- handleDrop: (view, event, _slice, _moved) => {
249
+ handleDrop: (view, event, _slice, moved) => {
213
250
  const dt = event.dataTransfer;
214
251
  if (!dt) return false;
215
252
 
253
+ // Internal node move (the user dragged an existing node within
254
+ // the document). ProseMirror's `moved` flag is true in this
255
+ // case; let it handle the reposition natively so width/height
256
+ // attributes are preserved and the source node is removed.
257
+ // Without this short-circuit, the browser also exposes the
258
+ // dragged `<img>` as a virtual file in `dataTransfer`, so the
259
+ // upload-and-insert path below would fire — producing a
260
+ // dimension-less duplicate next to the original.
261
+ if (moved) return false;
262
+
216
263
  // In-app drag from the MediaBin — insert without uploading
217
264
  const squisqRaw = dt.getData(SQUISQ_MEDIA_MIME);
218
265
  if (squisqRaw) {
@@ -226,7 +273,27 @@ export function WysiwygEditor({
226
273
  }
227
274
 
228
275
  const imageFiles = filesFromDataTransfer(dt);
229
- if (imageFiles.length === 0 || !mediaProviderRef.current) return false;
276
+ if (imageFiles.length === 0) {
277
+ // Nothing image-like in the drop. Let the browser / ProseMirror
278
+ // handle it (links, text, etc.). Log enough to debug if the user
279
+ // expected this to be an image drop — Windows/browser combos
280
+ // sometimes deliver image drags without a usable File payload.
281
+ if (dt.files.length > 0 || dt.items.length > 0) {
282
+ console.warn(
283
+ '[squisq-editor] Drop received with no recognizable image File. Types:',
284
+ Array.from(dt.types ?? []),
285
+ 'files:',
286
+ Array.from(dt.files).map((f) => `${f.name} (${f.type || 'no-type'})`),
287
+ );
288
+ }
289
+ return false;
290
+ }
291
+ if (!mediaProviderRef.current) {
292
+ console.warn(
293
+ '[squisq-editor] Image drop received but no MediaProvider is wired up; cannot persist the image. Pass `mediaProvider` to EditorShell / EditorProvider.',
294
+ );
295
+ return false;
296
+ }
230
297
 
231
298
  event.preventDefault();
232
299
  moveSelectionToDropPoint(view, event);
@@ -251,6 +318,68 @@ export function WysiwygEditor({
251
318
  if (editor) editor.setEditable(!readOnly);
252
319
  }, [editor, readOnly]);
253
320
 
321
+ // ── Template badge → popover ─────────────────────────────────────
322
+ // The HeadingWithTemplate extension renders an inert `.squisq-template-badge`
323
+ // span inside templated headings. We delegate clicks at the container
324
+ // level so we can locate the heading position and open the gallery
325
+ // anchored at that badge.
326
+ const containerRef = useRef<HTMLDivElement | null>(null);
327
+ const [badgeMenu, setBadgeMenu] = useState<{
328
+ rect: DOMRect;
329
+ template: string;
330
+ headingPos: number;
331
+ headingIndex: number;
332
+ } | null>(null);
333
+
334
+ useEffect(() => {
335
+ if (!editor) return;
336
+ const root = containerRef.current;
337
+ if (!root) return;
338
+ const onClick = (e: MouseEvent) => {
339
+ const target = e.target as HTMLElement | null;
340
+ if (!target) return;
341
+ const badge = target.closest('.squisq-template-badge') as HTMLElement | null;
342
+ if (!badge || !root.contains(badge)) return;
343
+ e.preventDefault();
344
+ e.stopPropagation();
345
+ // Find the parent heading element and resolve its document position.
346
+ const headingEl = badge.closest('h1,h2,h3,h4,h5,h6') as HTMLElement | null;
347
+ if (!headingEl) return;
348
+ let pos: number | null = null;
349
+ try {
350
+ pos = editor.view.posAtDOM(headingEl, 0);
351
+ } catch {
352
+ pos = null;
353
+ }
354
+ if (pos == null) return;
355
+ // posAtDOM returns the position *inside* the heading; subtract 1
356
+ // to land on the heading node itself so setNodeMarkup targets it.
357
+ const headingPos = Math.max(0, pos - 1);
358
+ const node = editor.state.doc.nodeAt(headingPos);
359
+ if (!node || node.type.name !== 'heading') return;
360
+ // Count how many headings precede this one so the markdown-source
361
+ // slice helper can locate the matching heading by index.
362
+ let headingIndex = 0;
363
+ let count = 0;
364
+ editor.state.doc.descendants((n, p) => {
365
+ if (n.type.name !== 'heading') return;
366
+ if (p === headingPos) {
367
+ headingIndex = count;
368
+ return false;
369
+ }
370
+ count++;
371
+ });
372
+ setBadgeMenu({
373
+ rect: badge.getBoundingClientRect(),
374
+ template: (node.attrs.dataTemplate as string | null) ?? '',
375
+ headingPos,
376
+ headingIndex,
377
+ });
378
+ };
379
+ root.addEventListener('mousedown', onClick);
380
+ return () => root.removeEventListener('mousedown', onClick);
381
+ }, [editor]);
382
+
254
383
  // Sync external changes into Tiptap
255
384
  useEffect(() => {
256
385
  if (!editor) return;
@@ -266,26 +395,117 @@ export function WysiwygEditor({
266
395
  }
267
396
  }, [markdownSource, editor]);
268
397
 
398
+ // Match the WYSIWYG editor's appearance to the active Squisq theme
399
+ // when one is set in frontmatter or picked in the preview dropdown.
400
+ // Driven by the View menu's "Theme inheritance" setting:
401
+ // - 'none' → don't inherit anything
402
+ // - 'fonts' → body + heading fonts only (historical default)
403
+ // - 'fonts-colors' → fonts plus the theme's canvas / text colors
404
+ // Pushed as CSS custom properties on the container so the stylesheet
405
+ // can pick them up (with sensible fallbacks for hosts that don't have
406
+ // a PreviewSettingsProvider in scope).
407
+ const previewSettings = usePreviewSettingsOptional();
408
+ const activeTheme = previewSettings?.activeTheme;
409
+ const themeStyle = useMemo<CSSProperties>(() => {
410
+ if (themeInheritance === 'none' || !activeTheme) return {};
411
+ const out: Record<string, string> = {
412
+ '--squisq-theme-body-font': resolveFontFamily(
413
+ activeTheme.typography.bodyFont,
414
+ FONT_FALLBACKS.sans,
415
+ ),
416
+ '--squisq-theme-title-font': resolveFontFamily(
417
+ activeTheme.typography.titleFont,
418
+ FONT_FALLBACKS.sans,
419
+ ),
420
+ };
421
+ if (themeInheritance === 'fonts-colors') {
422
+ const colors = activeTheme.colors;
423
+ out['--squisq-theme-bg'] = colors.background;
424
+ // backgroundLight gives a subtle on-canvas surface for inline emphasis
425
+ // (inline `code`, code blocks). Themes always define it.
426
+ out['--squisq-theme-bg-muted'] = colors.backgroundLight;
427
+ out['--squisq-theme-text'] = colors.text;
428
+ out['--squisq-theme-text-muted'] = colors.textMuted;
429
+ out['--squisq-theme-primary'] = colors.primary;
430
+ }
431
+ return out as CSSProperties;
432
+ }, [activeTheme, themeInheritance]);
433
+
269
434
  return (
270
435
  <div
271
436
  className={`squisq-wysiwyg-container${className ? ` ${className}` : ''}`}
272
- style={{ width: '100%', height: '100%', overflow: 'auto' }}
437
+ style={{ width: '100%', height: '100%', overflow: 'auto', ...themeStyle }}
273
438
  data-testid="wysiwyg-container"
439
+ data-block-tags={blockTagsVisible ? 'visible' : 'hidden'}
440
+ data-theme-inheritance={themeInheritance}
441
+ ref={containerRef}
274
442
  >
275
443
  <EditorContent editor={editor} style={{ height: '100%' }} />
444
+ {badgeMenu && (
445
+ <TemplateBadgePopover
446
+ anchorRect={badgeMenu.rect}
447
+ value={badgeMenu.template}
448
+ recommended={(() => {
449
+ const slice = findBlockSliceByHeadingIndex(markdownSource, badgeMenu.headingIndex);
450
+ if (!slice) return undefined;
451
+ const profile = profileBlockContents(slice);
452
+ return recommendTemplatesForBlock(profile, TEMPLATE_NAMES).recommended;
453
+ })()}
454
+ onChange={(name) => {
455
+ if (!editor) return;
456
+ const tr = editor.state.tr.setNodeMarkup(badgeMenu.headingPos, undefined, {
457
+ ...editor.state.doc.nodeAt(badgeMenu.headingPos)?.attrs,
458
+ dataTemplate: name === '' ? null : name,
459
+ });
460
+ editor.view.dispatch(tr);
461
+ }}
462
+ onClose={() => setBadgeMenu(null)}
463
+ />
464
+ )}
276
465
  </div>
277
466
  );
278
467
  }
279
468
 
280
469
  // ── Image drop / paste helpers ─────────────────────────────────────
281
470
 
282
- /** Extract image File objects from a DataTransfer (drop event). */
471
+ /** Extension-based fallback when a dragged file has no `type` set (rare
472
+ * but happens when sources omit the MIME — e.g. some screenshot tools). */
473
+ const IMAGE_EXT_RE = /\.(png|jpe?g|gif|webp|bmp|svg|avif|ico)$/i;
474
+
475
+ function looksLikeImageFile(file: File): boolean {
476
+ if (file.type.startsWith('image/')) return true;
477
+ return IMAGE_EXT_RE.test(file.name);
478
+ }
479
+
480
+ /** Extract image File objects from a DataTransfer (drop event). Reads
481
+ * from both `dt.files` and `dt.items`; some drag sources (cross-tab
482
+ * drags, certain native apps) populate only one of the two. */
283
483
  function filesFromDataTransfer(dt: DataTransfer): File[] {
284
484
  const files: File[] = [];
485
+ const seen = new Set<string>();
486
+
285
487
  for (let i = 0; i < dt.files.length; i++) {
286
488
  const file = dt.files[i];
287
- if (file.type.startsWith('image/')) files.push(file);
489
+ if (looksLikeImageFile(file)) {
490
+ files.push(file);
491
+ seen.add(`${file.name}|${file.size}`);
492
+ }
288
493
  }
494
+
495
+ if (dt.items) {
496
+ for (let i = 0; i < dt.items.length; i++) {
497
+ const item = dt.items[i];
498
+ if (item.kind !== 'file') continue;
499
+ const file = item.getAsFile();
500
+ if (!file) continue;
501
+ if (!looksLikeImageFile(file)) continue;
502
+ const key = `${file.name}|${file.size}`;
503
+ if (seen.has(key)) continue;
504
+ files.push(file);
505
+ seen.add(key);
506
+ }
507
+ }
508
+
289
509
  return files;
290
510
  }
291
511
 
@@ -70,19 +70,4 @@ describe('looksLikeMarkdown', () => {
70
70
  it('detects markdown with windows line endings', () => {
71
71
  expect(looksLikeMarkdown('# Heading\r\n\r\nBody text')).toBe(true);
72
72
  });
73
-
74
- it('detects a full resume-style document', () => {
75
- const text = `# Mike Ammerlaan
76
-
77
- ## Projects
78
-
79
- Qualla (qualla.com) - Designed and built a map-driven storytelling platform.
80
-
81
- ## **Experience**
82
-
83
- **Principal Product Manager, Minecraft**
84
- Jan 2021 - Present
85
- Driving the creator platform.`;
86
- expect(looksLikeMarkdown(text)).toBe(true);
87
- });
88
73
  });
@@ -0,0 +1,147 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { cleanup, fireEvent, render, screen, within } from '@testing-library/react';
3
+ import { DocumentSettingsDialog } from '../DocumentSettingsDialog';
4
+
5
+ afterEach(() => cleanup());
6
+
7
+ function open(source: string, onSave: (next: string) => void) {
8
+ return render(
9
+ <DocumentSettingsDialog markdownSource={source} onSave={onSave} onClose={() => {}} />,
10
+ );
11
+ }
12
+
13
+ function clickSave() {
14
+ fireEvent.click(screen.getByRole('button', { name: 'Save' }));
15
+ }
16
+
17
+ /**
18
+ * Open the Theme picker popover and click the theme whose row title
19
+ * matches `name`. The picker is portaled to `document.body`, so the
20
+ * listbox lives outside the dialog form — testing-library's `screen`
21
+ * still finds it because it queries the whole document.
22
+ */
23
+ function pickTheme(name: string) {
24
+ // Trigger button has aria-label "Theme"
25
+ fireEvent.click(screen.getByLabelText('Theme'));
26
+ // Scope the option lookup to the popover — the Transform <select> in
27
+ // the same dialog also exposes role="option" entries (one of which is
28
+ // literally "Documentary"), so a global query would collide.
29
+ const popover = document.getElementById('squisq-theme-picker-popover')!;
30
+ fireEvent.click(within(popover).getByRole('option', { name }));
31
+ }
32
+
33
+ describe('DocumentSettingsDialog', () => {
34
+ it('shows the inferred title as the placeholder when no title in frontmatter', () => {
35
+ const onSave = vi.fn();
36
+ open('# My Document\n\nBody.', onSave);
37
+ const input = screen.getByLabelText('Title') as HTMLInputElement;
38
+ expect(input.value).toBe('');
39
+ expect(input.placeholder).toBe('My Document');
40
+ });
41
+
42
+ it('does not write title when input is left empty', () => {
43
+ const onSave = vi.fn();
44
+ open('# My Document\n\nBody.', onSave);
45
+ clickSave();
46
+ const next = onSave.mock.calls[0][0] as string;
47
+ expect(next).not.toMatch(/^---[\s\S]*title:/);
48
+ });
49
+
50
+ it('does not write title when value matches the inferred title', () => {
51
+ const onSave = vi.fn();
52
+ open('# My Document\n\nBody.', onSave);
53
+ fireEvent.change(screen.getByLabelText('Title'), { target: { value: 'My Document' } });
54
+ clickSave();
55
+ const next = onSave.mock.calls[0][0] as string;
56
+ expect(next).not.toMatch(/^---[\s\S]*title:/);
57
+ });
58
+
59
+ it('strips an existing title key when the user clears the input', () => {
60
+ const onSave = vi.fn();
61
+ const src = '---\ntitle: Old\n---\n\n# My Document\n';
62
+ open(src, onSave);
63
+ const input = screen.getByLabelText('Title') as HTMLInputElement;
64
+ expect(input.value).toBe('Old');
65
+ fireEvent.change(input, { target: { value: '' } });
66
+ clickSave();
67
+ const next = onSave.mock.calls[0][0] as string;
68
+ expect(next).not.toMatch(/title:/);
69
+ });
70
+
71
+ it('writes a title that differs from the inferred value', () => {
72
+ const onSave = vi.fn();
73
+ open('# My Document\n\nBody.', onSave);
74
+ fireEvent.change(screen.getByLabelText('Title'), {
75
+ target: { value: 'Custom Title' },
76
+ });
77
+ clickSave();
78
+ const next = onSave.mock.calls[0][0] as string;
79
+ expect(next).toMatch(/^---\ntitle: Custom Title\n---/);
80
+ });
81
+
82
+ it('writes squisq-theme when the user picks a theme', () => {
83
+ const onSave = vi.fn();
84
+ open('# Doc\n', onSave);
85
+ pickTheme('Documentary');
86
+ clickSave();
87
+ const next = onSave.mock.calls[0][0] as string;
88
+ expect(next).toContain('squisq-theme: documentary');
89
+ });
90
+
91
+ it('clears the legacy theme key when switching themes', () => {
92
+ const onSave = vi.fn();
93
+ const src = '---\ntheme: bold\n---\n\n# Doc\n';
94
+ open(src, onSave);
95
+ pickTheme('Documentary');
96
+ clickSave();
97
+ const next = onSave.mock.calls[0][0] as string;
98
+ expect(next).toContain('squisq-theme: documentary');
99
+ expect(next).not.toMatch(/^theme:/m);
100
+ });
101
+
102
+ it('removes squisq-theme when reverting to default', () => {
103
+ const onSave = vi.fn();
104
+ const src = '---\nsquisq-theme: documentary\n---\n\n# Doc\n';
105
+ open(src, onSave);
106
+ pickTheme('Default');
107
+ clickSave();
108
+ const next = onSave.mock.calls[0][0] as string;
109
+ expect(next).not.toContain('squisq-theme');
110
+ });
111
+
112
+ it('writes squisq-transform when a transform is picked', () => {
113
+ const onSave = vi.fn();
114
+ open('# Doc\n', onSave);
115
+ const select = screen.getByLabelText('Transform') as HTMLSelectElement;
116
+ // Pick any non-empty option from the dropdown
117
+ const first = Array.from(select.options).find((o) => o.value !== '');
118
+ if (!first) throw new Error('no transform options available');
119
+ fireEvent.change(select, { target: { value: first.value } });
120
+ clickSave();
121
+ const next = onSave.mock.calls[0][0] as string;
122
+ expect(next).toContain(`squisq-transform: ${first.value}`);
123
+ });
124
+
125
+ it('writes squisq-captions when picked', () => {
126
+ const onSave = vi.fn();
127
+ open('# Doc\n', onSave);
128
+ fireEvent.change(screen.getByLabelText('Captions'), { target: { value: 'social' } });
129
+ clickSave();
130
+ const next = onSave.mock.calls[0][0] as string;
131
+ expect(next).toContain('squisq-captions: social');
132
+ });
133
+
134
+ it('closes when the Cancel button is clicked', () => {
135
+ const onClose = vi.fn();
136
+ render(<DocumentSettingsDialog markdownSource="# Doc" onSave={() => {}} onClose={onClose} />);
137
+ fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
138
+ expect(onClose).toHaveBeenCalled();
139
+ });
140
+
141
+ it('falls back to the H2 when no H1 exists', () => {
142
+ const onSave = vi.fn();
143
+ open('## Subtitle\n\nBody.', onSave);
144
+ const input = screen.getByLabelText('Title') as HTMLInputElement;
145
+ expect(input.placeholder).toBe('Subtitle');
146
+ });
147
+ });
@@ -0,0 +1,133 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+ import { EmojiPicker } from '../EmojiPicker';
4
+ import {
5
+ EMOJI_CATEGORIES,
6
+ ALL_EMOJIS,
7
+ searchEmojis,
8
+ PICKER_CATEGORIES,
9
+ ALL_PICKER_ENTRIES,
10
+ searchPickerEntries,
11
+ } from '../emojiData';
12
+
13
+ describe('emojiData — legacy emoji exports', () => {
14
+ it('exposes the standard CLDR category buckets', () => {
15
+ const ids = EMOJI_CATEGORIES.map((c) => c.id);
16
+ expect(ids).toEqual([
17
+ 'smileys',
18
+ 'people',
19
+ 'nature',
20
+ 'food',
21
+ 'travel',
22
+ 'activities',
23
+ 'objects',
24
+ 'symbols',
25
+ 'flags',
26
+ ]);
27
+ });
28
+
29
+ it('each entry has a non-empty char and name', () => {
30
+ for (const entry of ALL_EMOJIS) {
31
+ expect(entry.char.length).toBeGreaterThan(0);
32
+ expect(entry.name.length).toBeGreaterThan(0);
33
+ }
34
+ });
35
+
36
+ it('searchEmojis matches by name (case-insensitive)', () => {
37
+ const results = searchEmojis('HEART');
38
+ expect(results.some((e) => e.char === '❤️')).toBe(true);
39
+ });
40
+ });
41
+
42
+ describe('emojiData — unified picker entries', () => {
43
+ it('appends FA Brands, Solid, and Regular categories after the emoji buckets', () => {
44
+ const ids = PICKER_CATEGORIES.map((c) => c.id);
45
+ expect(ids).toContain('fa-brands');
46
+ expect(ids).toContain('fa-solid');
47
+ expect(ids).toContain('fa-regular');
48
+ // Order matters — emoji first, FA after.
49
+ expect(ids.indexOf('smileys')).toBeLessThan(ids.indexOf('fa-brands'));
50
+ });
51
+
52
+ it('FA categories carry icon-kind entries', () => {
53
+ const brands = PICKER_CATEGORIES.find((c) => c.id === 'fa-brands')!;
54
+ expect(brands.entries.length).toBeGreaterThan(0);
55
+ const github = brands.entries.find((e) => e.kind === 'icon' && e.name === 'github');
56
+ expect(github).toBeDefined();
57
+ expect(github!.kind).toBe('icon');
58
+ });
59
+
60
+ it('searchPickerEntries matches across emoji and icons', () => {
61
+ const emojiHit = searchPickerEntries('heart').find((e) => e.kind === 'emoji');
62
+ const iconHit = searchPickerEntries('github').find((e) => e.kind === 'icon');
63
+ expect(emojiHit).toBeDefined();
64
+ expect(iconHit).toBeDefined();
65
+ });
66
+
67
+ it('ALL_PICKER_ENTRIES is the concat of all categories', () => {
68
+ const sum = PICKER_CATEGORIES.reduce((acc, c) => acc + c.entries.length, 0);
69
+ expect(ALL_PICKER_ENTRIES.length).toBe(sum);
70
+ });
71
+ });
72
+
73
+ describe('EmojiPicker', () => {
74
+ it('does not render when closed', () => {
75
+ render(<EmojiPicker open={false} onSelect={() => {}} onClose={() => {}} />);
76
+ expect(screen.queryByTestId('emoji-picker')).toBeNull();
77
+ });
78
+
79
+ it('renders all emoji + FA tabs when open', () => {
80
+ render(<EmojiPicker open onSelect={() => {}} onClose={() => {}} />);
81
+ for (const cat of EMOJI_CATEGORIES) {
82
+ expect(screen.getByRole('tab', { name: cat.label })).toBeTruthy();
83
+ }
84
+ expect(screen.getByRole('tab', { name: 'Brands' })).toBeTruthy();
85
+ expect(screen.getByRole('tab', { name: 'Solid' })).toBeTruthy();
86
+ expect(screen.getByRole('tab', { name: 'Regular' })).toBeTruthy();
87
+ });
88
+
89
+ it('fires onSelect with an emoji PickerEntry when an emoji tile is clicked', () => {
90
+ const onSelect = vi.fn();
91
+ render(<EmojiPicker open onSelect={onSelect} onClose={() => {}} />);
92
+ fireEvent.click(screen.getByRole('button', { name: 'grinning' }));
93
+ expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ kind: 'emoji', char: '😀' }));
94
+ });
95
+
96
+ it('fires onSelect with an icon PickerEntry when an FA tile is clicked', () => {
97
+ const onSelect = vi.fn();
98
+ render(<EmojiPicker open onSelect={onSelect} onClose={() => {}} />);
99
+ fireEvent.click(screen.getByRole('tab', { name: 'Brands' }));
100
+ fireEvent.click(screen.getByRole('button', { name: 'Github' }));
101
+ expect(onSelect).toHaveBeenCalledWith(
102
+ expect.objectContaining({ kind: 'icon', family: 'brands', name: 'github' }),
103
+ );
104
+ });
105
+
106
+ it('switches categories when a tab is clicked', () => {
107
+ render(<EmojiPicker open onSelect={() => {}} onClose={() => {}} />);
108
+ fireEvent.click(screen.getByRole('tab', { name: 'Nature' }));
109
+ expect(screen.getByRole('button', { name: 'dog face' })).toBeTruthy();
110
+ });
111
+
112
+ it('shows search results across emoji + icons and hides the tabs', () => {
113
+ render(<EmojiPicker open onSelect={() => {}} onClose={() => {}} />);
114
+ const search = screen.getByLabelText('Search emoji & icons') as HTMLInputElement;
115
+ fireEvent.change(search, { target: { value: 'github' } });
116
+ expect(screen.getByRole('button', { name: 'Github' })).toBeTruthy();
117
+ expect(screen.queryByRole('tab', { name: 'Smileys' })).toBeNull();
118
+ });
119
+
120
+ it('shows a no-matches state when search returns nothing', () => {
121
+ render(<EmojiPicker open onSelect={() => {}} onClose={() => {}} />);
122
+ const search = screen.getByLabelText('Search emoji & icons') as HTMLInputElement;
123
+ fireEvent.change(search, { target: { value: 'thisdoesnotexist' } });
124
+ expect(screen.getByText(/No matches for/i)).toBeTruthy();
125
+ });
126
+
127
+ it('fires onClose on Escape', () => {
128
+ const onClose = vi.fn();
129
+ render(<EmojiPicker open onSelect={() => {}} onClose={onClose} />);
130
+ fireEvent.keyDown(document, { key: 'Escape' });
131
+ expect(onClose).toHaveBeenCalled();
132
+ });
133
+ });
@@ -93,4 +93,20 @@ describe('resolveFileKind', () => {
93
93
  it('accepts a language with no fileName', () => {
94
94
  expect(resolveFileKind(undefined, 'rust')).toEqual({ mode: 'code', language: 'rust' });
95
95
  });
96
+
97
+ it('returns image mode for raster image extensions', () => {
98
+ expect(resolveFileKind('foo.png')).toEqual({ mode: 'image', language: 'image' });
99
+ expect(resolveFileKind('photo.JPEG')).toEqual({ mode: 'image', language: 'image' });
100
+ expect(resolveFileKind('anim.gif')).toEqual({ mode: 'image', language: 'image' });
101
+ expect(resolveFileKind('hero.webp')).toEqual({ mode: 'image', language: 'image' });
102
+ expect(resolveFileKind('icon.avif')).toEqual({ mode: 'image', language: 'image' });
103
+ });
104
+
105
+ it('keeps SVG as code mode (xml) so the source stays editable', () => {
106
+ expect(resolveFileKind('logo.svg')).toEqual({ mode: 'code', language: 'xml' });
107
+ });
108
+
109
+ it('respects an explicit language=image override', () => {
110
+ expect(resolveFileKind(undefined, 'image')).toEqual({ mode: 'image', language: 'image' });
111
+ });
96
112
  });