@bendyline/squisq-editor-react 2.0.1 → 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/README.md +10 -2
- package/dist/index.d.ts +683 -60
- package/dist/index.js +9648 -3750
- package/dist/monaco.js +0 -1
- package/dist/styles/index.css +1160 -241
- package/package.json +10 -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 -1247
- package/src/EditorShell.tsx +0 -1334
- 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 -1250
- package/src/PreviewPanel.tsx +0 -422
- package/src/RawEditor.tsx +0 -731
- package/src/RecorderEntry.tsx +0 -159
- 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 -2587
- 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 -792
- 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 -48
- 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 -485
- 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__/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__/mediaAttachmentFlow.test.ts +0 -110
- package/src/__tests__/mediaBinDrop.test.tsx +0 -90
- package/src/__tests__/mediaReferences.test.ts +0 -82
- 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 -540
- 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 -566
- 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 -164
- 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__/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 -145
- 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 -405
- 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/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 -259
- 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/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 -470
- 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/mediaDragMime.ts +0 -32
- package/src/mediaEntries.ts +0 -12
- package/src/mediaReferences.ts +0 -299
- 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 -616
- package/src/scene/SceneBlockExtension.ts +0 -205
- package/src/scene/SceneBlockToolbar.tsx +0 -201
- 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/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__/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 -103
- 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/hooks/useSceneHitTest.ts +0 -105
- package/src/scene/hooks/useScenePanZoom.ts +0 -147
- 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 -792
- 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/diagram.css +0 -183
- package/src/styles/editor.css +0 -6811
- package/src/styles/folder-view.css +0 -210
- package/src/styles/image-edit-affordance.css +0 -201
- package/src/styles/index.css +0 -18
- 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/wysiwygImageUpload.ts +0 -113
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Write direction of the tree loop: outline command → fence rewrite.
|
|
3
|
-
*
|
|
4
|
-
* parse → op → render → verify-reparse → single-transaction fence replace
|
|
5
|
-
* (one undo step). Reuses the generic `replaceAsciiFenceText` (it only
|
|
6
|
-
* touches codeBlock text, attributes untouched).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { Editor } from '@tiptap/react';
|
|
10
|
-
import { parseTree, renderTree, type Tree } from '@bendyline/squisq/doc';
|
|
11
|
-
import { replaceAsciiFenceText } from '../asciiDiagram/asciiDiagramCommands';
|
|
12
|
-
import { findTreeBlockPos, parseTreeForNode } from './TreeViewExtension';
|
|
13
|
-
import {
|
|
14
|
-
addItemOp,
|
|
15
|
-
indentItemOp,
|
|
16
|
-
moveItemOp,
|
|
17
|
-
moveItemDownOp,
|
|
18
|
-
moveItemUpOp,
|
|
19
|
-
outdentItemOp,
|
|
20
|
-
removeItemOp,
|
|
21
|
-
renameItemOp,
|
|
22
|
-
toggleDirOp,
|
|
23
|
-
type TreeDropPosition,
|
|
24
|
-
} from './treeOps';
|
|
25
|
-
|
|
26
|
-
export type TreeCommand =
|
|
27
|
-
| {
|
|
28
|
-
kind: 'addItem';
|
|
29
|
-
targetId: string;
|
|
30
|
-
position: 'child' | 'siblingAfter';
|
|
31
|
-
label?: string;
|
|
32
|
-
isDir?: boolean;
|
|
33
|
-
}
|
|
34
|
-
| { kind: 'renameItem'; id: string; label: string }
|
|
35
|
-
| { kind: 'indentItem'; id: string }
|
|
36
|
-
| { kind: 'outdentItem'; id: string }
|
|
37
|
-
| { kind: 'moveItem'; id: string; targetId: string; position: TreeDropPosition }
|
|
38
|
-
| { kind: 'moveItemUp'; id: string }
|
|
39
|
-
| { kind: 'moveItemDown'; id: string }
|
|
40
|
-
| { kind: 'removeItem'; id: string }
|
|
41
|
-
| { kind: 'toggleDir'; id: string };
|
|
42
|
-
|
|
43
|
-
/** Re-export so the widget can rewrite raw fence text without a second copy. */
|
|
44
|
-
export { replaceAsciiFenceText as replaceTreeFenceText } from '../asciiDiagram/asciiDiagramCommands';
|
|
45
|
-
|
|
46
|
-
function countNodes(nodes: readonly Tree['roots'][number][]): number {
|
|
47
|
-
let n = 0;
|
|
48
|
-
for (const node of nodes) n += 1 + countNodes(node.children);
|
|
49
|
-
return n;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function applyOp(editor: Editor, blockId: string, op: (t: Tree) => Tree): boolean {
|
|
53
|
-
const pos = findTreeBlockPos(editor, blockId);
|
|
54
|
-
if (pos === null) return false;
|
|
55
|
-
const node = editor.state.doc.nodeAt(pos);
|
|
56
|
-
if (!node || node.type.name !== 'codeBlock') return false;
|
|
57
|
-
const tree = parseTreeForNode(node);
|
|
58
|
-
if (!tree) return false;
|
|
59
|
-
|
|
60
|
-
const next = op(tree);
|
|
61
|
-
if (next === tree) return false;
|
|
62
|
-
const rendered = renderTree(next);
|
|
63
|
-
|
|
64
|
-
// Verify before committing: the rendered art must re-parse to the same
|
|
65
|
-
// node count, or we'd write a tree the outline can't read back.
|
|
66
|
-
if (countNodes(parseTree(rendered).roots) !== countNodes(next.roots)) return false;
|
|
67
|
-
|
|
68
|
-
// Promote the fence language to the explicit `tree` tag so this block's
|
|
69
|
-
// identity survives a later flatten → markdown → re-import round-trip.
|
|
70
|
-
return replaceAsciiFenceText(editor, pos, rendered, 'tree');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function applyTreeCommand(editor: Editor, blockId: string, cmd: TreeCommand): boolean {
|
|
74
|
-
switch (cmd.kind) {
|
|
75
|
-
case 'addItem':
|
|
76
|
-
return applyOp(editor, blockId, (t) =>
|
|
77
|
-
addItemOp(t, cmd.targetId, cmd.position, cmd.label, cmd.isDir),
|
|
78
|
-
);
|
|
79
|
-
case 'renameItem':
|
|
80
|
-
return applyOp(editor, blockId, (t) => renameItemOp(t, cmd.id, cmd.label));
|
|
81
|
-
case 'indentItem':
|
|
82
|
-
return applyOp(editor, blockId, (t) => indentItemOp(t, cmd.id));
|
|
83
|
-
case 'outdentItem':
|
|
84
|
-
return applyOp(editor, blockId, (t) => outdentItemOp(t, cmd.id));
|
|
85
|
-
case 'moveItem':
|
|
86
|
-
return applyOp(editor, blockId, (t) => moveItemOp(t, cmd.id, cmd.targetId, cmd.position));
|
|
87
|
-
case 'moveItemUp':
|
|
88
|
-
return applyOp(editor, blockId, (t) => moveItemUpOp(t, cmd.id));
|
|
89
|
-
case 'moveItemDown':
|
|
90
|
-
return applyOp(editor, blockId, (t) => moveItemDownOp(t, cmd.id));
|
|
91
|
-
case 'removeItem':
|
|
92
|
-
return applyOp(editor, blockId, (t) => removeItemOp(t, cmd.id));
|
|
93
|
-
case 'toggleDir':
|
|
94
|
-
return applyOp(editor, blockId, (t) => toggleDirOp(t, cmd.id));
|
|
95
|
-
}
|
|
96
|
-
const _exhaustive: never = cmd;
|
|
97
|
-
void _exhaustive;
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Read direction of the tree loop: fence text → outline view.
|
|
3
|
-
*
|
|
4
|
-
* `useTreeViewData` re-derives on every editor transaction; parsing is
|
|
5
|
-
* memoized per PMNode by the extension's WeakMap caches, so untouched
|
|
6
|
-
* fences cost nothing.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
10
|
-
import type { Editor } from '@tiptap/react';
|
|
11
|
-
import type { Tree } from '@bendyline/squisq/doc';
|
|
12
|
-
import { findTreeBlockPos, parseTreeForNode } from './TreeViewExtension';
|
|
13
|
-
|
|
14
|
-
export interface TreeViewData {
|
|
15
|
-
tree: Tree;
|
|
16
|
-
text: string;
|
|
17
|
-
warnings: string[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Live view of one registered tree block; null when it no longer parses. */
|
|
21
|
-
export function useTreeViewData(editor: Editor, blockId: string): TreeViewData | null {
|
|
22
|
-
const [version, setVersion] = useState(0);
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const onUpdate = () => setVersion((v) => v + 1);
|
|
25
|
-
editor.on('transaction', onUpdate);
|
|
26
|
-
return () => {
|
|
27
|
-
editor.off('transaction', onUpdate);
|
|
28
|
-
};
|
|
29
|
-
}, [editor]);
|
|
30
|
-
|
|
31
|
-
return useMemo(() => {
|
|
32
|
-
const pos = findTreeBlockPos(editor, blockId);
|
|
33
|
-
if (pos === null) return null;
|
|
34
|
-
const node = editor.state.doc.nodeAt(pos);
|
|
35
|
-
if (!node || node.type.name !== 'codeBlock') return null;
|
|
36
|
-
const tree = parseTreeForNode(node);
|
|
37
|
-
if (!tree) return null;
|
|
38
|
-
return { tree, text: node.textContent, warnings: tree.warnings };
|
|
39
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
-
}, [editor, blockId, version]);
|
|
41
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Monaco ships `editor.main.js` (the full bundle with language contributions)
|
|
3
|
-
* but only publishes `.d.ts` for `editor.api`. We import the `.main.js`
|
|
4
|
-
* subpath at runtime to register all languages, then cast through
|
|
5
|
-
* `as unknown as typeof import('monaco-editor')` at the call site. This
|
|
6
|
-
* ambient declaration makes the subpath import resolve without a
|
|
7
|
-
* `@ts-expect-error` (which doesn't survive Prettier's formatting choice
|
|
8
|
-
* for the surrounding parenthesized expression — see useMonacoLoader.ts).
|
|
9
|
-
*/
|
|
10
|
-
declare module 'monaco-editor/esm/vs/editor/editor.main.js';
|
package/src/useBlockNavigator.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useBlockNavigator
|
|
3
|
-
*
|
|
4
|
-
* Standalone hook powering the block-at-a-time editing view. Given a
|
|
5
|
-
* `(source, setSource)` pair it slices the markdown into blocks (via
|
|
6
|
-
* `blockRange.ts`), tracks which block is active, and exposes a derived
|
|
7
|
-
* **content channel** (`editorSource` / `setEditorSource`) plus navigation.
|
|
8
|
-
*
|
|
9
|
-
* It depends only on its arguments — no `EditorContext`, no `EditorShell` —
|
|
10
|
-
* so any host (an embedded single-block editor, a chat composer, a review
|
|
11
|
-
* surface) can drive a block-at-a-time UI by calling it directly.
|
|
12
|
-
*
|
|
13
|
-
* When `enabled` is false the channel is an identity passthrough: the editors
|
|
14
|
-
* see and write the full source exactly as before.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { useCallback, useMemo, useState } from 'react';
|
|
18
|
-
import {
|
|
19
|
-
getBlockSlices,
|
|
20
|
-
spliceBlock,
|
|
21
|
-
lineToOffset,
|
|
22
|
-
offsetToLine,
|
|
23
|
-
sliceIndexAtOffset,
|
|
24
|
-
type BlockSlice,
|
|
25
|
-
} from './blockRange';
|
|
26
|
-
|
|
27
|
-
export interface BlockNavigator {
|
|
28
|
-
/** What the bound editor should show: the active slice (block mode) or the full source. */
|
|
29
|
-
editorSource: string;
|
|
30
|
-
/** What the bound editor writes through: splices back into the full source (block mode). */
|
|
31
|
-
setEditorSource: (s: string) => void;
|
|
32
|
-
/** Number of navigable blocks in the current source. */
|
|
33
|
-
blockCount: number;
|
|
34
|
-
/** Index of the active block (clamped into range). */
|
|
35
|
-
activeBlockKey: number;
|
|
36
|
-
/** Jump to a block by index (clamped). */
|
|
37
|
-
goToBlock: (key: number) => void;
|
|
38
|
-
/**
|
|
39
|
-
* Select the block that owns a given 1-based source line — used by the
|
|
40
|
-
* outline, which knows a heading's source line but not its slice index
|
|
41
|
-
* (slice order includes the optional preamble, so it needn't match
|
|
42
|
-
* `flattenBlocks` order).
|
|
43
|
-
*/
|
|
44
|
-
goToBlockByLine: (line: number) => void;
|
|
45
|
-
/** 1-based source line where the active block begins (for outline highlight). */
|
|
46
|
-
activeBlockStartLine: number | null;
|
|
47
|
-
/** Move to the previous block (no-op at the start). */
|
|
48
|
-
prevBlock: () => void;
|
|
49
|
-
/** Move to the next block (no-op at the end). */
|
|
50
|
-
nextBlock: () => void;
|
|
51
|
-
/** Insert a new heading-defined block after the active one and move to it. */
|
|
52
|
-
addBlock: () => void;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface UseBlockNavigatorOptions {
|
|
56
|
-
/**
|
|
57
|
-
* When false (the default), the channel passes through to the full source
|
|
58
|
-
* and navigation is inert. Hosts flip this on to enter block-at-a-time mode.
|
|
59
|
-
*/
|
|
60
|
-
enabled?: boolean;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Heading inserted by {@link BlockNavigator.addBlock}. */
|
|
64
|
-
const NEW_BLOCK_MARKDOWN = '## New section\n\n';
|
|
65
|
-
|
|
66
|
-
export function useBlockNavigator(
|
|
67
|
-
source: string,
|
|
68
|
-
setSource: (s: string) => void,
|
|
69
|
-
opts?: UseBlockNavigatorOptions,
|
|
70
|
-
): BlockNavigator {
|
|
71
|
-
const enabled = opts?.enabled ?? false;
|
|
72
|
-
|
|
73
|
-
// Slices are derived straight from the live `source`, not from any
|
|
74
|
-
// debounced parse, so they're always consistent with what we splice into.
|
|
75
|
-
const slices = useMemo<BlockSlice[]>(() => getBlockSlices(source), [source]);
|
|
76
|
-
const blockCount = slices.length;
|
|
77
|
-
|
|
78
|
-
const [rawKey, setRawKey] = useState(0);
|
|
79
|
-
// Effective key is always in range even when the source shrank under us —
|
|
80
|
-
// the state can lag a re-slice for a render, but derivations never read a
|
|
81
|
-
// stale/out-of-bounds slice.
|
|
82
|
-
const activeBlockKey = blockCount === 0 ? 0 : Math.min(Math.max(rawKey, 0), blockCount - 1);
|
|
83
|
-
const active = slices[activeBlockKey] ?? null;
|
|
84
|
-
|
|
85
|
-
const goToBlock = useCallback((key: number) => {
|
|
86
|
-
setRawKey(key);
|
|
87
|
-
}, []);
|
|
88
|
-
const prevBlock = useCallback(() => {
|
|
89
|
-
setRawKey(activeBlockKey - 1);
|
|
90
|
-
}, [activeBlockKey]);
|
|
91
|
-
const nextBlock = useCallback(() => {
|
|
92
|
-
setRawKey(activeBlockKey + 1);
|
|
93
|
-
}, [activeBlockKey]);
|
|
94
|
-
const goToBlockByLine = useCallback(
|
|
95
|
-
(line: number) => {
|
|
96
|
-
const offset = lineToOffset(source, line);
|
|
97
|
-
const idx = sliceIndexAtOffset(slices, offset);
|
|
98
|
-
if (idx >= 0) setRawKey(idx);
|
|
99
|
-
},
|
|
100
|
-
[source, slices],
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
const activeBlockStartLine = active ? offsetToLine(source, active.range.startOffset) : null;
|
|
104
|
-
|
|
105
|
-
const editorSource = enabled && active ? active.text : source;
|
|
106
|
-
|
|
107
|
-
const setEditorSource = useCallback(
|
|
108
|
-
(next: string) => {
|
|
109
|
-
if (!enabled || !active) {
|
|
110
|
-
setSource(next);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
// Keep a blank line before the following block's heading so the splice
|
|
114
|
-
// can't glue the edited block onto the next one (the editor may emit a
|
|
115
|
-
// slice with no trailing newline).
|
|
116
|
-
const hasFollowing = active.range.endOffset < source.length;
|
|
117
|
-
const normalized = hasFollowing ? next.replace(/\s*$/, '') + '\n\n' : next;
|
|
118
|
-
setSource(spliceBlock(source, active.range, normalized));
|
|
119
|
-
},
|
|
120
|
-
[enabled, active, source, setSource],
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const addBlock = useCallback(() => {
|
|
124
|
-
if (!active) {
|
|
125
|
-
// Empty / heading-less doc — append a heading and select it.
|
|
126
|
-
const base = source.replace(/\s*$/, '');
|
|
127
|
-
setSource((base ? base + '\n\n' : '') + NEW_BLOCK_MARKDOWN);
|
|
128
|
-
setRawKey(blockCount); // becomes the new last slice after re-slice
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const insertAt = active.range.endOffset;
|
|
132
|
-
const before = source.slice(0, insertAt);
|
|
133
|
-
const after = source.slice(insertAt);
|
|
134
|
-
const lead =
|
|
135
|
-
before === '' || before.endsWith('\n\n') ? '' : before.endsWith('\n') ? '\n' : '\n\n';
|
|
136
|
-
setSource(before + lead + NEW_BLOCK_MARKDOWN + after);
|
|
137
|
-
// The new block sits immediately after the active one.
|
|
138
|
-
setRawKey(activeBlockKey + 1);
|
|
139
|
-
}, [active, source, setSource, activeBlockKey, blockCount]);
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
editorSource,
|
|
143
|
-
setEditorSource,
|
|
144
|
-
blockCount,
|
|
145
|
-
activeBlockKey,
|
|
146
|
-
goToBlock,
|
|
147
|
-
goToBlockByLine,
|
|
148
|
-
activeBlockStartLine,
|
|
149
|
-
prevBlock,
|
|
150
|
-
nextBlock,
|
|
151
|
-
addBlock,
|
|
152
|
-
};
|
|
153
|
-
}
|
package/src/useHeadingLayout.ts
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useHeadingLayout
|
|
3
|
-
*
|
|
4
|
-
* Shared positioning hook for the editor gutters (`OutlinePanel` and
|
|
5
|
-
* `InlinePreviewGutter`). Each gutter needs to know:
|
|
6
|
-
* - where every heading sits vertically inside the wrapper
|
|
7
|
-
* - where the editor "page" edges sit horizontally
|
|
8
|
-
* - how to scroll the editor to a particular heading
|
|
9
|
-
*
|
|
10
|
-
* Two backends, picked from `EditorContext.activeView`:
|
|
11
|
-
* - **wysiwyg** — query DOM headings inside `.squisq-wysiwyg-container`,
|
|
12
|
-
* measure with `getBoundingClientRect`, scroll via `scrollIntoView`.
|
|
13
|
-
* Live updates via `ResizeObserver` + `MutationObserver`.
|
|
14
|
-
* - **raw** — walk `doc.blocks`, derive each heading's line number from
|
|
15
|
-
* `MarkdownHeading.position.start.line`, ask Monaco for the line's
|
|
16
|
-
* pixel offset via `getTopForLineNumber`, scroll via
|
|
17
|
-
* `revealLineInCenterIfOutsideViewport` + `setPosition`. Live updates
|
|
18
|
-
* via `onDidScrollChange` + `onDidChangeModelContent`.
|
|
19
|
-
*
|
|
20
|
-
* Both backends produce coordinates in the *wrapper's* reference frame
|
|
21
|
-
* (i.e., `.squisq-editor-with-gutter`), so the rendering layers — cards,
|
|
22
|
-
* extent bars, outline rows — don't care which editor is active.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
26
|
-
import type { RefObject } from 'react';
|
|
27
|
-
import type { Block } from '@bendyline/squisq/schemas';
|
|
28
|
-
import { flattenBlocks, hasTemplate } from '@bendyline/squisq/doc';
|
|
29
|
-
import { useEditorContext } from './EditorContext';
|
|
30
|
-
|
|
31
|
-
export interface HeadingLayoutEntry {
|
|
32
|
-
/** The block (heading-rooted unit) this entry represents. */
|
|
33
|
-
block: Block;
|
|
34
|
-
/** Heading top, in px relative to the wrapper's top edge. */
|
|
35
|
-
top: number;
|
|
36
|
-
/** Where this block ends — the next heading's top, or the editor bottom. */
|
|
37
|
-
bottom: number;
|
|
38
|
-
/** True when the heading has a recognised template annotation. */
|
|
39
|
-
annotated: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface HeadingLayout {
|
|
43
|
-
/** All headings, in document order. */
|
|
44
|
-
entries: HeadingLayoutEntry[];
|
|
45
|
-
/** Editor page's left/right edges, in px relative to the wrapper. */
|
|
46
|
-
pageEdges: { left: number; right: number } | null;
|
|
47
|
-
/** Scroll the active editor to bring the block's heading into view. */
|
|
48
|
-
scrollToBlock: (block: Block) => void;
|
|
49
|
-
/** True once a measurement has produced numbers (avoids flicker on mount). */
|
|
50
|
-
ready: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* @param refInsideWrapper — any DOM ref under `.squisq-editor-with-gutter`.
|
|
55
|
-
* The hook walks up to find the wrapper.
|
|
56
|
-
*/
|
|
57
|
-
export function useHeadingLayout(refInsideWrapper: RefObject<HTMLElement | null>): HeadingLayout {
|
|
58
|
-
const { doc, activeView, monacoEditor, tiptapEditor } = useEditorContext();
|
|
59
|
-
|
|
60
|
-
const flatBlocks = useMemo(() => (doc ? flattenBlocks(doc.blocks) : []), [doc]);
|
|
61
|
-
|
|
62
|
-
const [entries, setEntries] = useState<HeadingLayoutEntry[]>([]);
|
|
63
|
-
const [pageEdges, setPageEdges] = useState<{ left: number; right: number } | null>(null);
|
|
64
|
-
|
|
65
|
-
// ── WYSIWYG backend ────────────────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (activeView !== 'wysiwyg') return;
|
|
69
|
-
const node = refInsideWrapper.current;
|
|
70
|
-
if (!node) return;
|
|
71
|
-
const wrapper = findWrapper(node);
|
|
72
|
-
if (!wrapper) return;
|
|
73
|
-
const wysiwygContainer = wrapper.querySelector<HTMLElement>('.squisq-wysiwyg-container');
|
|
74
|
-
if (!wysiwygContainer) return;
|
|
75
|
-
|
|
76
|
-
let raf = 0;
|
|
77
|
-
const recompute = () => {
|
|
78
|
-
cancelAnimationFrame(raf);
|
|
79
|
-
raf = requestAnimationFrame(() => {
|
|
80
|
-
const headings = Array.from(
|
|
81
|
-
wysiwygContainer.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6'),
|
|
82
|
-
);
|
|
83
|
-
const wrapperRect = wrapper.getBoundingClientRect();
|
|
84
|
-
const editorRect = wysiwygContainer.getBoundingClientRect();
|
|
85
|
-
const next: HeadingLayoutEntry[] = [];
|
|
86
|
-
flatBlocks.forEach((block, i) => {
|
|
87
|
-
const h = headings[i];
|
|
88
|
-
if (!h) return;
|
|
89
|
-
const top = h.getBoundingClientRect().top - wrapperRect.top;
|
|
90
|
-
const nextH = headings[i + 1];
|
|
91
|
-
const bottom = nextH
|
|
92
|
-
? nextH.getBoundingClientRect().top - wrapperRect.top
|
|
93
|
-
: editorRect.bottom - wrapperRect.top;
|
|
94
|
-
next.push({
|
|
95
|
-
block,
|
|
96
|
-
top,
|
|
97
|
-
bottom,
|
|
98
|
-
annotated: !!h.getAttribute('data-template'),
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
setEntries((prev) => (sameEntries(prev, next) ? prev : next));
|
|
102
|
-
|
|
103
|
-
// Page edges — anchor to the .squisq-wysiwyg-editor (the centered "page").
|
|
104
|
-
const page = wysiwygContainer.querySelector<HTMLElement>('.squisq-wysiwyg-editor');
|
|
105
|
-
if (page) {
|
|
106
|
-
const pageRect = page.getBoundingClientRect();
|
|
107
|
-
const left = pageRect.left - wrapperRect.left;
|
|
108
|
-
const right = pageRect.right - wrapperRect.left;
|
|
109
|
-
setPageEdges((prev) =>
|
|
110
|
-
prev != null && Math.abs(prev.left - left) < 0.5 && Math.abs(prev.right - right) < 0.5
|
|
111
|
-
? prev
|
|
112
|
-
: { left, right },
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
recompute();
|
|
119
|
-
const ro = new ResizeObserver(recompute);
|
|
120
|
-
ro.observe(wysiwygContainer);
|
|
121
|
-
const editorSurface = wysiwygContainer.querySelector('.squisq-wysiwyg-editor');
|
|
122
|
-
if (editorSurface) ro.observe(editorSurface);
|
|
123
|
-
const mo = new MutationObserver(recompute);
|
|
124
|
-
mo.observe(wysiwygContainer, {
|
|
125
|
-
childList: true,
|
|
126
|
-
subtree: true,
|
|
127
|
-
characterData: true,
|
|
128
|
-
attributes: true,
|
|
129
|
-
attributeFilter: ['data-template', 'data-template-params'],
|
|
130
|
-
});
|
|
131
|
-
wysiwygContainer.addEventListener('scroll', recompute, { passive: true });
|
|
132
|
-
window.addEventListener('resize', recompute);
|
|
133
|
-
const settle = window.setTimeout(recompute, 250);
|
|
134
|
-
|
|
135
|
-
return () => {
|
|
136
|
-
cancelAnimationFrame(raf);
|
|
137
|
-
window.clearTimeout(settle);
|
|
138
|
-
ro.disconnect();
|
|
139
|
-
mo.disconnect();
|
|
140
|
-
wysiwygContainer.removeEventListener('scroll', recompute);
|
|
141
|
-
window.removeEventListener('resize', recompute);
|
|
142
|
-
};
|
|
143
|
-
}, [activeView, flatBlocks, refInsideWrapper]);
|
|
144
|
-
|
|
145
|
-
// ── Raw (Monaco) backend ───────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
if (activeView !== 'raw') return;
|
|
149
|
-
if (!monacoEditor) return;
|
|
150
|
-
const node = refInsideWrapper.current;
|
|
151
|
-
if (!node) return;
|
|
152
|
-
const wrapper = findWrapper(node);
|
|
153
|
-
if (!wrapper) return;
|
|
154
|
-
|
|
155
|
-
let raf = 0;
|
|
156
|
-
const recompute = () => {
|
|
157
|
-
cancelAnimationFrame(raf);
|
|
158
|
-
raf = requestAnimationFrame(() => {
|
|
159
|
-
const wrapperRect = wrapper.getBoundingClientRect();
|
|
160
|
-
const monacoRoot = monacoEditor.getDomNode() as HTMLElement | null;
|
|
161
|
-
if (!monacoRoot) return;
|
|
162
|
-
const monacoRect = monacoRoot.getBoundingClientRect();
|
|
163
|
-
// Y of the editor's content top in wrapper coordinates.
|
|
164
|
-
const monacoTop = monacoRect.top - wrapperRect.top;
|
|
165
|
-
const scrollTop = monacoEditor.getScrollTop();
|
|
166
|
-
|
|
167
|
-
const layoutInfo = monacoEditor.getLayoutInfo();
|
|
168
|
-
const next: HeadingLayoutEntry[] = [];
|
|
169
|
-
flatBlocks.forEach((block, i) => {
|
|
170
|
-
const line = block.sourceHeading?.position?.start.line;
|
|
171
|
-
if (typeof line !== 'number') return;
|
|
172
|
-
const lineTop = monacoEditor.getTopForLineNumber(line) - scrollTop + monacoTop;
|
|
173
|
-
// Bottom = next block's top, or the visible bottom of the editor.
|
|
174
|
-
const nextBlock = flatBlocks[i + 1];
|
|
175
|
-
const nextLine = nextBlock?.sourceHeading?.position?.start.line;
|
|
176
|
-
const bottom =
|
|
177
|
-
typeof nextLine === 'number'
|
|
178
|
-
? monacoEditor.getTopForLineNumber(nextLine) - scrollTop + monacoTop
|
|
179
|
-
: monacoTop + layoutInfo.height;
|
|
180
|
-
const tplName = block.sourceHeading?.templateAnnotation?.template;
|
|
181
|
-
next.push({
|
|
182
|
-
block,
|
|
183
|
-
top: lineTop,
|
|
184
|
-
bottom,
|
|
185
|
-
annotated: !!tplName && hasTemplate(tplName),
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
setEntries((prev) => (sameEntries(prev, next) ? prev : next));
|
|
189
|
-
|
|
190
|
-
// Page edges — Monaco's editor area extends to its container's
|
|
191
|
-
// box edges; treat the whole editor as the "page".
|
|
192
|
-
const left = monacoRect.left - wrapperRect.left;
|
|
193
|
-
const right = monacoRect.right - wrapperRect.left;
|
|
194
|
-
setPageEdges((prev) =>
|
|
195
|
-
prev != null && Math.abs(prev.left - left) < 0.5 && Math.abs(prev.right - right) < 0.5
|
|
196
|
-
? prev
|
|
197
|
-
: { left, right },
|
|
198
|
-
);
|
|
199
|
-
});
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
recompute();
|
|
203
|
-
const scrollSub = monacoEditor.onDidScrollChange(recompute);
|
|
204
|
-
const contentSub = monacoEditor.onDidChangeModelContent(recompute);
|
|
205
|
-
const layoutSub = monacoEditor.onDidLayoutChange(recompute);
|
|
206
|
-
const ro = new ResizeObserver(recompute);
|
|
207
|
-
const monacoRoot = monacoEditor.getDomNode();
|
|
208
|
-
if (monacoRoot) ro.observe(monacoRoot);
|
|
209
|
-
window.addEventListener('resize', recompute);
|
|
210
|
-
const settle = window.setTimeout(recompute, 250);
|
|
211
|
-
|
|
212
|
-
return () => {
|
|
213
|
-
cancelAnimationFrame(raf);
|
|
214
|
-
window.clearTimeout(settle);
|
|
215
|
-
scrollSub.dispose();
|
|
216
|
-
contentSub.dispose();
|
|
217
|
-
layoutSub.dispose();
|
|
218
|
-
ro.disconnect();
|
|
219
|
-
window.removeEventListener('resize', recompute);
|
|
220
|
-
};
|
|
221
|
-
}, [activeView, monacoEditor, flatBlocks, refInsideWrapper]);
|
|
222
|
-
|
|
223
|
-
// Reset edges when the active view changes — the previous view's
|
|
224
|
-
// measurements don't carry over to the next.
|
|
225
|
-
useEffect(() => {
|
|
226
|
-
setEntries([]);
|
|
227
|
-
setPageEdges(null);
|
|
228
|
-
}, [activeView]);
|
|
229
|
-
|
|
230
|
-
// ── scrollToBlock ──────────────────────────────────────────────────
|
|
231
|
-
|
|
232
|
-
const scrollToBlock = useCallback(
|
|
233
|
-
(block: Block) => {
|
|
234
|
-
if (activeView === 'wysiwyg') {
|
|
235
|
-
const node = refInsideWrapper.current;
|
|
236
|
-
const wrapper = node ? findWrapper(node) : null;
|
|
237
|
-
const wysiwygContainer = wrapper?.querySelector<HTMLElement>('.squisq-wysiwyg-container');
|
|
238
|
-
if (!wysiwygContainer) return;
|
|
239
|
-
const headings = wysiwygContainer.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6');
|
|
240
|
-
const index = flatBlocks.findIndex((b) => b.id === block.id);
|
|
241
|
-
if (index < 0 || index >= headings.length) return;
|
|
242
|
-
headings[index].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
243
|
-
if (tiptapEditor) {
|
|
244
|
-
try {
|
|
245
|
-
tiptapEditor.chain().focus().run();
|
|
246
|
-
} catch {
|
|
247
|
-
// ignore
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (activeView === 'raw' && monacoEditor) {
|
|
253
|
-
const line = block.sourceHeading?.position?.start.line;
|
|
254
|
-
if (typeof line !== 'number') return;
|
|
255
|
-
monacoEditor.revealLineInCenter(line);
|
|
256
|
-
monacoEditor.setPosition({ lineNumber: line, column: 1 });
|
|
257
|
-
monacoEditor.focus();
|
|
258
|
-
}
|
|
259
|
-
},
|
|
260
|
-
[activeView, flatBlocks, monacoEditor, refInsideWrapper, tiptapEditor],
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
entries,
|
|
265
|
-
pageEdges,
|
|
266
|
-
scrollToBlock,
|
|
267
|
-
ready: entries.length > 0 || pageEdges != null,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ── Helpers ────────────────────────────────────────────────────────
|
|
272
|
-
|
|
273
|
-
function findWrapper(node: HTMLElement): HTMLElement | null {
|
|
274
|
-
let cur: HTMLElement | null = node.parentElement;
|
|
275
|
-
while (cur) {
|
|
276
|
-
if (cur.classList.contains('squisq-editor-with-gutter')) return cur;
|
|
277
|
-
cur = cur.parentElement;
|
|
278
|
-
}
|
|
279
|
-
// Fallback: immediate parent (during transitional renames).
|
|
280
|
-
return node.parentElement;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function sameEntries(a: HeadingLayoutEntry[], b: HeadingLayoutEntry[]): boolean {
|
|
284
|
-
if (a.length !== b.length) return false;
|
|
285
|
-
for (let i = 0; i < a.length; i++) {
|
|
286
|
-
const x = a[i];
|
|
287
|
-
const y = b[i];
|
|
288
|
-
if (x.block.id !== y.block.id) return false;
|
|
289
|
-
if (x.annotated !== y.annotated) return false;
|
|
290
|
-
if (Math.abs(x.top - y.top) > 0.5) return false;
|
|
291
|
-
if (Math.abs(x.bottom - y.bottom) > 0.5) return false;
|
|
292
|
-
}
|
|
293
|
-
return true;
|
|
294
|
-
}
|
package/src/useMonacoLoader.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useMonacoLoader
|
|
3
|
-
*
|
|
4
|
-
* Idempotently dynamic-imports `monaco-editor` and points the
|
|
5
|
-
* `@monaco-editor/react` singleton loader at the bundled copy. Replaces
|
|
6
|
-
* the historical top-of-module `import * as monaco from 'monaco-editor';
|
|
7
|
-
* loader.config({ monaco })` pattern, which forced every consumer of
|
|
8
|
-
* `@bendyline/squisq-editor-react` — including ones that only import
|
|
9
|
-
* `JsonEditor` or a type — to drag in monaco's ~9MB worth of language
|
|
10
|
-
* services and workers at module evaluation time.
|
|
11
|
-
*
|
|
12
|
-
* Hosts that want the smallest possible bundle can keep aliasing
|
|
13
|
-
* `monaco-editor` to a slim entry as before; the behavior is identical
|
|
14
|
-
* once the dynamic import settles.
|
|
15
|
-
*
|
|
16
|
-
* The promise is cached at module scope so the first subscriber
|
|
17
|
-
* anywhere in the app pays the import cost and every later subscriber
|
|
18
|
-
* reuses the same settled value.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { useEffect, useState } from 'react';
|
|
22
|
-
import { loader } from '@monaco-editor/react';
|
|
23
|
-
|
|
24
|
-
/** In-flight (or settled) dynamic import shared across all callers. */
|
|
25
|
-
let monacoPromise: Promise<typeof import('monaco-editor')> | null = null;
|
|
26
|
-
|
|
27
|
-
/** Settled namespace once the promise resolves — read synchronously by mount-time consumers. */
|
|
28
|
-
let monacoNamespace: typeof import('monaco-editor') | null = null;
|
|
29
|
-
|
|
30
|
-
export interface UseMonacoLoaderResult {
|
|
31
|
-
/** The monaco namespace once loaded, or `null` while the import is in flight. */
|
|
32
|
-
monaco: typeof import('monaco-editor') | null;
|
|
33
|
-
/** Flips to `true` after the import settles. Gate `<Editor>` / `<DiffEditor>` renders on this. */
|
|
34
|
-
ready: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Subscribe to the lazy-loaded monaco namespace. The first caller
|
|
39
|
-
* triggers `import('monaco-editor')` and configures the
|
|
40
|
-
* `@monaco-editor/react` loader; subsequent callers receive the same
|
|
41
|
-
* cached value.
|
|
42
|
-
*/
|
|
43
|
-
export function useMonacoLoader(): UseMonacoLoaderResult {
|
|
44
|
-
const [state, setState] = useState<UseMonacoLoaderResult>(() => ({
|
|
45
|
-
monaco: monacoNamespace,
|
|
46
|
-
ready: monacoNamespace !== null,
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
if (state.ready) return;
|
|
51
|
-
if (!monacoPromise) {
|
|
52
|
-
// Load Monaco through squisq's canonical `./monaco` entry (see that
|
|
53
|
-
// file for exactly which slice of Monaco ships and why). Centralizing
|
|
54
|
-
// the choice there means the editor and any downstream app that imports
|
|
55
|
-
// `@bendyline/squisq-editor-react/monaco` share ONE instance and feature
|
|
56
|
-
// set — no host aliasing, no hand-rolled slim entry that silently drops
|
|
57
|
-
// the suggest widget. The import is dynamic so consumers that only use
|
|
58
|
-
// types or `JsonEditor` never pull Monaco into their graph.
|
|
59
|
-
//
|
|
60
|
-
// `./monaco` re-exports `editor.api` (the only barrel with a `.d.ts`)
|
|
61
|
-
// after side-effect-importing `editor.main.js`; the `as unknown as` cast
|
|
62
|
-
// pins the settled module to the full monaco namespace.
|
|
63
|
-
monacoPromise = (
|
|
64
|
-
import('./monaco.js') as unknown as Promise<typeof import('monaco-editor')>
|
|
65
|
-
).then((m) => {
|
|
66
|
-
loader.config({ monaco: m });
|
|
67
|
-
monacoNamespace = m;
|
|
68
|
-
return m;
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
let cancelled = false;
|
|
72
|
-
void monacoPromise.then((m) => {
|
|
73
|
-
if (cancelled) return;
|
|
74
|
-
setState({ monaco: m, ready: true });
|
|
75
|
-
});
|
|
76
|
-
return () => {
|
|
77
|
-
cancelled = true;
|
|
78
|
-
};
|
|
79
|
-
}, [state.ready]);
|
|
80
|
-
|
|
81
|
-
return state;
|
|
82
|
-
}
|