Skip to content

Instantly share code, notes, and snippets.

@uniqname
Last active April 1, 2024 18:32
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save uniqname/479a40788164e3f9332f to your computer and use it in GitHub Desktop.
Save uniqname/479a40788164e3f9332f to your computer and use it in GitHub Desktop.
JS REPL
<output></output>
<div class="custextareasdontdopseudoelements">
<textarea name="repl" id="repl"></textarea>
</div>

JS REPL

There's not a good, simple embeddable REPL for JS out there that I could find. I wanted one for presentations on JavaScript concepts. The developer tools' console would be ideal, but bailing out of my presentation and opening a console brakes the flow. Tools like codepen.io et al. are great, but more overhead and set up than I need. I just want to type JS and show the result. This is a decent first step for my needs.

A Pen by Cory on CodePen.

License.

(function () {
'use strict';
var parseQuery = function () {
var search = window.location.search,
q = {};
search.slice(1).split('&').forEach(function (term) {
var kv = term.split('=');
q[kv[0]] = (kv[1] !== undefined) ? decodeURIComponent(kv[1]) : true;
});
return q;
},
evalHeight = function (input) {
input.style.height = input.value.split('\n').length + 'em';
},
log = (function() {
var console = document.querySelector('output');
return function () {
var rep = document.createElement('div');
rep.classList.add('rep');
[].slice.call(arguments).forEach(function (arg) {
var entry = document.createElement('pre'),
result = document.createElement('pre'),
evaled,
resultType;
entry.classList.add('entry'); result.classList.add('result');
try {
evaled = eval.call(this, arg);
} catch (e) {
evaled = e.toString();
}
if (evaled === undefined) {
evaled = 'undefined';
resultType = 'undefined';
} else if (evaled === null) {
evaled = 'null';
resultType = 'null';
} else {
resultType = ({}).toString.call(evaled).replace('[object ', '').replace(']', '').toLowerCase();
}
if (resultType === 'object' || resultType === 'array') {
try {
evaled = JSON.stringify(evaled);
} catch (e) {
}
}
result.classList.add(resultType);
if (resultType === 'string') {
evaled = '"' + evaled + '"';
}
stack.unshift(arg);
entry.textContent = arg;
result.textContent = evaled;
rep.appendChild(entry);
rep.appendChild(result);
});
console.appendChild(rep);
}
})(),
input = document.querySelector('textarea'),
stack = [],
q = parseQuery();
input.addEventListener('keypress', function (evt) {
var key = evt.which,
retVal;
if (key === 13 && !evt.shiftKey) {
log(input.value);
input.value = '';
input.removeAttribute('style');
}
// TODO: implement stack navigation.
});
input.addEventListener('keyup', function (evt) {
if (evt.which === 13) {
if (!evt.shiftKey) {
input.value = '';
} else {
evalHeight(input);
}
}
// Backspace
if (evt.which === 8) {
input.style.height = input.value.split('\n').length + 'em';
}
});
input.addEventListener('keydown', function (evt) {
var val,
beforeTab,
afterTab,
caratPos;
// Tabs
if (evt.which === 9 ) {
evt.preventDefault();
val = input.value;
caratPos = input.selectionStart;
beforeTab = val.substring(0, input.selectionStart);
afterTab = val.substring(input.selectionEnd);
input.value = beforeTab + '\t' + afterTab;
input.setSelectionRange(caratPos + 1, caratPos + 1);
}
});
if (q.prime) {
input.value = q.prime;
evalHeight(input);
}
})();
(function () {
'use strict';
var parseQuery = function () {
var search = window.location.search,
q = {};
search.slice(1).split('&').forEach(function (term) {
var kv = term.split('=');
q[kv[0]] = (kv[1] !== undefined) ? decodeURIComponent(kv[1]) : true;
});
return q;
},
evalHeight = function (input) {
if (input.value === '') {
input.removeAttribute('style');
} else {
input.style.height = input.scrollHeight + 'px';
}
},
log = (function() {
var console = document.querySelector('output');
return function () {
var rep = document.createElement('div');
rep.classList.add('rep');
[].slice.call(arguments).forEach(function (arg) {
var entry = document.createElement('pre'),
result = document.createElement('pre'),
evaled,
resultType;
inputHistory.push(arg);
entry.classList.add('entry');
result.classList.add('result');
try {
evaled = eval.call(this, arg);
} catch (e) {
evaled = e.toString();
}
if (evaled === undefined) {
evaled = 'undefined';
resultType = 'undefined';
} else if (evaled === null) {
evaled = 'null';
resultType = 'null';
} else {
resultType = ({}).toString.call(evaled).replace('[object ', '').replace(']', '').toLowerCase();
}
if (resultType === 'object' || resultType === 'array') {
try {
evaled = JSON.stringify(evaled);
} catch (e) {
}
}
result.classList.add(resultType);
if (resultType === 'string') {
evaled = '"' + evaled + '"';
}
entry.textContent = arg;
result.textContent = evaled;
rep.appendChild(entry);
rep.appendChild(result);
});
console.appendChild(rep);
}
})(),
input = document.querySelector('textarea'),
inputHistory = (function() {
var stack = [],
partialVal,
resetCurrentIndex = function () {
currentIndex = -1; //-1 represents off the stack
},
currentIndex = resetCurrentIndex();
return {
push: function () {
stack.unshift.apply(stack, [].slice.call(arguments));
partialVal = '';
resetCurrentIndex();
},
next: function () {
if (currentIndex <= 0) {
resetCurrentIndex();
return partialVal;
} else {
currentIndex -= 1;
return stack[currentIndex];
}
},
prev: function () {
if (currentIndex >= stack.length) {
currentIndex = stack.length - 1;
return stack[currentIndex];
} else {
currentIndex += 1;
if (currentIndex === 0) {
partialVal = input.value;
}
return stack[currentIndex];
}
}
};
})(),
q = parseQuery(),
setInputValue = function (input, val) {
input.value = val;
evalHeight(input);
},
cursorPos = function (input) {
var start = input.selectionStart,
end = input.selectionEnd;
return {
start: start,
end: end
};
};
input.addEventListener('keydown', function (evt) {
var key = evt.which,
first = 0,
last = input.value.length,
pos = cursorPos(input),
val,
beforeTab,
afterTab,
caratPos;
if (evt.which === 9 ) { //Tab
evt.preventDefault();
val = input.value;
caratPos = input.selectionStart;
beforeTab = val.substring(0, input.selectionStart);
afterTab = val.substring(input.selectionEnd);
setInputValue(input, beforeTab + '\t' + afterTab);
input.setSelectionRange(caratPos + 1, caratPos + 1);
} else if (key === 13) { //enter
if (!evt.shiftKey) {
log(input.value);
//annoyed
setTimeout(function () {
setInputValue(input, '');
}, 0);
}
} else if (key === 38) { //up arrow
if (pos.start === first && pos.end === first) {
setInputValue(input, inputHistory.prev());
}
} else if (key === 40) { //down arrow
if (pos.start === last && pos.end === last) {
setInputValue(input, inputHistory.next());
}
}
});
input.addEventListener('input', function (evt) {
evalHeight(input);
});
if (q.prime) {
setInputValue(input, q.prime);
evalHeight(input);
}
})();
html {
background: hsla(0, 100%, 100%, .4);
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
}
body {
background:transparent;
display: flex;
flex-flow: column nowrap;
flex-direction: column;
font-family: monospace;
font-size: 2em;
position: relative;
min-height: 100vh;
margin: 0;
justify-content: flex-end;
}
.custextareasdontdopseudoelements {
display: flex;
flex-flow: row nowrap;
border-top: 1px solid #ccc;
padding: .25em;
/* position: absolute;
bottom: 0;*/
width: 100%;
background-color: #eee;
&::before {
content: '>';
display: block;
color: #ccc;
padding: .25em 0;
}
}
#repl {
height: 1.5em;
max-height: 80vh;
font-size: 1em;
border: none;
padding: .25em 0 .25em 1em;
font-family: monospace;
flex: 1 1 100%;
background: transparent;
tab-size: 4;
&:focus {
outline: none;
}
}
.rep {
margin: 1em 0;
}
.entry,
.result {
padding-left: 1em;
position: relative;
margin: 0;
&::before {
position: absolute;
left: .125em;
}
}
.entry {
color: hsla(200, 50%, 50%, 1);
&::before {
content: '>';
}
}
.result {
color: #333;
&::before {
color: hsla(200, 50%, 50%, 1);
content: '<';
}
}
.undefined,
.null {
color: #999;
}
.string {
color: hsl(10, 50%, 50%);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment