Stack Snippets are now live on the CR Meta and Main site too. Go on, play with it!
Note that the feature is now live on the main site, and also there is a mini competition (for bragging rights) to see who can take advantage of it really well...
Post an answer, impress us with your prowess; try to push it to its limits, find bugs and break the feature!
-
\$\begingroup\$ Related: Stack Snippets sandbox on Meta Stack Overflow \$\endgroup\$Mathieu Guindon– Mathieu Guindon2014年09月06日 04:03:58 +00:00Commented Sep 6, 2014 at 4:03
-
1\$\begingroup\$ One comment : the edit button should not be styled differently, it should have the same white on black button. \$\endgroup\$konijn– konijn2014年09月08日 15:04:32 +00:00Commented Sep 8, 2014 at 15:04
-
1\$\begingroup\$ @konijn - Snippets now live on main site too. \$\endgroup\$rolfl– rolfl Mod2014年09月11日 02:49:36 +00:00Commented Sep 11, 2014 at 2:49
-
\$\begingroup\$ For \$\LaTeX\$/mathjax see this. \$n=1\$. \$\endgroup\$Luuklag– Luuklag2020年06月26日 07:40:18 +00:00Commented Jun 26, 2020 at 7:40
3 Answers 3
Hello, World!
This is just a very quick overview - see the Feedback Requested post on Meta Stack Overflow for more information.
Markdown
Use begin snippet
and end snippet
HTML comments to denote the beginning and ending of a snipped, and specify the language with the usual language
comment:
<!-- begin snippet: js hide: false -- > <!-- language: lang-js -- > alert("hello, world!"); <!-- end snippet -- >
...or just click click the "Code snippet" button on the edit toolbar, right next to the "Image" button. Shortcut: Ctrl+M.
Render
The snippet block will render as an everyday code block, with a Run code snippet button:
alert("hello, world!");
Output
These executable snippets can be embedded anywhere in your question/answer:
alert("so each snippet gets its own 'Run code snippet' button.");
document.write("...and its own little output box.");
When editing a post with an embedded stack-snippet, an [Edit the above snippet] link brings the snippet editor, which also lets you enter html and css:
.foo {
background: yellow
}
<div class="foo">
foo
</div>
-
3\$\begingroup\$ That's the best you can do? \$\endgroup\$2014年09月06日 02:01:55 +00:00Commented Sep 6, 2014 at 2:01
-
7\$\begingroup\$ @rolfl anything else I'll have to post for review! ..and I bet you clicked that
[Run code snippet]
button anyway ;) \$\endgroup\$Mathieu Guindon– Mathieu Guindon2014年09月06日 02:49:27 +00:00Commented Sep 6, 2014 at 2:49
When using these snippets in my recent question, I wrote the entire snippet from scratch inside the Stack Exchange editor, so I used it quite a lot. From my usage, I have a few primary issues:
AUTOSAVE. After about one hour of editing and writing JavaScript I ran into yet another one of the JavaScript traps. I accidentally made an infinite loop (
someVar++
on the wrong variable...Whoops). After a few minutes my browser used 3 GB memory. There goes that process out the window. Had to re-code the entire thing from scratch.Ctrl + Shift, or simply just Shift. I don't know exactly when, or how, but sometimes the Shift button just isn't working. Pressing Shift + End is the same as pressing End. The text gets selected a bit first but quickly is deselected. Being at the start of the snippet and pressing Ctrl + Shift + End selects all the code breifly, and then replaces all of it with a single '-'. Using Ctrl + Z here did not help much, it only returned the code breifly but then replaced it with '-' again. Pressing it twice however, seemed to help. This is one nasty bug that I'm not really sure what it can possibly be caused by, but I'll gladly help you investigate it as much as I can.
Additionally, if there was a way to show the JavaScript errors without having to use the Web Developer Console, that would be very helpful. (or perhaps I'm spoiled with Eclipse and writing Java code all the time...)
-
\$\begingroup\$ Also the full-screen view looks funny on mobile.. and plays oddly with zooming. \$\endgroup\$Mathieu Guindon– Mathieu Guindon2014年09月11日 00:53:52 +00:00Commented Sep 11, 2014 at 0:53
Hmm, the code from this plunker and question does not run.
For some reason, it thinks controller.onContentLoaded
is not a function..
As per James Khoury a semicolon solved my problem, my new problem is that I clearly had a blind spot for this since I have never encountered this before..
function Box( cell1 , cell2 )
{ //Constructor for box instances by using 2 cells
//Find the upper left corner
var x1 = Math.min( cell1.x , cell2.x );
var y1 = Math.min( cell1.y , cell2.y );
//Find the bottom right corner
var x2 = Math.max( cell1.x , cell2.x );
var y2 = Math.max( cell1.y , cell2.y );
//Set the normalized from & to
this.from = { x : x1, y : y1 };
this.to = { x : x2, y : y2 };
}
Box.prototype.each = function( f )
{ //Loop over each cell, call f with an x,y object
with (this)
for( var x = from.x ; x <= to.x ; x++ )
for( var y = from.y ; y <= to.y ; y++ )
f( { x : x , y : y } );
}
Box.prototype.eachRow = function( f )
{ //Loop over each row, call f
with (this)
for( var y = from.y ; y <= to.y ; y++ )
f( y );
}
//Loop over each column, call f
Box.prototype.eachColumn = function( f )
{ //Loop over each row, call f
with (this)
for( var x = from.x ; x <= to.x ; x++ )
f( x );
}
function Cursor( x , y )
{ //Constructor for a cursor instance
this.x = Math.max( x || 0 , 0 );
this.y = Math.max( y || 0 , 0 );
}
Cursor.prototype.advance = function()
{ //Infinite columns!
this.x++;
}
Cursor.prototype.recede = function()
{ //Go left if you can, otherwise go up
this.x ? this.x-- : this.up();
}
Cursor.prototype.up = function()
{ //Go up if you can, undefined is NOOP
this.y ? this.y-- : undefined;
}
Cursor.prototype.down = function()
{ //Infinite rows!
this.y++;
}
/* Unidraw, because we can*/
//Documentation:
// http://en.wikipedia.org/wiki/Box-drawing_character
//Competition:
// http://www.asciidraw.com/#Draw
// http://asciiflow.com/
;(function IIFE(){
"use strict";
var canvas,
context,
clipboard;
var model = (function()
{
//Privates
var cells = [],
tabSize = 4;
//Exposed
var cursor = new Cursor();
function write( x, y, s )
{
//Make sure that we have an array for y
//Always assume overwrite mode
var originalX = x;
cells[y] = cells[y] || [];
for( var i = 0; i < s.length ; i++)
{
var c = s[i];
if( c.charCodeAt(0) > 31 )
{
cells[y][x++] = s[i];
}
else if ( c == "\n" )
{
y++;
cells[y] = cells[y] || [];
x = originalX;
}
else if ( c == '\t' )
{
x += tabSize;
}
}
return new Cursor( x, y );
}
function setCell( cursor , c )
{
return write( cursor.x , cursor.y , c );
}
function getCell( cursor )
{
return cells[cursor.y] ? cells[cursor.y][cursor.x] || " " : " ";
}
function stringify()
{
var s = '', x, y;
for( y = 0 ; y < cells.length ; y++ )
{
if( cells[y] )
for( x = 0 ; x < cells[y].length ; x++ )
s = s + ( cells[y][x] || " " );
s = s + '\n';
}
return s?s:" ";
}
function backspace()
{ //Move everything one character to the left of the cursor
if( cells[ model.cursor.y ] )
cells[ model.cursor.y ].splice( model.cursor.x-1 , 1 );
model.cursor.recede();
}
function addVersion( key )
{ //Called internally. add a version to a version array (found with `key`)
var json = localStorage[key];
var versions = json ? JSON.parse( json ) : [];
versions.push( stringify() );
localStorage[key] = JSON.stringify( versions );
}
function getVersion( key )
{ //Called internally, get a version (and remove it) from a version array
var json = localStorage[key];
var versions = json ? JSON.parse( json ) : [];
var version = versions.pop();
localStorage[key] = JSON.stringify( versions );
return version;
}
function storeVersion()
{ //Called from controller, removes all redo versions
addVersion( 'undo' , stringify() );
localStorage.removeItem( 'redo' );
}
function restoreVersion()
{ //Called from controller, adds a redo version
var version = getVersion( 'undo' );
if(version){
addVersion( 'redo' );
cells = [];
write( 0 , 0 , version );
}
}
function redo()
{ //Called from controller, puts version back on to undo
var version = getVersion( 'redo' );
if(version){
addVersion( 'undo' );
cells = [];
write( 0 , 0 , version );
}
}
function isLineCharacter( cursor, dx , dy , returnValue )
{
cursor = { x: cursor.x + dx , y: cursor.y + dy };
return ~'╔═╦╗║╠╬╣╚╩╝><'.indexOf( getCell( cursor ) ) ? returnValue : 0;
}
//Modulify
return {
write: write,
stringify: stringify,
setCell: setCell,
getCell: getCell,
cursor: cursor,
backspace: backspace,
storeVersion: storeVersion,
restoreVersion: restoreVersion,
redo: redo,
isLineCharacter: isLineCharacter
};
}());
var ui = (function()
{
//Privates
var fontSize = 15,
breatheDuration = 5 * 1000, //5 seconds
lightGrey = 211,
black = 0,
greyRange = lightGrey - black,
p = 20, //Padding..
magicalMultiplier = 0.8, //Dont ask
w, //Width
h, //Height
fh, //fontHeight
fw, //fontWidth
vo, //Vertical offset for writing
ho, //Horizontal offset for writing
metrics,
box;
//Exposed
function breathe()
{ //Set the `caret` in a grey shade that follows a breathing cycle
var rightNow = new Date(),
position = rightNow % breatheDuration,
radians = position / breatheDuration * Math.PI,
sine = Math.sin( radians ),
shade = Math.floor( lightGrey - greyRange/2 + sine * greyRange / 2 ),
cx = model.cursor.x,
cy = model.cursor.y;
context.strokeStyle = 'rgb(' + shade + ',' + shade + ',' + shade + ')';
context.lineWidth = 0.5;
context.beginPath();
context.moveTo(cx*fw + p, cy*fh + p);
context.lineTo(cx*fw + p + fw, cy*fh + p);
context.lineTo(cx*fw + p + fw, cy*fh + p + fh);
context.lineTo(cx*fw + p , cy*fh + p + fh);
context.lineTo(cx*fw + p, cy*fh + p);
context.stroke();
}
function drawBox()
{
context.strokeStyle = 'black';
context.lineWidth = 0.5;
context.beginPath();
context.moveTo(box.from.x *fw + p, box.from.y *fh + p); //Top left
context.lineTo((box.to.x+1) *fw + p, box.from.y *fh + p); //Top Right
context.lineTo((box.to.x+1) *fw + p, (box.to.y+1) *fh + p); //Bottom Right
context.lineTo(box.from.x *fw + p, (box.to.y+1) *fh + p); //Bottom Left
context.lineTo(box.from.x *fw + p, box.from.y *fh + p); //Top Left
context.stroke();
}
function setBox( cell1 , cell2 )
{
box = new Box( cell1 , cell2 );
}
function clearBox()
{
box = undefined;
}
function getBox()
{
return box;
}
function adapt()
{ //Adapt the UI to the current size of the body
//Clearly, the UI maintains it's own model
w = canvas.width = document.body.clientWidth;
h = canvas.height = window.innerHeight;
document.documentElement.style.overflow = 'hidden';
context.font = fontSize + (~navigator.userAgent.indexOf('Mac') ? "px Consolas" : "px Monospace"); //EVIL Mac Fix
metrics = context.measureText('A');
fh = fontSize+1;
fw = metrics.width;
vo = p+fh*magicalMultiplier;
ho = p;
drawGrid();
}
function drawGrid()
{
context.clearRect(0, 0, canvas.width, canvas.height);
for (var x = 0; x < w; x += fw)
{
context.moveTo(x + p, 0 + p);
context.lineTo(x + p, h );
}
for (var y = 0; y < h; y += fh)
{
context.moveTo(0 + p, y + p);
context.lineTo(w , y + p);
}
context.lineWidth = 0.1;
context.strokeStyle = "lightgrey";
context.stroke();
context.strokeStyle = "black";
context.fillStyle = "black";
var string = model.stringify();
if( string ){
var strings = string.split("\n");
for( var row = 0 ; row < strings.length ; row++ )
for( var col = 0 ; col < strings[row].length ; col++ )
context.fillText( strings[row][col] , ho + fw * col , vo + fh * row );
}
if( box )
drawBox( box.from , box.to );
}
function translate( cursor )
{ //Translate screen coordinates to cell coordinates
var x = Math.floor((cursor.x - p ) / fw ),
y = Math.floor((cursor.y - p ) / fh );
//Cheat on boundaries
x = x < 0 ? 0 : x;
y = y < 0 ? 0 : y;
//Return a new cell cursor object
return new Cursor(x,y);
}
//Modulify
return {
breathe : breathe,
drawGrid : drawGrid,
adapt: adapt,
translate: translate,
setBox: setBox,
clearBox: clearBox,
getBox: getBox
};
}());
var controller = (function()
{
var BACKSPACE = 8,
TAB = 9,
ARROW_LEFT = 37,
ARROW_UP = 38,
ARROW_RIGHT = 39,
ARROW_DOWN = 40,
DELETE = 46,
KEY_B = 66,
KEY_C = 67,
KEY_Y = 89,
KEY_Z = 90;
var startingCell,
currentCell;
function normalizeEvent(e)
{ //Normalize which for key events, inspiration:SO
if ( e.which === null && (e.charCode !== null || e.keyCode !== null) ) {
e.which = e.charCode !== null ? e.charCode : e.keyCode;
}
}
function onContentLoaded()
{ //Could have been called onInit
//Set the 3 globals
canvas = document.getElementById("canvas");
context = canvas.getContext ("2d");
clipboard = document.getElementById('clipboard');
//Occupy full body & draw the initial UI
ui.adapt();
//Set up listeners
window.addEventListener( "resize", ui.adapt );
canvas.addEventListener( "mouseover", onMouseOver );
canvas.addEventListener( "mousemove", onMouseOver );
canvas.addEventListener( "mousedown", onMouseDown );
canvas.addEventListener( "mouseup", onMouseUp );
canvas.addEventListener( "click", onClick );
document.addEventListener( "keypress", onKeyPress );
document.addEventListener( "keydown", onKeyDown );
document.addEventListener( "paste", onPaste );
//Make the cursor breathe
setInterval( ui.breathe , 1000/12 ); // 12 frames per second
}
function onPaste(e)
{ //Determine where to paste, paste, determine & set new cursor location, redraw everything
var cursor = model.cursor;
model.cursor = model.write( cursor.x , cursor.y , e.clipboardData.getData('text/plain') );
ui.drawGrid();
}
function onMouseDown(e)
{
//Remember where we start
startingCell = ui.translate( e );
//Clear any old boxes
ui.clearBox();
//Force the UI in onMouseOver to draw the new cursor without a mouse up
currentCell = { x : -1 , y : -1 };
onMouseOver(e);
}
function onMouseUp()
{
ui.setBox( startingCell , currentCell );
currentCell = startingCell = undefined;
}
function onMouseOver(e)
{ //Are we dragging?, which cell are we on, update if we are in a different cell, and draw
if(!startingCell)
return;
var cell = ui.translate( e );
if( cell.x != currentCell.x || cell.y != currentCell.y )
{
currentCell = cell;
model.cursor = cell;
ui.setBox( startingCell , currentCell );
ui.drawGrid();
}
}
function onClick(e)
{ //Move the cursor to where the user clicked
model.cursor = ui.translate( e );
ui.drawGrid();
}
function onKeyPress(e)
{ //console.log( e , String.fromCharCode( e.charCode || 32 ) );
if( e.ctrlKey )
return;
model.storeVersion();
model.setCell( model.cursor, String.fromCharCode( e.charCode || 32 ) );
ui.clearBox();
model.cursor.advance();
ui.drawGrid();
}
function onKeyDown(e)
{ //console.log( e , String.fromCharCode( e.charCode || 32 ) );
normalizeEvent(e);
var box = ui.getBox();
if( e.which == BACKSPACE )
{
model.backspace();
e.preventDefault();
}
else if( e.which == TAB )
{
model.cursor = model.setCell( model.cursor , '\t' );
e.preventDefault();
}
else if( e.which == ARROW_LEFT ){
model.cursor.recede();
}
else if( e.which == ARROW_RIGHT ){
model.cursor.advance();
}
else if( e.which == ARROW_UP ){
model.cursor.up();
}
else if( e.which == ARROW_DOWN ){
model.cursor.down();
}
else if( e.keyIdentifier == 'Home' && e.ctrlKey ){
model.cursor = new Cursor( 0, 0 );
}
else if( e.keyIdentifier == 'Home' )
{ //Move to complete left unless already there, in that case go top left
model.cursor.x ? model.cursor.x = 0 : model.cursor.y = 0;
}
else if( e.which == KEY_C && e.ctrlKey )
{ //Copy a box or a whole character
if( box )
{
var lines = [];
box.eachRow( function(y){ lines[ y - box.from.y ] = ''; } );
box.each( function(cursor){ lines[ cursor.y - box.from.y ] += model.getCell(cursor); } );
var line = lines.join("\n");
clipboard.value = line;
}
else
{
clipboard.value = model.getCell( ui.getCursor() ) || " ";
}
clipboard.focus();
clipboard.select();
}
else if( e.which == KEY_B && e.ctrlKey )
{
/* Styles:
╔═╦═╗ ⇓
║ ║ ║ ☺☺
╠═╬═╣
╚═╩═╝ */
var onLeft = 1; //Bitflag 1
var onRight = 2; //Bitflag 2
var onTop = 4; //Bitflag 3
var onBottom = 8; //Bitflag 4
var lineRules = {};
lineRules[onLeft+onRight] = '═';
lineRules[onTop+onBottom] = '║';
lineRules[onTop+onLeft] = '╝';
lineRules[onTop+onRight] = '╚';
lineRules[onBottom+onLeft] = '╗';
lineRules[onBottom+onRight] = '╔';
lineRules[onLeft+onRight+onTop+onBottom] = '╬';
lineRules[onLeft+onRight+onTop] = '╩';
lineRules[onLeft+onRight+onBottom] = '╦';
lineRules[onTop+onBottom+onLeft] = '╣';
lineRules[onTop+onBottom+onRight] = '╠';
if( box )
{
model.storeVersion();
//Show intent
box.eachRow( function(y){ model.write( box.from.x, y, '║' ); model.write( box.to.x, y , '║' ); } );
box.eachColumn( function(x){ model.write( x, box.from.y, '═' ); model.write( x, box.to.y , '═' ); } );
//Line up
box.each( function lineUp( cursor )
{
if( !model.isLineCharacter( cursor , 0 , 0 , true ) )
return;
var neighbourBitFlag =
model.isLineCharacter( cursor , -1 , +0 , onLeft ) +
model.isLineCharacter( cursor , +1 , +0 , onRight ) +
model.isLineCharacter( cursor , +0 , +1 , onBottom ) +
model.isLineCharacter( cursor , +0 , -1 , onTop );
if( lineRules[neighbourBitFlag] )
model.setCell( cursor , lineRules[neighbourBitFlag] );
});
}
}
else if ( e.which == DELETE )
{
if( box ){
box.each( function(cursor){ model.setCell( cursor, " " ); } );
}
}
else if ( e.which == KEY_Z && e.ctrlKey )
{ //Undo
model.restoreVersion();
}
else if ( e.which == KEY_Y && e.ctrlKey )
{ //Undo
model.redo();
}
//Clear the selection box after a key press (Control does not count)
if( e.keyIdentifier != "Control" && box )
{
ui.clearBox();
}
//Draw the grid in all cases
ui.drawGrid();
}
return {
onContentLoaded: onContentLoaded,
};
}());
//Engage!
document.addEventListener( "DOMContentLoaded",controller.onContentLoaded, false );
})();
/* Styles go here */
body { margin: 0 0 0 0}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="Author" content="[email protected]">
<title>Unidraw</title>
</head>
<body>
<!-- This is where all the magic shows -->
<canvas width="800" height="600" id="canvas"></canvas>
<!-- To be used when the user presses Control-C -->
<textarea type='text' id='clipboard' style='position:absolute;left: -1000px'></textarea>
<!-- This is where all the magic happens -->
</body>
</html>
-
\$\begingroup\$ The IIFE needs a preceding semicolon. \$\endgroup\$James Khoury– James Khoury2014年09月08日 01:23:26 +00:00Commented Sep 8, 2014 at 1:23
-
\$\begingroup\$ Which shows why
(function(){}())
is a bad pattern. Compare what happens with:var z = function x (){} (function y(){}());
vs using:var z = function x (){} (function y(){})();
but as I said a preceding semicolon would fix this. \$\endgroup\$James Khoury– James Khoury2014年09月08日 01:30:14 +00:00Commented Sep 8, 2014 at 1:30