@kiberon-labs/behave-graph-flow 2.0.0 → 3.0.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/.storybook/manager.ts +6 -0
- package/.storybook/preview.ts +49 -1
- package/.storybook/styles.css +9 -3
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +368 -0
- package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
- package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
- package/dist/index.css +36 -33
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1865 -550
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14357 -11221
- package/dist/index.js.map +1 -1
- package/dist/noteImpl-KkrrWgJd.js +242 -0
- package/dist/noteImpl-KkrrWgJd.js.map +1 -0
- package/dist/styles.module-CvmpDkZj.css +3 -0
- package/dist/styles.module-CvmpDkZj.css.map +1 -0
- package/dist/styles.module-DZxg8aW9.js +271 -0
- package/dist/styles.module-DZxg8aW9.js.map +1 -0
- package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
- package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
- package/docs/protocol.md +43 -20
- package/package.json +5 -9
- package/src/components/FloatingToolbar/index.module.css +5 -13
- package/src/components/FloatingToolbar/index.tsx +9 -9
- package/src/components/Flow.tsx +34 -23
- package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
- package/src/components/contextMenus/NodePicker.module.css +13 -13
- package/src/components/contextMenus/edge.tsx +9 -95
- package/src/components/contextMenus/node.tsx +9 -149
- package/src/components/contextMenus/selection.tsx +5 -71
- package/src/components/controls/any/AnyControlImpl.tsx +14 -0
- package/src/components/controls/any/index.tsx +13 -2
- package/src/components/edges/index.tsx +75 -69
- package/src/components/layoutController/index.module.css +3 -0
- package/src/components/layoutController/index.tsx +24 -1
- package/src/components/layoutController/utils.ts +46 -3
- package/src/components/menubar/defaults.tsx +55 -19
- package/src/components/menubar/menuItem.module.css +18 -3
- package/src/components/menubar/menuItem.tsx +34 -1
- package/src/components/nodes/behave/NodeContainer.module.css +26 -25
- package/src/components/nodes/group/index.tsx +3 -3
- package/src/components/nodes/wrapper/styles.module.css +6 -32
- package/src/components/panels/alignment/index.module.css +0 -10
- package/src/components/panels/alignment/index.tsx +4 -4
- package/src/components/panels/base/styles.module.css +2 -2
- package/src/components/panels/common/PanelHeader.module.css +24 -0
- package/src/components/panels/common/PanelHeader.tsx +22 -0
- package/src/components/panels/common/SectionTitle.module.css +13 -0
- package/src/components/panels/common/SectionTitle.tsx +10 -0
- package/src/components/panels/events/EditEventPanel.tsx +14 -5
- package/src/components/panels/events/ManageEventsPanel.tsx +11 -8
- package/src/components/panels/events/styles.module.css +6 -64
- package/src/components/panels/graphProperties/index.tsx +125 -0
- package/src/components/panels/history/index.tsx +2 -2
- package/src/components/panels/history/styles.module.css +0 -9
- package/src/components/panels/keymaps/index.module.css +3 -13
- package/src/components/panels/keymaps/index.tsx +1 -2
- package/src/components/panels/layers/index.tsx +20 -15
- package/src/components/panels/layers/styles.module.css +9 -12
- package/src/components/panels/legend/index.tsx +1 -1
- package/src/components/panels/logs/index.module.css +25 -19
- package/src/components/panels/logs/index.tsx +7 -7
- package/src/components/panels/nodeInputs/InputsGroup.tsx +1 -0
- package/src/components/panels/nodeInputs/NodeSettings.tsx +2 -2
- package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +1 -1
- package/src/components/panels/nodeInputs/OutputsGroup.tsx +2 -12
- package/src/components/panels/nodeInputs/index.module.css +99 -75
- package/src/components/panels/nodeInputs/index.tsx +21 -11
- package/src/components/panels/nodeInputs/useNodeHandlers.ts +2 -2
- package/src/components/panels/nodeInputs/useNodeInputsData.ts +23 -43
- package/src/components/panels/nodePicker/index.tsx +8 -8
- package/src/components/panels/panel/index.module.css +7 -7
- package/src/components/panels/search/index.module.css +0 -50
- package/src/components/panels/search/index.tsx +2 -2
- package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
- package/src/components/panels/systemSettings/index.tsx +221 -176
- package/src/components/panels/systemSettings/styles.module.css +135 -8
- package/src/components/panels/traces/GridLines.tsx +1 -1
- package/src/components/panels/traces/TimeGrid.tsx +3 -3
- package/src/components/panels/traces/TraceLane.tsx +1 -1
- package/src/components/panels/traces/index.module.css +1 -8
- package/src/components/panels/traces/index.tsx +8 -4
- package/src/components/panels/traces/useDerivedSpans.ts +241 -146
- package/src/components/panels/traces/utils.ts +8 -0
- package/src/components/panels/variables/CreateVariableScreen.tsx +3 -3
- package/src/components/panels/variables/ManageVariablesScreen.tsx +12 -9
- package/src/components/panels/variables/index.tsx +2 -2
- package/src/components/panels/variables/styles.module.css +4 -91
- package/src/components/primitives/icon.module.css +4 -4
- package/src/components/sockets/input/index.tsx +9 -2
- package/src/components/sockets/input/styles.module.css +2 -3
- package/src/components/sockets/output/index.tsx +10 -3
- package/src/components/sockets/output/styles.module.css +1 -6
- package/src/css/notes.css +135 -0
- package/src/css/prosemirror.css +3 -3
- package/src/css/rc-dock.css +143 -43
- package/src/css/rc-menu.css +56 -55
- package/src/css/themes/kiberon.css +127 -0
- package/src/css/vars.css +197 -13
- package/src/css/vscode-elements.css +124 -0
- package/src/generators/CallSubgraphGenerator.tsx +136 -0
- package/src/generators/CustomEventOnTriggeredGenerator.tsx +2 -2
- package/src/generators/GraphBoundaryGenerator.module.css +32 -0
- package/src/generators/GraphBoundaryGenerator.tsx +193 -0
- package/src/generators/SequenceGenerator.tsx +2 -2
- package/src/generators/SwitchOnIntegerGenerator.tsx +2 -2
- package/src/generators/SwitchOnStringGenerator.tsx +2 -2
- package/src/generators/callSubgraphSync.ts +126 -0
- package/src/generators/registerDefaultGenerators.ts +21 -0
- package/src/generators/registerDefaults.ts +26 -0
- package/src/hooks/useBehaveGraphFlow.ts +2 -2
- package/src/hooks/useFlowHandlers.ts +47 -9
- package/src/hooks/useWasdPan.ts +26 -4
- package/src/index.css +4 -16
- package/src/index.ts +17 -0
- package/src/manifest/contributionRegistry.ts +93 -0
- package/src/manifest/index.ts +4 -0
- package/src/manifest/loadManifest.ts +82 -0
- package/src/manifest/manifestPlugin.ts +29 -0
- package/src/manifest/passthroughValueType.ts +40 -0
- package/src/plugin/alignment/index.ts +22 -12
- package/src/plugin/autosave/controller.ts +366 -0
- package/src/plugin/autosave/index.tsx +114 -0
- package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
- package/src/plugin/autosave/panel/index.tsx +1 -0
- package/src/plugin/autosave/panel/styles.module.css +56 -0
- package/src/plugin/autosave/settings.ts +65 -0
- package/src/plugin/autosave/storage.ts +147 -0
- package/src/plugin/docs/index.tsx +2 -4
- package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
- package/src/plugin/docs/panel/index.tsx +15 -194
- package/src/plugin/docs/panel/styles.module.css +8 -8
- package/src/plugin/graphrunner/actions.ts +258 -185
- package/src/plugin/graphrunner/buttons.tsx +34 -26
- package/src/plugin/graphrunner/client.ts +4 -1
- package/src/plugin/graphrunner/index.tsx +29 -100
- package/src/plugin/graphrunner/panel.tsx +2 -2
- package/src/plugin/graphrunner/runController.ts +283 -0
- package/src/plugin/graphrunner/runner.ts +21 -192
- package/src/plugin/graphrunner/store.ts +14 -24
- package/src/plugin/graphrunner/styles.module.css +17 -57
- package/src/plugin/graphrunner/transport.ts +26 -0
- package/src/plugin/graphrunner/types.ts +21 -0
- package/src/plugin/graphrunner-local/execution-utils.ts +260 -80
- package/src/plugin/graphrunner-local/index.tsx +8 -2
- package/src/plugin/graphrunner-local/panel.tsx +131 -175
- package/src/plugin/graphrunner-local/styles.module.css +57 -76
- package/src/plugin/graphrunner-local/transport.ts +151 -184
- package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +2 -0
- package/src/plugin/graphrunner-webworker/index.tsx +4 -10
- package/src/plugin/graphrunner-webworker/store.ts +9 -0
- package/src/plugin/kitchen-sink/index.ts +38 -0
- package/src/{layout/dagre.tsx → plugin/layout/dagre.ts} +17 -5
- package/src/{layout → plugin/layout}/elk.ts +22 -6
- package/src/plugin/layout/index.ts +80 -0
- package/src/plugin/notes/FormatToolbar.tsx +200 -0
- package/src/plugin/notes/index.tsx +191 -0
- package/src/plugin/notes/nodeActions.ts +100 -0
- package/src/plugin/notes/note.tsx +20 -0
- package/src/plugin/notes/noteImpl.tsx +89 -0
- package/src/plugin/realtime/realtimeRunner.ts +58 -4
- package/src/specifics/CustomEventOnTriggeredSpecific.tsx +2 -2
- package/src/specifics/CustomEventTriggerSpecific.tsx +2 -2
- package/src/specifics/VariableGetSpecific.tsx +2 -2
- package/src/specifics/VariableSetSpecific.tsx +2 -2
- package/src/store/actions.tsx +5 -5
- package/src/store/commands.ts +278 -0
- package/src/store/contextMenu.ts +192 -0
- package/src/store/conversions.ts +47 -0
- package/src/store/flow.tsx +23 -38
- package/src/store/graphMeta.ts +39 -0
- package/src/store/hotKeys.tsx +301 -260
- package/src/store/layers.ts +3 -3
- package/src/store/registry.ts +12 -4
- package/src/store/selection.ts +3 -3
- package/src/store/settings.ts +82 -82
- package/src/store/settingsSchema.ts +210 -0
- package/src/store/tabs.ts +5 -1
- package/src/store/traces.ts +3 -3
- package/src/system/graph.ts +11 -14
- package/src/system/graphSession.ts +172 -0
- package/src/system/index.ts +3 -0
- package/src/system/notifications.ts +13 -0
- package/src/system/persistence.ts +82 -0
- package/src/system/plugin.ts +28 -0
- package/src/system/provider.tsx +64 -0
- package/src/system/system.ts +518 -88
- package/src/system/tabLoader.tsx +70 -32
- package/src/system/undoRedo.ts +1 -1
- package/src/transformers/Uigraph.ts +5 -4
- package/src/transformers/contract.ts +87 -0
- package/src/transformers/flowToBehave.ts +13 -5
- package/src/types/nodes.ts +8 -3
- package/src/types.ts +2 -0
- package/src/util/autoConvert.ts +200 -0
- package/src/util/isValidConnection.ts +23 -2
- package/stories/defaults/defaultStoryProvider.tsx +17 -14
- package/stories/defaults/systemGenerator.ts +6 -1
- package/stories/{components/nodes/comment.stories.tsx → plugins/notes.stories.tsx} +24 -30
- package/tests/autoConvert.test.ts +329 -0
- package/tests/autosavePlugin.test.ts +204 -0
- package/tests/callSubgraphSync.test.ts +148 -0
- package/tests/commandRegistry.test.ts +137 -0
- package/tests/contract.test.ts +51 -0
- package/tests/contractSerialize.test.ts +62 -0
- package/tests/deriveSpans.test.ts +71 -0
- package/tests/flowToBehave.test.ts +2 -1
- package/tests/hotkeys.test.ts +79 -0
- package/tests/keepAliveLifecycle.test.ts +167 -0
- package/tests/loadManifest.test.ts +113 -0
- package/tests/noteMarkdown.test.ts +65 -0
- package/tests/notesPlugin.test.ts +162 -0
- package/tests/persistence.test.ts +51 -0
- package/tests/saveLoad.test.ts +7 -6
- package/tests/settings.test.ts +178 -0
- package/tests/traceStore.test.ts +46 -0
- package/tests/visual/README.md +2 -2
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
- package/tests/visual/panels.visual.test.tsx +3 -3
- package/tests/wasdPan.test.ts +71 -0
- package/vitest.config.ts +1 -1
- package/vitest.visual.config.ts +7 -0
- package/.storybook/vscode.css +0 -814
- package/src/components/nodes/comment/FormatToolbar.tsx +0 -118
- package/src/components/nodes/comment/comment.tsx +0 -103
- package/src/components/nodes/comment/styles.module.css +0 -150
- package/src/components/panels/conversation/index.module.css +0 -151
- package/src/components/panels/conversation/index.tsx +0 -162
- package/src/components/panels/events/CustomEventsEditor.tsx +0 -384
- package/src/css/vscode.css +0 -13
- package/src/hooks/useDetachNodes.ts +0 -39
- package/src/plugin/graphrunner-webworker/types.ts +0 -17
- package/src/specifics/registerDefaultSpecifics.ts +0 -5
- package/src/store/chat.ts +0 -73
- package/src/store/graphRunnerClient.ts +0 -110
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { plugin } from '@/system/plugin';
|
|
2
|
+
import type { System } from '@/system/system';
|
|
3
|
+
import { applyDagreLayout } from './dagre';
|
|
4
|
+
import { applyElkLayout } from './elk';
|
|
5
|
+
|
|
6
|
+
export * from './dagre';
|
|
7
|
+
export * from './elk';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Available auto-layout engines. `Dagre` is a small, synchronous layered layout;
|
|
11
|
+
* the `Elk - *` options use elkjs (~1.4 MB, loaded lazily on first use) for
|
|
12
|
+
* higher-quality layouts.
|
|
13
|
+
*/
|
|
14
|
+
export const LAYOUT_TYPE = {
|
|
15
|
+
dagre: 'Dagre',
|
|
16
|
+
elkForce: 'Elk - Force',
|
|
17
|
+
elkRect: 'Elk - Rect',
|
|
18
|
+
elkLayered: 'Elk - Layered'
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export type LayoutType = (typeof LAYOUT_TYPE)[keyof typeof LAYOUT_TYPE];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run the currently-selected auto-layout engine against the focused graph.
|
|
25
|
+
* Reads the `layoutType` setting (registered by this plugin) to pick the engine.
|
|
26
|
+
*/
|
|
27
|
+
export const applyAutoLayout = (system: System): void => {
|
|
28
|
+
switch (system.getSetting<LayoutType>('layoutType')) {
|
|
29
|
+
case LAYOUT_TYPE.dagre:
|
|
30
|
+
void applyDagreLayout(system);
|
|
31
|
+
break;
|
|
32
|
+
case LAYOUT_TYPE.elkLayered:
|
|
33
|
+
void applyElkLayout(system, 'org.eclipse.elk.layered');
|
|
34
|
+
break;
|
|
35
|
+
case LAYOUT_TYPE.elkForce:
|
|
36
|
+
void applyElkLayout(system, 'org.eclipse.elk.force');
|
|
37
|
+
break;
|
|
38
|
+
case LAYOUT_TYPE.elkRect:
|
|
39
|
+
void applyElkLayout(system, 'org.eclipse.elk.rectpacking');
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Adds graph auto-layout (Dagre + ELK) to the editor. elkjs and dagre are heavy
|
|
46
|
+
* dependencies that not every host needs, so they live here rather than in the
|
|
47
|
+
* core editor — register this plugin (directly or via the kitchen-sink plugin)
|
|
48
|
+
* to opt in.
|
|
49
|
+
*
|
|
50
|
+
* The plugin:
|
|
51
|
+
* - registers the `layoutType` setting (the engine picker in the Settings panel);
|
|
52
|
+
* - registers the `editor.autoLayout` command that the "Auto Layout" hotkey and
|
|
53
|
+
* menu dispatch to.
|
|
54
|
+
*
|
|
55
|
+
* Without it, `editor.autoLayout` is simply unregistered (the hotkey no-ops).
|
|
56
|
+
*/
|
|
57
|
+
export const layoutPlugin = plugin(
|
|
58
|
+
(system: System) => {
|
|
59
|
+
system.registerSetting({
|
|
60
|
+
key: 'layoutType',
|
|
61
|
+
section: 'Layout',
|
|
62
|
+
type: 'enum',
|
|
63
|
+
default: LAYOUT_TYPE.dagre,
|
|
64
|
+
options: Object.values(LAYOUT_TYPE).map((value) => ({
|
|
65
|
+
value,
|
|
66
|
+
label: value
|
|
67
|
+
})),
|
|
68
|
+
title: 'Layout Type',
|
|
69
|
+
description:
|
|
70
|
+
'Select the type of layout engine to use in the graph editor.'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
system.commandStore.getState().register({
|
|
74
|
+
id: 'editor.autoLayout',
|
|
75
|
+
title: 'Auto Layout',
|
|
76
|
+
run: (ctx) => applyAutoLayout(ctx.editor)
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
{ name: 'layout' }
|
|
80
|
+
);
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useEditorState, type Editor } from '@tiptap/react';
|
|
3
|
+
import { VscodeButton, VscodeTextfield } from '@vscode-elements/react-elements';
|
|
4
|
+
import {
|
|
5
|
+
Bold,
|
|
6
|
+
Code,
|
|
7
|
+
Italic,
|
|
8
|
+
List,
|
|
9
|
+
NumberedListLeft,
|
|
10
|
+
Quote,
|
|
11
|
+
Strikethrough,
|
|
12
|
+
Youtube
|
|
13
|
+
} from 'iconoir-react';
|
|
14
|
+
|
|
15
|
+
interface FormatToolbarProps {
|
|
16
|
+
editor: Editor;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Formatting actions for the note editor. Buttons are the same VscodeButton
|
|
21
|
+
* component the FloatingToolbar uses, so the note toolbar stays visually
|
|
22
|
+
* consistent with the rest of the editor chrome; the active format renders as
|
|
23
|
+
* a primary (accent) button.
|
|
24
|
+
*
|
|
25
|
+
* The button row swallows mousedown so clicking a formatting button keeps the
|
|
26
|
+
* editor's text selection instead of blurring it. The video row must NOT: its
|
|
27
|
+
* text field needs focus. Video embedding uses an inline URL field rather than
|
|
28
|
+
* `window.prompt`, which is blocked in VS Code webviews.
|
|
29
|
+
*/
|
|
30
|
+
export const FormatToolbar = ({ editor }: FormatToolbarProps) => {
|
|
31
|
+
// null = embed row closed; a string = the URL being typed.
|
|
32
|
+
const [videoUrl, setVideoUrl] = useState<string | null>(null);
|
|
33
|
+
|
|
34
|
+
const active = useEditorState({
|
|
35
|
+
editor,
|
|
36
|
+
selector: ({ editor }) => ({
|
|
37
|
+
bold: editor.isActive('bold'),
|
|
38
|
+
italic: editor.isActive('italic'),
|
|
39
|
+
strike: editor.isActive('strike'),
|
|
40
|
+
code: editor.isActive('code'),
|
|
41
|
+
h1: editor.isActive('heading', { level: 1 }),
|
|
42
|
+
h2: editor.isActive('heading', { level: 2 }),
|
|
43
|
+
h3: editor.isActive('heading', { level: 3 }),
|
|
44
|
+
bulletList: editor.isActive('bulletList'),
|
|
45
|
+
orderedList: editor.isActive('orderedList'),
|
|
46
|
+
codeBlock: editor.isActive('codeBlock'),
|
|
47
|
+
blockquote: editor.isActive('blockquote')
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const embedVideo = () => {
|
|
52
|
+
const src = videoUrl?.trim();
|
|
53
|
+
if (src) {
|
|
54
|
+
editor.chain().focus().setYoutubeVideo({ src }).run();
|
|
55
|
+
}
|
|
56
|
+
setVideoUrl(null);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="notes-toolbar nodrag nopan">
|
|
61
|
+
<div
|
|
62
|
+
className="notes-toolbar__row"
|
|
63
|
+
onMouseDown={(e) => e.preventDefault()}
|
|
64
|
+
>
|
|
65
|
+
<VscodeButton
|
|
66
|
+
secondary={!active.bold}
|
|
67
|
+
iconOnly
|
|
68
|
+
title="Bold"
|
|
69
|
+
onClick={() => editor.chain().focus().toggleBold().run()}
|
|
70
|
+
>
|
|
71
|
+
<Bold />
|
|
72
|
+
</VscodeButton>
|
|
73
|
+
<VscodeButton
|
|
74
|
+
secondary={!active.italic}
|
|
75
|
+
iconOnly
|
|
76
|
+
title="Italic"
|
|
77
|
+
onClick={() => editor.chain().focus().toggleItalic().run()}
|
|
78
|
+
>
|
|
79
|
+
<Italic />
|
|
80
|
+
</VscodeButton>
|
|
81
|
+
<VscodeButton
|
|
82
|
+
secondary={!active.strike}
|
|
83
|
+
iconOnly
|
|
84
|
+
title="Strikethrough"
|
|
85
|
+
onClick={() => editor.chain().focus().toggleStrike().run()}
|
|
86
|
+
>
|
|
87
|
+
<Strikethrough />
|
|
88
|
+
</VscodeButton>
|
|
89
|
+
<VscodeButton
|
|
90
|
+
secondary={!active.code}
|
|
91
|
+
iconOnly
|
|
92
|
+
title="Code"
|
|
93
|
+
onClick={() => editor.chain().focus().toggleCode().run()}
|
|
94
|
+
>
|
|
95
|
+
<Code />
|
|
96
|
+
</VscodeButton>
|
|
97
|
+
<div className="notes-toolbar__separator" />
|
|
98
|
+
<VscodeButton
|
|
99
|
+
secondary={!active.h1}
|
|
100
|
+
title="Heading 1"
|
|
101
|
+
onClick={() =>
|
|
102
|
+
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
|
103
|
+
}
|
|
104
|
+
>
|
|
105
|
+
H1
|
|
106
|
+
</VscodeButton>
|
|
107
|
+
<VscodeButton
|
|
108
|
+
secondary={!active.h2}
|
|
109
|
+
title="Heading 2"
|
|
110
|
+
onClick={() =>
|
|
111
|
+
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
H2
|
|
115
|
+
</VscodeButton>
|
|
116
|
+
<VscodeButton
|
|
117
|
+
secondary={!active.h3}
|
|
118
|
+
title="Heading 3"
|
|
119
|
+
onClick={() =>
|
|
120
|
+
editor.chain().focus().toggleHeading({ level: 3 }).run()
|
|
121
|
+
}
|
|
122
|
+
>
|
|
123
|
+
H3
|
|
124
|
+
</VscodeButton>
|
|
125
|
+
<div className="notes-toolbar__separator" />
|
|
126
|
+
<VscodeButton
|
|
127
|
+
secondary={!active.bulletList}
|
|
128
|
+
iconOnly
|
|
129
|
+
title="Bullet List"
|
|
130
|
+
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
|
131
|
+
>
|
|
132
|
+
<List />
|
|
133
|
+
</VscodeButton>
|
|
134
|
+
<VscodeButton
|
|
135
|
+
secondary={!active.orderedList}
|
|
136
|
+
iconOnly
|
|
137
|
+
title="Numbered List"
|
|
138
|
+
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
|
139
|
+
>
|
|
140
|
+
<NumberedListLeft />
|
|
141
|
+
</VscodeButton>
|
|
142
|
+
<VscodeButton
|
|
143
|
+
secondary={!active.codeBlock}
|
|
144
|
+
iconOnly
|
|
145
|
+
title="Code Block"
|
|
146
|
+
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
|
147
|
+
>
|
|
148
|
+
<Code />
|
|
149
|
+
</VscodeButton>
|
|
150
|
+
<VscodeButton
|
|
151
|
+
secondary={!active.blockquote}
|
|
152
|
+
iconOnly
|
|
153
|
+
title="Quote"
|
|
154
|
+
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
|
155
|
+
>
|
|
156
|
+
<Quote />
|
|
157
|
+
</VscodeButton>
|
|
158
|
+
<div className="notes-toolbar__separator" />
|
|
159
|
+
<VscodeButton
|
|
160
|
+
secondary={videoUrl === null}
|
|
161
|
+
iconOnly
|
|
162
|
+
title="Embed YouTube Video"
|
|
163
|
+
onClick={() => setVideoUrl(videoUrl === null ? '' : null)}
|
|
164
|
+
>
|
|
165
|
+
<Youtube />
|
|
166
|
+
</VscodeButton>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{videoUrl !== null && (
|
|
170
|
+
<div
|
|
171
|
+
className="notes-toolbar__embed"
|
|
172
|
+
// Keep keystrokes (e.g. Backspace) from reaching React Flow, which
|
|
173
|
+
// would delete the selected note while the URL is being typed.
|
|
174
|
+
onKeyDown={(e) => {
|
|
175
|
+
e.stopPropagation();
|
|
176
|
+
if (e.key === 'Enter') embedVideo();
|
|
177
|
+
if (e.key === 'Escape') setVideoUrl(null);
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<VscodeTextfield
|
|
181
|
+
type="text"
|
|
182
|
+
placeholder="YouTube video URL"
|
|
183
|
+
value={videoUrl}
|
|
184
|
+
onInput={(e: any) => setVideoUrl(e.target.value)}
|
|
185
|
+
/>
|
|
186
|
+
<VscodeButton title="Embed" onClick={embedVideo}>
|
|
187
|
+
Embed
|
|
188
|
+
</VscodeButton>
|
|
189
|
+
<VscodeButton
|
|
190
|
+
secondary
|
|
191
|
+
title="Cancel"
|
|
192
|
+
onClick={() => setVideoUrl(null)}
|
|
193
|
+
>
|
|
194
|
+
Cancel
|
|
195
|
+
</VscodeButton>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Notes } from 'iconoir-react';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import { plugin } from '@/system/plugin';
|
|
4
|
+
import type { System } from '@/system/system';
|
|
5
|
+
import type { CommandContext } from '@/store/commands';
|
|
6
|
+
import type { INoteNode } from '@/types/nodes';
|
|
7
|
+
import { NoteNode } from './note';
|
|
8
|
+
import {
|
|
9
|
+
NOTE_NODE_TYPE,
|
|
10
|
+
LEGACY_COMMENT_NODE_TYPE,
|
|
11
|
+
noteAt,
|
|
12
|
+
duplicateNote,
|
|
13
|
+
deleteNote,
|
|
14
|
+
reorderNote
|
|
15
|
+
} from './nodeActions';
|
|
16
|
+
|
|
17
|
+
export { NoteNode } from './note';
|
|
18
|
+
export * from './nodeActions';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a note node on the graph the command targets. Placed at the given
|
|
22
|
+
* position (e.g. from a context menu) or, from surfaces without one (toolbar,
|
|
23
|
+
* hotkey), at the centre of the current viewport. Undo-aware.
|
|
24
|
+
*/
|
|
25
|
+
const addNote = (ctx: CommandContext): void => {
|
|
26
|
+
const reactflow = ctx.session.refStore.getState().getRef('reactflow');
|
|
27
|
+
const position = ctx.position ??
|
|
28
|
+
reactflow?.screenToFlowPosition({
|
|
29
|
+
x: window.innerWidth / 2,
|
|
30
|
+
y: window.innerHeight / 2
|
|
31
|
+
}) ?? { x: 0, y: 0 };
|
|
32
|
+
|
|
33
|
+
const note: INoteNode = {
|
|
34
|
+
id: uuidv4(),
|
|
35
|
+
type: NOTE_NODE_TYPE,
|
|
36
|
+
position,
|
|
37
|
+
selected: true,
|
|
38
|
+
// Only the header drags the node; the body is a text surface.
|
|
39
|
+
dragHandle: '.notes-node__header',
|
|
40
|
+
style: { width: 280, height: 180 },
|
|
41
|
+
data: { text: '' }
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
ctx.session.undoManager.execute({
|
|
45
|
+
name: 'Add note',
|
|
46
|
+
execute: () => {
|
|
47
|
+
ctx.session.nodeStore.getState().addNode(note);
|
|
48
|
+
},
|
|
49
|
+
undo: () => {
|
|
50
|
+
ctx.session.nodeStore
|
|
51
|
+
.getState()
|
|
52
|
+
.setNodes((existing) => existing.filter((n) => n.id !== note.id));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Adds markdown note nodes to the editor. Notes are purely presentational:
|
|
59
|
+
* they never appear in the compiled behave graph (only behave nodes do), but
|
|
60
|
+
* they persist with the UI graph JSON like any other canvas node.
|
|
61
|
+
*
|
|
62
|
+
* The note editor embeds tiptap/prosemirror, a heavy dependency most hosts do
|
|
63
|
+
* not need, so notes live here rather than in the core editor — register this
|
|
64
|
+
* plugin (directly or via the kitchen-sink plugin) to opt in.
|
|
65
|
+
*
|
|
66
|
+
* The plugin:
|
|
67
|
+
* - registers the `noteNode` component on every graph session (plus the legacy
|
|
68
|
+
* `commentNode` alias for graphs saved before notes moved here);
|
|
69
|
+
* - registers the `notes.addNote` command, an "Add Note" button on the
|
|
70
|
+
* floating toolbar, and a `Shift+N` hotkey that dispatch it;
|
|
71
|
+
* - registers note-specific node commands + context-menu items
|
|
72
|
+
* (duplicate / delete / bring to front / send to back).
|
|
73
|
+
*/
|
|
74
|
+
export const notesPlugin = plugin(
|
|
75
|
+
(system: System) => {
|
|
76
|
+
system.registerSessionExtension((session) => {
|
|
77
|
+
const { registerNodeType } = session.flowStore.getState();
|
|
78
|
+
registerNodeType(NOTE_NODE_TYPE, NoteNode);
|
|
79
|
+
registerNodeType(LEGACY_COMMENT_NODE_TYPE, NoteNode);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const commands = system.commandStore.getState();
|
|
83
|
+
commands.register({
|
|
84
|
+
id: 'notes.addNote',
|
|
85
|
+
title: 'Add Note',
|
|
86
|
+
run: addNote
|
|
87
|
+
});
|
|
88
|
+
commands.register({
|
|
89
|
+
id: 'note.duplicate',
|
|
90
|
+
title: 'Duplicate Note',
|
|
91
|
+
run: (ctx) => {
|
|
92
|
+
const note = noteAt(ctx);
|
|
93
|
+
if (note) duplicateNote(ctx.session, note);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
commands.register({
|
|
97
|
+
id: 'note.delete',
|
|
98
|
+
title: 'Delete Note',
|
|
99
|
+
run: (ctx) => {
|
|
100
|
+
const note = noteAt(ctx);
|
|
101
|
+
if (note) deleteNote(ctx.session, note);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
commands.register({
|
|
105
|
+
id: 'note.bringToFront',
|
|
106
|
+
title: 'Bring Note to Front',
|
|
107
|
+
run: (ctx) => {
|
|
108
|
+
const note = noteAt(ctx);
|
|
109
|
+
if (note) reorderNote(ctx.session, note, 'front');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
commands.register({
|
|
113
|
+
id: 'note.sendToBack',
|
|
114
|
+
title: 'Send Note to Back',
|
|
115
|
+
run: (ctx) => {
|
|
116
|
+
const note = noteAt(ctx);
|
|
117
|
+
if (note) reorderNote(ctx.session, note, 'back');
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Notes get their own node context menu; the behave items (trace, pin,
|
|
122
|
+
// ...) are guarded by `when` in the core defaults and stay hidden here.
|
|
123
|
+
const menu = system.contextMenuStore.getState();
|
|
124
|
+
const noteOnly = (ctx: CommandContext) => Boolean(noteAt(ctx));
|
|
125
|
+
menu.register({
|
|
126
|
+
id: 'note.duplicate',
|
|
127
|
+
target: 'node',
|
|
128
|
+
label: 'Duplicate',
|
|
129
|
+
order: 10,
|
|
130
|
+
group: 'note',
|
|
131
|
+
when: noteOnly,
|
|
132
|
+
commandId: 'note.duplicate'
|
|
133
|
+
});
|
|
134
|
+
menu.register({
|
|
135
|
+
id: 'note.bringToFront',
|
|
136
|
+
target: 'node',
|
|
137
|
+
label: 'Bring to Front',
|
|
138
|
+
order: 20,
|
|
139
|
+
group: 'note-order',
|
|
140
|
+
when: noteOnly,
|
|
141
|
+
commandId: 'note.bringToFront'
|
|
142
|
+
});
|
|
143
|
+
menu.register({
|
|
144
|
+
id: 'note.sendToBack',
|
|
145
|
+
target: 'node',
|
|
146
|
+
label: 'Send to Back',
|
|
147
|
+
order: 21,
|
|
148
|
+
group: 'note-order',
|
|
149
|
+
when: noteOnly,
|
|
150
|
+
commandId: 'note.sendToBack'
|
|
151
|
+
});
|
|
152
|
+
menu.register({
|
|
153
|
+
id: 'note.delete',
|
|
154
|
+
target: 'node',
|
|
155
|
+
label: 'Delete',
|
|
156
|
+
order: 30,
|
|
157
|
+
group: 'note-danger',
|
|
158
|
+
when: noteOnly,
|
|
159
|
+
commandId: 'note.delete'
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const dispatchAddNote = () => {
|
|
163
|
+
const session = system.session;
|
|
164
|
+
if (!session) return;
|
|
165
|
+
void system.commandStore
|
|
166
|
+
.getState()
|
|
167
|
+
.run('notes.addNote', { editor: system, session });
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
system.toolbarStore.getState().addGroup({
|
|
171
|
+
id: 'notes',
|
|
172
|
+
label: 'Notes',
|
|
173
|
+
buttons: [
|
|
174
|
+
{
|
|
175
|
+
id: 'notes.addNote',
|
|
176
|
+
icon: <Notes />,
|
|
177
|
+
label: 'Add Note',
|
|
178
|
+
onClick: dispatchAddNote
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
system.hotKeyStore.getState().register({
|
|
184
|
+
action: 'ADD_NOTE',
|
|
185
|
+
description: 'Add a markdown note to the graph',
|
|
186
|
+
trigger: 'shift+n',
|
|
187
|
+
handler: dispatchAddNote
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
{ name: 'notes' }
|
|
191
|
+
);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
import type { Node } from 'reactflow';
|
|
3
|
+
import type { CommandContext } from '@/store/commands';
|
|
4
|
+
import type { GraphSession } from '@/system/graphSession';
|
|
5
|
+
|
|
6
|
+
/** Node type registered for notes created by this plugin. */
|
|
7
|
+
export const NOTE_NODE_TYPE = 'noteNode';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type string notes used while they lived in the core editor as "comment"
|
|
11
|
+
* nodes. Registered as an alias so graphs saved before the move still render.
|
|
12
|
+
*/
|
|
13
|
+
export const LEGACY_COMMENT_NODE_TYPE = 'commentNode';
|
|
14
|
+
|
|
15
|
+
export const isNoteNode = (node: Node | undefined): node is Node =>
|
|
16
|
+
node?.type === NOTE_NODE_TYPE || node?.type === LEGACY_COMMENT_NODE_TYPE;
|
|
17
|
+
|
|
18
|
+
export const noteAt = (ctx: CommandContext): Node | undefined => {
|
|
19
|
+
const node = ctx.session.nodeStore
|
|
20
|
+
.getState()
|
|
21
|
+
.nodes.find((n) => n.id === ctx.nodeId);
|
|
22
|
+
return isNoteNode(node) ? node : undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const duplicateNote = (session: GraphSession, note: Node): void => {
|
|
26
|
+
const copy: Node = {
|
|
27
|
+
...note,
|
|
28
|
+
id: uuidv4(),
|
|
29
|
+
selected: false,
|
|
30
|
+
position: { x: note.position.x + 24, y: note.position.y + 24 },
|
|
31
|
+
data: { ...note.data }
|
|
32
|
+
};
|
|
33
|
+
session.undoManager.execute({
|
|
34
|
+
name: 'Duplicate note',
|
|
35
|
+
execute: () => {
|
|
36
|
+
session.nodeStore.getState().addNode(copy);
|
|
37
|
+
},
|
|
38
|
+
undo: () => {
|
|
39
|
+
session.nodeStore
|
|
40
|
+
.getState()
|
|
41
|
+
.setNodes((existing) => existing.filter((n) => n.id !== copy.id));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const deleteNote = (session: GraphSession, note: Node): void => {
|
|
47
|
+
const index = session.nodeStore
|
|
48
|
+
.getState()
|
|
49
|
+
.nodes.findIndex((n) => n.id === note.id);
|
|
50
|
+
session.undoManager.execute({
|
|
51
|
+
name: 'Delete note',
|
|
52
|
+
execute: () => {
|
|
53
|
+
session.nodeStore
|
|
54
|
+
.getState()
|
|
55
|
+
.setNodes((existing) => existing.filter((n) => n.id !== note.id));
|
|
56
|
+
},
|
|
57
|
+
undo: () => {
|
|
58
|
+
session.nodeStore.getState().setNodes((existing) => {
|
|
59
|
+
const next = [...existing];
|
|
60
|
+
next.splice(Math.min(index, next.length), 0, note);
|
|
61
|
+
return next;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Move a note within the nodes array: React Flow paints later nodes on top, so
|
|
69
|
+
* array order is z-order.
|
|
70
|
+
*/
|
|
71
|
+
export const reorderNote = (
|
|
72
|
+
session: GraphSession,
|
|
73
|
+
note: Node,
|
|
74
|
+
to: 'front' | 'back'
|
|
75
|
+
): void => {
|
|
76
|
+
const from = session.nodeStore
|
|
77
|
+
.getState()
|
|
78
|
+
.nodes.findIndex((n) => n.id === note.id);
|
|
79
|
+
if (from < 0) return;
|
|
80
|
+
|
|
81
|
+
const move = (position: 'front' | 'back' | number) => {
|
|
82
|
+
session.nodeStore.getState().setNodes((existing) => {
|
|
83
|
+
const index = existing.findIndex((n) => n.id === note.id);
|
|
84
|
+
if (index < 0) return existing;
|
|
85
|
+
const next = [...existing];
|
|
86
|
+
const [moved] = next.splice(index, 1);
|
|
87
|
+
if (moved === undefined) return existing;
|
|
88
|
+
if (position === 'front') next.push(moved);
|
|
89
|
+
else if (position === 'back') next.unshift(moved);
|
|
90
|
+
else next.splice(Math.min(position, next.length), 0, moved);
|
|
91
|
+
return next;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
session.undoManager.execute({
|
|
96
|
+
name: to === 'front' ? 'Bring note to front' : 'Send note to back',
|
|
97
|
+
execute: () => move(to),
|
|
98
|
+
undo: () => move(from)
|
|
99
|
+
});
|
|
100
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { lazy, memo, Suspense } from 'react';
|
|
2
|
+
import type { NodeProps } from 'reactflow';
|
|
3
|
+
import type { INoteNode } from '@/types/nodes';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Note nodes embed a tiptap/prosemirror rich-text editor (~320 KB). Most
|
|
7
|
+
* graphs have no note nodes, so load the implementation lazily — the editor
|
|
8
|
+
* weight only enters the bundle when a note node is actually rendered.
|
|
9
|
+
*/
|
|
10
|
+
const LazyNoteNode = lazy(() =>
|
|
11
|
+
import('./noteImpl').then((m) => ({ default: m.NoteNodeImpl }))
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const NoteNodeRaw = (props: NodeProps<INoteNode['data']>) => (
|
|
15
|
+
<Suspense fallback={null}>
|
|
16
|
+
<LazyNoteNode {...props} />
|
|
17
|
+
</Suspense>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export const NoteNode = memo(NoteNodeRaw);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { NodeResizer } from 'reactflow';
|
|
2
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
3
|
+
import { useEditor, EditorContent } from '@tiptap/react';
|
|
4
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
5
|
+
import Youtube from '@tiptap/extension-youtube';
|
|
6
|
+
import { useChangeNodeData } from '@/hooks/useChangeNodeData';
|
|
7
|
+
import { Markdown } from 'tiptap-markdown';
|
|
8
|
+
import type { INoteNode } from '@/types/nodes';
|
|
9
|
+
import { FormatToolbar } from './FormatToolbar';
|
|
10
|
+
import type { NodeProps } from 'reactflow';
|
|
11
|
+
|
|
12
|
+
const NOTE_MIN_WIDTH = 160;
|
|
13
|
+
const NOTE_MIN_HEIGHT = 80;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The actual note node, pulling in tiptap/prosemirror (~320 KB). It is loaded
|
|
17
|
+
* lazily by `note.tsx` so that weight only lands in the bundle when a note
|
|
18
|
+
* node is actually rendered.
|
|
19
|
+
*
|
|
20
|
+
* The note draws its own frame directly against the React Flow node bounds.
|
|
21
|
+
* It must NOT use BaseNodeWrapper: that adds behave-node chrome (a second
|
|
22
|
+
* bordered box) and breaks the height chain, so the frame sizes to content
|
|
23
|
+
* instead of the node and drifts out of the NodeResizer bounds.
|
|
24
|
+
*
|
|
25
|
+
* Layout: the root div only positions the NodeResizer and floating toolbar (it
|
|
26
|
+
* must not clip, or the toolbar disappears); the frame inside it clips. Only
|
|
27
|
+
* the header drags the node (`dragHandle` set by `addNote`) — the body is
|
|
28
|
+
* `nodrag nopan` so selecting text neither moves the node nor pans the canvas.
|
|
29
|
+
* The editor is always editable: a single click gives a caret; the format
|
|
30
|
+
* toolbar shows while the node is selected.
|
|
31
|
+
*
|
|
32
|
+
* Font size cascades from a React `style` on the root — never write to
|
|
33
|
+
* `editor.view.dom` in an effect; tiptap v3 throws if the view has not mounted.
|
|
34
|
+
*/
|
|
35
|
+
export const NoteNodeImpl = ({
|
|
36
|
+
data,
|
|
37
|
+
selected,
|
|
38
|
+
id
|
|
39
|
+
}: NodeProps<INoteNode['data']>) => {
|
|
40
|
+
const handleNodeChange = useChangeNodeData(id);
|
|
41
|
+
|
|
42
|
+
const editor = useEditor({
|
|
43
|
+
extensions: [StarterKit, Markdown, Youtube.configure({ nocookie: true })],
|
|
44
|
+
content: data.text || 'New note',
|
|
45
|
+
editorProps: {
|
|
46
|
+
attributes: {
|
|
47
|
+
class: 'nodrag nopan'
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
onUpdate: ({ editor }) => {
|
|
51
|
+
const markdown = (
|
|
52
|
+
editor.storage as { markdown?: { getMarkdown(): string } }
|
|
53
|
+
).markdown;
|
|
54
|
+
if (markdown) {
|
|
55
|
+
handleNodeChange('text', markdown.getMarkdown());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ErrorBoundary fallback={<div>Error rendering note</div>}>
|
|
62
|
+
<div
|
|
63
|
+
className="notes-node-root"
|
|
64
|
+
style={{ fontSize: data.fontSize || 'medium' }}
|
|
65
|
+
>
|
|
66
|
+
<NodeResizer
|
|
67
|
+
minWidth={NOTE_MIN_WIDTH}
|
|
68
|
+
minHeight={NOTE_MIN_HEIGHT}
|
|
69
|
+
color="#ff0071"
|
|
70
|
+
isVisible={selected || false}
|
|
71
|
+
/>
|
|
72
|
+
{editor && selected && <FormatToolbar editor={editor} />}
|
|
73
|
+
|
|
74
|
+
<div className="notes-node">
|
|
75
|
+
<div className="notes-node__header">
|
|
76
|
+
<span className="notes-node__grip" aria-hidden>
|
|
77
|
+
⠿
|
|
78
|
+
</span>
|
|
79
|
+
<span>Note</span>
|
|
80
|
+
</div>
|
|
81
|
+
<EditorContent
|
|
82
|
+
editor={editor}
|
|
83
|
+
className="notes-node__editor nodrag nopan"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</ErrorBoundary>
|
|
88
|
+
);
|
|
89
|
+
};
|