Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Mass check in of JS Bin 2.8 - lots of UI changes - in complete though…

… - IE to test.
  • Loading branch information...
commit 1ddb2a03604f1ecb9bb28378b671bdc87af930eb 1 parent a6a63e0
@remy remy authored
View
2  Makefile
@@ -1,2 +1,4 @@
all:
+ git submodule init
+ git submodule update
php ./build/build.php
View
2  VERSION
@@ -1 +1 @@
-2.7.4
+2.7.5
View
38 app.php
@@ -76,7 +76,10 @@
}
$sql = sprintf('insert into sandbox (javascript, html, created, last_viewed, url, revision) values ("%s", "%s", now(), now(), "%s", "%s")', mysql_real_escape_string($javascript), mysql_real_escape_string($html), mysql_real_escape_string($code_id), mysql_real_escape_string($revision));
- mysql_query($sql);
+ $ok = mysql_query($sql);
+
+ // error_log('saved: ' . $code_id . ' - ' . $revision . ' -- ' . $ok . ' ' . strlen($sql));
+ // error_log(mysql_error());
}
if (stripos($method, 'download') !== false) {
@@ -190,17 +193,6 @@ function encode($s) {
return '"' . str_replace($jsonReplaces[0], $jsonReplaces[1], $s) . '"';
}
-// returns the app loaded with json html + js content
-function edit() {
-
-}
-
-// saves current state - should I store regardless of content, to start their own
-// milestones?
-function save() {
-
-}
-
function getCodeIdParams($request) {
$revision = array_pop($request);
$code_id = array_pop($request);
@@ -271,8 +263,16 @@ function getCode($code_id, $revision, $testonly = false) {
function defaultCode($not_found = false) {
$library = '';
- if (@$_GET['html']) {
- $html = $_GET['html'];
+ $usingRequest = false;
+
+ if (isset($_REQUEST['html']) || isset($_REQUEST['js'])) {
+ $usingRequest = true;
+ }
+
+ if (@$_REQUEST['html']) {
+ $html = $_REQUEST['html'];
+ } else if ($usingRequest) {
+ $html = '';
} else {
$html = <<<HERE_DOC
<!DOCTYPE html>
@@ -297,14 +297,16 @@ function defaultCode($not_found = false) {
$javascript = '';
- if (!@$_GET['js']) {
+ if (@$_REQUEST['js']) {
+ $javascript = $_REQUEST['js'];
+ } else if ($usingRequest) {
+ $javascript = '';
+ } else {
if ($not_found) {
$javascript = 'document.getElementById("hello").innerHTML = "<strong>This URL does not have any code saved to it.</strong>";';
} else {
$javascript = "if (document.getElementById('hello')) {\n document.getElementById('hello').innerHTML = 'Hello World - this was inserted using JavaScript';\n}\n";
- }
- } else {
- $javascript = $_GET['js'];
+ }
}
return array(get_magic_quotes_gpc() ? stripslashes($html) : $html, get_magic_quotes_gpc() ? stripslashes($javascript) : $javascript);
View
377 css/style.css
@@ -17,7 +17,7 @@ body {
font-size: 13px;
min-width: 976px;
overflow: hidden;
- background: url(/images/jsbin-bg.gif) repeat-x top left;
+ background: url(/images/jsbin-bg.gif) repeat-x 0 -10px;
}
p {
@@ -25,23 +25,32 @@ p {
}
#control {
- height: 61px;
+ overflow: hidden;
+ height: 51px;
position: absolute;
/* width: 100%;*/
left: 0;
right: 0;
}
+#control, .label {
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
.control, .help, .starting {
- width: 100px;
- padding: 15px;
+/* width: 100px;*/
+ padding: 13px 10px;
float: left;
white-space: nowrap;
}
.control {
padding-right: 0;
- width: 50%;
+/* width: 50%;*/
}
.starting {
@@ -49,7 +58,7 @@ p {
}
.help {
- width: 40%;
+ width: 10%;
text-align: right;
float: right;
}
@@ -59,7 +68,7 @@ p {
}
.starting, .help {
- line-height: 32px;
+ line-height: 25px;
}
ul.flat {
@@ -106,8 +115,9 @@ a:hover {
}
#bin {
- top: 62px;
+ top: 52px;
width: 100%;
+ opacity: 0;
}
div.html {
@@ -128,8 +138,13 @@ div.html {
border: 0;
}
*/
-div.code {
- width: 50%;
+div.code, #live, .resize {
+ /* width: 50%;*/
+/* -webkit-transition: left ease-out 100ms, right ease-out 100ms;*/
+}
+
+.resize {
+ background: #ccc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAMCAYAAACnfgdqAAAAMElEQVQIHWP8//8/AxRoMAA5ZkDcAsT/mYCiN4GYBYidGIECUFUMDCAZOEDmUNkAAKKgK80+TE8oAAAAAElFTkSuQmCC) no-repeat left 45%;
}
div.preview {
@@ -149,7 +164,7 @@ div.preview {
.code .label p {
/* display: inline;*/
font-weight: bold;
- cursor: pointer;
+/* cursor: pointer;*/
}
.code .label label {
@@ -173,6 +188,10 @@ div.preview {
width: 140px;
}
+.javascript {
+ right: 50%;
+}
+
iframe.javascript {
border-right: 1px solid #ccc !important;
}
@@ -183,14 +202,14 @@ iframe.javascript {
margin-right: 10px;
}
-a.button {
+.button {
border: 1px solid #ccc;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
height: 12px;
line-height: 12px;
- padding: 10px;
+ padding: 6px 10px;
display: block;
float: left;
text-decoration: none;
@@ -199,19 +218,30 @@ a.button {
background: rgba(255, 255, 255, 0.3);
}
-a.gap {
+#control span {
+ display: block;
+ float: left;
+ height: 10px;
+ padding-top: 4px;
+ padding-bottom: 10px;
+ line-height: 20px;
+}
+
+.gap {
margin-right: 10px;
}
-.button:hover {
+a.button:hover {
-moz-box-shadow: #fff 0px 0px 5px;
-webkit-box-shadow: #fff 0px 0px 5px;
+ box-shadow: #fff 0px 0px 5px;
background: rgba(0, 0, 0, 0.05);
}
-.button:active, .button:focus {
+a.button:active, .button:focus {
-moz-box-shadow: #C8C8C8 0px 0px 3px;
-webkit-box-shadow: #C8C8C8 0px 0px 3px;
+ box-shadow: #C8C8C8 0px 0px 3px;
border-color: #fff;
outline: 0;
text-shadow: none;
@@ -225,6 +255,7 @@ body.preview a.preview {
border: 1px solid #ccc;
-moz-box-shadow: #fff 0px 0px 5px;
-webkit-box-shadow: #fff 0px 0px 5px;
+ box-shadow: #fff 0px 0px 5px;
background-image:
-webkit-gradient(
@@ -647,7 +678,6 @@ body.streaming {
color: #fff;
font-weight: bold;
line-height: 20px;
- padding-left: 15px;
text-shadow: #0A0 0px 1px 0px;
display: block;
background: #0c0;
@@ -655,6 +685,10 @@ body.streaming {
cursor: pointer;
}
+#streaming .msg {
+ padding-left: 15px;
+}
+
#streaming a {
text-shadow: #0A0 0px 1px 0px;
color: #fff;
@@ -702,10 +736,10 @@ body {
}
#streaming, #control, #bin {
- -webkit-transition: top 100ms ease-out;
- -o-transition: top 100ms ease-out;
- -moz-transition: top 100ms ease-out;
- transition: top 100ms ease-out;
+ -webkit-transition: top 100ms ease-out, opacity 50ms linear;
+ -o-transition: top 100ms ease-out, opacity 50ms linear;
+ -moz-transition: top 100ms ease-out, opacity 50ms linear;
+ transition: top 100ms ease-out, opacity 50ms linear;
}
body.streaming #control,
@@ -783,9 +817,16 @@ ie6, li {
.button.download {
padding-left: 24px;
- background-image: url(/images/arrow_down_12x12.png);
+/* background-image: url(/images/arrow_down_12x12.png);*/
+ background-image: url(/images/download.png);
+ background-repeat: no-repeat;
+ background-position: 8px -33px;
+}
+
+.button.download:hover {
+ background-image: url(/images/download.png);
background-repeat: no-repeat;
- background-position: 8px 55%;
+ background-position: 8px 7px;
}
/* attempt to get a live render preview in */
@@ -848,21 +889,281 @@ ie6, li {
50,
color-stop(0, #BFBFBF),
color-stop(1, #949494)
- );
-
+ );
}
-@media screen and (min-width: 1200px) {
- #live {
- background: white url(/images/jsbin-bg.gif) repeat-x 0 -62px;
- top: 0;
- left: 67%;
- border-top: 0;
- border-left: 1px solid #ccc;
- }
-
- .live #source {
- bottom: 0;
- right: 33%;
- }
+#live {
+ background: white url(/images/jsbin-bg.gif) repeat-x 0 -62px;
+ top: 0;
+ left: 67%;
+ border-top: 0;
+ border-left: 1px solid #ccc;
+}
+
+.live #source {
+ bottom: 0;
+ right: 33%;
}
+
+.autocomplete {
+ position: absolute;
+ overflow: hidden;
+ border: 2px solid #DFE0B4;
+}
+
+.autocomplete select {
+ margin: 0;
+ padding: 0;
+ outline: none !important;
+ background: #FFFFDB;
+ border: 0;
+ font-family: MenschRegular, Menlo, Monaco, consolas, monospace;
+ font-size: 12px;
+}
+
+.showtip #bin {
+ bottom: 26px;
+}
+
+#tip {
+ display: none;
+ border-top: 1px solid #ccc;
+ position: absolute;
+ bottom: 0;
+ font-size: 12px;
+ line-height: 20px;
+ background: #fdfece;
+ left: 0;
+ right: 0;
+ padding: 2px 10px 2px 20px;
+}
+
+#tip p {
+ margin: 0;
+}
+
+#tip a.dismiss {
+ position: absolute;
+ right: 20px;
+ top: 0;
+ text-decoration: none;
+}
+
+.showtip #tip {
+ display: block;
+}
+
+details {
+ position: absolute;
+ display: block;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ font-size: 12px;
+ background: #FEE0E0;
+ color: #bb0000;
+}
+
+summary {
+ cursor: pointer;
+ padding: 3px 5px;
+ display: block;
+ font-weight: bold;
+ background: #FC9B9F;
+}
+
+details ol {
+ padding-left: 21px;
+ max-height: 150px;
+ overflow: auto;
+}
+
+details li {
+ margin: 5px 0;
+ cursor: pointer;
+}
+
+/* codemirror2 styles */
+@font-face {
+ font-family: 'MenschRegular';
+ src: url('/font/mensch-webfont.eot');
+ src: url('/font/mensch-webfont.eot?#iefix') format('eot'),
+ url('/font/mensch-webfont.woff') format('woff'),
+ url('/font/mensch-webfont.ttf') format('truetype'),
+ url('/font/mensch-webfont.svg#webfont0UwCC656') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+/*
+.editbox {
+ margin: .4em;
+ padding: 0;
+ font-family: MenschRegular, Menlo, Monaco, consolas, monospace;
+ font-size: 14pt;
+ color: black;
+}*/
+
+.CodeMirror > div {
+ margin: .6em;
+}
+
+.javascript .CodeMirror > div {
+ margin-top: 25px;
+}
+
+.html .CodeMirror > div {
+ margin-top: 60px;
+}
+
+.CodeMirror pre {
+/* white-space: pre-wrap !important;*/
+}
+
+.editor .CodeMirror {
+ height: 100%;
+ top: 0;
+ bottom: 0;
+}
+
+.CodeMirror {
+ overflow: auto;
+ height: 300px;
+/* top: 0;*/
+/* bottom: 0;*/
+/* line-height: 1em;*/
+ font-size: 12px;
+ font-family: MenschRegular, Menlo, Monaco, consolas, monospace;
+ _position: relative; /* IE6 hack */
+}
+
+.CodeMirror-gutter {
+ position: absolute; left: 0; top: 0;
+ background-color: #f7f7f7;
+ border-right: 1px solid #eee;
+ min-width: 2em;
+ height: 100%;
+}
+.CodeMirror-gutter-text {
+ color: #aaa;
+ text-align: right;
+ padding: .4em .2em .4em .4em;
+}
+.CodeMirror-lines {
+ padding: .4em;
+}
+
+.CodeMirror pre {
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ -o-border-radius: 0;
+ border-radius: 0;
+ border-width: 0; margin: 0; padding: 0; background: transparent;
+ font-family: inherit;
+}
+
+.CodeMirror-cursor {
+ z-index: 10;
+ position: absolute;
+ visibility: hidden;
+ border-left: 1px solid black !important;
+}
+.CodeMirror-focused .CodeMirror-cursor {
+ visibility: visible;
+}
+
+span.CodeMirror-selected {
+ background: #ccc !important;
+ color: HighlightText !important;
+}
+.CodeMirror-focused span.CodeMirror-selected {
+ background: Highlight !important;
+}
+
+.CodeMirror-matchingbracket {color: #0f0 !important;}
+.CodeMirror-nonmatchingbracket {color: #f22 !important;}
+
+/* CM2 default */
+.cm-s-default span.cm-keyword {color: #708;}
+.cm-s-default span.cm-atom {color: #219;}
+.cm-s-default span.cm-number {color: #164;}
+.cm-s-default span.cm-def {color: #00f;}
+.cm-s-default span.cm-variable {color: black;}
+.cm-s-default span.cm-variable-2 {color: #05a;}
+.cm-s-default span.cm-variable-3 {color: #0a5;}
+.cm-s-default span.cm-property {color: black;}
+.cm-s-default span.cm-operator {color: black;}
+.cm-s-default span.cm-comment {color: #a50;}
+.cm-s-default span.cm-string {color: #a11;}
+.cm-s-default span.cm-meta {color: #555;}
+.cm-s-default span.cm-error {color: #f00;}
+.cm-s-default span.cm-qualifier {color: #555;}
+.cm-s-default span.cm-builtin {color: #30a;}
+.cm-s-default span.cm-bracket {color: #cc7;}
+.cm-s-default span.cm-tag {color: #170;}
+.cm-s-default span.cm-attribute {color: #00c;}
+
+/* jsbin - based on web inspector */
+.cm-s-jsbin span.cm-keyword {color: #AA0D91;}
+.cm-s-jsbin span.cm-atom {color: #219;}
+.cm-s-jsbin span.cm-number {color: #164;}
+.cm-s-jsbin span.cm-def {color: #00f;}
+.cm-s-jsbin span.cm-variable {color: black;}
+.cm-s-jsbin span.cm-variable-2 {color: #05a;}
+.cm-s-jsbin span.cm-variable-3 {color: #0a5;}
+.cm-s-jsbin span.cm-property {color: black;}
+.cm-s-jsbin span.cm-operator {color: black;}
+.cm-s-jsbin span.cm-comment {color: #236E25;}
+.cm-s-jsbin span.cm-string {color: #C41A16;}
+.cm-s-jsbin span.cm-meta {color: #555;}
+.cm-s-jsbin span.cm-error {color: #f00;}
+.cm-s-jsbin span.cm-qualifier {color: #555;}
+.cm-s-jsbin span.cm-builtin {color: #30a;}
+.cm-s-jsbin span.cm-bracket {color: #cc7;}
+.cm-s-jsbin span.cm-tag {color: #881280;}
+.cm-s-jsbin span.cm-attribute {color: #994500;}
+
+
+/* neat */
+.cm-s-neat span.cm-comment { color: #a86; }
+.cm-s-neat span.cm-keyword { font-weight: bold; color: blue; }
+.cm-s-neat span.cm-string { color: #a22; }
+.cm-s-neat span.cm-builtin { font-weight: bold; color: #077; }
+.cm-s-neat span.cm-special { font-weight: bold; color: #0aa; }
+.cm-s-neat span.cm-variable { color: black; }
+.cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; }
+.cm-s-neat span.cm-meta {color: #555;}
+
+/* elegant */
+.cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom {color: #762;}
+.cm-s-elegant span.cm-comment {color: #262;font-style: italic;}
+.cm-s-elegant span.cm-meta {color: #555;font-style: italic;}
+.cm-s-elegant span.cm-variable {color: black;}
+.cm-s-elegant span.cm-variable-2 {color: #b11;}
+.cm-s-elegant span.cm-qualifier {color: #555;}
+.cm-s-elegant span.cm-keyword {color: #730;}
+.cm-s-elegant span.cm-builtin {color: #30a;}
+.cm-s-elegant span.cm-error {background-color: #fdd;}
+
+/* Loosely based on the Midnight Textmate theme */
+
+.cm-s-night { background: #0a001f; color: #f8f8f8; }
+.cm-s-night span.CodeMirror-selected { background: #a8f !important; }
+.cm-s-night .CodeMirror-gutter { background: #0a001f; border-right: 1px solid #aaa; }
+.cm-s-night .CodeMirror-gutter-text { color: #f8f8f8; }
+.cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; }
+.cm-s-night span.cm-comment { color: #6900a1; }
+.cm-s-night span.cm-atom { color: #845dc4; }
+.cm-s-night span.cm-number { color: #ffd500; }
+.cm-s-night span.cm-keyword { color: #599eff; }
+.cm-s-night span.cm-string { color: #37f14a; }
+.cm-s-night span.cm-meta { color: #7678e2; }
+.cm-s-night span.cm-variable-2 { color: #99b2ff; }
+.cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { white; }
+.cm-s-night span.cm-error { color: #9d1e15; }
+.cm-s-night span.cm-bracket { color: #8da6ce; }
+.cm-s-night span.cm-comment { color: #6900a1; }
+.cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; }
+
+#bin.ready {
+ opacity: 1;
+}
View
BIN  images/download.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
75 index.php
@@ -22,38 +22,40 @@
<div class="control">
<div class="buttons">
<a class="tab button source group left" accesskey="1" href="#source">Code</a>
- <a class="tab button preview group right gap" accesskey="2" href="#preview">Preview</a>
- <a title="Revert" class="button light group left enable" id="revert" href="#"><img class="enabled" src="/images/revert.png" /><img class="disabled" src="/images/revert-disabled.png" /></a>
+ <a class="tab button preview group right gap" accesskey="2" href="#preview" title="Run with alerts, prompts, etc">Render</a>
+ <a title="Revert" class="button light group left" id="revert" href="#"><img class="enabled" src="/images/revert.png" /><img class="disabled" src="/images/revert-disabled.png" /></a>
<?php if ($code_id) : ?>
<a id="jsbinurl" class="button group light left" href="<?=HOST . $code_id?>"><?=HOST . $code_id?></a>
<?php else : ?>
- <a id="save" class="button save group left right" href="/save">Save</a>
+ <a id="save" class="button save group left" href="/save">Save</a>
<?php endif ?>
- <?php if ($code_id) : ?><a id="save" class="button gap light save group right" href="<?=$code_id_path?>/save">Save changes</a><?php endif ?>
- <a id="stream" class="button left right" href="#stream">Stream</a>
+ <?php if ($code_id) : ?><a id="save" class="button light save group" href="<?=$code_id_path?>/save">Save</a><?php endif ?>
+ <a id="download" class="button download group right light gap" href="">Download</a>
+
+ <span id="panelsvisible" class="gap">View:
+ <input type="checkbox" data-panel="javascript" id="showjavascript"><label for="showjavascript">JavaScript</label>
+ <input type="checkbox" data-panel="html" id="showhtml"><label for="showhtml">HTML</label>
+ <input type="checkbox" data-panel="live" id="showlive"><label for="showlive">Real-time preview</label>
+ </span>
</div>
</div>
- <!-- <div class="starting">
-
- </div> -->
<div class="help">
<ul class="flat">
- <li><a id="startingpoint" href="#"><span>Save as my template</span></a></li>
- <!-- <li><a class="video" href="/about">About</a></li>
- <li><a class="video" href="#">Ajax Debugging</a></li> -->
- <li><a href="/help">Help &amp; tutorials</a></li>
+ <li><a target="_blank" href="http://jsbin.tumblr.com">Help &amp; tutorials</a></li>
</ul>
</div>
</div>
-<div id="bin" class="stretch">
+<div id="bin" class="stretch" style="opacity: 0">
<div id="source" class="binview stretch">
<div class="code stretch javascript">
- <div class="label"><p><strong id="jslabel">JavaScript</strong><span> (<span class="hide">hide</span><span class="show">show</span> HTML)</span></p></div>
- <textarea id="javascript"></textarea>
+ <div class="label"><p><strong id="jslabel">JavaScript</strong><!-- <span> (<span class="hide">hide</span><span class="show">show</span> HTML)</span> --></p></div>
+ <div class="editbox">
+ <textarea id="javascript"></textarea>
+ </div>
</div>
<div class="code stretch html">
<div class="label">
- <p>HTML<span> (<span class="hide">hide</span><span class="show">show</span> JavaScript)</span></p>
+ <p>HTML<!-- <span> (<span class="hide">hide</span><span class="show">show</span> JavaScript)</span> --></p>
<label for="library">Include</label>
<select id="library">
<option value="none">None</option>
@@ -67,7 +69,9 @@
<option value="ext">Ext js</option>
</select>
</div>
- <textarea id="html"></textarea>
+ <div class="editbox">
+ <textarea id="html"></textarea>
+ </div>
</div>
</div>
<div id="live" class="stretch livepreview"><span class="close"></span></div>
@@ -76,8 +80,11 @@
<input type="hidden" name="method" />
</form>
</div>
-<div id="help"><p><a href="/help/index.html">Help Menu</a></p><div id="content"></div></div>
-<?php
+<div id="tip"><p>You can jump to the latest bin by adding <code>/latest</code> to your URL</p><a class="dismiss" href="#">Dismiss x</a></div>
+<!-- <div id="help"><p><a href="/help/index.html">Help Menu</a></p><div id="content"></div></div> -->
+<script>
+<?php
+/*
// construct the correct query string, if we're injecting the html or JS
$qs = '';
if (isset($_GET['js']) || isset($_GET['html']) || (@$_POST['inject'] && isset($_POST['html'])) ) {
@@ -95,18 +102,36 @@
if (@$_GET['html']) {
$qs .= 'html=' . rawurlencode(stripslashes($_GET['html']));
}
+*/
if (@$_POST['inject'] && @$_POST['html']) :
$jsonReplaces = array(array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"'), array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"'));
$html = '"' . str_replace($jsonReplaces[0], $jsonReplaces[1], $_POST['html']) . '"';
?>
-<script>var template = { html : <?=$html?>, javascript: '' };</script>
-<?php else : ?>
-<script src="<?=$code_id_path ?>/source/<?=$qs?>"></script>
-<?php endif ?>
-<script src="http://forbind.net/js/?apikey=2796bc83070164231a3ab8c90227dbca"></script>
+var template = { html : <?=$html?>, javascript: '' };
+<?php else :
+/* <script src="<?=$code_id_path ?>/source/<?=$qs?>"></script> */
+ list($code_id, $revision) = getCodeIdParams($request);
+
+ $edit_mode = false;
+
+ if ($code_id) {
+ list($latest_revision, $html, $javascript) = getCode($code_id, $revision);
+ } else {
+ list($html, $javascript) = defaultCode();
+ }
+
+ $url = HOST . $code_id . ($revision == 1 ? '' : '/' . $revision);
+ if (!$ajax) {
+ echo 'var template = ';
+ }
+ // doubles as JSON
+ echo '{"url":"' . $url . '","html" : ' . encode($html) . ',"javascript":' . encode($javascript) . '}';
+
+endif ?>
+</script>
+<script>jsbin = { version: "<?=VERSION?>" };</script>
<script src="/js/<?=VERSION?>/jsbin.js"></script>
-<script>jsbin.version = "<?=VERSION?>";</script>
<?php if (!OFFLINE) : ?>
<script>
var _gaq = _gaq || [];
View
84 js/chrome/app.js
@@ -1,10 +1,13 @@
-//= require "storage"
-//= require "events"
-//= require "navigation"
-//= require "save"
-//= require "file-drop"
+//= require "errors"
+//= require "download"
+//= require "../render/live"
+//= require "tips"
+this.livePreview = function () {
+ $('#live').trigger('toggle');
+};
-var debug = false,
+var debug = jsbin.settings.debug === undefined ? false : jsbin.settings.debug,
+ documentTitle = null, // null = JS Bin
$bin = $('#bin'),
loadGist,
$document = $(document),
@@ -13,17 +16,24 @@ var debug = false,
sessionStorage.setItem('html', editors.html.getCode());
sessionStorage.setItem('url', template.url);
+ localStorage.setItem('settings', JSON.stringify(jsbin.settings));
+
var panel = getFocusedPanel();
sessionStorage.setItem('panel', panel);
try { // this causes errors in IE9 - so we'll use a try/catch to get through it
- sessionStorage.setItem('line', editors[panel].currentLine());
- sessionStorage.setItem('character', editors[panel].cursorPosition().character);
+ sessionStorage.setItem('line', editors[panel].getCursor().line);
+ sessionStorage.setItem('character', editors[panel].getCursor().ch);
} catch (e) {
sessionStorage.setItem('line', 0);
sessionStorage.setItem('character', 0);
}
};
+//= require "storage"
+//= require "events"
+//= require "navigation"
+//= require "save"
+//= require "file-drop"
$(window).unload(unload);
@@ -49,9 +59,26 @@ if (window.location.hash == '#preview') {
}
$document.one('jsbinReady', function () {
- if (localStorage && localStorage.getItem('livepreview') == 'true') { // damn string coersion
- $('#live').trigger('show');
+ // if (localStorage && localStorage.getItem('livepreview') == 'true') { // damn string coersion
+ // $('#live').trigger('show');
+ // }
+
+ $('.code.html').splitter();
+ $live.splitter();
+
+ for (panel in jsbin.settings.show) {
+ if (jsbin.settings.show[panel]) {
+ $('#show' + panel).attr('checked', 'checked')[0].checked = true;
+ } else {
+ $('#show' + panel).removeAttr('checked')[0].checked = false;
+ }
+ }
+
+ for (panel in jsbin.settings.show) {
+ updatePanel(panel, jsbin.settings.show[panel]);
}
+
+ $bin.removeAttr('style').addClass('ready');
});
// if a gist has been requested, lazy load the gist library and plug it in
@@ -70,39 +97,10 @@ if (/gist\/\d+/.test(window.location.pathname) && (!sessionStorage.getItem('java
}
}
-$('div.label p').click(function () {
- // determine which side was clicked
- var panel = $(this).closest('.code').is('.javascript') ? 'javascript' : 'html',
- otherpanel = panel == 'javascript' ? 'html' : 'javascript',
- mustshow = $bin.is('.' + panel + '-only'),
- speed = 150,
- animatePanel = animateOtherPanel = {};
-
- if ($bin.is('.' + panel + '-only')) { // showing the panel
- // only the html tab could have been clicked
- animatePanel = panel == 'html' ? { left: '50%', width: '50%' } : { left: '0%', width: '50%' };
- animateOtherPanel = otherpanel == 'javascript' ? { left: '0%' } : { left: '50%' };
- $bin.find('div.' + panel).animate(animatePanel, speed);
- $bin.find('div.' + otherpanel).show().animate(animateOtherPanel, speed, function () {
- $bin.removeClass(panel + '-only');
- localStorage && localStorage.removeItem('visible-panel');
- });
- } else { // hiding other panel
- animatePanel = panel == 'html' ? { left: '0%', width: '100%' } : { width: '100%' };
- animateOtherPanel = otherpanel == 'javascript' ? { left: '-50%' } : { left: '100%' };
-
- $bin.find('div.' + panel).animate(animatePanel, speed);
- $bin.find('div.' + otherpanel).animate(animateOtherPanel, speed, function () {
- $(this).hide();
- $bin.addClass(panel + '-only');
- // makes me sad, but we have to put this in a try/catch because Safari
- // sometimes throws an error when using localStorage, then jQuery goes
- // in to an infinite loop if an animation callback throws an exeception!
- try {
- // we're not reading 'true', only that it's been set
- localStorage && localStorage.setItem('visible-panel', panel);
- } catch (e) {}
- });
+$document.keydown(function (event) {
+ if (event.metaKey && event.which == 83) {
+ $('#save').click();
+ event.preventDefault();
}
});
View
101 js/chrome/beta.js
@@ -1,17 +1,10 @@
-window.jsbin = {};
-
// once these features are live, they come out of the jsbin beta box
(function () {
var $body = $('body');
- //= require "download"
-
this.on = function () {
localStorage.setItem('beta', 'true');
$body.addClass('beta');
-
- enableDownload();
- enableLive();
};
this.off = function () {
@@ -22,95 +15,5 @@ window.jsbin = {};
this.active = localStorage.getItem('beta') == 'true' || false;
if (this.active) this.on();
- //= require "stream"
- // expose...for now
- window.stream = this.stream;
-
- //= require "../render/live"
- this.livePreview = function () {
- $('#live').trigger('toggle');
- };
-
- //= require "../vendor/jshint/jshint"
- //= require "../vendor/jquery.tipsy"
- this.jshint = function () {
- var source = editors.javascript.getCode();
- var ok = JSHINT(source);
-
- return ok ? true : JSHINT.data();
- };
-
- var $error = $('<em>errors</em>').hide();
- $('#jslabel').append($error);
-
- // modify JSHINT to only return errors that are of value (to me, for displaying)
- JSHINT._data = JSHINT.data;
- JSHINT.data = function (onlyErrors) {
- var data = JSHINT._data(),
- errors = [];
-
- if (onlyErrors && data.errors) {
- for (var i = 0; i < data.errors.length; i++) {
- if (data.errors[i] !== null && data.errors[i].evidence) { // ignore JSHINT just quitting
- errors.push(data.errors[i]);
- }
- }
- return {
- errors: errors
- };
- } else {
- return data;
- }
- };
-
- $error.tipsy({
- title: function () {
- var html = ['<ul>'],
- errors = JSHINT.data(true).errors;
- for (var i = 0; i < errors.length; i++) {
- html.push('Line ' + errors[i].line + ': ' + errors[i].evidence + ' --- ' + errors[i].reason);
- }
-
- return html.join('<li>') + '</ul>';
- },
- gravity: 'nw',
- html: true
- });
-
- $error.click(function () {
- var errors = JSHINT.data(true).errors;
- if (errors.length) {
- var line = editors.javascript.nthLine(errors[0].line);
- editors.javascript.jumpToLine(line);
- editors.javascript.selectLines(line, 0, editors.javascript.nthLine(errors[0].line + 1), 0);
- return false;
- }
- });
-
- var checkForErrors = function () {
- var jshint = jsbin.jshint(),
- errors = '';
-
- if (jshint === true) {
- $error.text('').hide();
- } else {
- errors = JSHINT.data(true).errors.length;
- errors = errors == 1 ? '1 error' : errors + ' errors';
- $error.text('(' + errors + ')').show();
- }
- };
-
- $(document).bind('codeChange', throttle(checkForErrors, 1000));
- $(document).bind('jsbinReady', checkForErrors);
-}).call(jsbin);
-
-function throttle(fn, delay) {
- var timer = null;
- return function () {
- var context = this, args = arguments;
- clearTimeout(timer);
- timer = setTimeout(function () {
- fn.apply(context, args);
- }, delay);
- };
-}
+ //= require "stream"
+}).call(jsbin);
View
9 js/chrome/download.js
@@ -1,6 +1,5 @@
-function enableDownload() {
- $('#save').removeClass('right gap').after('<a id="download" class="button download group right light gap" href="">Download</a>');
-
+(function () {
+
var $revert = $('#revert');
$('#download').click(function (event) {
event.preventDefault();
@@ -15,4 +14,6 @@ function enableDownload() {
}); // triggers via ajax
}
});
-}
+
+
+})();
View
107 js/chrome/errors.js
@@ -0,0 +1,107 @@
+//= require "../vendor/jshint/jshint"
+//= require "../vendor/jquery.tipsy"
+var jshint = function () {
+ var source = editors.javascript.getCode();
+ var ok = JSHINT(source);
+
+ return ok ? true : JSHINT.data();
+};
+
+var detailsSupport = 'open' in document.createElement('details');
+
+var $error = $('<details><summary>errors</summary></details>').hide();
+$('#source .javascript').append($error);
+
+$error.find('summary').click(function () {
+ if (!detailsSupport) {
+ $(this).nextAll().toggle();
+ $error[0].open = !$error[0].open;
+ }
+ // trigger a resize after the click has completed and the details is close
+ setTimeout(function () {
+ $document.trigger('sizeeditors');
+ }, 10);
+});
+
+if (!detailsSupport) {
+ $error[0].open = false;
+}
+
+// modify JSHINT to only return errors that are of value (to me, for displaying)
+JSHINT._data = JSHINT.data;
+JSHINT.data = function (onlyErrors) {
+ var data = JSHINT._data(),
+ errors = [];
+
+ if (onlyErrors && data.errors) {
+ for (var i = 0; i < data.errors.length; i++) {
+ if (data.errors[i] !== null && data.errors[i].evidence) { // ignore JSHINT just quitting
+ errors.push(data.errors[i]);
+ }
+ }
+ return {
+ errors: errors
+ };
+ } else {
+ data.errors = [];
+ return data;
+ }
+};
+
+// $error.tipsy({
+// title: function () {
+// var html = ['<ul>'],
+// errors = JSHINT.data(true).errors;
+// for (var i = 0; i < errors.length; i++) {
+// html.push('Line ' + errors[i].line + ': ' + errors[i].evidence + ' --- ' + errors[i].reason);
+// }
+//
+// return html.join('<li>') + '</ul>';
+// },
+// gravity: 'nw',
+// html: true
+// });
+
+$error.delegate('li', 'click', function () {
+ var errors = JSHINT.data(true).errors;
+ if (errors.length) {
+ var i = $error.find('li').index(this);
+ editors.javascript.setSelection({ line: errors[i].line - 1, ch: 0 }, { line: errors[i].line - 1 });
+ editors.javascript.focus();
+ // var line = editors.javascript.nthLine(errors[0].line);
+ // editors.javascript.jumpToLine(line);
+ // editors.javascript.selectLines(line, 0, editors.javascript.nthLine(errors[0].line + 1), 0);
+ return false;
+ }
+});
+
+var checkForErrors = function () {
+ var hint = jshint(),
+ jshintErrors = JSHINT.data(true),
+ errors = '',
+ visible = $error.is(':visible');
+
+ if (hint === true && visible) {
+ $error.hide();
+ $document.trigger('sizeeditors');
+ } else if (jshintErrors.errors.length) {
+ var html = ['<ol>'],
+ errors = jshintErrors.errors;
+ for (var i = 0; i < errors.length; i++) {
+ html.push('Line ' + errors[i].line + ': ' + errors[i].evidence + ' --- ' + errors[i].reason);
+ }
+
+ html = html.join('<li>') + '</ol>';
+
+ $error.find('summary').text(jshintErrors.errors.length == 1 ? '1 error' : jshintErrors.errors.length + ' errors');
+ $error.find('ol').remove();
+
+ if (!detailsSupport && $error[0].open == false) html = $(html).hide();
+
+ $error.append(html).show();
+ $document.trigger('sizeeditors');
+ }
+};
+
+$(document).bind('codeChange', throttle(checkForErrors, 1000));
+$(document).bind('jsbinReady', checkForErrors);
View
148 js/chrome/navigation.js
@@ -1,22 +1,76 @@
-$('#startingpoint').click(function () {
- if (localStorage) {
- localStorage.setItem('saved-javascript', editors.javascript.getCode());
- localStorage.setItem('saved-html', editors.html.getCode());
+// $('#startingpoint').click(function () {
+// if (localStorage) {
+// localStorage.setItem('saved-javascript', editors.javascript.getCode());
+// localStorage.setItem('saved-html', editors.html.getCode());
+//
+// // fade text out - then show "saved", then bring it back in again
+// $(this).find('span:first').fadeOut(200, function () {
+// $(this).next().fadeIn(200).animate({ foo: 1 }, 1000, function () {
+// $(this).fadeOut(200, function () {
+// $(this).prev().fadeIn(150);
+// });
+// });
+// });
+// }
+// return false;
+// }).find('span').after('<span style="display: none;">Saved</span>');
+
+var $htmlpanel = $('.code.html'),
+ htmlsplitter = null;
+
+function updatePanel(panel, show) {
+ jsbin.settings.show[panel] = show;
+ htmlsplitter = htmlsplitter || $htmlpanel.data().splitter;
+
+ if (panel == 'live') {
+ $('#live').trigger(show ? 'show' : 'hide');
+ htmlsplitter.trigger('init'); // update the position of the html splitter
+ } else {
+ var $panel = $bin.find('.code.' + panel)[show ? 'show' : 'hide']();
- // fade text out - then show "saved", then bring it back in again
- $(this).find('span:first').fadeOut(200, function () {
- $(this).next().fadeIn(200).animate({ foo: 1 }, 1000, function () {
- $(this).fadeOut(200, function () {
- $(this).prev().fadeIn(150);
- });
- });
- });
+ if (!show) {
+ htmlsplitter.hide();
+ } else {
+ htmlsplitter.show();
+ }
+
+ var $otherpanel = panel == 'html' ? $bin.find('.code.javascript') : $bin.find('.code.html'),
+ visible = $panelsvisible.filter(':not([data-panel="live"]):checked').length,
+ $othercheckbox = $panelsvisible.filter('[data-panel=' + (panel == 'html' ? 'javascript' : 'html') + ']');
+
+ // logic was only revealed by going through every possible combination. Hey, it was late :(
+ if (visible === 1 && show == false) {
+ // stretch
+ $othercheckbox.attr('disabled', 'disabled');
+ if (panel == 'html') { // only JavaScript remains
+ $otherpanel.data('style', { 'right': $otherpanel.css('right') });
+ $otherpanel.css('right', '0');
+ } else if (panel == 'javascript') { // only HTML remains
+ $otherpanel.data('style', {'left' : $otherpanel.css('left') });
+ $otherpanel.css('left', '0');
+ }
+ } else {
+ $othercheckbox.removeAttr('disabled');
+ // restore CSS positions
+ $otherpanel.attr('style', $otherpanel.data('style'));
+ }
+
+ if (show) {
+ editors[panel].refresh();
+ }
+
+ htmlsplitter.trigger('init'); // on show or hide - recalc the splitter position
}
- return false;
-}).find('span').after('<span style="display: none;">Saved</span>');
+}
+
+var $panelsvisible = $('#panelsvisible input').click(function () {
+ var checked = this.checked,
+ panel = $(this).data('panel');
+
+ updatePanel(panel, checked);
+});
var $revert = $('#revert').click(function () {
-
if ($revert.is(':not(.enable)')) {
return false;
}
@@ -39,51 +93,49 @@ var $revert = $('#revert').click(function () {
return false;
});
-var $stream = $('#stream').click(function () {
- stream.create();
- return false;
-});
$('#control .tab').click(function (event) {
- event.preventDefault();
+ // event.preventDefault();
$('body').removeClass('source preview').addClass(this.hash.substr(1));
if ($(this).is('.preview')) {
+ $('#preview iframe').remove();
$('#preview').append('<iframe class="stretch"></iframe>');
renderPreview();
} else {
// remove iframe and thus removing any (I *think*) memory resident JS
$('#preview iframe').remove();
+ editors[getFocusedPanel()].focus();
}
});
-$('#control div.help a:last').click(function () {
- $(window).trigger('togglehelp');
- return false;
-});
+// $('#control div.help a:last').click(function () {
+// $(window).trigger('togglehelp');
+// return false;
+// });
-$('#help a:host(' + window.location.host + ')').live('click', function () {
- $('#help #content').load(this.href + '?' + Math.random());
- return false;
-});
-
-var helpOpen = false;
-$(window).bind('togglehelp', function () {
- var s = 100, right = helpOpen ? 0 : 300;
+// $('#help a:host(' + window.location.host + ')').live('click', function () {
+// $('#help #content').load(this.href + '?' + Math.random());
+// return false;
+// });
- if (helpOpen == false) {
- $('#help #content').load('/help/index.html?' + Math.random());
- }
- $bin.find('> div').animate({ right: right }, { duration: s });
- $('#control').animate({ right: right }, { duration: s });
-
- $('#help').animate({ right: helpOpen ? -300 : 0 }, { duration: s});
-
- helpOpen = helpOpen ? false : true;
-});
-
-$(document).keyup(function (event) {
- if (helpOpen && event.keyCode == 27) {
- $(window).trigger('togglehelp');
- }
-});
+// var helpOpen = false;
+// $(window).bind('togglehelp', function () {
+// var s = 100, right = helpOpen ? 0 : 300;
+//
+// if (helpOpen == false) {
+// $('#help #content').load('/help/index.html?' + Math.random());
+// }
+// $bin.find('> div').animate({ right: right }, { duration: s });
+// $('#control').animate({ right: right }, { duration: s });
+//
+// $('#help').animate({ right: helpOpen ? -300 : 0 }, { duration: s});
+//
+// helpOpen = helpOpen ? false : true;
+// });
+//
+// $(document).keyup(function (event) {
+// if (helpOpen && event.keyCode == 27) {
+// $(window).trigger('togglehelp');
+// }
+// });
View
2  js/chrome/save.js
@@ -28,7 +28,7 @@ function saveCode(method, ajax, ajaxCallback) {
if (window.history && window.history.pushState) {
window.history.pushState(null, data.edit, data.edit);
- $('#jsbinurl').attr('href', data.edit).text(data.url);
+ $('#jsbinurl').attr('href', data.url).text(data.url);
} else {
window.location = data.edit;
}
View
86 js/chrome/splitter.js
@@ -0,0 +1,86 @@
+$.fn.splitter = function () {
+ var $document = $(document);
+ var splitterSettings = JSON.parse(localStorage.getItem('splitterSettings') || '[]');
+ return this.each(function () {
+ var $el = $(this),
+ guid = $.fn.splitter.guid++,
+ $parent = $el.parent(),
+ $prev = $el.prev(),
+ $handle = $('<div class="resize"></div>'),
+ $blocker = $('<div class="block" />').css({ cursor: 'pointer', position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, 'z-index': 99999, width: '100%', height: '100%' }),
+ dragging = false,
+ width = $parent.width(),
+ left = $parent.offset().left,
+ settings = splitterSettings[guid] || {};
+
+ function moveSplitter(posX) {
+ var x = posX - left,
+ split = 100 / width * x;
+
+ if (split > 10 && split < 90) {
+ $el.css('left', split + '%');
+ $prev.css('right', (100 - split) + '%');
+ $handle.css({
+ left: split + '%'
+ });
+ settings.x = posX;
+ splitterSettings[guid] = settings;
+ console.log('set: ', JSON.stringify(splitterSettings));
+ localStorage.setItem('splitterSettings', JSON.stringify(splitterSettings));
+ }
+ }
+
+ $document.mouseup(function () {
+ dragging = false;
+ $blocker.remove();
+ $handle.css('opacity', '0');
+ }).mousemove(function (event) {
+ if (dragging) {
+ moveSplitter(event.pageX);
+ }
+ });
+
+ $handle.mousedown(function (e) {
+ dragging = true;
+ $('body').append($blocker);
+ // TODO layer on div to block iframes from stealing focus
+ width = $parent.width();
+ left = $parent.offset().left;
+ e.preventDefault();
+ }).hover(function () {
+ $handle.css('opacity', '1');
+ }, function () {
+ if (!dragging) {
+ $handle.css('opacity', '0');
+ }
+ });
+
+ $handle.bind('init', function (event, x) {
+ $handle.css({
+ top: 0,
+ // left: (100 / width * $el.offset().left) + '%',
+ bottom: 0,
+ width: 4,
+ opacity: 0,
+ position: 'absolute',
+ cursor: 'pointer',
+ 'border-left': '1px solid rgba(218, 218, 218, 0.5)',
+ 'z-index': 99999
+ });
+
+ if ($el.is(':hidden')) {
+ $handle.hide();
+ } else {
+ moveSplitter(x || $el.offset().left);
+ }
+ }).trigger('init', settings.x || $el.offset().left);
+
+ $prev.css('width', 'auto');
+ $el.data('splitter', $handle);
+ $el.before($handle);
+
+
+ });
+};
+
+$.fn.splitter.guid = 0;
View
2  js/chrome/storage.js
@@ -17,7 +17,7 @@ var requiresCookies = (function () {
// Firefox with Cookies disabled triggers a security error when we probe window.sessionStorage
// currently we're just disabling all the session features if that's the case.
-var sessionStorage, localStorage;
+var sessionStorage = window.sessionStorage, localStorage = window.localStorage;
if (!requiresCookies && window.sessionStorage) {
sessionStorage = window.sessionStorage;
View
360 js/chrome/stream.js
@@ -1,191 +1,245 @@
-(function (global) {
-var $stream = $('<div id="streaming"><span class="msg"></span><span class="n"></span><span class="listen"> (click here to <span class="resume">resume</span><span class="pause">pause</span>)</span></div>').prependTo('body'),
- streaming = false,
- $body = $('body'),
- key = null,
- captureTimer = null,
- last = {},
- owner = false;
-
-function capture() {
- var javascript = editors.javascript.getCode(),
- html = editors.html.getCode(),
- changed = false,
- msg = {};
-
- if (javascript != last.javascript) {
- msg.javascript = javascript;
- changed = true;
+//= require "../vendor/diff_match_patch_uncompressed"
+
+var context = this;
+
+var script = document.createElement('script');
+script.src = 'http://forbind.net/js/';
+document.body.appendChild(script);
+
+setTimeout(function forbindReady() {
+ if (typeof window.forbind !== 'undefined') {
+ forbind.apikey = '2796bc83070164231a3ab8c90227dbca';
+ console.log('forbind ready');
+ initForbind(context);
+ } else {
+ setTimeout(forbindReady, 20);
}
+}, 20);
+
+function initForbind(global) {
+ var $stream = $('<div id="streaming"><span class="msg"></span><span class="n"></span><span class="listen"> (click here to <span class="resume">resume</span><span class="pause">pause</span>)</span></div>').prependTo('body'),
+ streaming = false,
+ $body = $('body'),
+ key = null,
+ captureTimer = null,
+ last = {},
+ owner = false;
+
+ function changes(lang, code) {
+ var msg = {},
+ diff,
+ patch,
+ result;
+
+ if (last[lang] === undefined) {
+ msg.text = code;
+ msg.diff = false;
+ } else {
+ diff = new diff_match_patch();
+ // 1. get diffs
+ patch = diff.patch_make(last[lang], code);
+ // 2. apply patch to old javascript
+ result = diff.patch_apply(patch, last[lang]);
+
+ // 3. if it matches, then send diff
+ if (result[0] == code) {
+ msg.text = diff.patch_toText(patch);
+ msg.diff = true;
+ // 4. otherwise, send entire code
+ } else {
+ msg.text = code;
+ msg.diff = false;
+ }
+ }
+
+ last[lang] = code;
- if (html != last.html) {
- msg.html = html;
- changed = true;
+ return msg;
}
+
+ function capture() {
+ var javascript = editors.javascript.getCode(),
+ html = editors.html.getCode(),
+ changed = false,
+ cursor,
+ msg = {};
- if (changed) {
- last = {
- javascript: javascript,
- html: html
- };
-
- msg.panel = getFocusedPanel();
+ msg.javascript = changes('javascript', javascript);
+ msg.html = changes('html', html);
+
+ if (msg.html.text || msg.javascript.text) {
+ msg.panel = getFocusedPanel();
+
+ cursor = editors[msg.panel].getCursor();
+
+ msg.line = cursor.line;
+ msg.ch = cursor.ch;
- msg.line = editors[msg.panel].currentLine();
- msg.character = editors[msg.panel].cursorPosition().character;
+ console.log('sending', msg);
- forbind.send(msg);
+ forbind.send(msg);
+ }
}
-}
-forbind.on({
- join: function (event) {
- $body.addClass('streaming').removeClass('pausestream');
- streaming = true;
+ if (typeof window.forbind !== 'undefined') forbind.on({
+ join: function (event) {
+ $body.addClass('streaming').removeClass('pausestream');
+ streaming = true;
- if (event.isme) {
- $('#stream').fadeOut('fast').prev().addClass('right');
- }
+ if (event.isme) {
+ $('#stream').fadeOut('fast').prev().addClass('right');
+ }
- if (event.isme && event.readonlykey) {
- owner = true;
- sessionStorage.setItem('streamwritekey', event.readonlykey);
- sessionStorage.setItem('streamkey', key);
+ if (event.isme && event.readonlykey) {
+ owner = true;
+ sessionStorage.setItem('streamwritekey', event.readonlykey);
+ sessionStorage.setItem('streamkey', key);
- var type, editorTimer = { javascript: null, html: null };
+ var type, editorTimer = { javascript: null, html: null };
- // this code is completely over the top - need to simplify
- $stream.find('.msg').html('streaming on <a href="/?stream=' + key + '">http://jsbin.com/?stream=' + key + '</a> to #');
+ // this code is completely over the top - need to simplify
+ $stream.find('.msg').html('streaming on <a href="/?stream=' + key + '">http://jsbin.com/?stream=' + key + '</a> to #');
- $stream.removeClass('listen');
+ $stream.removeClass('listen');
- for (type in editors) {
- (function (type) {
- try {
- $(editors[type].win.document).bind('keyup', function () {
- if (streaming) {
- clearTimeout(editorTimer[type]);
- editorTimer[type] = setTimeout(capture, 250);
- }
- });
- } catch (e) {}
- })(type);
- }
+ // for (type in editors) {
+ // (function (type) {
+ // try {
+ // $(editors[type].win.document).bind('keyup', throttle(function () {
+ // if (streaming) {
+ // console.log('capture?');
+ // capture();
+ // }
+ // }, 250));
+ // } catch (e) {}
+ // })(type);
+ // }
- $(document).bind('codeChange', capture);
+ $(document).bind('codeChange', throttle(capture, 250));
- }
+ }
- updateCount(event);
- },
- leave: function (event) {
- if (event.isme) {
- if (!owner) {
- $body.addClass('pausestream');
- streaming = false;
-
- $stream.one('click', function () {
- window.location.search.replace(/stream=(.+?)\b/, function (n, key) {
- global.stream.join(key);
- });
- });
- } else {
- $body.removeClass('streaming');
- owner = false;
- sessionStorage.removeItem('streamkey');
- sessionStorage.removeItem('streamwritekey');
+ updateCount(event);
+ },
+ leave: function (event) {
+ if (event.isme) {
+ if (!owner) {
+ $body.addClass('pausestream');
+ streaming = false;
+
+ $stream.one('click', function () {
+ window.location.search.replace(/stream=(.+?)\b/, function (n, key) {
+ global.stream.join(key);
+ });
+ });
+ } else {
+ $body.removeClass('streaming');
+ owner = false;
+ sessionStorage.removeItem('streamkey');
+ sessionStorage.removeItem('streamwritekey');
+ }
}
- }
- updateCount(event);
- },
- message: function (msg) {
- var code = msg.data;
- if (code.javascript) {
- editors.javascript.setCode(code.javascript);
- }
+ updateCount(event);
+ },
+ message: function (event) {
+ var msg = event.data;
+ updateCode(msg.javascript, 'javascript');
+ updateCode(msg.html, 'html');
- if (code.html) {
- editors.html.setCode(code.html);
- $(document).trigger('codeChange');
+ // update preview if required
+ if ($body.is('.preview')) {
+ $('#preview').remove('iframe').append('<iframe class="stretch"></iframe>');
+ renderPreview();
+ } else {
+ var focused = editors[msg.panel];
+ focused.focus();
+ focused.setSelection({ line: msg.line, ch: msg.ch });
+ $(document).trigger('codeChange'); // does this bubble to our send function?
+ }
+ },
+ error: function (data) {
+ console.log('error in forbind', data);
}
+ });
+
+ function updateCode(msg, lang) {
+ var diff, patch, result, code;
- // update preview if required
- if ($body.is('.preview')) {
- $('#preview').append('<iframe class="stretch"></iframe>');
- renderPreview();
- } else {
- var focused = editors[code.panel];
- focused.focus();
- focused.selectLines(focused.nthLine(code.line), code.character);
-
+ if (msg.text) {
+ if (msg.diff) {
+ diff = new diff_match_patch();
+ code = editors[lang].getCode();
+ console.log(msg.text);
+ var patch = diff.patch_fromText(msg.text);
+ var result = diff.patch_apply(patch, code);
+ editors[lang].setCode(result[0]);
+ } else {
+ editors[lang].setCode(msg.text);
+ }
}
- },
- error: function (data) {
- console.log('error in forbind', data);
}
-});
-function updateCount(data) {
- if (owner) {
- var txt = (data.total - 1) == 1 ? ' user' : ' users';
- $stream.find('.n').html((data.total - 1) + txt);
+ function updateCount(data) {
+ if (owner) {
+ var txt = (data.total - 1) == 1 ? ' user' : ' users';
+ $stream.find('.n').html((data.total - 1) + txt);
+ }
}
-}
-global.stream = {
- create: function () {
- key = (Math.abs(~~(Math.random()*+new Date))).toString(32); // OTT?
+ window.stream = global.stream = {
+ create: function () {
+ key = (Math.abs(~~(Math.random()*+new Date))).toString(32); // OTT?
- forbind.create(key);
+ forbind.create(key);
- return key;
- },
- join: function (key) {
- forbind.join(key);
+ return key;
+ },
+ join: function (key) {
+ forbind.join(key);
- owner = false;
- sessionStorage.removeItem('streamkey');
- sessionStorage.removeItem('streamwritekey');
+ owner = false;
+ sessionStorage.removeItem('streamkey');
+ sessionStorage.removeItem('streamwritekey');
- $stream.addClass('listen');
+ $stream.addClass('listen');
- $(document).one('keyup', function (event) {
- if (streaming && event.which == 27) {
+ $(document).one('keyup', function (event) {
+ if (streaming && event.which == 27) {
+ global.stream.leave();
+ }
+ });
+
+ $stream.one('click', function () {
global.stream.leave();
+ });
+
+ for (var type in editors) {
+ try {
+ $(editors[type].win.document).one('keyup', function (event) {
+ if (event.which == 27) {
+ global.stream.leave();
+ }
+ });
+ } catch (e) {
+ // because it sometimes throw an error on reconnecting trying to read win.document
+ }
}
- });
-
- $stream.one('click', function () {
- global.stream.leave();
- });
-
- for (var type in editors) {
- try {
- $(editors[type].win.document).one('keyup', function (event) {
- if (event.which == 27) {
- global.stream.leave();
- }
- });
- } catch (e) {
- // because it sometimes throw an error on reconnecting trying to read win.document
- }
- }
- $stream.find('.msg').html('following live stream...');
- },
- leave: function () {
- forbind.leave();
- }
-};
+ $stream.find('.msg').html('following live stream...');
+ },
+ leave: function () {
+ forbind.leave();
+ }
+ };
-window.location.search.replace(/stream=(.+?)\b/, function (n, key) {
- global.stream.join(key);
-});
+ window.location.search.replace(/stream=(.+?)\b/, function (n, key) {
+ global.stream.join(key);
+ });
-if (sessionStorage.getItem('streamkey')) {
- key = sessionStorage.getItem('streamkey');
- forbind.join(key, sessionStorage.getItem('streamwritekey') || undefined);
-}
+ if (sessionStorage.getItem('streamkey')) {
+ key = sessionStorage.getItem('streamkey');
+ forbind.join(key, sessionStorage.getItem('streamwritekey') || undefined);
+ }
-})(this);
+}
View
15 js/chrome/tips.js
@@ -0,0 +1,15 @@
+var $html = $(document.documentElement);
+
+$('#tip a.dismiss').click(function () {
+ $html.removeClass('showtip');
+ $(window).resize();
+ sessionStorage.setItem('tips', 'false');
+ return false;
+});
+
+var showTips = sessionStorage.getItem('tips');
+if (showTips === null) {
+ // $html.addClass('showtip');
+}
+
+// remove this setting after a few days (or a new set of tips come in)
View
190 js/editors/autocomplete.js
@@ -0,0 +1,190 @@
+// Minimal event-handling wrapper.
+function stopEvent() {
+ if (this.preventDefault) {this.preventDefault(); this.stopPropagation();}
+ else {this.returnValue = false; this.cancelBubble = true;}
+}
+function addStop(event) {
+ if (!event.stop) event.stop = stopEvent;
+ return event;
+}
+function connect(node, type, handler) {
+ function wrapHandler(event) {handler(addStop(event || window.event));}
+ if (typeof node.addEventListener == "function")
+ node.addEventListener(type, wrapHandler, false);
+ else
+ node.attachEvent("on" + type, wrapHandler);
+}
+
+function forEach(arr, f) {
+ for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
+}
+
+function startTagComplete(editor) {
+ // We want a single cursor position.
+ if (editor.somethingSelected()) return;
+ // Find the token at the cursor
+ var cur = editor.getCursor(false), token = editor.getTokenAt(cur), tprop = token;
+ // If it's not a 'word-style' token, ignore the token.
+ if (!/^[\w$_]*$/.test(token.string)) {
+ token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
+ className: token.string == "." ? "js-property" : null};
+ }
+
+ // If it is a property, find out what it is a property of.
+ while (tprop.className == "js-property") {
+ tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
+ if (tprop.string != ".") return;
+ tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
+ if (!context) var context = [];
+ context.push(tprop);
+ }
+
+ function insert(str) {
+ editor.replaceRange(str, {line: cur.line, ch: token.start}, {line: cur.line, ch: token.end});
+ }
+
+ insert('<></>');
+ editor.focus();
+ editor.setCursor({ line: cur.line, ch: token.end });
+ return true;
+}
+
+function startComplete(editor) {
+ // We want a single cursor position.
+ if (editor.somethingSelected()) return;
+ // Find the token at the cursor
+ var cur = editor.getCursor(false), token = editor.getTokenAt(cur), tprop = token;
+ // If it's not a 'word-style' token, ignore the token.
+
+ if (token.string == '') return;
+
+ if (!/^[\w$_]*$/.test(token.string)) {
+ token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
+ className: token.string == "." ? "js-property" : null};
+ }
+
+ // If it is a property, find out what it is a property of.
+ while (tprop.className == "js-property") {
+ tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
+ if (tprop.string != ".") return;
+ tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
+ if (!context) var context = [];
+ context.push(tprop);
+ }
+
+ if (token.string == '' && context === undefined) return;
+
+ var completions = getCompletions(token, context, editor);
+ if (!completions.length) return;
+ function insert(str) {
+ editor.replaceRange(str, {line: cur.line, ch: token.start}, {line: cur.line, ch: token.end});
+ }
+ // When there is only one completion, use it directly.
+ if (completions.length == 1) {insert(completions[0]); return true;}
+
+ // Build the select widget
+ var complete = document.createElement("div");
+ complete.className = "completions";
+ var sel = complete.appendChild(document.createElement("select"));
+ sel.multiple = true;
+ for (var i = 0; i < completions.length; ++i) {
+ var opt = sel.appendChild(document.createElement("option"));
+ opt.appendChild(document.createTextNode(completions[i]));
+ }
+ sel.firstChild.selected = true;
+ sel.size = Math.min(10, completions.length);
+ var pos = editor.cursorCoords();
+ complete.style.left = pos.x + "px";
+ complete.style.top = pos.yBot + "px";
+ complete.style.position = 'absolute';
+ complete.style.outline = 'none';
+ complete.className = 'autocomplete';
+ document.body.appendChild(complete);
+
+ // Hack to hide the scrollbar.
+ if (completions.length <= 10) {
+ complete.style.width = (sel.clientWidth - 1) + "px";
+ }
+
+ var done = false;
+ function close() {
+ if (done) return;
+ done = true;
+ complete.parentNode.removeChild(complete);
+ }
+ function pick() {
+ insert(sel.options[sel.selectedIndex].value);
+ close();
+ setTimeout(function(){editor.focus();}, 50);
+ }
+
+ function pickandclose() {
+ pick()
+ setTimeout(function () { editor.focus(); }, 50);
+ }
+
+ connect(sel, "blur", close);
+ connect(sel, "keydown", function(event) {
+ var code = event.keyCode;
+ // Enter and space
+ if (code == 13 || code == 32) { event.stop(); pick();}
+ // Escape
+ else if (code == 27) {event.stop(); close(); editor.focus();}
+ else if (code != 38 && code != 40) {close(); editor.focus(); setTimeout(function () { startComplete(editor) }, 50);}
+ });
+ connect(sel, "dblclick", pick);
+
+ sel.focus();
+ // Opera sometimes ignores focusing a freshly created node
+ if (window.opera) setTimeout(function(){if (!done) sel.focus();}, 100);
+ return true;
+}
+
+var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
+ "toUpperCase toLowerCase split concat match replace search").split(" ");
+var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
+ "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
+var funcProps = "prototype apply call bind".split(" ");
+var keywords = ("break case catch continue debugger default delete do else false finally for function " +
+ "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
+
+function getCompletions(token, context, editor) {
+ var found = [], start = token.string;
+ function maybeAdd(str) {
+ if (str && str != start && str.indexOf(start) == 0 && found.indexOf(str) === -1) found.push(str);
+ }
+ function gatherCompletions(obj) {
+ if (typeof obj == "string") forEach(stringProps, maybeAdd);
+ else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
+ else if (obj instanceof Function) forEach(funcProps, maybeAdd);
+ for (var name in obj) maybeAdd(name);
+ }
+
+ if (context) {
+ // If this is a property, see if it belongs to some object we can
+ // find in the current environment.
+ var obj = context.pop(), base;
+ if (obj.className == "js-variable")
+ base = window[obj.string];
+ else if (obj.className == "js-string")
+ base = "";
+ else if (obj.className == "js-atom")
+ base = 1;
+ while (base != null && context.length)
+ base = base[context.pop().string];
+ if (base != null) gatherCompletions(base);
+ }
+ else {
+ // If not, just look in the window object and any local scope
+ // (reading into JS mode internals to get at the local variables)
+ for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
+ gatherCompletions(window);
+ forEach(keywords, maybeAdd);
+ }
+
+ // also look up symbols in the current document
+ var code = editor.getValue().split(/\W/);
+ forEach(code, maybeAdd);
+
+ return found;
+}
View
28 js/editors/codemirror.js
@@ -1,12 +1,18 @@
// used to generate the ../vendor/codemirror/basefiles.js
-//= require "../vendor/codemirror/util"
-//= require "../vendor/codemirror/stringstream"
-//= require "../vendor/codemirror/undo"
-//= require "../vendor/codemirror/editor"
-//= require "../vendor/codemirror/tokenize"
-//= require "../vendor/codemirror/select"
-//= require "../vendor/codemirror/parsexml"
-//= require "../vendor/codemirror/parsecss"
-//= require "../vendor/codemirror/tokenizejavascript"
-//= require "../vendor/codemirror/parsejavascript"
-//= require "../vendor/codemirror/parsehtmlmixed"
+// = require "../vendor/codemirror/util"
+// = require "../vendor/codemirror/stringstream"
+// = require "../vendor/codemirror/undo"
+// = require "../vendor/codemirror/editor"
+// = require "../vendor/codemirror/tokenize"
+// = require "../vendor/codemirror/select"
+// = require "../vendor/codemirror/parsexml"
+// = require "../vendor/codemirror/parsecss"
+// = require "../vendor/codemirror/tokenizejavascript"
+// = require "../vendor/codemirror/parsejavascript"
+// = require "../vendor/codemirror/parsehtmlmixed"
+
+//= require "../vendor/codemirror2/codemirror"
+//= require "../vendor/codemirror2/xml"
+//= require "../vendor/codemirror2/css"
+//= require "../vendor/codemirror2/javascript"
+//= require "../vendor/codemirror2/htmlmixed"
View
213 js/editors/editors.js
@@ -1,68 +1,62 @@
-//= require <codemirror>
+// = require <codemirror>
+//= require "codemirror"
//= require "mobileCodeMirror"
//= require "library"
//= require "unsaved"
+//= require "autocomplete"
var focusPanel = 'javascript';
var editors = {};
-editors.html = CodeMirror.fromTextArea('html', {
- basefiles: ['basefiles.js'],
+
+window.editors = editors;
+
+editors.html = CodeMirror.fromTextArea(document.getElementById('html'), {
parserfile: [],
- stylesheet: ["/css/codemirror.css", "/css/htmlcodeframe.css"],
- path: '/js/vendor/codemirror/',
tabMode: 'shift',
- iframeClass: 'stretch codeframe',
- initCallback: function () {
- setupEditor('html');
- }
+ mode: 'text/html',
+ onChange: changecontrol,
+ theme: jsbin.settings.theme
});
-editors.javascript = CodeMirror.fromTextArea('javascript', {
- basefiles: ['basefiles.js'],
- parserfile: ['parsejavascript.js'], // forces a switch back to JS parsing
- stylesheet: ["/css/codemirror.css", "/css/codeframe.css"],
- path: '/js/vendor/codemirror/',
- iframeClass: 'stretch codeframe javascript',
+editors.javascript = CodeMirror.fromTextArea(document.getElementById('javascript'), {
+ mode: 'javascript',
tabMode: 'shift',
- initCallback: function () {
- setupEditor('javascript');
- }
+ onChange: changecontrol,
+ theme: jsbin.settings.theme
});
+setupEditor('javascript');
+setupEditor('html');
+
var editorsReady = setInterval(function () {
if (editors.html.ready && editors.javascript.ready) {
clearInterval(editorsReady);
editors.ready = true;
if (typeof editors.onReady == 'function') editors.onReady();
- $(document).trigger('jsbinReady');
+
+ $document.bind('sizeeditors', function () {
+ var $el = $(editors.html.win),
+ top = 0, //$el.offset().top,
+ height = $('#bin').height();
+ $el.height(height - top);
+ $(editors.javascript.win).height(height - top - $error.filter(':visible').height());
+ editors.javascript.refresh();
+ editors.html.refresh();
+ });
+
+ $(window).resize(function () {
+ setTimeout(function () {
+ $document.trigger('sizeeditors');
+ }, 100);
+ });
+
+ $document.trigger('sizeeditors');
+ $document.trigger('jsbinReady');