@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,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Command pipeline through a REAL tiptap editor: fence text in →
|
|
3
|
-
* `applyAsciiDiagramCommand` → fence text out, verified by re-parsing.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Editor } from '@tiptap/core';
|
|
7
|
-
import StarterKit from '@tiptap/starter-kit';
|
|
8
|
-
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
|
|
9
|
-
import { ASCII_CHAR_H, ASCII_CHAR_W, parseAsciiDiagram } from '@bendyline/squisq/doc';
|
|
10
|
-
import { markdownToTiptap } from '../../tiptapBridge';
|
|
11
|
-
import { HeadingWithTemplate } from '../../TemplateAnnotation';
|
|
12
|
-
import { AsciiDiagramExtension, ASCII_DIAGRAM_KEY } from '../AsciiDiagramExtension';
|
|
13
|
-
import { applyAsciiDiagramCommand, replaceAsciiFenceText } from '../asciiDiagramCommands';
|
|
14
|
-
|
|
15
|
-
beforeAll(() => {
|
|
16
|
-
if (typeof globalThis.ResizeObserver === 'undefined') {
|
|
17
|
-
class ResizeObserverStub {
|
|
18
|
-
observe(): void {}
|
|
19
|
-
unobserve(): void {}
|
|
20
|
-
disconnect(): void {}
|
|
21
|
-
}
|
|
22
|
-
globalThis.ResizeObserver = ResizeObserverStub as unknown as typeof ResizeObserver;
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const ART = [
|
|
27
|
-
'┌────────┐',
|
|
28
|
-
'│ Alpha │',
|
|
29
|
-
'└───┬────┘',
|
|
30
|
-
' │',
|
|
31
|
-
' ▼',
|
|
32
|
-
'┌────────┐',
|
|
33
|
-
'│ Beta │',
|
|
34
|
-
'└────────┘',
|
|
35
|
-
].join('\n');
|
|
36
|
-
|
|
37
|
-
let editors: Editor[] = [];
|
|
38
|
-
|
|
39
|
-
function makeEditor(md: string): Editor {
|
|
40
|
-
const editor = new Editor({
|
|
41
|
-
extensions: [
|
|
42
|
-
StarterKit.configure({
|
|
43
|
-
heading: false,
|
|
44
|
-
codeBlock: { HTMLAttributes: { class: 'squisq-code-block' } },
|
|
45
|
-
}),
|
|
46
|
-
HeadingWithTemplate.configure({ levels: [1, 2, 3, 4, 5, 6] }),
|
|
47
|
-
AsciiDiagramExtension,
|
|
48
|
-
],
|
|
49
|
-
content: markdownToTiptap(md),
|
|
50
|
-
});
|
|
51
|
-
editors.push(editor);
|
|
52
|
-
return editor;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
afterEach(() => {
|
|
56
|
-
for (const e of editors) e.destroy();
|
|
57
|
-
editors = [];
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
function firstBlockId(editor: Editor): string {
|
|
61
|
-
const state = ASCII_DIAGRAM_KEY.getState(editor.state);
|
|
62
|
-
expect(state?.entries.length).toBeGreaterThan(0);
|
|
63
|
-
return state?.entries[0].id as string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function fenceOf(editor: Editor): { text: string; language: string | null } {
|
|
67
|
-
let text = '';
|
|
68
|
-
let language: string | null = null;
|
|
69
|
-
editor.state.doc.descendants((node) => {
|
|
70
|
-
if (node.type.name === 'codeBlock' && text === '') {
|
|
71
|
-
text = node.textContent;
|
|
72
|
-
language = (node.attrs as { language?: string | null }).language ?? null;
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
return true;
|
|
76
|
-
});
|
|
77
|
-
return { text, language };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
describe('applyAsciiDiagramCommand', () => {
|
|
81
|
-
it('moveNode rewrites the fence with the node at the new cell', () => {
|
|
82
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
83
|
-
const id = firstBlockId(editor);
|
|
84
|
-
const ok = applyAsciiDiagramCommand(editor, id, {
|
|
85
|
-
kind: 'moveNode',
|
|
86
|
-
nodeId: 'beta',
|
|
87
|
-
x: 20 * ASCII_CHAR_W,
|
|
88
|
-
y: 10 * ASCII_CHAR_H,
|
|
89
|
-
});
|
|
90
|
-
expect(ok).toBe(true);
|
|
91
|
-
const reparsed = parseAsciiDiagram(fenceOf(editor).text);
|
|
92
|
-
expect(reparsed.nodes.map((n) => n.id).sort()).toEqual(['alpha', 'beta']);
|
|
93
|
-
expect(reparsed.edges).toEqual([{ source: 'alpha', target: 'beta', directed: true }]);
|
|
94
|
-
expect(reparsed.nodes.find((n) => n.id === 'beta')?.col).toBe(20);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('commits a north/west resize position and size in one fence rewrite', () => {
|
|
98
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
99
|
-
const id = firstBlockId(editor);
|
|
100
|
-
const onTransaction = vi.fn();
|
|
101
|
-
editor.on('transaction', onTransaction);
|
|
102
|
-
|
|
103
|
-
const ok = applyAsciiDiagramCommand(editor, id, {
|
|
104
|
-
kind: 'resizeNode',
|
|
105
|
-
nodeId: 'beta',
|
|
106
|
-
x: 18 * ASCII_CHAR_W,
|
|
107
|
-
y: 10 * ASCII_CHAR_H,
|
|
108
|
-
width: 14 * ASCII_CHAR_W,
|
|
109
|
-
height: 5 * ASCII_CHAR_H,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
expect(ok).toBe(true);
|
|
113
|
-
expect(onTransaction).toHaveBeenCalledTimes(1);
|
|
114
|
-
const beta = parseAsciiDiagram(fenceOf(editor).text).nodes.find((n) => n.id === 'beta');
|
|
115
|
-
expect(beta).toMatchObject({ col: 18, row: 10, wCols: 14, hRows: 5 });
|
|
116
|
-
|
|
117
|
-
editor.commands.undo();
|
|
118
|
-
const restored = parseAsciiDiagram(fenceOf(editor).text).nodes.find((n) => n.id === 'beta');
|
|
119
|
-
expect(restored).toMatchObject({ col: 0, row: 5, wCols: 10, hRows: 3 });
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('persists a virtual gutter with the first edit of legacy origin-hugging art', () => {
|
|
123
|
-
const editor = makeEditor('```diagram\n' + ART + '\n```\n');
|
|
124
|
-
const id = firstBlockId(editor);
|
|
125
|
-
|
|
126
|
-
expect(
|
|
127
|
-
applyAsciiDiagramCommand(
|
|
128
|
-
editor,
|
|
129
|
-
id,
|
|
130
|
-
{
|
|
131
|
-
kind: 'resizeNode',
|
|
132
|
-
nodeId: 'alpha',
|
|
133
|
-
x: 6 * ASCII_CHAR_W,
|
|
134
|
-
y: 1 * ASCII_CHAR_H,
|
|
135
|
-
width: 12 * ASCII_CHAR_W,
|
|
136
|
-
height: 4 * ASCII_CHAR_H,
|
|
137
|
-
},
|
|
138
|
-
{ diagramOffset: { col: 8, row: 2 } },
|
|
139
|
-
),
|
|
140
|
-
).toBe(true);
|
|
141
|
-
|
|
142
|
-
const nodes = parseAsciiDiagram(fenceOf(editor).text).nodes;
|
|
143
|
-
expect(nodes.find((node) => node.id === 'alpha')).toMatchObject({
|
|
144
|
-
col: 6,
|
|
145
|
-
row: 1,
|
|
146
|
-
wCols: 12,
|
|
147
|
-
hRows: 4,
|
|
148
|
-
});
|
|
149
|
-
// The untouched peer receives the persisted origin shift, preserving the
|
|
150
|
-
// same coordinates that were already projected on the canvas.
|
|
151
|
-
expect(nodes.find((node) => node.id === 'beta')).toMatchObject({ col: 8, row: 7 });
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('addConnection adds a reciprocal edge (renders as a double arrow)', () => {
|
|
155
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
156
|
-
const id = firstBlockId(editor);
|
|
157
|
-
expect(
|
|
158
|
-
applyAsciiDiagramCommand(editor, id, {
|
|
159
|
-
kind: 'addConnection',
|
|
160
|
-
source: 'beta',
|
|
161
|
-
target: 'alpha',
|
|
162
|
-
}),
|
|
163
|
-
).toBe(true);
|
|
164
|
-
const reparsed = parseAsciiDiagram(fenceOf(editor).text);
|
|
165
|
-
expect(reparsed.edges).toHaveLength(2);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('renameNode rewrites labels (and therefore ids)', () => {
|
|
169
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
170
|
-
const id = firstBlockId(editor);
|
|
171
|
-
expect(
|
|
172
|
-
applyAsciiDiagramCommand(editor, id, {
|
|
173
|
-
kind: 'renameNode',
|
|
174
|
-
nodeId: 'alpha',
|
|
175
|
-
newLabel: 'Gateway',
|
|
176
|
-
}),
|
|
177
|
-
).toBe(true);
|
|
178
|
-
const reparsed = parseAsciiDiagram(fenceOf(editor).text);
|
|
179
|
-
expect(reparsed.nodes.map((n) => n.id).sort()).toEqual(['beta', 'gateway']);
|
|
180
|
-
expect(reparsed.edges).toEqual([{ source: 'gateway', target: 'beta', directed: true }]);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('addNode / removeNode change the box count', () => {
|
|
184
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
185
|
-
const id = firstBlockId(editor);
|
|
186
|
-
expect(
|
|
187
|
-
applyAsciiDiagramCommand(editor, id, { kind: 'addNode', x: 40 * ASCII_CHAR_W, y: 0 }),
|
|
188
|
-
).toBe(true);
|
|
189
|
-
expect(parseAsciiDiagram(fenceOf(editor).text).nodes).toHaveLength(3);
|
|
190
|
-
expect(applyAsciiDiagramCommand(editor, id, { kind: 'removeNode', nodeId: 'node-1' })).toBe(
|
|
191
|
-
true,
|
|
192
|
-
);
|
|
193
|
-
expect(parseAsciiDiagram(fenceOf(editor).text).nodes).toHaveLength(2);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('promotes the fence language to the explicit `diagram` tag on edit', () => {
|
|
197
|
-
// A once-edited diagram should carry `language: 'diagram'` so its identity
|
|
198
|
-
// survives a later flatten → markdown → re-import round-trip (the language
|
|
199
|
-
// class round-trips; fence meta does not).
|
|
200
|
-
const editor = makeEditor('```text\n' + ART + '\n```\n');
|
|
201
|
-
const id = firstBlockId(editor);
|
|
202
|
-
expect(fenceOf(editor).language).toBe('text');
|
|
203
|
-
applyAsciiDiagramCommand(editor, id, {
|
|
204
|
-
kind: 'moveNode',
|
|
205
|
-
nodeId: 'beta',
|
|
206
|
-
x: 15 * ASCII_CHAR_W,
|
|
207
|
-
y: 9 * ASCII_CHAR_H,
|
|
208
|
-
});
|
|
209
|
-
expect(fenceOf(editor).language).toBe('diagram');
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('keeps the block id stable across the language-promotion rewrite', () => {
|
|
213
|
-
// The promotion is a `setNodeMarkup` boundary rewrite; the position
|
|
214
|
-
// registry must still recognize the same block so a follow-up command
|
|
215
|
-
// resolves it (and the widget's React root survives).
|
|
216
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
217
|
-
const id = firstBlockId(editor);
|
|
218
|
-
applyAsciiDiagramCommand(editor, id, {
|
|
219
|
-
kind: 'moveNode',
|
|
220
|
-
nodeId: 'beta',
|
|
221
|
-
x: 15 * ASCII_CHAR_W,
|
|
222
|
-
y: 9 * ASCII_CHAR_H,
|
|
223
|
-
});
|
|
224
|
-
expect(firstBlockId(editor)).toBe(id);
|
|
225
|
-
expect(fenceOf(editor).language).toBe('diagram');
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('one undo restores the original fence bytes', () => {
|
|
229
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
230
|
-
const id = firstBlockId(editor);
|
|
231
|
-
const before = fenceOf(editor).text;
|
|
232
|
-
applyAsciiDiagramCommand(editor, id, {
|
|
233
|
-
kind: 'moveNode',
|
|
234
|
-
nodeId: 'beta',
|
|
235
|
-
x: 22 * ASCII_CHAR_W,
|
|
236
|
-
y: 12 * ASCII_CHAR_H,
|
|
237
|
-
});
|
|
238
|
-
expect(fenceOf(editor).text).not.toBe(before);
|
|
239
|
-
editor.commands.undo();
|
|
240
|
-
expect(fenceOf(editor).text).toBe(before);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('no-op commands leave the document untouched', () => {
|
|
244
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
245
|
-
const id = firstBlockId(editor);
|
|
246
|
-
const beta = parseAsciiDiagram(fenceOf(editor).text).nodes.find((n) => n.id === 'beta');
|
|
247
|
-
const json = JSON.stringify(editor.state.doc.toJSON());
|
|
248
|
-
const ok = applyAsciiDiagramCommand(editor, id, {
|
|
249
|
-
kind: 'moveNode',
|
|
250
|
-
nodeId: 'beta',
|
|
251
|
-
x: (beta?.col ?? 0) * ASCII_CHAR_W,
|
|
252
|
-
y: (beta?.row ?? 0) * ASCII_CHAR_H,
|
|
253
|
-
});
|
|
254
|
-
expect(ok).toBe(false);
|
|
255
|
-
expect(JSON.stringify(editor.state.doc.toJSON())).toBe(json);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('returns false for an unknown block id', () => {
|
|
259
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
260
|
-
expect(
|
|
261
|
-
applyAsciiDiagramCommand(editor, 'ascii-999', {
|
|
262
|
-
kind: 'removeNode',
|
|
263
|
-
nodeId: 'alpha',
|
|
264
|
-
}),
|
|
265
|
-
).toBe(false);
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
describe('replaceAsciiFenceText', () => {
|
|
270
|
-
it('replaces only the text content, not attributes', () => {
|
|
271
|
-
const editor = makeEditor('```text\n' + ART + '\n```\n');
|
|
272
|
-
const state = ASCII_DIAGRAM_KEY.getState(editor.state);
|
|
273
|
-
const pos = state?.entries[0].pos as number;
|
|
274
|
-
expect(replaceAsciiFenceText(editor, pos, 'replacement')).toBe(true);
|
|
275
|
-
expect(fenceOf(editor)).toEqual({ text: 'replacement', language: 'text' });
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('no-ops on identical text', () => {
|
|
279
|
-
const editor = makeEditor('```\n' + ART + '\n```\n');
|
|
280
|
-
const state = ASCII_DIAGRAM_KEY.getState(editor.state);
|
|
281
|
-
const pos = state?.entries[0].pos as number;
|
|
282
|
-
expect(replaceAsciiFenceText(editor, pos, ART)).toBe(false);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { parseAsciiDiagram, type AsciiDiagram } from '@bendyline/squisq/doc';
|
|
3
|
-
import {
|
|
4
|
-
addEdgeOp,
|
|
5
|
-
addNodeOp,
|
|
6
|
-
moveNodeOp,
|
|
7
|
-
removeEdgeOp,
|
|
8
|
-
removeNodeOp,
|
|
9
|
-
renameNodeOp,
|
|
10
|
-
resizeNodeOp,
|
|
11
|
-
sanitizeAsciiLabel,
|
|
12
|
-
} from '../asciiDiagramOps';
|
|
13
|
-
|
|
14
|
-
const TWO_BOX = [
|
|
15
|
-
'┌────────┐',
|
|
16
|
-
'│ Alpha │',
|
|
17
|
-
'└───┬────┘',
|
|
18
|
-
' │',
|
|
19
|
-
' ▼',
|
|
20
|
-
'┌────────┐',
|
|
21
|
-
'│ Beta │',
|
|
22
|
-
'└────────┘',
|
|
23
|
-
].join('\n');
|
|
24
|
-
|
|
25
|
-
const NESTED = [
|
|
26
|
-
'┌─── Cluster ────┐',
|
|
27
|
-
'│ ┌──────────┐ │',
|
|
28
|
-
'│ │ Worker │ │',
|
|
29
|
-
'│ └──────────┘ │',
|
|
30
|
-
'└────────────────┘',
|
|
31
|
-
].join('\n');
|
|
32
|
-
|
|
33
|
-
function base(): AsciiDiagram {
|
|
34
|
-
return parseAsciiDiagram(TWO_BOX);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
describe('sanitizeAsciiLabel', () => {
|
|
38
|
-
it('strips structural glyphs, backticks, and newlines', () => {
|
|
39
|
-
expect(sanitizeAsciiLabel('a │ b ── c ▼ `d`\ne')).toBe('a b c d e');
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('asciiDiagramOps', () => {
|
|
44
|
-
it('moveNodeOp translates a node without mutating the input', () => {
|
|
45
|
-
const d = base();
|
|
46
|
-
const next = moveNodeOp(d, 'beta', 20, 10);
|
|
47
|
-
expect(next.nodes.find((n) => n.id === 'beta')).toMatchObject({ col: 20, row: 10 });
|
|
48
|
-
expect(d.nodes.find((n) => n.id === 'beta')).toMatchObject({ col: 0, row: 5 });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('moveNodeOp on a container translates the whole subtree', () => {
|
|
52
|
-
const d = parseAsciiDiagram(NESTED);
|
|
53
|
-
const worker = d.nodes.find((n) => n.id === 'worker');
|
|
54
|
-
const next = moveNodeOp(d, 'cluster', 10, 8);
|
|
55
|
-
const movedWorker = next.nodes.find((n) => n.id === 'worker');
|
|
56
|
-
expect(movedWorker?.col).toBe((worker?.col ?? 0) + 10);
|
|
57
|
-
expect(movedWorker?.row).toBe((worker?.row ?? 0) + 8);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('resizeNodeOp clamps to the label minimum', () => {
|
|
61
|
-
const next = resizeNodeOp(base(), 'alpha', 1, 1);
|
|
62
|
-
const alpha = next.nodes.find((n) => n.id === 'alpha');
|
|
63
|
-
expect(alpha?.wCols).toBeGreaterThanOrEqual('Alpha'.length + 4);
|
|
64
|
-
expect(alpha?.hRows).toBeGreaterThanOrEqual(3);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('addEdgeOp adds directed edges and dedupes', () => {
|
|
68
|
-
const d = base();
|
|
69
|
-
const withEdge = addEdgeOp(d, 'beta', 'alpha', 'back');
|
|
70
|
-
expect(withEdge.edges).toHaveLength(2);
|
|
71
|
-
expect(addEdgeOp(withEdge, 'beta', 'alpha', 'back').edges).toHaveLength(2);
|
|
72
|
-
// Self-edges and unknown endpoints are no-ops.
|
|
73
|
-
expect(addEdgeOp(d, 'alpha', 'alpha')).toBe(d);
|
|
74
|
-
expect(addEdgeOp(d, 'alpha', 'ghost')).toBe(d);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('removeEdgeOp removes the first (source, target) match', () => {
|
|
78
|
-
const next = removeEdgeOp(base(), 'alpha', 'beta');
|
|
79
|
-
expect(next.edges).toHaveLength(0);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('renameNodeOp sanitizes and updates the label', () => {
|
|
83
|
-
const next = renameNodeOp(base(), 'alpha', 'Gate │ way');
|
|
84
|
-
expect(next.nodes.find((n) => n.id === 'alpha')?.label).toBe('Gate way');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('addNodeOp picks a fresh label and inherits containment', () => {
|
|
88
|
-
const nested = parseAsciiDiagram(NESTED);
|
|
89
|
-
const cluster = nested.nodes.find((n) => n.id === 'cluster');
|
|
90
|
-
const inside = addNodeOp(nested, {
|
|
91
|
-
col: (cluster?.col ?? 0) + 2,
|
|
92
|
-
row: (cluster?.row ?? 0) + 1,
|
|
93
|
-
});
|
|
94
|
-
const added = inside.diagram.nodes[inside.diagram.nodes.length - 1];
|
|
95
|
-
expect(added.label).toBe('Node 1');
|
|
96
|
-
expect(added.containerId).toBe('cluster');
|
|
97
|
-
|
|
98
|
-
const outside = addNodeOp(nested, { col: 100, row: 100 });
|
|
99
|
-
const addedOutside = outside.diagram.nodes[outside.diagram.nodes.length - 1];
|
|
100
|
-
expect(addedOutside.containerId).toBeUndefined();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('removeNodeOp drops incident edges and promotes children', () => {
|
|
104
|
-
const d = base();
|
|
105
|
-
const next = removeNodeOp(d, 'beta');
|
|
106
|
-
expect(next.nodes.map((n) => n.id)).toEqual(['alpha']);
|
|
107
|
-
expect(next.edges).toHaveLength(0);
|
|
108
|
-
|
|
109
|
-
const nested = parseAsciiDiagram(NESTED);
|
|
110
|
-
const promoted = removeNodeOp(nested, 'cluster');
|
|
111
|
-
expect(promoted.nodes.map((n) => n.id)).toEqual(['worker']);
|
|
112
|
-
expect(promoted.nodes[0].containerId).toBeUndefined();
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { shouldPasteAsAsciiFence } from '../asciiPaste';
|
|
3
|
-
|
|
4
|
-
const ASCII_ART = [
|
|
5
|
-
'+--------+ +--------+',
|
|
6
|
-
'| Input | --> | Output |',
|
|
7
|
-
'+--------+ +--------+',
|
|
8
|
-
].join('\n');
|
|
9
|
-
|
|
10
|
-
const UNICODE_ART = [
|
|
11
|
-
'┌────────┐',
|
|
12
|
-
'│ Alpha │',
|
|
13
|
-
'└───┬────┘',
|
|
14
|
-
' │',
|
|
15
|
-
' ▼',
|
|
16
|
-
'┌────────┐',
|
|
17
|
-
'│ Beta │',
|
|
18
|
-
'└────────┘',
|
|
19
|
-
].join('\n');
|
|
20
|
-
|
|
21
|
-
describe('shouldPasteAsAsciiFence', () => {
|
|
22
|
-
it('accepts bare ASCII art (which would otherwise misdetect as a GFM table)', () => {
|
|
23
|
-
expect(shouldPasteAsAsciiFence(ASCII_ART)).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('accepts bare Unicode box art', () => {
|
|
27
|
-
expect(shouldPasteAsAsciiFence(UNICODE_ART)).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('rejects a real GFM table', () => {
|
|
31
|
-
const table = ['| Name | Role |', '|------|------|', '| Ada | Eng |'].join('\n');
|
|
32
|
-
expect(shouldPasteAsAsciiFence(table)).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('rejects text that already contains a fence', () => {
|
|
36
|
-
expect(shouldPasteAsAsciiFence('```\n' + ASCII_ART + '\n```')).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('rejects prose and empty input', () => {
|
|
40
|
-
expect(shouldPasteAsAsciiFence('Just a normal paragraph of text.')).toBe(false);
|
|
41
|
-
expect(shouldPasteAsAsciiFence('')).toBe(false);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Write direction of the ASCII diagram loop: canvas command → fence rewrite.
|
|
3
|
-
*
|
|
4
|
-
* Every semantic edit is one functional transformation of the fence text
|
|
5
|
-
* (`renderAscii(applyOp(parseAscii(text)))`) committed as a single
|
|
6
|
-
* ProseMirror transaction (= a single undo step). Before committing, the
|
|
7
|
-
* rendered art is re-parsed and verified — if the renderer ever produced
|
|
8
|
-
* something the parser disagrees with, the command aborts instead of
|
|
9
|
-
* corrupting the fence.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { Editor } from '@tiptap/react';
|
|
13
|
-
import {
|
|
14
|
-
canvasToAsciiCell,
|
|
15
|
-
ASCII_CHAR_H,
|
|
16
|
-
ASCII_CHAR_W,
|
|
17
|
-
parseAsciiDiagram,
|
|
18
|
-
renderAsciiDiagram,
|
|
19
|
-
repairAsciiDiagram,
|
|
20
|
-
type AsciiDiagram,
|
|
21
|
-
} from '@bendyline/squisq/doc';
|
|
22
|
-
import type { DiagramCommand } from '../diagram/DiagramCanvas';
|
|
23
|
-
import { findAsciiDiagramBlockPos, parseAsciiDiagramForNode } from './AsciiDiagramExtension';
|
|
24
|
-
import { findRepairableBlockPos } from './RepairableDiagramExtension';
|
|
25
|
-
import {
|
|
26
|
-
addEdgeOp,
|
|
27
|
-
addNodeOp,
|
|
28
|
-
moveNodeOp,
|
|
29
|
-
removeEdgeOp,
|
|
30
|
-
removeNodeOp,
|
|
31
|
-
renameNodeOp,
|
|
32
|
-
resizeNodeOp,
|
|
33
|
-
translateDiagramOp,
|
|
34
|
-
} from './asciiDiagramOps';
|
|
35
|
-
|
|
36
|
-
export interface ApplyAsciiDiagramCommandOptions {
|
|
37
|
-
/**
|
|
38
|
-
* Virtual grid offset currently shown by the canvas for legacy art. It is
|
|
39
|
-
* folded into the source in the same transaction as the user's first edit.
|
|
40
|
-
*/
|
|
41
|
-
diagramOffset?: { col: number; row: number };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Replace the TEXT inside the codeBlock at `pos` and, when `ensureLanguage`
|
|
46
|
-
* is given, promote its `language` attribute in the SAME transaction (one
|
|
47
|
-
* undo step). Promoting the language to the explicit `diagram`/`tree` tag is
|
|
48
|
-
* how a semantic edit makes the block's identity "sticky" — the language
|
|
49
|
-
* class survives markdown ↔ Tiptap round-trips, so a once-edited diagram/tree
|
|
50
|
-
* is re-recognized even after it's flattened. No-op (returns false) only when
|
|
51
|
-
* BOTH the text is already identical AND the language already matches, so
|
|
52
|
-
* history stays clean on a true no-op.
|
|
53
|
-
*/
|
|
54
|
-
export function replaceAsciiFenceText(
|
|
55
|
-
editor: Editor,
|
|
56
|
-
pos: number,
|
|
57
|
-
nextText: string,
|
|
58
|
-
ensureLanguage?: string,
|
|
59
|
-
): boolean {
|
|
60
|
-
return editor
|
|
61
|
-
.chain()
|
|
62
|
-
.command(({ tr, state }) => {
|
|
63
|
-
const node = tr.doc.nodeAt(pos);
|
|
64
|
-
if (!node || node.type.name !== 'codeBlock') return false;
|
|
65
|
-
const textChanged = node.textContent !== nextText;
|
|
66
|
-
const currentLang = (node.attrs as { language?: string | null }).language ?? null;
|
|
67
|
-
const langChanged = ensureLanguage !== undefined && currentLang !== ensureLanguage;
|
|
68
|
-
if (!textChanged && !langChanged) return false;
|
|
69
|
-
if (langChanged) {
|
|
70
|
-
tr.setNodeMarkup(pos, undefined, { ...node.attrs, language: ensureLanguage });
|
|
71
|
-
}
|
|
72
|
-
if (textChanged) {
|
|
73
|
-
tr.replaceWith(pos + 1, pos + node.nodeSize - 1, state.schema.text(nextText));
|
|
74
|
-
}
|
|
75
|
-
return true;
|
|
76
|
-
})
|
|
77
|
-
.run();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Reconstruct a broken box-art fence into clean, `diagram`-tagged art in a
|
|
82
|
-
* single transaction (one undo step). The AsciiDiagramExtension then claims
|
|
83
|
-
* the tagged fence and mounts the interactive canvas. Returns false when the
|
|
84
|
-
* block is gone or nothing recoverable (the button simply no-ops).
|
|
85
|
-
*/
|
|
86
|
-
export function applyRepairCommand(editor: Editor, blockId: string): boolean {
|
|
87
|
-
const pos = findRepairableBlockPos(editor, blockId);
|
|
88
|
-
if (pos === null) return false;
|
|
89
|
-
const node = editor.state.doc.nodeAt(pos);
|
|
90
|
-
if (!node || node.type.name !== 'codeBlock') return false;
|
|
91
|
-
const repaired = repairAsciiDiagram(node.textContent);
|
|
92
|
-
if (!repaired) return false;
|
|
93
|
-
return replaceAsciiFenceText(editor, pos, repaired.art, 'diagram');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Apply one pure op to a registered block's parsed diagram. */
|
|
97
|
-
function applyOp(
|
|
98
|
-
editor: Editor,
|
|
99
|
-
blockId: string,
|
|
100
|
-
op: (diagram: AsciiDiagram) => AsciiDiagram,
|
|
101
|
-
diagramOffset?: { col: number; row: number },
|
|
102
|
-
): boolean {
|
|
103
|
-
// Resolve the position at dispatch time — captured positions go stale
|
|
104
|
-
// the moment anything above the block changes.
|
|
105
|
-
const pos = findAsciiDiagramBlockPos(editor, blockId);
|
|
106
|
-
if (pos === null) return false;
|
|
107
|
-
const node = editor.state.doc.nodeAt(pos);
|
|
108
|
-
if (!node || node.type.name !== 'codeBlock') return false;
|
|
109
|
-
const sourceDiagram = parseAsciiDiagramForNode(node);
|
|
110
|
-
if (!sourceDiagram) return false;
|
|
111
|
-
const diagram = diagramOffset
|
|
112
|
-
? translateDiagramOp(sourceDiagram, diagramOffset.col, diagramOffset.row)
|
|
113
|
-
: sourceDiagram;
|
|
114
|
-
|
|
115
|
-
const next = op(diagram);
|
|
116
|
-
if (next === diagram && diagram === sourceDiagram) return false;
|
|
117
|
-
const rendered = renderAsciiDiagram(next);
|
|
118
|
-
|
|
119
|
-
// Verify before committing: the rendered art must re-parse to the same
|
|
120
|
-
// node count, or we'd write art the canvas can't read back.
|
|
121
|
-
const verification = parseAsciiDiagram(rendered);
|
|
122
|
-
if (verification.nodes.length !== next.nodes.length) return false;
|
|
123
|
-
|
|
124
|
-
// Promote the fence language to the explicit `diagram` tag so this block's
|
|
125
|
-
// identity survives a later flatten → markdown → re-import round-trip.
|
|
126
|
-
return replaceAsciiFenceText(editor, pos, rendered, 'diagram');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** Full pipeline for a canvas command against a registered fence block. */
|
|
130
|
-
export function applyAsciiDiagramCommand(
|
|
131
|
-
editor: Editor,
|
|
132
|
-
blockId: string,
|
|
133
|
-
cmd: DiagramCommand,
|
|
134
|
-
options: ApplyAsciiDiagramCommandOptions = {},
|
|
135
|
-
): boolean {
|
|
136
|
-
const apply = (op: (diagram: AsciiDiagram) => AsciiDiagram) =>
|
|
137
|
-
applyOp(editor, blockId, op, options.diagramOffset);
|
|
138
|
-
switch (cmd.kind) {
|
|
139
|
-
case 'moveNode': {
|
|
140
|
-
const { col, row } = canvasToAsciiCell(cmd.x, cmd.y);
|
|
141
|
-
return apply((d) => moveNodeOp(d, cmd.nodeId, col, row));
|
|
142
|
-
}
|
|
143
|
-
case 'resizeNode': {
|
|
144
|
-
const wCols = Math.max(3, Math.round(cmd.width / ASCII_CHAR_W));
|
|
145
|
-
const hRows = Math.max(3, Math.round(cmd.height / ASCII_CHAR_H));
|
|
146
|
-
return apply((d) => {
|
|
147
|
-
// A north/west resize changes position and size. Apply both to the
|
|
148
|
-
// same parsed model and render once so collision/layout normalization
|
|
149
|
-
// cannot run between the two halves of one pointer gesture.
|
|
150
|
-
let next = d;
|
|
151
|
-
if (cmd.x !== undefined && cmd.y !== undefined) {
|
|
152
|
-
const { col, row } = canvasToAsciiCell(cmd.x, cmd.y);
|
|
153
|
-
next = moveNodeOp(next, cmd.nodeId, col, row);
|
|
154
|
-
}
|
|
155
|
-
return resizeNodeOp(next, cmd.nodeId, wCols, hRows);
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
case 'addConnection':
|
|
159
|
-
return apply((d) => addEdgeOp(d, cmd.source, cmd.target, cmd.type));
|
|
160
|
-
case 'removeConnection':
|
|
161
|
-
return apply((d) => removeEdgeOp(d, cmd.source, cmd.target, cmd.type));
|
|
162
|
-
case 'renameNode':
|
|
163
|
-
return apply((d) => renameNodeOp(d, cmd.nodeId, cmd.newLabel));
|
|
164
|
-
case 'addNode': {
|
|
165
|
-
const { col, row } = canvasToAsciiCell(cmd.x, cmd.y);
|
|
166
|
-
return apply((d) => addNodeOp(d, { col, row }).diagram);
|
|
167
|
-
}
|
|
168
|
-
case 'removeNode':
|
|
169
|
-
return apply((d) => removeNodeOp(d, cmd.nodeId));
|
|
170
|
-
}
|
|
171
|
-
const _exhaustive: never = cmd;
|
|
172
|
-
void _exhaustive;
|
|
173
|
-
return false;
|
|
174
|
-
}
|