11import { component } from "../model/components.ts" ;
2- import { Node } from "../model/mod.ts" ;
3-
4-
2+ import { Workbench , Context } from "../workbench/mod.ts" ;
53export interface CodeExecutor {
64 // executes the source and returns an output string.
75 // exceptions in execution should be caught and returned as a string.
8- async execute ( source : string , options : ExecuteOptions ) : string ;
6+ execute ( source : string , options : ExecuteOptions ) : Promise < string > ;
97
108 canExecute ( options : ExecuteOptions ) : boolean ;
119}
@@ -16,22 +14,25 @@ export interface ExecuteOptions {
1614
1715// defaultExecutor can be replaced with an external service, etc
1816export let defaultExecutor : CodeExecutor = {
19- async execute ( source : string , options : ExecuteOptions ) : Promise < string > {
17+ async execute (
18+ source : string ,
19+ options : ExecuteOptions
20+ ) : Promise < string > {
2021 if ( options . language !== "javascript" ) {
2122 return `Unsupported language: ${ options . language } ` ;
2223 }
23- return JSON . stringify ( window . eval ( source ) ) ;
24+ let output = window . eval ( source ) ;
25+ //return JSON.stringify(output);
26+ return output . toString ( ) ;
2427 } ,
2528
2629 canExecute ( options : ExecuteOptions ) : boolean {
2730 if ( options . language === "javascript" ) {
2831 return true ;
2932 }
3033 return false ;
31- }
32- }
33-
34-
34+ } ,
35+ } ;
3536
3637@component
3738export class CodeBlock {
@@ -44,12 +45,23 @@ export class CodeBlock {
4445 }
4546
4647 childrenView ( ) {
47- return CodeEditor ;
48+ return CodeEditorWithOutput ;
4849 }
4950
5051 handleIcon ( collapsed : boolean = false ) : any {
5152 return (
52- < svg xmlns = "http://www.w3.org/2000/svg" width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" class = "node-bullet" >
53+ < svg
54+ xmlns = "http://www.w3.org/2000/svg"
55+ width = "15"
56+ height = "15"
57+ viewBox = "0 0 24 24"
58+ fill = "none"
59+ stroke = "currentColor"
60+ stroke-width = "2"
61+ stroke-linecap = "round"
62+ stroke-linejoin = "round"
63+ class = "node-bullet"
64+ >
5365 < polyline points = "16 18 22 12 16 6" > </ polyline >
5466 < polyline points = "8 6 2 12 8 18" > </ polyline >
5567 </ svg >
@@ -63,45 +75,91 @@ export class CodeBlock {
6375 when : ( ctx : Context ) => {
6476 if ( ! ctx . node ) return false ;
6577 if ( ctx . node . raw . Rel === "Fields" ) return false ;
66- if ( ctx . node . parent && ctx . node . parent . hasComponent ( Document ) ) return false ;
78+ if ( ctx . node . parent && ctx . node . parent . hasComponent ( Document ) )
79+ return false ;
6780 return true ;
6881 } ,
6982 action : ( ctx : Context ) => {
7083 const com = new CodeBlock ( ) ;
71- ctx . node . addComponent ( com ) ;
72- ctx . node . changed ( ) ;
73- workbench . workspace . setExpanded ( ctx . path . head , ctx . path . node , true ) ;
74- }
84+ if ( ctx ?. node ) {
85+ ctx . node . addComponent ( com ) ;
86+ ctx . node . changed ( ) ;
87+ workbench . workspace . setExpanded (
88+ ctx . path . head ,
89+ ctx . path . node ,
90+ true
91+ ) ;
92+ }
93+ } ,
7594 } ) ;
7695 }
77-
7896}
7997
80-
8198const CodeEditor = {
82- oncreate ( { dom, attrs : { path} } ) {
99+ oncreate ( vnode ) {
100+ const {
101+ dom,
102+ attrs : { path } ,
103+ } = vnode ;
83104 const snippet = path . node . getComponent ( CodeBlock ) ;
105+
106+ //@ts -ignore
84107 dom . jarEditor = new window . CodeJar ( dom , ( editor ) => {
85108 // highlight.js does not trim old tags,
86109 // let's do it by this hack.
87110 editor . textContent = editor . textContent ;
111+ //@ts -ignore
88112 window . hljs . highlightBlock ( editor ) ;
89- snippet . language = window . hljs . highlightAuto ( editor . textContent ) . language || "" ;
113+ snippet . language =
114+ //@ts -ignore
115+ window . hljs . highlightAuto ( editor . textContent ) . language || "" ;
90116 } ) ;
91117 dom . jarEditor . updateCode ( snippet . code ) ;
92- dom . jarEditor . onUpdate ( code => {
118+ dom . jarEditor . onUpdate ( ( code ) => {
93119 snippet . code = code ;
94120 path . node . changed ( ) ;
95121 } ) ;
96122 } ,
97123
98- view ( { attrs : { workbench, path} } ) {
124+ view ( { attrs : { workbench, path } } ) {
99125 // this cancels the keydown on the outline node
100126 // so you can use arrow keys normally
101127 const onkeydown = ( e ) => e . stopPropagation ( ) ;
102-
128+
129+ return < div class = "code-editor" onkeydown = { onkeydown } > </ div > ;
130+ } ,
131+ } ;
132+
133+ const Output = {
134+ view ( { dom, state, attrs : { path } } ) {
135+ const snippet = path . node . getComponent ( CodeBlock ) ;
136+
137+ let handleClick = async ( ) => {
138+ state . output = "Running..." ;
139+ try {
140+ const res = await defaultExecutor . execute ( snippet . code , {
141+ language : snippet . language ,
142+ } ) ;
143+
144+ // Update output using m.prop to ensure it's persistent across re-renders
145+ state . output = res ; // Call m.prop with the new value
146+ } catch ( error ) {
147+ state . output = error . toString ( ) ;
148+ }
149+ } ;
103150 return (
104- < div class = "code-editor" onkeydown = { onkeydown } > </ div >
105- )
151+ < div className = "code-editor-output" >
152+ < p > { state . output ? "Output: " + state . output : "" } </ p >
153+ < button type = "button" onclick = { handleClick } >
154+ Run
155+ </ button >
156+ </ div >
157+ ) ;
158+ } ,
159+ } ;
160+
161+ class CodeEditorWithOutput {
162+ view ( vnode ) {
163+ return [ m ( CodeEditor , vnode . attrs ) , m ( Output , vnode . attrs ) ] ;
106164 }
107- }
165+ }
0 commit comments