Skip to content

Commit 605974a

Browse files
committed
feat: add code note editor with execution and error handling
1 parent 8204284 commit 605974a

File tree

10 files changed

+1451
-17
lines changed

10 files changed

+1451
-17
lines changed

src/components/code/CodeEditor.tsx

Lines changed: 961 additions & 0 deletions
Large diffs are not rendered by default.

src/components/code/templates.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/**
2+
* Default code templates for executable code notes
3+
*/
4+
5+
export const DEFAULT_CODE = {
6+
language: 'javascript',
7+
code: `// Welcome to your code note!
8+
// Write your code here and click Run to execute
9+
10+
console.log('Hello, World!');
11+
12+
// Example: Calculate factorial
13+
function factorial(n) {
14+
if (n <= 1) return 1;
15+
return n * factorial(n - 1);
16+
}
17+
18+
console.log('Factorial of 5:', factorial(5));`
19+
};
20+
21+
export const CODE_TEMPLATES = {
22+
javascript: {
23+
language: 'javascript',
24+
code: `// JavaScript Example
25+
console.log('Hello from JavaScript!');
26+
27+
// Array manipulation
28+
const numbers = [1, 2, 3, 4, 5];
29+
const doubled = numbers.map(n => n * 2);
30+
console.log('Doubled:', doubled);
31+
32+
// Object destructuring
33+
const person = { name: 'Alice', age: 30 };
34+
const { name, age } = person;
35+
console.log(\`\${name} is \${age} years old\`);`
36+
},
37+
python: {
38+
language: 'python',
39+
code: `# Python Example
40+
print('Hello from Python!')
41+
42+
# List comprehension
43+
numbers = [1, 2, 3, 4, 5]
44+
doubled = [n * 2 for n in numbers]
45+
print('Doubled:', doubled)
46+
47+
# Dictionary
48+
person = {'name': 'Alice', 'age': 30}
49+
print(f"{person['name']} is {person['age']} years old")
50+
51+
# Function
52+
def greet(name):
53+
return f"Hello, {name}!"
54+
55+
print(greet('World'))`
56+
},
57+
typescript: {
58+
language: 'typescript',
59+
code: `// TypeScript Example
60+
interface Person {
61+
name: string;
62+
age: number;
63+
}
64+
65+
const greet = (person: Person): string => {
66+
return \`Hello, \${person.name}! You are \${person.age} years old.\`;
67+
};
68+
69+
const alice: Person = { name: 'Alice', age: 30 };
70+
console.log(greet(alice));
71+
72+
// Type inference
73+
const numbers: number[] = [1, 2, 3, 4, 5];
74+
const doubled = numbers.map(n => n * 2);
75+
console.log('Doubled:', doubled);`
76+
},
77+
java: {
78+
language: 'java',
79+
code: `// Java Example
80+
public class Main {
81+
public static void main(String[] args) {
82+
System.out.println("Hello from Java!");
83+
84+
// Array
85+
int[] numbers = {1, 2, 3, 4, 5};
86+
int sum = 0;
87+
for (int num : numbers) {
88+
sum += num;
89+
}
90+
System.out.println("Sum: " + sum);
91+
92+
// Method call
93+
System.out.println(greet("World"));
94+
}
95+
96+
public static String greet(String name) {
97+
return "Hello, " + name + "!";
98+
}
99+
}`
100+
},
101+
cpp: {
102+
language: 'cpp',
103+
code: `// C++ Example
104+
#include <iostream>
105+
#include <vector>
106+
using namespace std;
107+
108+
int main() {
109+
cout << "Hello from C++!" << endl;
110+
111+
// Vector
112+
vector<int> numbers = {1, 2, 3, 4, 5};
113+
int sum = 0;
114+
for (int num : numbers) {
115+
sum += num;
116+
}
117+
cout << "Sum: " << sum << endl;
118+
119+
return 0;
120+
}`
121+
},
122+
python_data: {
123+
language: 'python',
124+
code: `# Python Data Analysis Example
125+
# Calculate statistics
126+
127+
data = [10, 20, 30, 40, 50]
128+
129+
# Mean
130+
mean = sum(data) / len(data)
131+
print(f'Mean: {mean}')
132+
133+
# Median
134+
sorted_data = sorted(data)
135+
n = len(sorted_data)
136+
median = sorted_data[n//2] if n % 2 != 0 else (sorted_data[n//2-1] + sorted_data[n//2]) / 2
137+
print(f'Median: {median}')
138+
139+
# Range
140+
range_val = max(data) - min(data)
141+
print(f'Range: {range_val}')`
142+
},
143+
javascript_async: {
144+
language: 'javascript',
145+
code: `// JavaScript Async/Await Example
146+
// Working with Promises
147+
148+
async function processData(id) {
149+
// Return a resolved promise immediately
150+
return Promise.resolve({
151+
id,
152+
name: \`User \${id}\`,
153+
status: 'active'
154+
});
155+
}
156+
157+
async function main() {
158+
console.log('Processing user data...');
159+
160+
const user = await processData(1);
161+
console.log('User:', JSON.stringify(user));
162+
163+
// Multiple async operations
164+
const users = await Promise.all([
165+
processData(1),
166+
processData(2),
167+
processData(3)
168+
]);
169+
console.log('Total users:', users.length);
170+
171+
console.log('Done!');
172+
}
173+
174+
main();`
175+
},
176+
python_algorithm: {
177+
language: 'python',
178+
code: `# Python Algorithm Example
179+
# Binary Search
180+
181+
def binary_search(arr, target):
182+
left, right = 0, len(arr) - 1
183+
184+
while left <= right:
185+
mid = (left + right) // 2
186+
if arr[mid] == target:
187+
return mid
188+
elif arr[mid] < target:
189+
left = mid + 1
190+
else:
191+
right = mid - 1
192+
193+
return -1
194+
195+
# Test
196+
numbers = [1, 3, 5, 7, 9, 11, 13, 15]
197+
target = 7
198+
result = binary_search(numbers, target)
199+
print(f'Found {target} at index: {result}')`
200+
}
201+
};

