add journal
This commit is contained in:
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from backend.node_registry import register_node
|
||||
|
||||
|
||||
@register_node(display_name="Text Note")
|
||||
@register_node(display_name="Journal")
|
||||
class TextNote:
|
||||
"""A floating text card for annotating workflows. Supports Markdown."""
|
||||
|
||||
|
||||
@@ -873,6 +873,7 @@ function Flow() {
|
||||
const canvasRightZoomRef = useRef(null);
|
||||
const suppressPaneContextMenuUntilRef = useRef(0);
|
||||
const loadNodeOutputRequestVersionsRef = useRef(new Map());
|
||||
const journalContentRef = useRef('');
|
||||
const reactFlow = useReactFlow();
|
||||
|
||||
// ── WebSocket ───────────────────────────────────────────────────────
|
||||
@@ -1959,12 +1960,13 @@ function Flow() {
|
||||
const openJournalTab = useCallback(() => {
|
||||
setHelpTabs((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');
|
||||
}, []);
|
||||
|
||||
const updateTabContent = useCallback((label, content) => {
|
||||
if (label === 'Journal') journalContentRef.current = content;
|
||||
setHelpTabs((prev) => prev.map((t) => t.label === label ? { ...t, content } : t));
|
||||
}, []);
|
||||
|
||||
@@ -1993,6 +1995,10 @@ function Flow() {
|
||||
setNodes(sortNodesForParentOrder(hydrated.nodes));
|
||||
setEdges(hydrated.edges);
|
||||
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, setNodes, setEdges]);
|
||||
|
||||
@@ -2095,6 +2101,7 @@ function Flow() {
|
||||
|
||||
const stampedBlob = await stampLogoOnBlob(blob);
|
||||
const workflow = serializeWorkflowState(allNodes, reactFlow.getEdges());
|
||||
if (journalContentRef.current) workflow.journalContent = journalContentRef.current;
|
||||
return embedWorkflow(stampedBlob, workflow);
|
||||
}, [reactFlow]);
|
||||
|
||||
@@ -3003,6 +3010,7 @@ function Flow() {
|
||||
onTabSelect={setActiveHelpTab}
|
||||
onTabClose={closeHelpTab}
|
||||
onTabContentChange={updateTabContent}
|
||||
onOpenJournal={openJournalTab}
|
||||
/>
|
||||
</NodeContext.Provider>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,11 @@ import { marked } from 'marked';
|
||||
|
||||
function JournalTab({ content, onChange }) {
|
||||
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 (
|
||||
<div className="node-help-journal">
|
||||
@@ -34,23 +38,26 @@ function JournalTab({ content, onChange }) {
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
) : renderedHtml ? (
|
||||
<div
|
||||
className="node-help-panel-body node-help-journal-preview nowheel"
|
||||
onDoubleClick={() => setIsEditing(true)}
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
|
||||
function HelpPanelManager({ tabs, activeTab, onTabSelect, onTabClose, onTabContentChange }) {
|
||||
function HelpPanelManager({ tabs, activeTab, onTabSelect, onTabClose, onTabContentChange, onOpenJournal }) {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -92,6 +99,15 @@ function HelpPanelManager({ tabs, activeTab, onTabSelect, onTabClose, onTabConte
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{!tabs.some((t) => t.type === 'journal') && (
|
||||
<button
|
||||
className="node-help-tab-add"
|
||||
title="Open Journal"
|
||||
onClick={onOpenJournal}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
|
||||
@@ -543,6 +543,25 @@ html, body, #root {
|
||||
}
|
||||
.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 {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
|
||||
Reference in New Issue
Block a user