11import './style.css' ;
2+ // Code Mirror (https://codemirror.net/)
3+ // https://github.com/codemirror/codemirror
24import CodeMirror from 'codemirror' ;
35import 'codemirror/mode/python/python' ;
46import 'codemirror/addon/comment/comment' ;
57import 'codemirror/lib/codemirror.css' ;
68
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+
726let rp ;
827
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' ) ;
1430
1531// A dependency graph that contains any wasm must be imported asynchronously.
1632import ( 'rustpython' )
@@ -25,11 +41,11 @@ import('rustpython')
2541 document . getElementById ( 'error' ) . textContent = e ;
2642 } ) ;
2743
28- // Code Mirror code editor
44+ // Code Editor
2945const editor = CodeMirror . fromTextArea ( document . getElementById ( 'code' ) , {
3046 extraKeys : {
31- 'Ctrl-Enter' : runCodeFromTextarea ,
32- 'Cmd-Enter' : runCodeFromTextarea ,
47+ 'Ctrl-Enter' : parseCodeFromEditor ,
48+ 'Cmd-Enter' : parseCodeFromEditor ,
3349 'Shift-Tab' : 'indentLess' ,
3450 'Ctrl-/' : 'toggleComment' ,
3551 'Cmd-/' : 'toggleComment' ,
@@ -41,61 +57,127 @@ const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
4157 lineNumbers : true ,
4258 mode : 'text/x-python' ,
4359 indentUnit : 4 ,
44- autofocus : true
60+ autofocus : true ,
61+ lineWrapping : true
4562} ) ;
4663
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 ( ) {
4967 // 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+ }
52109
53- const code = editor . getValue ( ) ;
110+ // Run Python code
111+ function runPython ( code ) {
54112 try {
55113 rp . pyExec ( code , {
56114 stdout : output => {
57- consoleElement . innerHTML += output ;
115+ notebook . innerHTML += output ;
58116 }
59117 } ) ;
60118 } catch ( err ) {
61119 if ( err instanceof WebAssembly . RuntimeError ) {
62120 err = window . __RUSTPYTHON_ERROR || err ;
63121 }
64- errorElement . textContent = err ;
65- console . error ( err ) ;
122+ error . textContent = err ;
66123 }
67124}
68125
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+
69146function onReady ( ) {
70- document
71- . getElementById ( 'run-btn' )
72- . addEventListener ( 'click' , runCodeFromTextarea ) ;
73147
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 */
75152 const readyElement = document . createElement ( 'div' ) ;
76153 readyElement . id = 'rp_loaded' ;
77154 document . head . appendChild ( readyElement ) ;
155+ // set the notebook to empty
156+ notebook . innerHTML = "" ;
78157}
79158
159+ // on click, parse the code
160+ document . getElementById ( 'run-btn' ) . addEventListener ( 'click' , parseCodeFromEditor ) ;
161+
80162// import button
81163// show a url input + fetch button
82164// takes a url where there is raw code
83- fetchbtnElement . addEventListener ( "click" , function ( ) {
165+ document . getElementById ( "fetch-code" ) . addEventListener ( "click" , function ( ) {
84166 let url = document
85167 . getElementById ( 'snippet-url' )
86168 . value ;
87169 // minimal js fetch code
88- // needs better error handling
170+ // TODO: better error handling
89171 fetch ( url )
90- . then ( response => {
172+ . then ( response => {
91173 if ( ! response . ok ) { throw response }
92- return response . text ( )
174+ return response . text ( )
93175 } )
94176 . then ( text => {
95177 // set the value of the code editor
96178 editor . setValue ( text ) ;
97179 // hide the ui
98- urlConainerElement . classList . add ( "d-none" ) ;
180+ document . getElementById ( 'url-container' ) . classList . add ( "d-none" ) ;
99181 } ) . catch ( err => {
100182 // show the error as is for troubleshooting.
101183 document
@@ -105,6 +187,8 @@ fetchbtnElement.addEventListener("click", function () {
105187
106188} ) ;
107189
190+ // UI for the fetch button
191+ // after clicking fetch, hide the UI
108192document . getElementById ( "snippet-btn" ) . addEventListener ( "click" , function ( ) {
109- urlConainerElement . classList . remove ( "d-none" ) ;
193+ document . getElementById ( 'url-container' ) . classList . remove ( "d-none" ) ;
110194} ) ;
0 commit comments