diff --git a/src/components/graph/ConfigNode.tsx b/src/components/graph/ConfigNode.tsx
index cf0c9eb..cbee766 100644
--- a/src/components/graph/ConfigNode.tsx
+++ b/src/components/graph/ConfigNode.tsx
@@ -26,10 +26,13 @@ import {
import { Code2, ScrollText, Variable } from 'lucide-react'
import {
MenubarItem,
+ MenubarSeparator,
+ MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
} from '../ui/menubar'
+import { Kbd } from '../ui/kbd'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
import { InputHandle, OutputHandle } from './NodeHandles'
import { NodeFooterEdgeIndicators } from './NodeFooterEdgeIndicators'
@@ -191,31 +194,55 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
const [editorHeight, editorContainerRef] = useResizeHeight(180)
const insertBlocksContent = useMemo(() => {
- return configType.insertBlocks.map((block, idx) =>
- isGroup(block) ? (
-
- {block.label}
-
- {block.items.map(({ label, snippet }) => (
- insertAt(snippet, 'cursor')}
- >
- {label}
-
- ))}
-
-
- ) : (
+ const templatingGroup = configType.insertBlocks.find(
+ (b): b is import('../../lib/configTypes').InsertBlockGroup => isGroup(b) && b.label === 'Templating'
+ )
+ const typeBlocks = configType.insertBlocks.filter((b) => !(isGroup(b) && b.label === 'Templating'))
+ const insertShortcut = Insert
+ const typeItems = typeBlocks.flatMap((block) =>
+ isGroup(block)
+ ? block.items.map(({ label, snippet }) => (
+ insertAt(snippet, 'cursor')}
+ >
+ {label}
+ {insertShortcut}
+
+ ))
+ : [
+ insertAt(block.snippet, 'cursor')}
+ >
+ {block.label}
+ {insertShortcut}
+ ,
+ ]
+ )
+ const templatingItems =
+ templatingGroup?.items.map(({ label, snippet }) => (
insertAt(block.snippet, 'cursor')}
+ key={label}
+ className="text-xs flex items-center group"
+ onClick={() => insertAt(snippet, 'cursor')}
>
- {block.label}
+ {label}
+ Insert
- )
+ )) ?? []
+ return (
+ <>
+ {typeItems}
+ {templatingItems.length > 0 && (
+ <>
+
+ {templatingItems}
+ >
+ )}
+ >
)
}, [configType.insertBlocks, insertAt])
@@ -250,7 +277,7 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
{connectedConfigNodes.map((n: any) => (
@@ -261,63 +288,59 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
insertExtendsFromNode(n, 'cursor')}
>
Extend
+ Insert
insertIncludeFromNode(n, 'cursor')}
>
Include
+ Insert
insertImportFromNode(n, 'cursor')}
>
Import
+ Insert
))}
{connectedVariableNodes.map((n: any) => (
-
-
-
- {n.id}
-
-
- insertVariableReference(n, 'cursor')}
- >
- Insert
-
-
-
+ insertVariableReference(n, 'cursor')}
+ >
+
+ {n.id}
+ Insert
+
))}
{connectedFunctionNodes.map((n: any) => (
-
-
-
- {n.id}
-
-
- insertFunctionCall(n, 'cursor')}
- >
- Insert
-
-
-
+ insertFunctionCall(n, 'cursor')}
+ >
+
+ {n.id}
+ Insert
+
))}
>
- ) : undefined
+ ) : (
+ Connect nodes to insert references
+ )
}
+ insertMenuLabel="Blocks"
+ insertContentDirect
insertTagsContent={insertBlocksContent}
- insertTagsLabel={configType.label}
/>
@@ -338,7 +361,7 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
- {content ? `${content.length} chars` : 'none'}
+ {configType.label} · {content ? `${content.length} chars` : 'none'}
diff --git a/src/components/graph/FunctionNode.tsx b/src/components/graph/FunctionNode.tsx
index 75872c1..fd6ea8f 100644
--- a/src/components/graph/FunctionNode.tsx
+++ b/src/components/graph/FunctionNode.tsx
@@ -15,7 +15,8 @@ import { InputHandle, OutputHandle } from './NodeHandles'
import { NodeFooterEdgeIndicators } from './NodeFooterEdgeIndicators'
import { NodeHeaderTitle } from './NodeHeaderTitle'
import { NodeMenubar } from './NodeMenubar'
-import { MenubarItem, MenubarSub, MenubarSubContent, MenubarSubTrigger } from '../ui/menubar'
+import { MenubarItem, MenubarShortcut } from '../ui/menubar'
+import { Kbd } from '../ui/kbd'
import { Code2, Variable } from 'lucide-react'
type Props = {
@@ -110,43 +111,35 @@ export const FunctionNode = memo(function FunctionNode({ id, data, width, height
{connectedVariableNodes.map((n: any) => (
-
-
-
- {n.id}
-
-
- insertVariableAtCursor(n)}
- >
- Insert at cursor
-
-
-
+ insertVariableAtCursor(n)}
+ >
+
+ {n.id}
+ Insert
+
))}
{connectedFunctionNodes.map((n: any) => (
-
-
-
- {n.id}
-
-
- insertFunctionAtCursor(n)}
- >
- Insert at cursor
-
-
-
+ insertFunctionAtCursor(n)}
+ >
+
+ {n.id}
+ Insert
+
))}
>
- ) : undefined
+ ) : (
+ Connect nodes to insert at cursor
+ )
}
/>
@@ -167,7 +160,7 @@ export const FunctionNode = memo(function FunctionNode({ id, data, width, height
- {bodyValue ? `Javascript | ${bodyValue.length} chars` : 'none'}
+ {bodyValue ? `JavaScript · ${bodyValue.length} chars` : 'JavaScript · none'}
diff --git a/src/components/graph/NodeMenubar.tsx b/src/components/graph/NodeMenubar.tsx
index f26b2d5..70a586f 100644
--- a/src/components/graph/NodeMenubar.tsx
+++ b/src/components/graph/NodeMenubar.tsx
@@ -20,17 +20,23 @@ type NodeType = 'config' | 'render' | 'variable' | 'function'
type Props = {
nodeId: string
nodeType: NodeType
- /** Content for Insert → Inputs (config and function nodes only) */
+ /** Content for Insert → Inputs (function nodes); when inputsMenuContent is set, Inputs is a separate menu and this is not used in Insert */
editInputsContent?: React.ReactNode
+ /** When set, Inputs is rendered as its own top-level menu (config nodes); Insert then only shows markup-specific options */
+ inputsMenuContent?: React.ReactNode
/** Content for Insert → [Blocks/Tags] (e.g. type-specific snippets, config nodes) */
insertTagsContent?: React.ReactNode
- /** Label for Insert submenu that shows insertTagsContent (default "Tags") */
+ /** Label for the Insert/Blocks menu trigger (default "Insert") */
+ insertMenuLabel?: string
+ /** When true, render insertTagsContent directly in the menu (no submenu level). Use for flat Blocks list. */
+ insertContentDirect?: boolean
+ /** Label for Insert submenu that shows insertTagsContent when not insertContentDirect (default "Tags") */
insertTagsLabel?: string
/** Extra content in Node menu (e.g. Export submenu for render nodes), before the separator */
nodeMenuExtraContent?: React.ReactNode
}
-export function NodeMenubar({ nodeId, nodeType, editInputsContent, insertTagsContent, insertTagsLabel = 'Tags', nodeMenuExtraContent }: Props) {
+export function NodeMenubar({ nodeId, nodeType, editInputsContent, inputsMenuContent, insertTagsContent, insertMenuLabel = 'Insert', insertContentDirect, insertTagsLabel = 'Tags', nodeMenuExtraContent }: Props) {
const ctx = useContext(FlowContext)
const nodes = ctx?.nodes ?? []
const setNodes = ctx?.setNodes
@@ -108,30 +114,45 @@ export function NodeMenubar({ nodeId, nodeType, editInputsContent, insertTagsCon
- {hasEdit && (
+ {inputsMenuContent != null && (
+
+
+ Inputs
+
+
+ {inputsMenuContent}
+
+
+ )}
+ {hasEdit && (insertTagsContent != null || (editInputsContent != null && inputsMenuContent == null)) && (
- Insert
+ {insertMenuLabel}
-
-
- Inputs
-
-
- {editInputsContent}
-
-
- {insertTagsContent != null && (
+ {inputsMenuContent == null && editInputsContent != null && (
-
- {insertTagsLabel}
+
+ Inputs
- {insertTagsContent}
+ {editInputsContent}
)}
+ {insertTagsContent != null &&
+ (insertContentDirect ? (
+ insertTagsContent
+ ) : (
+
+
+ {insertTagsLabel}
+
+
+ {insertTagsContent}
+
+
+ ))}
)}
diff --git a/src/components/graph/RenderingNode.tsx b/src/components/graph/RenderingNode.tsx
index a62588d..c104e75 100644
--- a/src/components/graph/RenderingNode.tsx
+++ b/src/components/graph/RenderingNode.tsx
@@ -599,7 +599,11 @@ export const RenderingNode = memo(function RenderingNode({ id, width, height }:
- {renderedContent ? (isSvgOutput ? 'Diagram' : 'Markdown') : error ? 'Error' : '—'}
+ {renderedContent
+ ? `${isSvgOutput ? 'Diagram' : 'Markdown'} · ${renderedContent.length} chars`
+ : error
+ ? 'Error'
+ : '—'}
diff --git a/src/components/graph/VariableNode.tsx b/src/components/graph/VariableNode.tsx
index 419d02e..ee02d0d 100644
--- a/src/components/graph/VariableNode.tsx
+++ b/src/components/graph/VariableNode.tsx
@@ -135,7 +135,9 @@ export const VariableNode = memo(function VariableNode({ id, data }: Props) {
-
+
+ {`${valueType.charAt(0).toUpperCase() + valueType.slice(1)} · ${displayValue.length ? `${displayValue.length} chars` : 'none'}`}
+
)
diff --git a/src/lib/configTypes.ts b/src/lib/configTypes.ts
index 10d727b..03f68ff 100644
--- a/src/lib/configTypes.ts
+++ b/src/lib/configTypes.ts
@@ -38,7 +38,7 @@ const PLANTUML_INSERT_BLOCKS: InsertBlockOrGroup[] = [
{ label: 'Arrow', snippet: ' -> ' },
{ label: 'Note', snippet: 'note right of ' },
{
- label: 'Nunjucks',
+ label: 'Templating',
items: [
{ label: 'Variable {{ }}', snippet: '{{ }}' },
{ label: 'if / endif', snippet: '{% if %}\n \n{% endif %}' },
@@ -64,7 +64,7 @@ const MARKDOWN_INSERT_BLOCKS: InsertBlockOrGroup[] = [
{ label: 'List item', snippet: '- ' },
{ label: 'Blockquote', snippet: '> ' },
{
- label: 'Nunjucks',
+ label: 'Templating',
items: [
{ label: 'Variable {{ }}', snippet: '{{ }}' },
{ label: 'if / endif', snippet: '{% if %}\n \n{% endif %}' },