src/components/editor/index.tsx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { useEditorState } from '@/components/editor/hooks/useEditorState';
3838
import { useEditorEffects } from '@/components/editor/hooks/useEditorEffects';
3939
import { fileService } from '@/services/fileService';
4040
import DiagramEditor from '@/components/diagrams/DiagramEditor';
41+
import CodeEditor from '@/components/code/CodeEditor';
4142
import type { Note, Folder as FolderType, FileAttachment } from '@/types/note';
4243
import type { WebSocketStatus } from '@/types/websocket';
4344

@@ -646,6 +647,134 @@ export default function Index({
646647
);
647648
}
648649

650+
// Show code editor if this is a code note
651+
if (note.type === 'code') {
652+
// Parse the JSON content to extract language, code, and lastExecution
653+
let parsedContent: {
654+
language: string;
655+
code: string;
656+
lastExecution?: {
657+
output: string;
658+
error?: string;
659+
executionTime: number;
660+
status?: { id: number; description: string };
661+
timestamp?: string;
662+
} | null;
663+
} = { language: 'javascript', code: '', lastExecution: null };
664+
665+
try {
666+
const parsed = JSON.parse(note.content);
667+
668+
// Validate the structure of the parsed content
669+
if (typeof parsed !== 'object' || parsed === null) {
670+
throw new Error('Invalid code note structure: content is not an object');
671+
}
672+
673+
if (!('code' in parsed)) {
674+
throw new Error('Invalid code note structure: missing "code" field');
675+
}
676+
677+
// Set parsed content with defaults for missing fields
678+
parsedContent = {
679+
language: typeof parsed.language === 'string' ? parsed.language : 'javascript',
680+
code: typeof parsed.code === 'string' ? parsed.code : '',
681+
lastExecution: parsed.lastExecution || null,
682+
};
683+
} catch (error) {
684+
// Log the error for debugging
685+
console.error('Failed to parse code note content:', error, 'Note ID:', note.id);
686+
687+
// If parsing fails, show an error state
688+
return (
689+
<div className="flex h-full flex-col items-center justify-center gap-4 p-8">
690+
<div className="text-destructive flex flex-col items-center gap-2">
691+
<div className="text-4xl">⚠️</div>
692+
<h3 className="text-lg font-semibold">Failed to Load Code Note</h3>
693+
<p className="text-muted-foreground text-center text-sm">
694+
This code note appears to be corrupted or in an invalid format.
695+
</p>
696+
<p className="text-muted-foreground text-center text-xs">
697+
Note ID: {note.id}
698+
</p>
699+
</div>
700+
<div className="flex gap-2">
701+
{onRefreshNote && (
702+
<Button
703+
onClick={() => onRefreshNote(note.id)}
704+
variant="outline"
705+
>
706+
<RefreshCw className="mr-2 h-4 w-4" />
707+
Refresh
708+
</Button>
709+
)}
710+
<Button
711+
onClick={() => {
712+
// Copy error details to clipboard for debugging
713+
navigator.clipboard.writeText(
714+
`Note ID: ${note.id}\nError: ${error}\nContent: ${note.content.substring(0, 200)}`
715+
);
716+
}}
717+
variant="outline"
718+
>
719+
Copy Debug Info
720+
</Button>
721+
</div>
722+
</div>
723+
);
724+
}
725+
726+
const handleUpdateCode = async (
727+
code: string,
728+
title: string,
729+
language: string,
730+
lastExecution?: {
731+
output: string;
732+
error?: string;
733+
executionTime: number;
734+
status?: { id: number; description: string };
735+
timestamp?: string;
736+
} | null
737+
) => {
738+
const newContent = JSON.stringify({ language, code, lastExecution });
739+
// Only update if content or title actually changed
740+
if (newContent !== note.content || title !== note.title) {
741+
await onUpdateNote(note.id, { content: newContent, title });
742+
}
743+
};
744+
745+
const handleMoveNoteCode = (noteId: string, updates: { folderId: string | null }) => {
746+
onUpdateNote(noteId, updates);
747+
};
748+
749+
return (
750+
<CodeEditor
751+
noteId={note.id}
752+
initialCode={parsedContent.code}
753+
initialLanguage={parsedContent.language}
754+
initialTitle={note.title}
755+
initialLastExecution={parsedContent.lastExecution}
756+
createdAt={note.createdAt}
757+
updatedAt={note.updatedAt}
758+
starred={note.starred}
759+
hidden={note.hidden}
760+
folders={folders}
761+
folderId={note.folderId}
762+
onSave={handleUpdateCode}
763+
onToggleStar={onToggleStar}
764+
starringStar={starringStar}
765+
onHideNote={onHideNote}
766+
onUnhideNote={onUnhideNote}
767+
hidingNote={hidingNote}
768+
onRefreshNote={onRefreshNote}
769+
onArchiveNote={onArchiveNote}
770+
onDeleteNote={onDeleteNote}
771+
onMoveNote={handleMoveNoteCode}
772+
onToggleNotesPanel={onToggleNotesPanel}
773+
isNotesPanelOpen={isNotesPanelOpen}
774+
/>
775+
);
776+
}
777+
649778
const currentFolder = getCurrentFolder();
650779

