@webwriter/code
Version:
Write and run code as a code cell. Supports several languages (HTML/CSS/JS, TypeScript, Python).
167 lines (151 loc) • 5.16 kB
text/typescript
import { javascript } from '@codemirror/lang-javascript';
import Code from '../ww-code-javascript';
import workerurl from './jsWorker'
// bind function to code cell
// capture console calls
// Redirect results to code cell output
const javascriptWorker: Worker = new Worker(workerurl, {type: "classic"})
const callbacks: object = {};
javascriptWorker.onmessage = (event) => {
const { id, ...data } = event.data;
const onSuccess = callbacks[id];
delete callbacks[id];
onSuccess(data);
};
const asyncRun = (() => {
let id = 0;
return (script: any, context: any) => {
id = (id + 1) % Number.MAX_SAFE_INTEGER;
return new Promise((onSuccess) => {
callbacks[id] = onSuccess;
javascriptWorker.postMessage({
...context,
code: script,
id,
});
});
};
})();
const executeJavascript = async (code: string, context: Code) => {
const oldConsole = window.console;
window.console = customConsole(context);
try {
const result = context.globalExecution ? unScopeEval(code, context) : await asyncRun(code, "undefined");
context.globalExecution ? {} : JSON.parse(result["results"]).forEach(entry => {if(!context.results.includes(entry)){context.results.push(entry)}});
return result;
} catch (e) {
context.results.push({ color: 'red', text: e.message });
return e;
} finally {
window.console = oldConsole;
}
context.results = [...context.results];
};
function scopedEval(code: string, context: Code) {
return Function(`
const codeToExecute = [${code
.split('\n')
.map((line) => `'${line.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`)
.join(',')}];
return eval(codeToExecute.join("\\n"));
`)();
}
function unScopeEval(code: string, context: Code) {
const st = document.createElement('script');
st.innerHTML = code;
if (context.runAsModule) {
st.type = 'module';
}
document.body.appendChild(st);
document.body.removeChild(st);
return undefined;
}
export const javascriptModule = {
name: 'JS',
executionFunction: executeJavascript,
languageExtension: javascript(),
};
function customConsole(context: Code) {
return {
log: (...objs: any[]) => {
objs.forEach((obj) =>
context.results.push({
color: 'inherit',
text: typeof obj === 'string' ? String(obj) : JSON.stringify(obj),
})
);
},
info: (...objs: any[]) => {
objs.forEach((obj) =>
context.results.push({
color: 'inherit',
text: typeof obj === 'string' ? String(obj) : JSON.stringify(obj),
})
);
},
warn: (...objs: any[]) => {
objs.forEach((obj) =>
context.results.push({
color: 'orange',
text: typeof obj === 'string' ? String(obj) : JSON.stringify(obj),
})
);
},
error: (...objs: any[]) => {
objs.forEach((obj) =>
context.results.push({
color: 'red',
text: typeof obj === 'string' ? String(obj) : JSON.stringify(obj),
})
);
},
trace: (...objs: any[]) => {
objs.forEach((obj) =>
context.results.push({
color: 'inherit',
text: typeof obj === 'string' ? String(obj) : JSON.stringify(obj),
})
);
},
table: (...objs: any[]) => {
objs.forEach((obj) =>
context.results.push({
color: 'inherit',
text: typeof obj === 'string' ? String(obj) : JSON.stringify(obj),
})
);
},
assert: (assertion: boolean, ...objs: any[]) => {
assertion &&
objs.forEach((obj) =>
context.results.push({
color: 'inherit',
text: typeof obj === 'string' ? String(obj) : JSON.stringify(obj),
})
);
},
clear: () => {},
count: () => {},
countReset: () => {},
debug: (...objs: any[]) => {
objs.forEach((obj) =>
context.results.push({
color: 'inherit',
text: typeof obj === 'string' ? String(obj) : JSON.stringify(obj),
})
);
},
dir: () => {},
dirxml: () => {},
group: () => {},
groupCollapsed: () => {},
groupEnd: () => {},
profile: () => {},
profileEnd: () => {},
time: () => {},
timeEnd: () => {},
timeLog: () => {},
timeStamp: () => {},
Console: window.console.Console,
};
}