Skip to content

Instantly share code, notes, and snippets.

@dergachev
Forked from anonymous/scratchpad.js
Created December 17, 2012 21:12
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dergachev/4322317 to your computer and use it in GitHub Desktop.
Save dergachev/4322317 to your computer and use it in GitHub Desktop.
Source code from scratchpad.io
<!DOCTYPE html>
<html lang="en">
<head>
<!-- added by alex dergachev -->
<base href="http://scratchpad.io/">
<title>Scratchpad</title>
<link href="css/style.css" rel="stylesheet" type="text/css" />
</head>
<body id="scratchpad" tabindex="0">
<div id="menu-pane">
<h1>Scratchpad</h1>
<p class='subtitle'>Instant HTML &amp; CSS Editor</p>
<h2>About</h2>
<p>Scratchpad is an easy way to write HTML and CSS, created by <a href="http://twitter.com/intent/user?screen_name=nbashaw">@nbashaw</a> and <a href="http://twitter.com/intent/user?screen_name=brycecolquitt">@brycecolquitt</a>.</p>
<h2>Recent</h2>
<ul id="recent-scratchpads">
<li>Loading...</li>
</ul>
</div>
<div id="commandbar">
<a href="javascript:void(0)" id="menu"><span id="menu-button">&nbsp;</span></a>
<a href="javascript:void(0)" id="title">Loading...</a>
<ul id="connections"></ul>
<a href="javascript:void(0)" id="toggle-fullscreen">
<img src="/img/fullscreen.png" width="20" height="13">
<div class="tooltip">Keyboard Shortcut: &#8984; + i</div>
</a>
</div>
<div id="editor"></div>
<div id="footer">
<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://scratchpad.io" data-text="Cool! A realtime HTML &amp; CSS editor">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
Powered by <a id="firebase" href="https://www.firebase.com/" target="_blank">Firebase</a>
</div>
<iframe id="preview"></iframe>
<div id="help">
<h2>HTML &amp; CSS Quick Reference</h2>
<dl>
<dt>Headings</dt>
<dd><code>&lt;h1&gt;, &lt;h2&gt;, &lt;h3&gt;, &lt;h4&gt;, &lt;h5&gt;, &lt;h6&gt;</code></dd>
<dt>Link</dt>
<dd><code>&lt;a href="http://google.com"&gt;Google&lt;a&gt;</code></dd>
</dl>
</div>
<script>
var Scratchpad = {document_id: 'VLeYU2O3a5'}
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" type="text/javascript"></script>
<script src='https://static.firebase.com/v0/firebase.js' type="text/javascript" charset='utf-8'></script>
<script src="scripts/ace/ace.js" type="text/javascript" charset='utf-8'></script>
<script src="scripts/libs.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f);b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==
typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.increment".split(" ");for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);
mixpanel.init("e950aa5fcedfe3b41d9be7e2f085d925");
mixpanel.track("Viewed A Scratchpad");
</script>
<script src="scripts/scratchpad.js" type="text/javascript"></script>
<script type="text/javascript" src="//platform.twitter.com/widgets.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-36391778-3']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>
// Underscore.js 1.4.2
// keymaster.js
// ace.js (same as https://d1n0x3qji82z53.cloudfront.net/src-min-noconflict/ace.js)
$(function(){
// Scratchpad Intro
//--------------------------------------------------------------------------------
var intro =
['<style>',
' body {background: #DDD6B2;}',
' .container {',
' background: #fff;',
' padding: 50px;',
' margin: 50px auto;',
' width: 400px;',
' font-family: sans-serif;',
' border-radius: 4px;',
' }',
'</style>',
'<div class="container">',
' <h1><img src="/img/logo-dark.png" alt="Scratchpad"></h1>',
' <p>Hello!</p>',
' <p>My name is <a target="blank" href="http://twitter.com/nbashaw">Nathan Bashaw</a> and I am a designer and hacker living in San Francisco. I built Scratchpad to make it simpler for beginners to learn HTML and CSS. It\'s also a supplement to the book I\'m writing called <a target="blank" href="http://enoughtobedanger.us">Enough To Be Dangerous</a> &mdash; a step-by-step guide to coding your first web application. Happy hacking!</p>',
" <b>How to use Scratchpad:</b>",
" <ol>",
" <li>Write HTML &amp; CSS &mdash; watch it render instantly</li>",
" <li>Press &#8984; + i to toggle fullscreen view</li>",
" <li>Share the URL with friends (it updates in realtime)</li>",
" </ol>",
" <em>Et Voil&agrave;!</em>",
'</div>'].join('\n');
// Ace code edtor
//--------------------------------------------------------------------------------
var editor = ace.edit("editor");
editor.setTheme("ace/theme/tomorrow_night_eighties");
editor.getSession().setMode("ace/mode/html");
editor.setHighlightActiveLine(false);
editor.getSession().setTabSize(2);
document.getElementById('editor').style.fontSize='11px';
editor.commands.removeCommand('gotoline');
editor.setShowPrintMargin(false);
editor.commands.addCommand({
name: 'showHelp',
bindKey: {win: 'Ctrl-/', mac: 'Command-/'},
exec: function(editor) {
$('#help').toggleClass('visible');
}
});
editor.commands.addCommand({
name: 'toggleFullscreen',
bindKey: {win: 'Ctrl-i', mac: 'Command-i'},
exec: function(editor) {
toggleFullscreen();
}
});
// Set up iframe.
var iframe = document.getElementById('preview'),
iframedoc = iframe.contentDocument || iframe.contentWindow.document;
iframedoc.body.setAttribute('tabindex', 0);
// Base firebase ref
//--------------------------------------------------------------------------------
var scratchpadRef = new Firebase('https://scratchpad.firebaseio.com/' + Scratchpad.document_id);
var now = new Date();
scratchpadRef.child('updatedAt').set(now.toString());
// Multiple client stuff
//--------------------------------------------------------------------------------
// Push a new child to clients that kills itself on disconnect
var thisClientRef = scratchpadRef.child('clients').push('idle');
thisClientRef.removeOnDisconnect();
// Keep track of the number of active connections
scratchpadRef.child('clients').on('value', function(dataSnapshot){
if (dataSnapshot.val() === null) {
scratchpadRef.child('clients').set({});
} else {
var numClients = dataSnapshot.numChildren();
// Label the tooltip appropriately
$('#connections-tooltip').remove();
if (numClients === 2) {
$('#connections').after('<span id="connections-tooltip"> 1 other viewer</span>');
} else if (numClients === 1) {
// do nothing
} else {
$('#connections').after('<span id="connections-tooltip"> '+ (numClients - 1) + ' other viewers</span>');
}
// Append proper number of dots
$('#connections').html('');
for (i = 1; i < dataSnapshot.numChildren(); i++) {
$('#connections').append('<li>&nbsp;</li>');
}
}
});
$('#connections').hover(function(){
$('#connections-tooltip').css('opacity', 1);
}, function(){
$('#connections-tooltip').css('opacity', 0);
});
// Code Editing
//--------------------------------------------------------------------------------
var scratchpadEditorRef = scratchpadRef.child('editor');
// When code changes, put it into the editor
scratchpadEditorRef.on('value', function(dataSnapshot) {
var thisClientStatus;
thisClientRef.once('value', function(dataSnapshot){
thisClientStatus = dataSnapshot.val();
});
// If this is a new scratchpad, put in our intro
var clearReadOnlyMode;
if (dataSnapshot.child('code').val() == null) {
editor.setValue(intro);
} else if (thisClientStatus == 'typing') {
// do nothing, we're the ones typing in the first place
} else {
window.clearTimeout(clearReadOnlyMode);
editor.setReadOnly(true);
editor.setValue(dataSnapshot.child('code').val());
clearReadOnlyMode = setTimeout(function(){
editor.setReadOnly(false);
}, 2000);
}
// Clear selection and move cursor to where it needs to be
editor.clearSelection();
editor.moveCursorToPosition(dataSnapshot.child('cursor').val());
});
// On keyup, save the code and cursor data to firebase
var typingTimeout;
$('#editor').on('keyup', function(){
// Tell firebase who is editing
window.clearTimeout(typingTimeout);
thisClientRef.set('typing')
// Get cursor position
var startrow = editor.selection.getRange().start.row;
var startcolumn = editor.selection.getRange().start.column;
var endrow = editor.selection.getRange().end.row;
var endcolumn = editor.selection.getRange().end.column;
// If nothing is highlighted, ship contents of editor and cursor data to Firebase
if (startrow == endrow && startcolumn == endcolumn) {
scratchpadEditorRef.set({code: editor.getValue(), cursor: editor.selection.getCursor()});
}
// Set a timeout for 2 seconds that tells firebase who is typing
typingTimeout = setTimeout(function(){
thisClientRef.set('idle')
}, 2000) ;
});
// On data change, re-render the code in the iframe.
editor.getSession().on('change', function(e) {
iframedoc.body.innerHTML = editor.getValue();
// Resize the menu icon if appropriate
var linesOfCode = editor.session.getLength();
if (linesOfCode < 10) {
$('#menu').attr('class', 'small')
} else if ( linesOfCode > 9 && linesOfCode < 99) {
$('#menu').attr('class', 'medium')
} else if ( linesOfCode > 99 && linesOfCode < 999) {
$('#menu').attr('class', 'large')
} else if (linesOfCode > 999){
$('#menu').attr('class', 'x-large')
}
});
// Filename Stuff
//--------------------------------------------------------------------------------
var scratchpadTitleRef = scratchpadRef.child('title');
// Show title on top, keep updated from server
scratchpadTitleRef.on('value', function(titleSnapshot) {
if (titleSnapshot.val() == null) {
scratchpadTitleRef.set('Untitled document');
} else {
$('#title').text(titleSnapshot.val());
}
document.title = titleSnapshot.val();
});
// Let users update title when they click it
$('#title').click(function(){
var newTitle = prompt('What do you want to name your file?', $(this).text());
if (newTitle != null) {
scratchpadTitleRef.set(newTitle);
}
});
// Stupid (webkit only?) hover bug fix
$('#title').hover(function(){$(this).addClass('hover')}, function(){$(this).removeClass('hover')});
// Fullscreen mode stuff
//--------------------------------------------------------------------------------
// Toggle fullscreen mode.
function toggleFullscreen() {
if ($('#scratchpad').hasClass('menu')) {
$('#scratchpad').removeClass('menu');
}
$('#scratchpad').toggleClass('fullscreen');
location.hash = $('#scratchpad').attr('class');
}
// When the button is clicked, call toggleFullscreen.
$('#toggle-fullscreen').click(function() {
toggleFullscreen();
});
// Even when iframe has focus, still toggleFullscreen
$("#preview").contents().find("body").on('keydown', function(e){
if (e.keyCode == 73) {
toggleFullscreen();
}
});
// For good measure, always toggleFullscreen
key('⌘+i, ctrl+i', function(){
toggleFullscreen();
});
// Automatically go into fullscreen mode when pageload includes #fullscreen
if (location.hash == '#fullscreen') {
$('#scratchpad').toggleClass('fullscreen');
}
// History (Recent Scratchpads)
//--------------------------------------------------------------------------------
if (typeof(Storage)!=="undefined") {
// Initialize recentScratchpads row in localStorage if needed
if (localStorage['recentScratchpads'] === undefined) {
localStorage['recentScratchpads'] = JSON.stringify([]);
}
function getRecentScratchpads() {
var scratchpadIds = JSON.parse(localStorage['recentScratchpads']);
return scratchpadIds;
}
function addToRecentScratchpads(id) {
var recentScratchpadsArr = [];
recentScratchpadsArr = JSON.parse(localStorage['recentScratchpads']) || [];
if (!_.contains(recentScratchpadsArr, id)) {
recentScratchpadsArr.push(id);
localStorage['recentScratchpads'] = JSON.stringify(recentScratchpadsArr);
} else {
recentScratchpadsArr = _.without(recentScratchpadsArr, id);
recentScratchpadsArr.push(id);
localStorage['recentScratchpads'] = JSON.stringify(recentScratchpadsArr);
}
}
function renderRecentScratchpads(listOfRecentScratchpads) {
if (listOfRecentScratchpads.length > 1) {
// Clear the loading text, save state that it's been loaded
$('#recent-scratchpads').html('');
var recentScratchpadTemplate = '<li><a class="recent-scratchpad" href="/<%= scratchpadId %>" target="_blank"><%= thisScratchpadTitle %> <time><%= dateTemplate %></time></a><a class="delete" data-id="<%= scratchpadId %>" href="javascript:void(0)">&times;</a></li>';
_.each(listOfRecentScratchpads, function(scratchpadId) {
if (Scratchpad.document_id != scratchpadId) {
var thisScratchpadRef = new Firebase('https://scratchpad.firebaseio.com/' + scratchpadId);
thisScratchpadRef.once('value', function(dataSnapshot) {
var thisScratchpadTitle = dataSnapshot.child('title').val();
dateObj = new Date(dataSnapshot.child('updatedAt').val());
dateTemplate = dateObj.getDate() +'/'+ dateObj.getMonth() +'/'+ dateObj.getFullYear();
thisScratchpadTemplate = _.template(recentScratchpadTemplate, {scratchpadId: scratchpadId, thisScratchpadTitle: thisScratchpadTitle, dateTemplate: dateTemplate});
$('#recent-scratchpads').prepend(thisScratchpadTemplate);
});
}
});
} else {
$('#recent-scratchpads').html('<li>No recent scratchpads!</li>');
}
Scratchpad.loadedRecentScratchpads = true;
}
function deleteRecentScratchpadFromList (id) {
// Delete from localstore
var recentScratchpadsArr;
recentScratchpadsArr = JSON.parse(localStorage['recentScratchpads']);
recentScratchpadsArr = _.without(recentScratchpadsArr, id);
localStorage['recentScratchpads'] = JSON.stringify(recentScratchpadsArr);
// Delete from DOM
$('#recent-scratchpads li').each(function(index){
if ($(this).children('.delete').data('id') == id) {
$(this).remove();
}
});
}
$('#recent-scratchpads').on('click', '.delete', function(e) {
deleteRecentScratchpadFromList($(this).data('id'));
});
addToRecentScratchpads(Scratchpad.document_id);
} else {
// Sorry! No web storage support.
$('#recent-scratchpads').html('Sorry! Your browser doesn\'t support HTML5 local storage.');
}
// Menu stuff
//--------------------------------------------------------------------------------
// Toggle fullscreen mode on menu click
$('#menu').click(function(){
$('#scratchpad').toggleClass('menu');
mixpanel.track("Menu toggle");
if (Scratchpad.loadedRecentScratchpads != true) {
renderRecentScratchpads(getRecentScratchpads());
}
})
// Show different tooltip for Windows users.
var isMac = navigator.platform.toUpperCase().indexOf('MAC')!==-1;
if (isMac != true) {
$('.tooltip').html('Keyboard Shortcut: Control + i');
}
});
body {
background: #1C1C1C;
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
}
a {
color: #99CC99;
}
#preview {
background: #fff;
transition: all 0.15s;
-moz-transition: all 0.15s;
-webkit-transition: all 0.15s;
}
#commandbar {
position: absolute;
height: 31px;
top: 0;
left: 0;
right: 60%;
background: #1c1c1c;
color: #666666;
transition: all 0.15s;
-moz-transition: all 0.15s;
-webkit-transition: all 0.15s;
overflow: hidden;
}
#menu {
text-indent: -999999px;
float: left;
height: 31px;
text-align: center;
opacity: 0.5;
width: 47px;
}
#menu:hover {
opacity: 1;
background-color: #2D2D2D;
}
#menu-button {
background-image: url(/img/menu-icon.png);
width: 15px;
height: 13px;
display: block;
margin: 9px auto 0px auto;
}
.small#menu {width: 40px;}
.medium#menu {width: 47px;}
.large#menu {width: 54px;}
.x-large#menu {width: 61px;}
#commandbar #title {
display: inline-block;
text-decoration: none;
border: 0;
color: #aaa;
padding: 8px 5px 7px 5px;
font-size: 12px;
}
#commandbar #title.hover {
background: #2D2D2D;
}
#connections {
margin: 0;
padding: 0;
display: inline-block;
width: inherit;
}
#connections:hover {
cursor: default;
}
#connections li {
display: inline-block;
width: 4px;
height: 4px;
margin-right: 5px;
margin-bottom: 2px;
background: #99CC99;
border-radius: 100%;
box-shadow: 0px 0px 5px #99CC99;
}
#connections-tooltip {
margin-left: -3px;
font-size: 10px;
opacity: 1;
color: #AAAAAA;
}
#commandbar #toggle-fullscreen {
position: absolute;
right: 10px;
top: 7px;
opacity: 0.75;
transition: none;
-moz-transition: none;
-webkit-transition: none;
}
#commandbar #toggle-fullscreen:hover {
opacity: 1;
}
#commandbar #toggle-fullscreen .tooltip {
background: #1C1C1C;
box-shadow: -6px 0px 10px #1c1c1c;
display: none;
position: absolute;
top: 0;
right: 30px;
font-size: 12px;
color: #666;
text-align: right;
width: 180px;
}
#commandbar #toggle-fullscreen:hover .tooltip {
display: block;
}
#footer {
position: absolute;
bottom: 0px;
left: 0px;
right: 60%;
background: #1C1C1C;
text-align: center;
color: #666666;
padding: 7px 0px 8px 0px;
font-size: 12px;
transition: all 0.15s;
-moz-transition: all 0.15s;
-webkit-transition: all 0.15s;
}
#footer a {
color: #666666;
text-indent: -9999px;
display: inline-block;
width: 77px;
opacity: 0.5;
background-image: url('/img/firebase.png');
background-size: 100%;
transition: all 0.15s;
-moz-transition: all 0.15s;
-webkit-transition: all 0.15s;
}
#footer a:hover {
opacity: 1;
}
#footer iframe {
position: absolute;
left: 10px;
}
#editor {
position: absolute;
top: 32px;
right: 60%;
bottom: 31px;
left: 0;
transition: all 0.15s;
-moz-transition: all 0.15s;
-webkit-transition: all 0.15s;
background: #2d2d2d;
}
#preview {
position: absolute;
top: 0px;
bottom: 0px;
left: 40%;
right: 0;
border: 0;
width: 60%;
height: 100%;
border-radius: 5px 0px 0px 5px;
/*box-shadow: -4px 0px 15px #1C1C1C;*/
}
#help {
font-family: helvetica;
display: none;
position: fixed;
top: 40px;
width: 500px;
padding: 20px;
border-radius: 10px;
left: 50%;
margin-left: -250px;
z-index: 10;
box-shadow: 0px 0px 20px rgba(0,0,0,0.75);
background: #fff;
}
#help.visible {
display: block;
}
/* ---------------------------------------------------------------------------------
Fullscren Mode.
-------------------------------------------------------------------------------- */
#scratchpad.fullscreen #commandbar {
border: none;
width: 3%;
background: #212121;
}
#scratchpad.fullscreen #title, #scratchpad.fullscreen #menu, #scratchpad.fullscreen #footer, #scratchpad.fullscreen #menu-pane{
opacity: 0;
}
#scratchpad.fullscreen #commandbar .tooltip {
display: none;
}
#scratchpad.fullscreen #toggle-fullscreen {
transform:rotate(180deg);
-ms-transform:rotate(180deg);
-moz-transform:rotate(180deg);
-webkit-transform:rotate(180deg);
-o-transform:rotate(180deg);
top: 3px;
}
#scratchpad.fullscreen #editor {
right: 97%;
}
#scratchpad.fullscreen #preview {
left: 3%;
width: 97%;
}
/* ---------------------------------------------------------------------------------
Menu Mode.
-------------------------------------------------------------------------------- */
#menu-pane {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 80%;
box-shadow: inset -4px 0px 15px #0f0f0f;
color: #aaa;
padding: 20px;
opacity: 0;
transition: all 0.15s;
-moz-transition: all 0.15s;
-webkit-transition: all 0.15s;
overflow-y: scroll;
}
#menu-pane h1 {
width: 176px;
height: 29px;
text-indent: -99999px;
background-image: url(/img/logo.png);
margin: 10px;
}
#menu-pane p.subtitle {
font-size: 9px;
text-align: center;
color: #666;
}
#menu-pane h2 {
border-bottom: 1px dashed #2D2D2D;
text-transform: uppercase;
font-size: 13px;
padding-bottom: 5px;
margin-top: 25px;
font-weight: normal;
}
#menu-pane p {
font-size: 11px;
margin-top: 0;
}
#menu-pane ul {
margin: 0;
padding: 0;
}
#menu-pane li {
font-size: 11px;
list-style-type: none;
}
#recent-scratchpads li {
position: relative;
}
#recent-scratchpads .recent-scratchpad {
color: #aaa;
display: block;
padding: 5px 0;
text-decoration: none;
overflow-x: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#recent-scratchpads .recent-scratchpad:hover {
background: #222;
color: #ccc;
}
#recent-scratchpads time {
color: #555;
font-size: 8px;
}
#recent-scratchpads .delete {
position: absolute;
right: 5px;
top: 2px;
font-size: 14px;
color: #555;
text-decoration: none;
opacity: 0;
background: #222;
}
#recent-scratchpads .delete:hover {
color: #aaa;
background: none;
}
#recent-scratchpads li:hover .delete {
opacity: 1;
}
#scratchpad.menu #menu-pane {
opacity: 1;
}
#scratchpad.menu #commandbar, #scratchpad.menu #editor, #scratchpad.menu #footer {
left: 20%;
right: 40%;
}
#scratchpad.menu #preview {
left: 60%;
width: 40%;
right: 0;
}
/* scrollbar */
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar:vertical {
width: 4px;
}
::-webkit-scrollbar:horizontal {
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb:vertical {
background: #5a5a5a;
}
::-webkit-scrollbar-thumb:horizontal {
background: #444;
}
@lifeluvr
Copy link

Nice UI... It's beautiful and elegant!
Thank you!

@mayberrydotpdr
Copy link

I just found it. Wow! Good job! Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment