add journal
This commit is contained in:
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from backend.node_registry import register_node
|
from backend.node_registry import register_node
|
||||||
|
|
||||||
|
|
||||||
@register_node(display_name="Text Note")
|
@register_node(display_name="Journal")
|
||||||
class TextNote:
|
class TextNote:
|
||||||
"""A floating text card for annotating workflows. Supports Markdown."""
|
"""A floating text card for annotating workflows. Supports Markdown."""
|
||||||
|
|
||||||
|
|||||||
@@ -873,6 +873,7 @@ function Flow() {
|
|||||||
const canvasRightZoomRef = useRef(null);
|
const canvasRightZoomRef = useRef(null);
|
||||||
const suppressPaneContextMenuUntilRef = useRef(0);
|
const suppressPaneContextMenuUntilRef = useRef(0);
|
||||||
const loadNodeOutputRequestVersionsRef = useRef(new Map());
|
const loadNodeOutputRequestVersionsRef = useRef(new Map());
|
||||||
|
const journalContentRef = useRef('');
|
||||||
const reactFlow = useReactFlow();
|
const reactFlow = useReactFlow();
|
||||||
|
|
||||||
// ── WebSocket ───────────────────────────────────────────────────────
|
// ── WebSocket ───────────────────────────────────────────────────────
|
||||||
@@ -1959,12 +1960,13 @@ function Flow() {
|
|||||||
const openJournalTab = useCallback(() => {
|
const openJournalTab = useCallback(() => {
|
||||||
setHelpTabs((prev) => {
|
setHelpTabs((prev) => {
|
||||||
if (prev.find((t) => t.label === 'Journal')) return prev;
|
if (prev.find((t) => t.label === 'Journal')) return prev;
|
||||||
return [...prev, { label: 'Journal', type: 'journal', content: '' }];
|
return [...prev, { label: 'Journal', type: 'journal', content: journalContentRef.current }];
|
||||||
});
|
});
|
||||||
setActiveHelpTab('Journal');
|
setActiveHelpTab('Journal');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateTabContent = useCallback((label, content) => {
|
const updateTabContent = useCallback((label, content) => {
|
||||||
|
if (label === 'Journal') journalContentRef.current = content;
|
||||||
setHelpTabs((prev) => prev.map((t) => t.label === label ? { ...t, content } : t));
|
setHelpTabs((prev) => prev.map((t) => t.label === label ? { ...t, content } : t));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -1993,6 +1995,10 @@ function Flow() {
|
|||||||
setNodes(sortNodesForParentOrder(hydrated.nodes));
|
setNodes(sortNodesForParentOrder(hydrated.nodes));
|
||||||
setEdges(hydrated.edges);
|
setEdges(hydrated.edges);
|
||||||
nextIdRef.current = hydrated.nextNodeId;
|
nextIdRef.current = hydrated.nextNodeId;
|
||||||
|
journalContentRef.current = data.journalContent || '';
|
||||||
|
setHelpTabs((prev) => prev.map((t) =>
|
||||||
|
t.label === 'Journal' ? { ...t, content: journalContentRef.current } : t,
|
||||||
|
));
|
||||||
initializeDynamicNodes(hydrated.nodes);
|
initializeDynamicNodes(hydrated.nodes);
|
||||||
}, [initializeDynamicNodes, setNodes, setEdges]);
|
}, [initializeDynamicNodes, setNodes, setEdges]);
|
||||||
|
|
||||||
@@ -2095,6 +2101,7 @@ function Flow() {
|
|||||||
|
|
||||||
const stampedBlob = await stampLogoOnBlob(blob);
|
const stampedBlob = await stampLogoOnBlob(blob);
|
||||||
const workflow = serializeWorkflowState(allNodes, reactFlow.getEdges());
|
const workflow = serializeWorkflowState(allNodes, reactFlow.getEdges());
|
||||||
|
if (journalContentRef.current) workflow.journalContent = journalContentRef.current;
|
||||||
return embedWorkflow(stampedBlob, workflow);
|
return embedWorkflow(stampedBlob, workflow);
|
||||||
}, [reactFlow]);
|
}, [reactFlow]);
|
||||||
|
|
||||||
@@ -3003,6 +3010,7 @@ function Flow() {
|
|||||||
onTabSelect={setActiveHelpTab}
|
onTabSelect={setActiveHelpTab}
|
||||||
onTabClose={closeHelpTab}
|
onTabClose={closeHelpTab}
|
||||||
onTabContentChange={updateTabContent}
|
onTabContentChange={updateTabContent}
|
||||||
|
onOpenJournal={openJournalTab}
|
||||||
/>
|
/>
|
||||||
</NodeContext.Provider>
|
</NodeContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import { marked } from 'marked';
|
|||||||
|
|
||||||
function JournalTab({ content, onChange }) {
|
function JournalTab({ content, onChange }) {
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const renderedHtml = content?.trim() ? marked.parse(content) : '';
|
|
||||||
|
let renderedHtml = '';
|
||||||
|
if (!isEditing && content?.trim()) {
|
||||||
|
try { renderedHtml = marked.parse(content); } catch { /* fallback to raw */ renderedHtml = content; }
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="node-help-journal">
|
<div className="node-help-journal">
|
||||||
@@ -34,23 +38,26 @@ function JournalTab({ content, onChange }) {
|
|||||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
) : (
|
) : renderedHtml ? (
|
||||||
<div
|
<div
|
||||||
className="node-help-panel-body node-help-journal-preview nowheel"
|
className="node-help-panel-body node-help-journal-preview nowheel"
|
||||||
onDoubleClick={() => setIsEditing(true)}
|
onDoubleClick={() => setIsEditing(true)}
|
||||||
// eslint-disable-next-line react/no-danger
|
// eslint-disable-next-line react/no-danger
|
||||||
dangerouslySetInnerHTML={renderedHtml ? { __html: renderedHtml } : undefined}
|
dangerouslySetInnerHTML={{ __html: renderedHtml }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="node-help-panel-body node-help-journal-preview nowheel"
|
||||||
|
onDoubleClick={() => setIsEditing(true)}
|
||||||
>
|
>
|
||||||
{!renderedHtml && (
|
<span className="node-help-journal-placeholder">Double-click to write…</span>
|
||||||
<span className="node-help-journal-placeholder">Double-click to write…</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HelpPanelManager({ tabs, activeTab, onTabSelect, onTabClose, onTabContentChange }) {
|
function HelpPanelManager({ tabs, activeTab, onTabSelect, onTabClose, onTabContentChange, onOpenJournal }) {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -92,6 +99,15 @@ function HelpPanelManager({ tabs, activeTab, onTabSelect, onTabClose, onTabConte
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{!tabs.some((t) => t.type === 'journal') && (
|
||||||
|
<button
|
||||||
|
className="node-help-tab-add"
|
||||||
|
title="Open Journal"
|
||||||
|
onClick={onOpenJournal}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
|
|||||||
@@ -543,6 +543,25 @@ html, body, #root {
|
|||||||
}
|
}
|
||||||
.node-help-tab-close:hover { opacity: 1; }
|
.node-help-tab-close:hover { opacity: 1; }
|
||||||
|
|
||||||
|
.node-help-tab-add {
|
||||||
|
background: none;
|
||||||
|
border: 1px dashed #334155;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
align-self: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: color 0.12s, border-color 0.12s;
|
||||||
|
}
|
||||||
|
.node-help-tab-add:hover { color: #f1f5f9; border-color: #64748b; }
|
||||||
|
|
||||||
.node-help-panel {
|
.node-help-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 60px;
|
top: 60px;
|
||||||
|
|||||||
Reference in New Issue
Block a user