1
1
import './style.css' ;
2
+ // Code Mirror (https://codemirror.net/)
3
+ // https://github.com/codemirror/codemirror
2
4
import CodeMirror from 'codemirror' ;
3
5
import 'codemirror/mode/python/python' ;
4
6
import 'codemirror/addon/comment/comment' ;
5
7
import 'codemirror/lib/codemirror.css' ;
6
8
9
+ // MarkedJs (https://marked.js.org/)
10
+ // Renders Markdown
11
+ // https://github.com/markedjs/marked
12
+ import marked from 'marked'
13
+
14
+ // KaTex (https://katex.org/)
15
+ // Renders Math
16
+ // https://github.com/KaTeX/KaTeX
17
+ import katex from "katex" ;
18
+ import "katex/dist/katex.min.css" ;
19
+
20
+ // Parses the code and splits it to chunks
21
+ // uses %% keyword for separators
22
+ // copied from iodide project
23
+ // https://github.com/iodide-project/iodide/blob/master/src/editor/iomd-tools/iomd-parser.js
24
+ import { iomdParser } from "./parser" ;
25
+
7
26
let rp ;
8
27
9
- // UI elements
10
- const consoleElement = document . getElementById ( 'console' ) ;
11
- const errorElement = document . getElementById ( 'error' ) ;
12
- const fetchbtnElement = document . getElementById ( "fetch-code" ) ;
13
- const urlConainerElement = document . getElementById ( 'url-container' ) ;
28
+ const notebook = document . getElementById ( 'rp-notebook' ) ;
29
+ const error = document . getElementById ( 'error' ) ;
14
30
15
31
// A dependency graph that contains any wasm must be imported asynchronously.
16
32
import ( 'rustpython' )
@@ -25,11 +41,11 @@ import('rustpython')
25
41
document . getElementById ( 'error' ) . textContent = e ;
26
42
} ) ;
27
43
28
- // Code Mirror code editor
44
+ // Code Editor
29
45
const editor = CodeMirror . fromTextArea ( document . getElementById ( 'code' ) , {
30
46
extraKeys : {
31
- 'Ctrl-Enter' : runCodeFromTextarea ,
32
- 'Cmd-Enter' : runCodeFromTextarea ,
47
+ 'Ctrl-Enter' : parseCodeFromEditor ,
48
+ 'Cmd-Enter' : parseCodeFromEditor ,
33
49
'Shift-Tab' : 'indentLess' ,
34
50
'Ctrl-/' : 'toggleComment' ,
35
51
'Cmd-/' : 'toggleComment' ,
@@ -41,61 +57,127 @@ const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
41
57
lineNumbers : true ,
42
58
mode : 'text/x-python' ,
43
59
indentUnit : 4 ,
44
- autofocus : true
60
+ autofocus : true ,
61
+ lineWrapping : true
45
62
} ) ;
46
63
47
- // Runs the code the the code editor
48
- function runCodeFromTextarea ( ) {
64
+ // Parses what is the code editor
65
+ // either runs python or renders math or markdown
66
+ function parseCodeFromEditor ( ) {
49
67
// Clean the console and errors
50
- consoleElement . innerHTML = '' ;
51
- errorElement . textContent = '' ;
68
+ notebook . innerHTML = '' ;
69
+ error . textContent = '' ;
70
+
71
+ // gets the code from code editor
72
+ let code = editor . getValue ( ) ;
73
+
74
+ /*
75
+ Split code into chunks.
76
+ Uses %%keyword or %% keyword as separator
77
+ Implemented %%py %%md %%math for python, markdown and math.
78
+ Returned object has:
79
+ - chunkContent, chunkType, chunkId,
80
+ - evalFlags, startLine, endLine
81
+ */
82
+ let parsed_code = iomdParser ( code ) ;
83
+
84
+ parsed_code . forEach ( chunk => {
85
+ // For each type of chunk, do somthing
86
+ // so far have py for python, md for markdown and math for math ;p
87
+ let content = chunk . chunkContent ;
88
+ switch ( chunk . chunkType ) {
89
+ case 'py' :
90
+ runPython ( content ) ;
91
+ break ;
92
+ case 'md' :
93
+ notebook . innerHTML += renderMarkdown ( content ) ;
94
+ break ;
95
+ case 'math' :
96
+ notebook . innerHTML += renderMath ( content , true ) ;
97
+ break ;
98
+ case 'math-inline' :
99
+ notebook . innerHTML += renderMath ( content , false )
100
+ break ;
101
+ default :
102
+ // by default assume this is python code
103
+ // so users don't have to type py manually
104
+ runPython ( code ) ;
105
+ }
106
+ } ) ;
107
+
108
+ }
52
109
53
- const code = editor . getValue ( ) ;
110
+ // Run Python code
111
+ function runPython ( code ) {
54
112
try {
55
113
rp . pyExec ( code , {
56
114
stdout : output => {
57
- consoleElement . innerHTML += output ;
115
+ notebook . innerHTML += output ;
58
116
}
59
117
} ) ;
60
118
} catch ( err ) {
61
119
if ( err instanceof WebAssembly . RuntimeError ) {
62
120
err = window . __RUSTPYTHON_ERROR || err ;
63
121
}
64
- errorElement . textContent = err ;
65
- console . error ( err ) ;
122
+ error . textContent = err ;
66
123
}
67
124
}
68
125
126
+ // Render Markdown with imported marked compiler
127
+ function renderMarkdown ( md ) {
128
+ // TODO: add error handling and output sanitization
129
+ let settings = {
130
+ headerIds : true ,
131
+ breaks : true
132
+ }
133
+
134
+ return marked ( md , settings ) ;
135
+ }
136
+
137
+ // Render Math with Katex
138
+ function renderMath ( math , display_mode ) {
139
+ // TODO: definetly add error handling.
140
+ return katex . renderToString ( math , {
141
+ displayMode : display_mode ,
142
+ "macros" : { "\\f" : "#1f(#2)" }
143
+ } ) ;
144
+ }
145
+
69
146
function onReady ( ) {
70
- document
71
- . getElementById ( 'run-btn' )
72
- . addEventListener ( 'click' , runCodeFromTextarea ) ;
73
147
74
- // so that the test knows that we're ready
148
+ /* By default the notebook has the keyword "loading"
149
+ once python and doc is ready:
150
+ create an empty div and set the id to 'rp_loaded'
151
+ so that the test knows that we're ready */
75
152
const readyElement = document . createElement ( 'div' ) ;
76
153
readyElement . id = 'rp_loaded' ;
77
154
document . head . appendChild ( readyElement ) ;
155
+ // set the notebook to empty
156
+ notebook . innerHTML = "" ;
78
157
}
79
158
159
+ // on click, parse the code
160
+ document . getElementById ( 'run-btn' ) . addEventListener ( 'click' , parseCodeFromEditor ) ;
161
+
80
162
// import button
81
163
// show a url input + fetch button
82
164
// takes a url where there is raw code
83
- fetchbtnElement . addEventListener ( "click" , function ( ) {
165
+ document . getElementById ( "fetch-code" ) . addEventListener ( "click" , function ( ) {
84
166
let url = document
85
167
. getElementById ( 'snippet-url' )
86
168
. value ;
87
169
// minimal js fetch code
88
- // needs better error handling
170
+ // TODO: better error handling
89
171
fetch ( url )
90
- . then ( response => {
172
+ . then ( response => {
91
173
if ( ! response . ok ) { throw response }
92
- return response . text ( )
174
+ return response . text ( )
93
175
} )
94
176
. then ( text => {
95
177
// set the value of the code editor
96
178
editor . setValue ( text ) ;
97
179
// hide the ui
98
- urlConainerElement . classList . add ( "d-none" ) ;
180
+ document . getElementById ( 'url-container' ) . classList . add ( "d-none" ) ;
99
181
} ) . catch ( err => {
100
182
// show the error as is for troubleshooting.
101
183
document
@@ -105,6 +187,8 @@ fetchbtnElement.addEventListener("click", function () {
105
187
106
188
} ) ;
107
189
190
+ // UI for the fetch button
191
+ // after clicking fetch, hide the UI
108
192
document . getElementById ( "snippet-btn" ) . addEventListener ( "click" , function ( ) {
109
- urlConainerElement . classList . remove ( "d-none" ) ;
193
+ document . getElementById ( 'url-container' ) . classList . remove ( "d-none" ) ;
110
194
} ) ;
0 commit comments