diff --git a/index.html b/index.html
index f52ac615b8..d53ae26010 100644
--- a/index.html
+++ b/index.html
@@ -52,7 +52,13 @@
-
+
+
+
diff --git a/js/js-export/interface.js b/js/js-export/interface.js
index a1fed72050..ad3caa5733 100644
--- a/js/js-export/interface.js
+++ b/js/js-export/interface.js
@@ -634,8 +634,1179 @@ class JSInterface {
return finalArgs;
}
+
+ _methodArgConstraints = {
+ // Rhythm blocks
+ playNote: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ playNoteMillis: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100000,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ dot: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 10,
+ integer: true
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ tie: [
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ multiplyNoteValue: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ swing: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ // Meter blocks
+ setMeter: [
+ {
+ type: "number",
+ constraints: {
+ min: 1,
+ max: 16,
+ integer: true
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ }
+ ],
+ PICKUP: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ }
+ ],
+ setBPM: [
+ {
+ type: "number",
+ constraints: {
+ min: 40,
+ max: 208,
+ integer: true
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 10,
+ integer: false
+ }
+ }
+ ],
+ setMasterBPM: [
+ {
+ type: "number",
+ constraints: {
+ min: 40,
+ max: 208,
+ integer: true
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 10,
+ integer: false
+ }
+ }
+ ],
+ onEveryNoteDo: [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ }
+ ],
+ onEveryBeatDo: [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ }
+ ],
+ onStrongBeatDo: [
+ {
+ type: "number",
+ constraints: {
+ min: 1,
+ max: 16,
+ integer: true
+ }
+ },
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ }
+ ],
+ onWeakBeatDo: [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ }
+ ],
+ setNoClock: [
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ getNotesPlayed: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ }
+ ],
+ // Pitch blocks
+ playPitch: [
+ {
+ type: "string",
+ constraints: {
+ type: "solfegeorletter"
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 1,
+ max: 8,
+ integer: true
+ }
+ }
+ ],
+ stepPitch: [
+ {
+ type: "number",
+ constraints: {
+ min: -7,
+ max: 7,
+ integer: true
+ }
+ }
+ ],
+ playNthModalPitch: [
+ {
+ type: "number",
+ constraints: {
+ min: -7,
+ max: 7,
+ integer: true
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 1,
+ max: 8,
+ integer: true
+ }
+ }
+ ],
+ playPitchNumber: [
+ {
+ type: "number",
+ constraints: {
+ min: -3,
+ max: 12,
+ integer: true
+ }
+ }
+ ],
+ playHertz: [
+ {
+ type: "number",
+ constraints: {
+ min: 20,
+ max: 20000,
+ integer: false
+ }
+ }
+ ],
+ setAccidental: [
+ {
+ type: "string",
+ constraints: {
+ type: "accidental"
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ setScalarTranspose: [
+ {
+ type: "number",
+ constraints: {
+ min: -10,
+ max: 10,
+ integer: true
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ setSemitoneTranspose: [
+ {
+ type: "number",
+ constraints: {
+ min: -10,
+ max: 10,
+ integer: true
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ setRegister: [
+ {
+ type: "number",
+ constraints: {
+ min: -3,
+ max: 3,
+ integer: true
+ }
+ }
+ ],
+ invert: [
+ {
+ type: "string",
+ constraints: {
+ type: "solfegeorletter"
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 1,
+ max: 8,
+ integer: true
+ }
+ },
+ {
+ type: "string",
+ constraints: {
+ type: "oneof",
+ values: ["even", "odd", "scalar"],
+ defaultIndex: 0
+ }
+ }
+ ],
+ setPitchNumberOffset: [
+ {
+ type: "string",
+ constraints: {
+ type: "solfegeorletter"
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 1,
+ max: 8,
+ integer: true
+ }
+ }
+ ],
+ numToPitch: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ numToOctave: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ // Intervals blocks
+ setKey: [
+ {
+ type: "string",
+ constraints: {
+ type: "letterkey"
+ }
+ },
+ {
+ type: "string",
+ constraints: {
+ type: "oneof",
+ values: [
+ "major",
+ "ionian",
+ "dorian",
+ "phrygian",
+ "lydian",
+ "myxolydian",
+ "minor",
+ "aeolian"
+ ],
+ defaultIndex: 0
+ }
+ }
+ ],
+ MOVABLEDO: [
+ {
+ type: "boolean"
+ }
+ ],
+ setScalarInterval: [
+ {
+ type: "number",
+ constraints: {
+ min: -7,
+ max: 7,
+ integer: true
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ setSemitoneInterval: [
+ {
+ type: "number",
+ constraints: {
+ min: -12,
+ max: 12,
+ integer: true
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ setTemperament: [
+ {
+ type: "string",
+ constraints: {
+ type: "oneof",
+ values: [
+ "equal",
+ "just intonation",
+ "Pythagorean",
+ "1/3 comma meantone",
+ "1/4 comma meantone"
+ ],
+ defaultIndex: 0
+ }
+ },
+ {
+ type: "string",
+ constraints: {
+ type: "solfegeorletter"
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 10,
+ integer: true
+ }
+ }
+ ],
+ // Tone blocks
+ setInstrument: [
+ {
+ type: "string",
+ constraints: {
+ type: "synth"
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ doVibrato: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 10,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 10,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ doChorus: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 10,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 10,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ doPhaser: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 20,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 3,
+ integer: true
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 20,
+ max: 20000,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ doTremolo: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 20,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ doDistortion: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ doHarmonic: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 11,
+ integer: true
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ // Ornament blocks
+ setStaccato: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ setSlur: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ doNeighbor: [
+ {
+ type: "number",
+ constraints: {
+ min: -7,
+ max: 7,
+ integer: true
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 1000,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ // Volume blocks
+ doCrescendo: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ doDecrescendo: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ PANNING: [
+ {
+ type: "number",
+ constraints: {
+ min: -100,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ MASTERVOLUME: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ setRelativeVolume: [
+ {
+ type: "number",
+ constraints: {
+ min: -50,
+ max: 50,
+ integer: false
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ setSynthVolume: [
+ {
+ type: "string",
+ constraints: {
+ type: "synth"
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: false
+ }
+ }
+ ],
+ getSynthVolume: [
+ {
+ type: "string",
+ constraints: {
+ type: "synth"
+ }
+ }
+ ],
+ // Drum blocks
+ playDrum: [
+ {
+ type: "string",
+ constraints: {
+ type: "drum"
+ }
+ }
+ ],
+ setDrum: [
+ {
+ type: "string",
+ constraints: {
+ type: "drum"
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ mapPitchToDrum: [
+ {
+ type: "string",
+ constraints: {
+ type: "drum"
+ }
+ },
+ {
+ type: "function",
+ constraints: {
+ async: true
+ }
+ }
+ ],
+ playNoise: [
+ {
+ type: "string",
+ constraints: {
+ type: "noise"
+ }
+ }
+ ],
+ // Graphics blocks
+ goForward: [
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ }
+ ],
+ goBackward: [
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ }
+ ],
+ turnRight: [
+ {
+ type: "number",
+ constraints: {
+ min: -360,
+ max: 360,
+ integer: false
+ }
+ }
+ ],
+ turnLeft: [
+ {
+ type: "number",
+ constraints: {
+ min: -360,
+ max: 360,
+ integer: false
+ }
+ }
+ ],
+ setXY: [
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ }
+ ],
+ setHeading: [
+ {
+ type: "number",
+ constraints: {
+ min: -360,
+ max: 360,
+ integer: false
+ }
+ }
+ ],
+ drawArc: [
+ {
+ type: "number",
+ constraints: {
+ min: -360,
+ max: 360,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100000,
+ integer: false
+ }
+ }
+ ],
+ drawBezier: [
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ }
+ ],
+ setBezierControlPoint1: [
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ }
+ ],
+ // setBezierControlPoint1: [
+ // {
+ // type: "number",
+ // constraints: {
+ // min: -100000,
+ // max: 100000,
+ // integer: false
+ // }
+ // },
+ // {
+ // type: "number",
+ // constraints: {
+ // min: -100000,
+ // max: 100000,
+ // integer: false
+ // }
+ // }
+ // ],
+ scrollXY: [
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: -100000,
+ max: 100000,
+ integer: false
+ }
+ }
+ ],
+ // Pen blocks
+ setColor: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ setGrey: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ setShade: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ setHue: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ setTranslucency: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ setPensize: [
+ {
+ type: "number",
+ constraints: {
+ min: 0,
+ max: 100,
+ integer: true
+ }
+ }
+ ],
+ // Dictionary blocks
+ setValue: [
+ [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: -1000,
+ max: 1000,
+ integer: true
+ }
+ }
+ ],
+ [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: -1000,
+ max: 1000,
+ integer: true
+ }
+ }
+ ],
+ [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ },
+ {
+ type: "undefined"
+ }
+ ]
+ ],
+ getValue: [
+ [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ },
+ {
+ type: "number",
+ constraints: {
+ min: -1000,
+ max: 1000,
+ integer: true
+ }
+ }
+ ],
+ [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ },
+ {
+ type: "undefined"
+ }
+ ]
+ ],
+ getDict: [
+ [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ },
+ {
+ type: "undefined"
+ }
+ ]
+ ],
+ showDict: [
+ [
+ {
+ type: "string",
+ constraints: {
+ type: "any"
+ }
+ },
+ {
+ type: "undefined"
+ }
+ ]
+ ]
+ }
}
if (typeof module !== "undefined" && module.exports) {
module.exports = JSInterface;
}
-
\ No newline at end of file
diff --git a/js/widgets/jseditor.js b/js/widgets/jseditor.js
index 9ecc9ac7f2..aa62099d65 100644
--- a/js/widgets/jseditor.js
+++ b/js/widgets/jseditor.js
@@ -65,11 +65,188 @@ class JSEditor {
return link;
});
this._styles[this._currentStyle].removeAttribute("disabled");
+ this._addErrorStyles();
this._setup();
this._setLinesCount(this._code);
}
+ /**
+ * Adds CSS styles for error highlighting
+ * @returns {void}
+ */
+ _addErrorStyles() {
+ if (document.getElementById("js-error-styles")) {
+ return;
+ }
+
+ const style = document.createElement("style");
+ style.id = "js-error-styles";
+ style.textContent = `
+ .error {
+ background-color: #ff4444 !important;
+ color: white !important;
+ border-radius: 2px;
+ padding: 1px 2px;
+ position: relative;
+ }
+
+ .hljs-keyword {
+ color: #007acc !important;
+ font-weight: bold;
+ }
+
+ .hljs-built_in {
+ color: #00d4aa !important;
+ }
+
+ .hljs-title.function_ {
+ color: #ffcc00 !important;
+ }
+
+ .hljs-number {
+ color: #4ec9b0 !important;
+ }
+
+ .hljs-string {
+ color: #ff8c00 !important;
+ }
+
+ .hljs-subst {
+ color: #ff8c00 !important;
+ background-color: rgba(255, 140, 0, 0.1) !important;
+ }
+
+ .hljs-comment {
+ color: #57a64a !important;
+ font-style: italic;
+ }
+
+ .hljs-title.class_ {
+ color: #c586c0 !important;
+ }
+
+ .hljs-variable {
+ color: #4fc1ff !important;
+ }
+
+ .hljs-params {
+ color: #4fc1ff !important;
+ }
+
+ .hljs-property {
+ color: #ff79c6 !important;
+ }
+
+ .hljs-literal {
+ color: #007acc !important;
+ }
+
+ .hljs-type {
+ color: #00d4aa !important;
+ }
+
+ .hljs-operator {
+ color: #ffffff !important;
+ }
+
+ .hljs-punctuation {
+ color: #cccccc !important;
+ }
+
+ .hljs-regexp {
+ color: #ff5555 !important;
+ }
+
+ .hljs-symbol {
+ color: #ffcc00 !important;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ /**
+ * Highlights syntax errors in the editor
+ * @param {HTMLElement} editor - the editor element
+ * @returns {void}
+ */
+ _highlightErrors(editor) {
+ const existingErrors = editor.querySelectorAll(".error");
+ existingErrors.forEach(el => {
+ const text = el.textContent;
+ el.replaceWith(text);
+ });
+
+ try {
+ const code = editor.textContent;
+ acorn.parse(code, { ecmaVersion: 2020 });
+ } catch (error) {
+ if (error.pos !== undefined) {
+ this._markErrorAtPosition(editor, error.pos, error.message);
+ }
+
+ JSEditor.logConsole(`Syntax Error at position ${error.pos}: ${error.message}`);
+ }
+ }
+
+ /**
+ * Marks an error at a specific position in the editor
+ * @param {HTMLElement} editor - the editor element
+ * @param {Number} position - the character position of the error
+ * @param {String} message - the error message
+ * @returns {void}
+ */
+ _markErrorAtPosition(editor, position, message) {
+ const text = editor.textContent;
+
+ let errorStart = position;
+ let errorEnd = position;
+
+ while (errorStart > 0) {
+ const char = text.charAt(errorStart - 1);
+ if (char === " " || char === "\n" || char === "\t" || char === ";" || char === "{" || char === "}" || char === "(" || char === ")" || char === ",") {
+ break;
+ }
+ errorStart--;
+ }
+
+ while (errorEnd < text.length) {
+ const char = text.charAt(errorEnd);
+ if (char === " " || char === "\n" || char === "\t" || char === ";" || char === "{" || char === "}" || char === "(" || char === ")" || char === ",") {
+ break;
+ }
+ errorEnd++;
+ }
+
+ if (errorStart === errorEnd) {
+ errorEnd = Math.min(errorStart + 1, text.length);
+ }
+
+ this._markErrorSpan(editor, errorStart, errorEnd, message);
+ }
+
+ /**
+ * Marks an error span in the editor with a simple approach
+ * @param {HTMLElement} editor - the editor element
+ * @param {Number} start - the start position of the error
+ * @param {Number} end - the end position of the error
+ * @param {String} message - the error message
+ * @returns {void}
+ */
+ _markErrorSpan(editor, start, end, message) {
+ const text = editor.textContent;
+ const errorText = text.substring(start, end);
+
+ const beforeError = text.substring(0, start);
+ const afterError = text.substring(end);
+
+ const highlightedHTML = beforeError +
+ `${errorText}` +
+ afterError;
+
+ editor.innerHTML = highlightedHTML;
+ }
+
/**
* Renders the editor and all the subcomponents in the DOM.
* Sets up CodeJar.
@@ -277,7 +454,7 @@ class JSEditor {
const codebox = document.createElement("div");
codebox.classList.add("editor");
- codebox.classList.add("language-js");
+ codebox.classList.add("language-javascript");
codebox.style.width = "100%";
codebox.style.height = "100%";
codebox.style.position = "absolute";
@@ -352,8 +529,16 @@ class JSEditor {
this._editor.appendChild(editorconsole);
const highlight = (editor) => {
- // editor.textContent = editor.textContent;
- hljs.highlightBlock(editor);
+ // Configure highlight.js for JavaScript
+ hljs.configure({
+ languages: ["javascript"]
+ });
+
+ // Apply highlight.js syntax highlighting for JavaScript
+ hljs.highlightElement(editor);
+
+ // Add error highlighting
+ this._highlightErrors(editor);
};
this._jar = new CodeJar(codebox, highlight);
@@ -478,11 +663,22 @@ class JSEditor {
JSEditor.clearConsole();
+ try {
+ acorn.parse(this._code, { ecmaVersion: 2020 });
+ } catch (e) {
+ JSEditor.logConsole(`Syntax Error: ${e.message}`, "red");
+ return;
+ }
+
try {
MusicBlocks.init(true);
new Function(this._code)();
+ JSEditor.logConsole("Code executed successfully!", "green");
} catch (e) {
- JSEditor.logConsole(e, "maroon");
+ JSEditor.logConsole(`Runtime Error: ${e.message}`, "maroon");
+ if (e.stack) {
+ JSEditor.logConsole(`Stack trace: ${e.stack}`, "maroon");
+ }
}
}