651780
return (

src/components/layout/MainLayout.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,8 @@ export default function MainLayout() {
8585

8686
const handleCreateDiagram = useCallback(async (templateCode?: string) => {
8787
try {
88-
// Use provided template code or default template
89-
const defaultDiagramCode = `graph TD
90-
A[Start] --> B{Is it working?}
91-
B -->|Yes| C[Great!]
92-
B -->|No| D[Debug]
93-
D --> B
94-
C --> E[End]`;
95-
96-
const content = templateCode || defaultDiagramCode;
88+
// If templateCode is provided, use it; otherwise create blank diagram
89+
const content = templateCode || '';
9790

9891
await createNote(undefined, {
9992
title: 'Untitled Diagram',
@@ -107,7 +100,26 @@ export default function MainLayout() {
107100
}
108101
}, [createNote, filesPanelOpen]);
109102

103+
const handleCreateCode = useCallback(async (templateData?: { language: string; code: string }) => {
104+
try {
105+
// If templateData is provided, use it; otherwise create blank code note
106+
const language = templateData?.language || 'javascript';
107+
const code = templateData?.code || '';
108+
109+
// Store both language and code in content as JSON
110+
const content = JSON.stringify({ language, code });
110111

112+
await createNote(undefined, {
113+
title: 'Untitled Code',
114+
content,
115+
type: 'code'
116+
});
117+
118+
if (!filesPanelOpen) setFilesPanelOpen(true);
119+
} catch (error) {
120+
console.error('Failed to create code note:', error);
121+
}
122+
}, [createNote, filesPanelOpen]);
111123

112124
const handleEmptyTrash = useCallback(async () => {
113125
try {
@@ -260,6 +272,7 @@ export default function MainLayout() {
260272
onToggleStar: toggleStar,
261273
onCreateNote: handleCreateNote,
262274
onCreateDiagram: handleCreateDiagram,
275+
onCreateCode: handleCreateCode,
263276
onToggleFolderPanel: handleToggleFolderPanel,
264277
onEmptyTrash: handleEmptyTrash,
265278
creatingNote,

0 commit comments

Comments
 (0)