@bendyline/squisq-editor-react 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +40 -0
- package/dist/index.d.ts +188 -55
- package/dist/index.js +5094 -2948
- package/dist/monaco.js +0 -1
- package/dist/styles/index.css +234 -145
- package/package.json +9 -6
- package/dist/index.js.map +0 -1
- package/dist/monaco.js.map +0 -1
- package/src/BlockCardView.tsx +0 -121
- package/src/BlockPreviewPanel.tsx +0 -69
- package/src/BlockPropertiesPopover.tsx +0 -222
- package/src/DocumentSettingsDialog.tsx +0 -272
- package/src/DropZoneOverlay.tsx +0 -137
- package/src/EditorContext.tsx +0 -1274
- package/src/EditorShell.tsx +0 -1358
- package/src/EmojiPicker.tsx +0 -332
- package/src/FolderView.tsx +0 -131
- package/src/Icon.tsx +0 -26
- package/src/ImageEditor.tsx +0 -395
- package/src/ImageNodeView.tsx +0 -284
- package/src/ImageViewer.tsx +0 -221
- package/src/InlineIcon.ts +0 -84
- package/src/InlinePreviewGutter.tsx +0 -590
- package/src/LinkDialog.tsx +0 -276
- package/src/MediaBin.tsx +0 -500
- package/src/MentionExtension.tsx +0 -261
- package/src/OutlinePanel.tsx +0 -547
- package/src/PlainHtmlPreview.tsx +0 -277
- package/src/PreviewControls.tsx +0 -1265
- package/src/PreviewPanel.tsx +0 -426
- package/src/RawEditor.tsx +0 -743
- package/src/RecorderEntry.tsx +0 -162
- package/src/StatusBar.tsx +0 -51
- package/src/TemplateAnnotation.ts +0 -167
- package/src/TemplateContentPreview.tsx +0 -56
- package/src/TemplatePicker.tsx +0 -1314
- package/src/ThemeCustomizerPanel.tsx +0 -304
- package/src/ThemePicker.tsx +0 -433
- package/src/TimelineBlockPreview.tsx +0 -37
- package/src/TimelineTrack.tsx +0 -671
- package/src/Toolbar.tsx +0 -2885
- package/src/Tooltip.tsx +0 -142
- package/src/TransitionPicker.tsx +0 -390
- package/src/VersionHistoryPanel.tsx +0 -359
- package/src/ViewMenuPanel.tsx +0 -189
- package/src/ViewSwitcher.tsx +0 -56
- package/src/WysiwygEditor.tsx +0 -806
- package/src/__tests__/blockProperties.test.ts +0 -92
- package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +0 -33
- package/src/__tests__/blockRange.test.ts +0 -105
- package/src/__tests__/blockTagActivity.test.ts +0 -183
- package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +0 -75
- package/src/__tests__/buildPreviewDocContent.test.ts +0 -65
- package/src/__tests__/buildPreviewDocTransition.test.ts +0 -92
- package/src/__tests__/canvasSurfaceTextEditing.test.tsx +0 -60
- package/src/__tests__/codeContextSectionView.test.tsx +0 -97
- package/src/__tests__/codeContextZoneManager.test.ts +0 -127
- package/src/__tests__/createShapeLayer.test.ts +0 -46
- package/src/__tests__/customLayoutManagerTheme.test.tsx +0 -34
- package/src/__tests__/detectMarkdown.test.ts +0 -73
- package/src/__tests__/diffContextSections.test.ts +0 -39
- package/src/__tests__/documentSettingsDialog.test.tsx +0 -197
- package/src/__tests__/drawingShapeRoundTrip.test.ts +0 -49
- package/src/__tests__/editorScrollHandoff.test.tsx +0 -102
- package/src/__tests__/editorScrollSync.test.ts +0 -65
- package/src/__tests__/editorShellCodeContext.test.tsx +0 -86
- package/src/__tests__/editorShellProps.test.tsx +0 -550
- package/src/__tests__/embeddedMedia.test.ts +0 -48
- package/src/__tests__/emojiPicker.test.tsx +0 -93
- package/src/__tests__/fileKind.test.ts +0 -112
- package/src/__tests__/findMode.test.tsx +0 -104
- package/src/__tests__/findModel.test.ts +0 -55
- package/src/__tests__/headingTransition.test.ts +0 -176
- package/src/__tests__/imageEditAffordance.test.tsx +0 -272
- package/src/__tests__/imageEditModalContract.test.tsx +0 -119
- package/src/__tests__/imageEditorShell.test.tsx +0 -94
- package/src/__tests__/imageEditorState.test.ts +0 -171
- package/src/__tests__/inlinePreviewGutter.test.tsx +0 -62
- package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +0 -103
- package/src/__tests__/jsonEditor.test.tsx +0 -223
- package/src/__tests__/layersPanel.test.tsx +0 -167
- package/src/__tests__/layoutChildRoundTrip.test.ts +0 -71
- package/src/__tests__/linkDialogDocPicker.test.tsx +0 -137
- package/src/__tests__/majorApiTypes.test.ts +0 -26
- package/src/__tests__/markdownCodeFence.test.ts +0 -45
- package/src/__tests__/mediaAttachmentFlow.test.ts +0 -110
- package/src/__tests__/mediaBinDrop.test.tsx +0 -90
- package/src/__tests__/mediaReferences.test.ts +0 -97
- package/src/__tests__/narrationSave.test.ts +0 -148
- package/src/__tests__/outlinePanel.test.tsx +0 -169
- package/src/__tests__/outlineSource.test.ts +0 -217
- package/src/__tests__/plainHtmlPreview.test.tsx +0 -125
- package/src/__tests__/presentationMode.test.tsx +0 -290
- package/src/__tests__/previewControls.test.tsx +0 -571
- package/src/__tests__/previewPanelAudioFailure.test.tsx +0 -46
- package/src/__tests__/previewPanelPresentation.test.tsx +0 -126
- package/src/__tests__/previewPanelTransforms.test.tsx +0 -117
- package/src/__tests__/propertiesPanel.test.tsx +0 -69
- package/src/__tests__/rawEditorModelIsolation.test.ts +0 -19
- package/src/__tests__/recorderFormats.test.ts +0 -146
- package/src/__tests__/recorderMediaInsert.test.ts +0 -86
- package/src/__tests__/recorderTheme.test.tsx +0 -42
- package/src/__tests__/recorderTimingJson.test.ts +0 -41
- package/src/__tests__/resolveBlockVisual.test.ts +0 -43
- package/src/__tests__/selectionConversions.test.ts +0 -80
- package/src/__tests__/statusBar.test.tsx +0 -27
- package/src/__tests__/teleprompter.test.tsx +0 -190
- package/src/__tests__/templateAnnotationRoundTrip.test.ts +0 -99
- package/src/__tests__/templateContentPreview.test.ts +0 -101
- package/src/__tests__/templatePickerMetadata.test.ts +0 -32
- package/src/__tests__/templatePickerPortal.test.tsx +0 -62
- package/src/__tests__/timelineSource.test.ts +0 -134
- package/src/__tests__/tiptapBridge.test.ts +0 -575
- package/src/__tests__/tiptapBridgeConformance.test.ts +0 -47
- package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +0 -109
- package/src/__tests__/tiptapImageRoundTrip.test.ts +0 -73
- package/src/__tests__/toolbarSelectionConversion.test.tsx +0 -190
- package/src/__tests__/tooltip.test.tsx +0 -72
- package/src/__tests__/transformStyleId.test.ts +0 -13
- package/src/__tests__/transitionCatalog.test.ts +0 -64
- package/src/__tests__/useBlockNavigator.test.tsx +0 -67
- package/src/__tests__/useImageEditor.test.tsx +0 -226
- package/src/__tests__/useJsonEditorTokens.test.ts +0 -59
- package/src/__tests__/useMediaRecorder.test.ts +0 -302
- package/src/__tests__/useTimelineClock.test.ts +0 -21
- package/src/__tests__/versionHistory.test.tsx +0 -197
- package/src/__tests__/viewMenuPanel.test.tsx +0 -130
- package/src/__tests__/writeCanvasSettings.test.ts +0 -15
- package/src/__tests__/wysiwygImageUpload.test.ts +0 -69
- package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +0 -56
- package/src/asciiDiagram/AsciiDiagramExtension.ts +0 -343
- package/src/asciiDiagram/AsciiDiagramWidget.tsx +0 -254
- package/src/asciiDiagram/RepairableDiagramExtension.ts +0 -200
- package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +0 -164
- package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +0 -159
- package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +0 -284
- package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +0 -114
- package/src/asciiDiagram/__tests__/asciiPaste.test.ts +0 -43
- package/src/asciiDiagram/asciiDiagramCommands.ts +0 -174
- package/src/asciiDiagram/asciiDiagramData.ts +0 -149
- package/src/asciiDiagram/asciiDiagramOps.ts +0 -238
- package/src/asciiDiagram/asciiPaste.ts +0 -19
- package/src/blockProperties.ts +0 -88
- package/src/blockRange.ts +0 -132
- package/src/blockSlice.ts +0 -75
- package/src/blockTagActivity.ts +0 -233
- package/src/buildDocumentPreviewMarkdown.ts +0 -168
- package/src/buildPreviewDoc.ts +0 -407
- package/src/codeContext/CodeContextSectionView.tsx +0 -124
- package/src/codeContext/CodeContextZoneManager.ts +0 -149
- package/src/codeContext/CodeContextZones.tsx +0 -121
- package/src/codeContext/diffContextSections.ts +0 -38
- package/src/codeContext/types.ts +0 -75
- package/src/codeSnippet/CodeSnippetExtension.ts +0 -205
- package/src/codeSnippet/CodeSnippetWidget.tsx +0 -109
- package/src/codeSnippet/__tests__/CodeSnippetExtension.test.ts +0 -95
- package/src/codeSnippet/__tests__/codeSnippetLanguages.test.ts +0 -50
- package/src/codeSnippet/codeSnippetCommands.ts +0 -21
- package/src/codeSnippet/codeSnippetData.ts +0 -43
- package/src/codeSnippet/codeSnippetLanguages.ts +0 -216
- package/src/customTemplates/AddBin.tsx +0 -126
- package/src/customTemplates/CustomLayoutManager.tsx +0 -234
- package/src/customTemplates/CustomTemplateContext.tsx +0 -188
- package/src/customTemplates/LayerToolbar.tsx +0 -580
- package/src/customTemplates/ShapeGlyph.tsx +0 -47
- package/src/customTemplates/TemplateDesigner.tsx +0 -433
- package/src/customTemplates/__tests__/library.test.ts +0 -96
- package/src/customTemplates/__tests__/normalizePositions.test.ts +0 -109
- package/src/customTemplates/__tests__/shapeDefs.test.ts +0 -49
- package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +0 -108
- package/src/customTemplates/designer.css +0 -732
- package/src/customTemplates/index.ts +0 -31
- package/src/customTemplates/library.ts +0 -110
- package/src/customTemplates/normalizePositions.ts +0 -75
- package/src/customTemplates/shapeDefs.ts +0 -131
- package/src/customTemplates/thumbnail.tsx +0 -63
- package/src/customTemplates/tokenDefs.ts +0 -60
- package/src/customTemplates/useDocCustomTemplates.ts +0 -52
- package/src/customTemplates/useMemoryLayerAdapter.ts +0 -129
- package/src/customThemes/CustomThemeContext.tsx +0 -185
- package/src/customThemes/CustomThemeDialog.tsx +0 -316
- package/src/customThemes/ImportThemeSection.tsx +0 -178
- package/src/customThemes/__tests__/CustomThemeContext.test.tsx +0 -64
- package/src/customThemes/__tests__/CustomThemeDialog.test.tsx +0 -47
- package/src/customThemes/__tests__/customThemeLibrary.test.ts +0 -60
- package/src/customThemes/__tests__/importThemeSection.test.tsx +0 -192
- package/src/customThemes/customThemeLibrary.ts +0 -100
- package/src/customThemes/index.ts +0 -37
- package/src/customThemes/themeControls.tsx +0 -229
- package/src/customThemes/themeDraft.ts +0 -288
- package/src/customThemes/useDocCustomThemes.ts +0 -49
- package/src/detectMarkdown.ts +0 -62
- package/src/diagram/DiagramCanvas.tsx +0 -260
- package/src/diagram/DiagramMaximizedOverlay.tsx +0 -46
- package/src/diagram/diagramCommands.ts +0 -604
- package/src/diagram/diagramConstants.ts +0 -17
- package/src/diagram/types.ts +0 -35
- package/src/editorScrollSync.ts +0 -73
- package/src/embeddedMedia.ts +0 -78
- package/src/emojiData.ts +0 -1317
- package/src/fileKind.ts +0 -158
- package/src/find/FindHighlightExtension.ts +0 -81
- package/src/find/FindToolbar.tsx +0 -314
- package/src/find/findModel.ts +0 -77
- package/src/frontmatter.ts +0 -29
- package/src/frontmatterSettings.ts +0 -23
- package/src/headingTransition.ts +0 -291
- package/src/hooks/useFileDrop.ts +0 -266
- package/src/imageEditor/CanvasSurface.tsx +0 -703
- package/src/imageEditor/ImageVersionHistoryDropdown.tsx +0 -396
- package/src/imageEditor/LayersPanel.tsx +0 -218
- package/src/imageEditor/PropertiesPanel.tsx +0 -474
- package/src/imageEditor/Toolbar.tsx +0 -459
- package/src/imageEditor/createShapeLayer.ts +0 -280
- package/src/imageEditor/icons.tsx +0 -68
- package/src/imageEditor/image-editor.css +0 -574
- package/src/imageEditor/layers/EditorImageLayer.tsx +0 -45
- package/src/imageEditor/layers/EditorShapeLayer.tsx +0 -62
- package/src/imageEditor/layers/EditorTextLayer.tsx +0 -45
- package/src/imageEditor/layers/SelectionHandles.tsx +0 -86
- package/src/imageEditor/state.ts +0 -173
- package/src/imageEditor/useImageEditor.ts +0 -385
- package/src/imageEditor/useImageEditorTokens.ts +0 -70
- package/src/index.ts +0 -559
- package/src/jsonEditor/EmbeddedRichTextField.tsx +0 -81
- package/src/jsonEditor/JsonEditor.tsx +0 -81
- package/src/jsonEditor/JsonEditorContext.tsx +0 -81
- package/src/jsonEditor/RenderNode.tsx +0 -87
- package/src/jsonEditor/editors.tsx +0 -745
- package/src/jsonEditor/index.ts +0 -2
- package/src/jsonEditor/json-editor.css +0 -463
- package/src/jsonEditor/useJsonEditorTokens.ts +0 -33
- package/src/markdownCodeFence.ts +0 -72
- package/src/mediaDragMime.ts +0 -32
- package/src/mediaEntries.ts +0 -12
- package/src/mediaReferences.ts +0 -301
- package/src/mermaid/MermaidDiagramCanvas.tsx +0 -693
- package/src/mermaid/MermaidDiagramExtension.ts +0 -243
- package/src/mermaid/MermaidDiagramTypeThumbnail.tsx +0 -184
- package/src/mermaid/MermaidDiagramWidget.tsx +0 -653
- package/src/mermaid/MermaidShapePalette.tsx +0 -245
- package/src/mermaid/__tests__/MermaidDiagramExtension.test.ts +0 -361
- package/src/mermaid/__tests__/mermaidDiagramTypes.test.ts +0 -35
- package/src/mermaid/__tests__/mermaidRenderer.test.ts +0 -49
- package/src/mermaid/__tests__/mermaidSourceOps.test.ts +0 -213
- package/src/mermaid/__tests__/mermaidSyntax.test.ts +0 -71
- package/src/mermaid/mermaidCommands.ts +0 -34
- package/src/mermaid/mermaidData.ts +0 -31
- package/src/mermaid/mermaidDiagramTypes.ts +0 -325
- package/src/mermaid/mermaidModel.ts +0 -31
- package/src/mermaid/mermaidRenderer.ts +0 -181
- package/src/mermaid/mermaidShapes.ts +0 -113
- package/src/mermaid/mermaidSourceOps.ts +0 -454
- package/src/monaco.ts +0 -35
- package/src/monacoWorkers.ts +0 -89
- package/src/outlineSource.ts +0 -171
- package/src/presentation/PresentationMode.tsx +0 -596
- package/src/rawEditorIsolation.ts +0 -18
- package/src/recorder/RecorderButton.tsx +0 -80
- package/src/recorder/RecorderModal.tsx +0 -706
- package/src/recorder/RecorderPanel.tsx +0 -77
- package/src/recorder/formats.ts +0 -159
- package/src/recorder/hooks/useMediaRecorder.ts +0 -436
- package/src/recorder/hooks/useStreamPreview.ts +0 -47
- package/src/recorder/insertMediaBlock.ts +0 -30
- package/src/recorder/sources/cameraStream.ts +0 -32
- package/src/recorder/sources/micStream.ts +0 -25
- package/src/recorder/sources/screenStream.ts +0 -162
- package/src/recorder/timingJson.ts +0 -66
- package/src/resolveBlockVisual.ts +0 -39
- package/src/scene/Scene.tsx +0 -647
- package/src/scene/SceneBlockExtension.ts +0 -205
- package/src/scene/SceneBlockToolbar.tsx +0 -216
- package/src/scene/SceneBlockWidget.tsx +0 -441
- package/src/scene/ScenePropsBar.tsx +0 -85
- package/src/scene/SceneSelection.tsx +0 -111
- package/src/scene/SceneSideToolbar.tsx +0 -89
- package/src/scene/SceneViewControls.tsx +0 -43
- package/src/scene/SceneViewport.tsx +0 -106
- package/src/scene/ShapePalette.tsx +0 -196
- package/src/scene/__tests__/DiagramAdapter.test.ts +0 -142
- package/src/scene/__tests__/SceneBlockExtension.test.ts +0 -33
- package/src/scene/__tests__/bezierEdit.test.ts +0 -85
- package/src/scene/__tests__/blockLayers.test.ts +0 -57
- package/src/scene/__tests__/fitScale.test.ts +0 -19
- package/src/scene/__tests__/sceneIsolation.test.tsx +0 -119
- package/src/scene/__tests__/shapeLayers.test.ts +0 -106
- package/src/scene/__tests__/useSceneHitTest.test.ts +0 -90
- package/src/scene/__tests__/useScenePanZoom.test.ts +0 -130
- package/src/scene/adapters/DiagramAdapter.ts +0 -79
- package/src/scene/adapters/DrawingAdapter.ts +0 -420
- package/src/scene/adapters/LayoutAdapter.ts +0 -313
- package/src/scene/adapters/blockLayers.ts +0 -159
- package/src/scene/commands/SceneCommand.ts +0 -84
- package/src/scene/commands/drawingCommands.ts +0 -318
- package/src/scene/commands/layoutCommands.ts +0 -301
- package/src/scene/fitScale.ts +0 -17
- package/src/scene/hooks/useSceneHitTest.ts +0 -105
- package/src/scene/hooks/useScenePanZoom.ts +0 -154
- package/src/scene/hooks/useSceneSelection.ts +0 -62
- package/src/scene/index.ts +0 -90
- package/src/scene/layers/DiagramEdges.tsx +0 -142
- package/src/scene/layers/edgeGeometry.ts +0 -104
- package/src/scene/layers/nodeCard.tsx +0 -164
- package/src/scene/layers/renderLayer.tsx +0 -70
- package/src/scene/layers/shapeLayers.ts +0 -201
- package/src/scene/paths/bezierEdit.ts +0 -208
- package/src/scene/scene.css +0 -874
- package/src/scene/text/SceneTextOverlay.tsx +0 -161
- package/src/scene/text/sceneTextChannel.ts +0 -46
- package/src/scene/text/sceneTextConfig.ts +0 -31
- package/src/scene/text/sceneTiptap.ts +0 -36
- package/src/scene/text/useSceneTextEditing.ts +0 -39
- package/src/scene/tools/ConnectTool.ts +0 -209
- package/src/scene/tools/DrawingConnectTool.ts +0 -231
- package/src/scene/tools/PathTool.ts +0 -158
- package/src/scene/tools/PlaceTool.ts +0 -47
- package/src/scene/tools/SceneTool.ts +0 -85
- package/src/scene/tools/SelectTool.ts +0 -300
- package/src/scene/tools/ShapeTool.ts +0 -143
- package/src/scene/tools/TextTool.ts +0 -72
- package/src/scene/tools/TokenTool.ts +0 -95
- package/src/scene/tools/createDrawShapeTool.ts +0 -82
- package/src/selectionConversions.ts +0 -155
- package/src/styles/ascii-diagram.css +0 -79
- package/src/styles/ascii-timeline.css +0 -596
- package/src/styles/code-context.css +0 -155
- package/src/styles/code-snippet.css +0 -76
- package/src/styles/diagram.css +0 -183
- package/src/styles/editor.css +0 -7131
- package/src/styles/folder-view.css +0 -210
- package/src/styles/image-edit-affordance.css +0 -201
- package/src/styles/index.css +0 -20
- package/src/styles/mermaid-diagram.css +0 -487
- package/src/styles/tree-view.css +0 -189
- package/src/teleprompter/TeleprompterControls.tsx +0 -218
- package/src/teleprompter/TeleprompterSelfView.tsx +0 -22
- package/src/teleprompter/TeleprompterSurface.tsx +0 -267
- package/src/teleprompter/TeleprompterView.tsx +0 -338
- package/src/teleprompter/canvasRenderer.ts +0 -161
- package/src/teleprompter/floatingWindow.ts +0 -352
- package/src/teleprompter/index.ts +0 -61
- package/src/teleprompter/pcmWorklet.ts +0 -62
- package/src/teleprompter/recording/insertPreamble.ts +0 -80
- package/src/teleprompter/recording/narrationSave.ts +0 -134
- package/src/teleprompter/recording/useNarrationRecorder.ts +0 -367
- package/src/teleprompter/scrollModel.ts +0 -85
- package/src/teleprompter/teleprompterTheme.ts +0 -303
- package/src/teleprompter/types.ts +0 -38
- package/src/teleprompter/useFloatingWindow.ts +0 -74
- package/src/teleprompter/useMicAnalysis.ts +0 -211
- package/src/teleprompter/useTeleprompter.ts +0 -421
- package/src/templateContentPreviewResolver.ts +0 -360
- package/src/timeline/TimelineEditorWidget.tsx +0 -861
- package/src/timeline/TimelineViewExtension.ts +0 -252
- package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +0 -414
- package/src/timeline/__tests__/TimelineViewExtension.test.ts +0 -163
- package/src/timeline/__tests__/timelineCommands.test.ts +0 -359
- package/src/timeline/__tests__/timelineOps.test.ts +0 -277
- package/src/timeline/__tests__/timelinePaste.test.ts +0 -34
- package/src/timeline/timelineCommands.ts +0 -421
- package/src/timeline/timelineData.ts +0 -54
- package/src/timeline/timelineOps.ts +0 -381
- package/src/timeline/timelinePaste.ts +0 -8
- package/src/timelineSource.ts +0 -244
- package/src/tiptap/TiptapAudio.tsx +0 -86
- package/src/tiptap/TiptapVideo.tsx +0 -119
- package/src/tiptap/useResolvedMediaSrc.ts +0 -47
- package/src/tiptapBridge.ts +0 -986
- package/src/tooltipPlacement.ts +0 -13
- package/src/transformStyleId.ts +0 -17
- package/src/transitionCatalog.ts +0 -159
- package/src/treeview/TreeOutlineWidget.tsx +0 -390
- package/src/treeview/TreeViewExtension.ts +0 -250
- package/src/treeview/__tests__/TreeOutlineWidget.test.tsx +0 -156
- package/src/treeview/__tests__/TreeViewExtension.test.ts +0 -122
- package/src/treeview/__tests__/treeOps.test.ts +0 -177
- package/src/treeview/__tests__/treePaste.test.ts +0 -40
- package/src/treeview/__tests__/treeViewCommands.test.ts +0 -179
- package/src/treeview/treeOps.ts +0 -231
- package/src/treeview/treePaste.ts +0 -13
- package/src/treeview/treeViewCommands.ts +0 -99
- package/src/treeview/treeViewData.ts +0 -41
- package/src/types/monaco-shims.d.ts +0 -10
- package/src/useBlockNavigator.ts +0 -153
- package/src/useHeadingLayout.ts +0 -294
- package/src/useMonacoLoader.ts +0 -82
- package/src/useTimelineClock.ts +0 -76
- package/src/utils/collectInlineFontAwesomeCss.ts +0 -69
- package/src/utils/dropUtils.ts +0 -136
- package/src/utils/normalizeMalformedAssetUrl.ts +0 -22
- package/src/writeCanvasSettings.ts +0 -30
- package/src/wysiwygImageUpload.ts +0 -113
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TimelineViewExtension — mounts a WYSIWYG timeline editor over qualifying
|
|
3
|
-
* authored timeline code fences while keeping the fence text as source of
|
|
4
|
-
* truth. Every semantic edit is rendered back through the core codec.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Extension } from '@tiptap/core';
|
|
8
|
-
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
9
|
-
import type { Transaction } from '@tiptap/pm/state';
|
|
10
|
-
import type { Node as PMNode } from '@tiptap/pm/model';
|
|
11
|
-
import { Decoration, DecorationSet } from '@tiptap/pm/view';
|
|
12
|
-
import type { Editor } from '@tiptap/react';
|
|
13
|
-
import { createElement } from 'react';
|
|
14
|
-
import { createRoot, type Root } from 'react-dom/client';
|
|
15
|
-
import {
|
|
16
|
-
detectAsciiTimeline,
|
|
17
|
-
isEligibleAsciiTimelineFenceLang,
|
|
18
|
-
isExplicitTimelineLang,
|
|
19
|
-
type AsciiTimeline,
|
|
20
|
-
} from '@bendyline/squisq/doc';
|
|
21
|
-
import { TimelineEditorWidget } from './TimelineEditorWidget';
|
|
22
|
-
|
|
23
|
-
export interface TimelineBlockEntry {
|
|
24
|
-
/** Synthetic session id, stable across edits to one fence. */
|
|
25
|
-
id: string;
|
|
26
|
-
/** Current document position of the codeBlock node. */
|
|
27
|
-
pos: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface TimelineViewPluginState {
|
|
31
|
-
entries: TimelineBlockEntry[];
|
|
32
|
-
decorations: DecorationSet;
|
|
33
|
-
seq: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const TIMELINE_VIEW_KEY = new PluginKey<TimelineViewPluginState>('squisq-timeline-view');
|
|
37
|
-
|
|
38
|
-
const detectCache = new WeakMap<PMNode, AsciiTimeline | null>();
|
|
39
|
-
const parseCache = new WeakMap<PMNode, AsciiTimeline | null>();
|
|
40
|
-
|
|
41
|
-
function fenceLangOf(node: PMNode): string | null {
|
|
42
|
-
const language = (node.attrs as { language?: string | null }).language;
|
|
43
|
-
return typeof language === 'string' ? language : null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function eventCount(timeline: AsciiTimeline): number {
|
|
47
|
-
return timeline.tracks.reduce((sum, track) => sum + track.events.length, 0);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Conservative full-detection gate used when first claiming a code fence. */
|
|
51
|
-
export function getTimelineForNode(node: PMNode): AsciiTimeline | null {
|
|
52
|
-
if (node.type.name !== 'codeBlock') return null;
|
|
53
|
-
const cached = detectCache.get(node);
|
|
54
|
-
if (cached !== undefined) return cached;
|
|
55
|
-
|
|
56
|
-
let result: AsciiTimeline | null = null;
|
|
57
|
-
const language = fenceLangOf(node);
|
|
58
|
-
if (isEligibleAsciiTimelineFenceLang(language)) {
|
|
59
|
-
const detection = detectAsciiTimeline(node.textContent, {
|
|
60
|
-
explicit: isExplicitTimelineLang(language),
|
|
61
|
-
});
|
|
62
|
-
if (detection.isTimeline && detection.timeline && eventCount(detection.timeline) > 0) {
|
|
63
|
-
result = detection.timeline;
|
|
64
|
-
parseCache.set(node, result);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
detectCache.set(node, result);
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Relaxed/sticky gate for a block already registered by this extension. */
|
|
72
|
-
export function parseTimelineForNode(node: PMNode): AsciiTimeline | null {
|
|
73
|
-
if (node.type.name !== 'codeBlock') return null;
|
|
74
|
-
const cached = parseCache.get(node);
|
|
75
|
-
if (cached !== undefined) return cached;
|
|
76
|
-
|
|
77
|
-
let result: AsciiTimeline | null = null;
|
|
78
|
-
if (isEligibleAsciiTimelineFenceLang(fenceLangOf(node))) {
|
|
79
|
-
const detection = detectAsciiTimeline(node.textContent, { explicit: true });
|
|
80
|
-
if (detection.isTimeline && detection.timeline && eventCount(detection.timeline) > 0) {
|
|
81
|
-
result = detection.timeline;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
parseCache.set(node, result);
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function findTimelineBlockPos(editor: Editor, blockId: string): number | null {
|
|
89
|
-
return (
|
|
90
|
-
TIMELINE_VIEW_KEY.getState(editor.state)?.entries.find((entry) => entry.id === blockId)?.pos ??
|
|
91
|
-
null
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
interface TimelineWidgetHost extends HTMLElement {
|
|
96
|
-
__squisqTimelineRoot?: Root;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function buildDecorations(
|
|
100
|
-
doc: PMNode,
|
|
101
|
-
entries: readonly TimelineBlockEntry[],
|
|
102
|
-
editor: Editor,
|
|
103
|
-
): DecorationSet {
|
|
104
|
-
const decorations: Decoration[] = [];
|
|
105
|
-
for (const entry of entries) {
|
|
106
|
-
const node = doc.nodeAt(entry.pos);
|
|
107
|
-
if (!node || node.type.name !== 'codeBlock') continue;
|
|
108
|
-
|
|
109
|
-
decorations.push(
|
|
110
|
-
Decoration.node(entry.pos, entry.pos + node.nodeSize, {
|
|
111
|
-
class: 'squisq-ascii-timeline-fence-hidden',
|
|
112
|
-
}),
|
|
113
|
-
);
|
|
114
|
-
decorations.push(
|
|
115
|
-
Decoration.widget(
|
|
116
|
-
entry.pos + node.nodeSize,
|
|
117
|
-
() => {
|
|
118
|
-
const container = document.createElement('div') as TimelineWidgetHost;
|
|
119
|
-
container.className = 'squisq-ascii-timeline-widget-host';
|
|
120
|
-
container.contentEditable = 'false';
|
|
121
|
-
// The widget contains real form controls inside a ProseMirror
|
|
122
|
-
// decoration. Keep their complete edit/composition stream inside
|
|
123
|
-
// the React root so browser/IME `beforeinput` events cannot also be
|
|
124
|
-
// interpreted as text insertion at the document selection.
|
|
125
|
-
for (const eventName of [
|
|
126
|
-
'pointerdown',
|
|
127
|
-
'mousedown',
|
|
128
|
-
'keydown',
|
|
129
|
-
'keyup',
|
|
130
|
-
'beforeinput',
|
|
131
|
-
'input',
|
|
132
|
-
'change',
|
|
133
|
-
'compositionstart',
|
|
134
|
-
'compositionupdate',
|
|
135
|
-
'compositionend',
|
|
136
|
-
'paste',
|
|
137
|
-
'cut',
|
|
138
|
-
]) {
|
|
139
|
-
container.addEventListener(eventName, (event) => event.stopPropagation());
|
|
140
|
-
}
|
|
141
|
-
const root = createRoot(container);
|
|
142
|
-
root.render(createElement(TimelineEditorWidget, { editor, blockId: entry.id }));
|
|
143
|
-
container.__squisqTimelineRoot = root;
|
|
144
|
-
return container;
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
destroy: (dom) => {
|
|
148
|
-
const root = (dom as TimelineWidgetHost).__squisqTimelineRoot;
|
|
149
|
-
if (root) setTimeout(() => root.unmount(), 0);
|
|
150
|
-
},
|
|
151
|
-
side: 1,
|
|
152
|
-
ignoreSelection: true,
|
|
153
|
-
key: `squisq-timeline-${entry.id}`,
|
|
154
|
-
},
|
|
155
|
-
),
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
return DecorationSet.create(doc, decorations);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function applyState(
|
|
162
|
-
tr: Transaction,
|
|
163
|
-
previous: TimelineViewPluginState,
|
|
164
|
-
editor: Editor,
|
|
165
|
-
doc: PMNode,
|
|
166
|
-
): TimelineViewPluginState {
|
|
167
|
-
if (!tr.docChanged) return previous;
|
|
168
|
-
|
|
169
|
-
const mapped = new Map<number, string>();
|
|
170
|
-
const claimed = new Set<string>();
|
|
171
|
-
for (const entry of previous.entries) {
|
|
172
|
-
const result = tr.mapping.mapResult(entry.pos, 1);
|
|
173
|
-
if (!result.deleted) {
|
|
174
|
-
mapped.set(result.pos, entry.id);
|
|
175
|
-
claimed.add(entry.id);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// `setNodeMarkup` during language promotion can mark the opening token as
|
|
179
|
-
// deleted even though the codeBlock still occupies the mapped position.
|
|
180
|
-
for (const entry of previous.entries) {
|
|
181
|
-
if (claimed.has(entry.id)) continue;
|
|
182
|
-
const result = tr.mapping.mapResult(entry.pos, 1);
|
|
183
|
-
if (mapped.has(result.pos)) continue;
|
|
184
|
-
if (doc.nodeAt(result.pos)?.type.name === 'codeBlock') {
|
|
185
|
-
mapped.set(result.pos, entry.id);
|
|
186
|
-
claimed.add(entry.id);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
let seq = previous.seq;
|
|
191
|
-
const entries: TimelineBlockEntry[] = [];
|
|
192
|
-
doc.descendants((node, pos) => {
|
|
193
|
-
if (node.type.name !== 'codeBlock') return;
|
|
194
|
-
const knownId = mapped.get(pos);
|
|
195
|
-
const timeline = knownId ? parseTimelineForNode(node) : getTimelineForNode(node);
|
|
196
|
-
if (!timeline) return false;
|
|
197
|
-
entries.push({ id: knownId ?? `timeline-${++seq}`, pos });
|
|
198
|
-
return false;
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
return { entries, seq, decorations: buildDecorations(doc, entries, editor) };
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export interface TimelineViewExtensionOptions {
|
|
205
|
-
/** When false, the extension does not claim fences or mount widgets. */
|
|
206
|
-
enabled?: boolean;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export const TimelineViewExtension = Extension.create<TimelineViewExtensionOptions>({
|
|
210
|
-
name: 'squisqTimelineView',
|
|
211
|
-
|
|
212
|
-
addOptions() {
|
|
213
|
-
return { enabled: true };
|
|
214
|
-
},
|
|
215
|
-
|
|
216
|
-
addProseMirrorPlugins() {
|
|
217
|
-
const editor = this.editor as Editor;
|
|
218
|
-
if (this.options.enabled === false) return [];
|
|
219
|
-
|
|
220
|
-
return [
|
|
221
|
-
new Plugin<TimelineViewPluginState>({
|
|
222
|
-
key: TIMELINE_VIEW_KEY,
|
|
223
|
-
state: {
|
|
224
|
-
init: (_config, state) => {
|
|
225
|
-
let seq = 0;
|
|
226
|
-
const entries: TimelineBlockEntry[] = [];
|
|
227
|
-
state.doc.descendants((node, pos) => {
|
|
228
|
-
if (node.type.name !== 'codeBlock') return;
|
|
229
|
-
if (!getTimelineForNode(node)) return false;
|
|
230
|
-
entries.push({ id: `timeline-${++seq}`, pos });
|
|
231
|
-
return false;
|
|
232
|
-
});
|
|
233
|
-
return {
|
|
234
|
-
entries,
|
|
235
|
-
seq,
|
|
236
|
-
decorations: buildDecorations(state.doc, entries, editor),
|
|
237
|
-
};
|
|
238
|
-
},
|
|
239
|
-
apply: (tr, previous, _oldState, newState) =>
|
|
240
|
-
applyState(tr, previous, editor, newState.doc),
|
|
241
|
-
},
|
|
242
|
-
props: {
|
|
243
|
-
decorations(state) {
|
|
244
|
-
return this.getState(state)?.decorations ?? DecorationSet.empty;
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
}),
|
|
248
|
-
];
|
|
249
|
-
},
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
export default TimelineViewExtension;
|
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
import { Editor } from '@tiptap/core';
|
|
2
|
-
import StarterKit from '@tiptap/starter-kit';
|
|
3
|
-
import { EditorContent } from '@tiptap/react';
|
|
4
|
-
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
5
|
-
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
|
6
|
-
import { parseAsciiTimeline, renderAsciiTimeline, type AsciiTimeline } from '@bendyline/squisq/doc';
|
|
7
|
-
import { markdownToTiptap } from '../../tiptapBridge';
|
|
8
|
-
import { HeadingWithTemplate } from '../../TemplateAnnotation';
|
|
9
|
-
import { TimelineEditorWidget } from '../TimelineEditorWidget';
|
|
10
|
-
import { TIMELINE_VIEW_KEY, TimelineViewExtension } from '../TimelineViewExtension';
|
|
11
|
-
import { useTimelineData, type TimelineViewData } from '../timelineData';
|
|
12
|
-
|
|
13
|
-
const TIMELINE: AsciiTimeline = {
|
|
14
|
-
tracks: [
|
|
15
|
-
{
|
|
16
|
-
id: 'kernel',
|
|
17
|
-
label: 'Kernel',
|
|
18
|
-
row: 0,
|
|
19
|
-
startColumn: 0,
|
|
20
|
-
endColumn: 100,
|
|
21
|
-
events: [
|
|
22
|
-
{ id: 'start', label: 'Start', column: 0, side: 'above', marker: 'filled' },
|
|
23
|
-
{ id: 'review', label: 'Review', column: 50, side: 'below', marker: 'hollow' },
|
|
24
|
-
],
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: 'client',
|
|
28
|
-
label: 'Client',
|
|
29
|
-
row: 1,
|
|
30
|
-
startColumn: 0,
|
|
31
|
-
endColumn: 100,
|
|
32
|
-
events: [
|
|
33
|
-
{
|
|
34
|
-
id: 'frame',
|
|
35
|
-
label: 'f',
|
|
36
|
-
column: 25,
|
|
37
|
-
side: 'above',
|
|
38
|
-
callout: false,
|
|
39
|
-
marker: 'hollow',
|
|
40
|
-
},
|
|
41
|
-
{ id: 'paint', label: 'Paint', column: 100, side: 'below', marker: 'filled' },
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
links: [{ source: 'review', target: 'paint', label: 'handoff' }],
|
|
46
|
-
width: 101,
|
|
47
|
-
height: 2,
|
|
48
|
-
style: 'unicode',
|
|
49
|
-
warnings: [],
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const ART = renderAsciiTimeline(TIMELINE);
|
|
53
|
-
const editors: Editor[] = [];
|
|
54
|
-
|
|
55
|
-
beforeAll(() => {
|
|
56
|
-
if (typeof globalThis.PointerEvent !== 'undefined') return;
|
|
57
|
-
class PointerEventStub extends MouseEvent {
|
|
58
|
-
readonly pointerId: number;
|
|
59
|
-
|
|
60
|
-
constructor(type: string, init: PointerEventInit = {}) {
|
|
61
|
-
super(type, init);
|
|
62
|
-
this.pointerId = init.pointerId ?? 0;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
globalThis.PointerEvent = PointerEventStub as typeof PointerEvent;
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
function makeEditor(editable = true, art = ART): Editor {
|
|
69
|
-
const editor = new Editor({
|
|
70
|
-
editable,
|
|
71
|
-
extensions: [
|
|
72
|
-
StarterKit.configure({
|
|
73
|
-
heading: false,
|
|
74
|
-
codeBlock: { HTMLAttributes: { class: 'squisq-code-block' } },
|
|
75
|
-
}),
|
|
76
|
-
HeadingWithTemplate.configure({ levels: [1, 2, 3, 4, 5, 6] }),
|
|
77
|
-
TimelineViewExtension,
|
|
78
|
-
],
|
|
79
|
-
content: markdownToTiptap(`\`\`\`timeline\n${art}\n\`\`\`\n`),
|
|
80
|
-
});
|
|
81
|
-
editors.push(editor);
|
|
82
|
-
return editor;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function blockIdOf(editor: Editor): string {
|
|
86
|
-
const entries = TIMELINE_VIEW_KEY.getState(editor.state)?.entries ?? [];
|
|
87
|
-
expect(entries).toHaveLength(1);
|
|
88
|
-
return entries[0].id;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function renderWidget(editable = true): Editor {
|
|
92
|
-
const editor = makeEditor(editable);
|
|
93
|
-
render(<TimelineEditorWidget editor={editor} blockId={blockIdOf(editor)} />);
|
|
94
|
-
return editor;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function timelineOf(editor: Editor): AsciiTimeline {
|
|
98
|
-
let fence = '';
|
|
99
|
-
editor.state.doc.descendants((node) => {
|
|
100
|
-
if (node.type.name !== 'codeBlock') return true;
|
|
101
|
-
fence = node.textContent;
|
|
102
|
-
return false;
|
|
103
|
-
});
|
|
104
|
-
return parseAsciiTimeline(fence);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function eventOf(editor: Editor, id: string) {
|
|
108
|
-
return timelineOf(editor)
|
|
109
|
-
.tracks.flatMap((track) => track.events)
|
|
110
|
-
.find((event) => event.id === id);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function trackOf(editor: Editor, id: string) {
|
|
114
|
-
return timelineOf(editor).tracks.find((track) => track.id === id);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
afterEach(() => {
|
|
118
|
-
cleanup();
|
|
119
|
-
for (const editor of editors) editor.destroy();
|
|
120
|
-
editors.length = 0;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe('TimelineEditorWidget', () => {
|
|
124
|
-
it('creates, renames, selects, and deletes timeline lines', async () => {
|
|
125
|
-
const editor = renderWidget();
|
|
126
|
-
expect(screen.queryByRole('textbox', { name: 'Rename line: Kernel' })).toBeNull();
|
|
127
|
-
fireEvent.click(screen.getByRole('button', { name: 'Rename line: Kernel' }));
|
|
128
|
-
expect(
|
|
129
|
-
(screen.getByRole('textbox', { name: 'Rename line: Kernel' }) as HTMLInputElement).value,
|
|
130
|
-
).toBe('Kernel');
|
|
131
|
-
fireEvent.keyDown(screen.getByRole('textbox', { name: 'Rename line: Kernel' }), {
|
|
132
|
-
key: 'Escape',
|
|
133
|
-
});
|
|
134
|
-
expect(screen.queryByRole('textbox', { name: 'Rename line: Kernel' })).toBeNull();
|
|
135
|
-
|
|
136
|
-
fireEvent.click(screen.getByRole('button', { name: /Add line/ }));
|
|
137
|
-
|
|
138
|
-
await waitFor(() => expect(trackOf(editor, 'new-line')).toBeTruthy());
|
|
139
|
-
expect(timelineOf(editor).tracks).toHaveLength(3);
|
|
140
|
-
expect(trackOf(editor, 'new-line')?.events).toHaveLength(1);
|
|
141
|
-
expect(screen.getByRole('button', { name: /Edit New event, New line/ })).toBeTruthy();
|
|
142
|
-
const lineName = screen.getByRole('textbox', {
|
|
143
|
-
name: 'Rename line: New line',
|
|
144
|
-
}) as HTMLInputElement;
|
|
145
|
-
expect(lineName.value).toBe('New line');
|
|
146
|
-
|
|
147
|
-
fireEvent.change(lineName, { target: { value: 'Release' } });
|
|
148
|
-
fireEvent.blur(lineName);
|
|
149
|
-
await waitFor(() => expect(trackOf(editor, 'new-line')?.label).toBe('Release'));
|
|
150
|
-
expect(screen.getByRole('group', { name: 'Release timeline rail' })).toBeTruthy();
|
|
151
|
-
|
|
152
|
-
fireEvent.click(screen.getByRole('button', { name: 'Delete line: Release' }));
|
|
153
|
-
await waitFor(() => expect(trackOf(editor, 'new-line')).toBeUndefined());
|
|
154
|
-
expect(timelineOf(editor).tracks).toHaveLength(2);
|
|
155
|
-
expect(screen.getByRole('button', { name: 'Rename line: Kernel' })).toBeTruthy();
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('selects different dots, including a cadence-only dot, and exposes branches', async () => {
|
|
159
|
-
const editor = makeEditor();
|
|
160
|
-
const rendered = render(<TimelineEditorWidget editor={editor} blockId={blockIdOf(editor)} />);
|
|
161
|
-
|
|
162
|
-
const start = screen.getByRole('button', { name: /Edit Start, Kernel/ });
|
|
163
|
-
await waitFor(() => expect(start.getAttribute('aria-pressed')).toBe('true'));
|
|
164
|
-
|
|
165
|
-
const review = screen.getByRole('button', { name: /Edit Review, Kernel/ });
|
|
166
|
-
fireEvent.click(review);
|
|
167
|
-
expect(review.getAttribute('aria-pressed')).toBe('true');
|
|
168
|
-
expect((screen.getByLabelText('Label') as HTMLInputElement).value).toBe('Review');
|
|
169
|
-
|
|
170
|
-
const cadence = screen.getByRole('button', { name: /Edit f, Client/ });
|
|
171
|
-
fireEvent.click(cadence);
|
|
172
|
-
expect(cadence.getAttribute('aria-pressed')).toBe('true');
|
|
173
|
-
expect((screen.getByLabelText('Label') as HTMLInputElement).value).toBe('f');
|
|
174
|
-
|
|
175
|
-
const branchList = screen.getByRole('list', { name: 'Timeline branches' });
|
|
176
|
-
expect(screen.getAllByRole('listitem')).toHaveLength(1);
|
|
177
|
-
expect(branchList.textContent).toContain('review');
|
|
178
|
-
expect(branchList.textContent).toContain('paint');
|
|
179
|
-
expect(branchList.textContent).toContain('handoff');
|
|
180
|
-
expect(
|
|
181
|
-
rendered.container.querySelectorAll('.squisq-ascii-timeline-branches path'),
|
|
182
|
-
).toHaveLength(1);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('rewrites label and callout text on blur while preserving the event id and branch', async () => {
|
|
186
|
-
const editor = renderWidget();
|
|
187
|
-
fireEvent.click(screen.getByRole('button', { name: /Edit Review, Kernel/ }));
|
|
188
|
-
|
|
189
|
-
const label = screen.getByLabelText('Label') as HTMLInputElement;
|
|
190
|
-
fireEvent.change(label, { target: { value: 'Gate' } });
|
|
191
|
-
fireEvent.blur(label);
|
|
192
|
-
|
|
193
|
-
await waitFor(() => expect(eventOf(editor, 'review')?.label).toBe('Gate'));
|
|
194
|
-
expect(timelineOf(editor).links).toEqual([
|
|
195
|
-
{ source: 'review', target: 'paint', label: 'handoff' },
|
|
196
|
-
]);
|
|
197
|
-
|
|
198
|
-
const callout = screen.getByLabelText('Callout text') as HTMLTextAreaElement;
|
|
199
|
-
fireEvent.change(callout, { target: { value: 'Interpolate the latest snapshot' } });
|
|
200
|
-
fireEvent.blur(callout);
|
|
201
|
-
|
|
202
|
-
await waitFor(() =>
|
|
203
|
-
expect(eventOf(editor, 'review')?.description).toBe('Interpolate the latest snapshot'),
|
|
204
|
-
);
|
|
205
|
-
expect((screen.getByLabelText('Label') as HTMLInputElement).value).toBe('Gate');
|
|
206
|
-
expect(
|
|
207
|
-
screen.getByRole('button', { name: /Edit Gate, Kernel/ }).getAttribute('aria-pressed'),
|
|
208
|
-
).toBe('true');
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('adds one point on an armed rail, then returns to selection mode', async () => {
|
|
212
|
-
const editor = renderWidget();
|
|
213
|
-
const rail = screen.getByRole('group', { name: 'Kernel timeline rail' });
|
|
214
|
-
rail.getBoundingClientRect = () =>
|
|
215
|
-
({
|
|
216
|
-
x: 100,
|
|
217
|
-
y: 0,
|
|
218
|
-
left: 100,
|
|
219
|
-
top: 0,
|
|
220
|
-
right: 500,
|
|
221
|
-
bottom: 40,
|
|
222
|
-
width: 400,
|
|
223
|
-
height: 40,
|
|
224
|
-
toJSON: () => ({}),
|
|
225
|
-
}) as DOMRect;
|
|
226
|
-
|
|
227
|
-
const addPoint = screen.getByRole('button', { name: 'Add point to Kernel timeline' });
|
|
228
|
-
expect(addPoint.getAttribute('aria-pressed')).toBe('false');
|
|
229
|
-
fireEvent.click(addPoint);
|
|
230
|
-
expect(addPoint.getAttribute('aria-pressed')).toBe('true');
|
|
231
|
-
expect(screen.getByText('Click the line to add a point · Esc to cancel')).toBeTruthy();
|
|
232
|
-
|
|
233
|
-
fireEvent.click(rail, { clientX: 240 });
|
|
234
|
-
|
|
235
|
-
await waitFor(() => expect(eventOf(editor, 'new-event')?.column).toBe(35));
|
|
236
|
-
const marker = screen.getByRole('button', { name: /Edit New event, Kernel/ });
|
|
237
|
-
expect(marker.getAttribute('aria-pressed')).toBe('true');
|
|
238
|
-
expect((screen.getByLabelText('Label') as HTMLInputElement).value).toBe('New event');
|
|
239
|
-
expect(screen.getByText('New timeline point added. Edit its text below.')).toBeTruthy();
|
|
240
|
-
expect(addPoint.getAttribute('aria-pressed')).toBe('false');
|
|
241
|
-
expect(screen.getByText('Use + to add a point · Drag dots to move')).toBeTruthy();
|
|
242
|
-
|
|
243
|
-
fireEvent.click(rail, { clientX: 400 });
|
|
244
|
-
await Promise.resolve();
|
|
245
|
-
expect(timelineOf(editor).tracks[0].events).toHaveLength(3);
|
|
246
|
-
expect(eventOf(editor, 'new-event-2')).toBeUndefined();
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('previews a dragged dot and commits its new position only on drop', async () => {
|
|
250
|
-
const editor = renderWidget();
|
|
251
|
-
const rail = screen.getByRole('group', { name: 'Kernel timeline rail' });
|
|
252
|
-
rail.getBoundingClientRect = () =>
|
|
253
|
-
({
|
|
254
|
-
x: 100,
|
|
255
|
-
y: 0,
|
|
256
|
-
left: 100,
|
|
257
|
-
top: 0,
|
|
258
|
-
right: 500,
|
|
259
|
-
bottom: 40,
|
|
260
|
-
width: 400,
|
|
261
|
-
height: 40,
|
|
262
|
-
toJSON: () => ({}),
|
|
263
|
-
}) as DOMRect;
|
|
264
|
-
|
|
265
|
-
const review = screen.getByRole('button', { name: /Edit Review, Kernel/ });
|
|
266
|
-
fireEvent.pointerDown(review, { pointerId: 7, button: 0, clientX: 300 });
|
|
267
|
-
fireEvent.pointerMove(review, { pointerId: 7, clientX: 400 });
|
|
268
|
-
|
|
269
|
-
await waitFor(() =>
|
|
270
|
-
expect(review.closest('.squisq-ascii-timeline-point')?.getAttribute('style')).toContain(
|
|
271
|
-
'75%',
|
|
272
|
-
),
|
|
273
|
-
);
|
|
274
|
-
expect(eventOf(editor, 'review')?.column).toBe(50);
|
|
275
|
-
expect(screen.queryAllByRole('button', { name: /Add point to Kernel at/ })).toHaveLength(0);
|
|
276
|
-
|
|
277
|
-
fireEvent.pointerUp(review, { pointerId: 7, button: 0, clientX: 400 });
|
|
278
|
-
|
|
279
|
-
await waitFor(() => expect(eventOf(editor, 'review')?.column).toBe(75));
|
|
280
|
-
expect(screen.getByText('Review moved to 75 percent.')).toBeTruthy();
|
|
281
|
-
expect(
|
|
282
|
-
screen
|
|
283
|
-
.getByRole('button', { name: /Edit Review, Kernel, 75 percent/ })
|
|
284
|
-
.getAttribute('aria-pressed'),
|
|
285
|
-
).toBe('true');
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('cancels a pointer drag without rewriting the timeline', async () => {
|
|
289
|
-
const editor = renderWidget();
|
|
290
|
-
const rail = screen.getByRole('group', { name: 'Kernel timeline rail' });
|
|
291
|
-
rail.getBoundingClientRect = () =>
|
|
292
|
-
({ left: 0, right: 400, top: 0, bottom: 40, width: 400, height: 40 }) as DOMRect;
|
|
293
|
-
const before = JSON.stringify(editor.state.doc.toJSON());
|
|
294
|
-
const review = screen.getByRole('button', { name: /Edit Review, Kernel/ });
|
|
295
|
-
|
|
296
|
-
fireEvent.pointerDown(review, { pointerId: 8, button: 0, clientX: 200 });
|
|
297
|
-
fireEvent.pointerMove(review, { pointerId: 8, clientX: 320 });
|
|
298
|
-
fireEvent.pointerCancel(review, { pointerId: 8 });
|
|
299
|
-
|
|
300
|
-
await waitFor(() => expect(screen.getByText('Timeline point move cancelled.')).toBeTruthy());
|
|
301
|
-
expect(JSON.stringify(editor.state.doc.toJSON())).toBe(before);
|
|
302
|
-
expect(eventOf(editor, 'review')?.column).toBe(50);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('always exposes a keyboard-reachable add-point control for each track', async () => {
|
|
306
|
-
const editor = renderWidget();
|
|
307
|
-
const addPoint = screen.getByRole('button', { name: 'Add point to Kernel timeline' });
|
|
308
|
-
fireEvent.click(addPoint);
|
|
309
|
-
expect(addPoint.getAttribute('aria-pressed')).toBe('true');
|
|
310
|
-
fireEvent.click(screen.getByRole('button', { name: 'Add point to Kernel at 25 percent' }));
|
|
311
|
-
|
|
312
|
-
await waitFor(() => expect(eventOf(editor, 'new-event')?.column).toBe(25));
|
|
313
|
-
expect(screen.getByRole('button', { name: /Edit New event, Kernel/ })).toBeTruthy();
|
|
314
|
-
expect(addPoint.getAttribute('aria-pressed')).toBe('false');
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('keeps dots selectable but prevents every mutation in read-only mode', async () => {
|
|
318
|
-
const editor = renderWidget(false);
|
|
319
|
-
const before = JSON.stringify(editor.state.doc.toJSON());
|
|
320
|
-
expect(screen.getByText('Read only')).toBeTruthy();
|
|
321
|
-
|
|
322
|
-
const review = screen.getByRole('button', { name: /Edit Review, Kernel/ });
|
|
323
|
-
fireEvent.click(review);
|
|
324
|
-
expect(review.getAttribute('aria-pressed')).toBe('true');
|
|
325
|
-
expect((screen.getByLabelText('Label') as HTMLInputElement).value).toBe('Review');
|
|
326
|
-
expect(
|
|
327
|
-
(screen.getByLabelText('Label').closest('fieldset') as HTMLFieldSetElement).disabled,
|
|
328
|
-
).toBe(true);
|
|
329
|
-
|
|
330
|
-
const rail = screen.getByRole('group', { name: 'Kernel timeline rail' });
|
|
331
|
-
fireEvent.click(rail, { clientX: 200 });
|
|
332
|
-
await Promise.resolve();
|
|
333
|
-
expect(JSON.stringify(editor.state.doc.toJSON())).toBe(before);
|
|
334
|
-
expect(screen.queryByRole('button', { name: /Add point to Kernel at/ })).toBeNull();
|
|
335
|
-
expect(screen.queryByRole('button', { name: 'Add point to Kernel timeline' })).toBeNull();
|
|
336
|
-
expect(screen.queryByRole('button', { name: /Add line/ })).toBeNull();
|
|
337
|
-
expect(screen.queryByRole('button', { name: 'Rename line: Kernel' })).toBeNull();
|
|
338
|
-
expect(screen.queryByRole('button', { name: 'Delete line: Kernel' })).toBeNull();
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it('reacts when a mounted editor toggles between editable and read-only', async () => {
|
|
342
|
-
const editor = renderWidget();
|
|
343
|
-
expect(screen.getByRole('button', { name: 'Add point to Kernel timeline' })).toBeTruthy();
|
|
344
|
-
|
|
345
|
-
editor.setEditable(false);
|
|
346
|
-
await screen.findByText('Read only');
|
|
347
|
-
expect(screen.queryByRole('button', { name: 'Add point to Kernel timeline' })).toBeNull();
|
|
348
|
-
expect(screen.queryByRole('button', { name: /Add line/ })).toBeNull();
|
|
349
|
-
expect(
|
|
350
|
-
(screen.getByLabelText('Label').closest('fieldset') as HTMLFieldSetElement).disabled,
|
|
351
|
-
).toBe(true);
|
|
352
|
-
|
|
353
|
-
editor.setEditable(true);
|
|
354
|
-
await waitFor(() =>
|
|
355
|
-
expect(screen.getByRole('button', { name: 'Add point to Kernel timeline' })).toBeTruthy(),
|
|
356
|
-
);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it('retains parsed timeline identity across unrelated editor transactions', async () => {
|
|
360
|
-
const editor = makeEditor();
|
|
361
|
-
const blockId = blockIdOf(editor);
|
|
362
|
-
const observed: TimelineViewData[] = [];
|
|
363
|
-
function Probe() {
|
|
364
|
-
const data = useTimelineData(editor, blockId);
|
|
365
|
-
if (data) observed.push(data);
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
render(<Probe />);
|
|
369
|
-
await waitFor(() => expect(observed.length).toBeGreaterThan(0));
|
|
370
|
-
const first = observed[observed.length - 1];
|
|
371
|
-
|
|
372
|
-
act(() => {
|
|
373
|
-
editor.commands.insertContentAt(0, '<p>Unrelated content</p>');
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
await waitFor(() => expect(observed.length).toBeGreaterThan(1));
|
|
377
|
-
expect(observed[observed.length - 1]).toBe(first);
|
|
378
|
-
expect(observed[observed.length - 1]?.timeline).toBe(first.timeline);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it('pauses visual mutations when source cannot be rewritten losslessly', async () => {
|
|
382
|
-
const unsafeArt = `${ART}\nbranch: review -> missing : preserve me`;
|
|
383
|
-
const editor = makeEditor(true, unsafeArt);
|
|
384
|
-
render(<TimelineEditorWidget editor={editor} blockId={blockIdOf(editor)} />);
|
|
385
|
-
const before = JSON.stringify(editor.state.doc.toJSON());
|
|
386
|
-
|
|
387
|
-
expect(screen.getByText('Source repair needed')).toBeTruthy();
|
|
388
|
-
expect(screen.getByRole('status').textContent).toContain('Visual editing paused');
|
|
389
|
-
expect(screen.queryByRole('button', { name: 'Add point to Kernel timeline' })).toBeNull();
|
|
390
|
-
expect(
|
|
391
|
-
(screen.getByLabelText('Label').closest('fieldset') as HTMLFieldSetElement).disabled,
|
|
392
|
-
).toBe(true);
|
|
393
|
-
|
|
394
|
-
fireEvent.click(screen.getByRole('button', { name: /Edit Review, Kernel/ }));
|
|
395
|
-
expect((screen.getByLabelText('Label') as HTMLInputElement).value).toBe('Review');
|
|
396
|
-
expect(JSON.stringify(editor.state.doc.toJSON())).toBe(before);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
it('keeps mounted widget input events out of the underlying ProseMirror selection', async () => {
|
|
400
|
-
const editor = makeEditor();
|
|
401
|
-
render(<EditorContent editor={editor} />);
|
|
402
|
-
|
|
403
|
-
const review = await screen.findByRole('button', { name: /Edit Review, Kernel/ });
|
|
404
|
-
fireEvent.click(review);
|
|
405
|
-
const label = screen.getByLabelText('Label') as HTMLInputElement;
|
|
406
|
-
fireEvent.change(label, { target: { value: 'Gate' } });
|
|
407
|
-
fireEvent.blur(label);
|
|
408
|
-
|
|
409
|
-
await waitFor(() => expect(eventOf(editor, 'review')?.label).toBe('Gate'));
|
|
410
|
-
expect(editor.state.doc.childCount).toBe(1);
|
|
411
|
-
expect(editor.state.doc.firstChild?.type.name).toBe('codeBlock');
|
|
412
|
-
expect(editor.state.doc.firstChild?.textContent).toContain('Gate');
|
|
413
|
-
});
|
|
414
|
-
});
|