diff options
author | Andrew Gerrand <adg@golang.org> | 2013-09-17 17:14:57 +1000 |
---|---|---|
committer | Andrew Gerrand <adg@golang.org> | 2013-09-17 17:14:57 +1000 |
commit | 7b08321066f219dca44c9447fd63d32cf05ff733 (patch) | |
tree | 6630df33f0347125513937c1a283ea9a2acda991 /static | |
parent | 18e34e7ae7fa717b074f7fad0fed540222faf43c (diff) |
go.blog: move blog-specific JavaScript into root template
Also update playground JavaScripts to the latest version.
This will make it easier for us to switch to godoc's static assets down
the line.
R=r, dsymonds
CC=golang-dev
https://golang.org/cl/13412049
Diffstat (limited to 'static')
-rw-r--r-- | static/play/play.js | 175 | ||||
-rw-r--r-- | static/play/playground.js | 330 | ||||
-rw-r--r-- | static/script.js | 29 |
3 files changed, 299 insertions, 235 deletions
diff --git a/static/play/play.js b/static/play/play.js index b6bd939..3b8c340 100644 --- a/static/play/play.js +++ b/static/play/play.js @@ -2,107 +2,98 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -(function() { - "use strict"; +function initPlayground(transport) { + "use strict"; - var runFunc; - var count = 0; + function text(node) { + var s = ""; + for (var i = 0; i < node.childNodes.length; i++) { + var n = node.childNodes[i]; + if (n.nodeType === 1 && n.tagName === "SPAN" && n.className != "number") { + var innerText = n.innerText === undefined ? "textContent" : "innerText"; + s += n[innerText] + "\n"; + continue; + } + if (n.nodeType === 1 && n.tagName !== "BUTTON") { + s += text(n); + } + } + return s; + } - function getId() { - return "code" + (count++); - } + function init(code) { + var output = document.createElement('div'); + var outpre = document.createElement('pre'); + var running; - function text(node) { - var s = ""; - for (var i = 0; i < node.childNodes.length; i++) { - var n = node.childNodes[i]; - if (n.nodeType === 1 && n.tagName === "SPAN" && n.className != "number") { - var innerText = n.innerText === undefined ? "textContent" : "innerText"; - s += n[innerText] + "\n"; - continue; - } - if (n.nodeType === 1 && n.tagName !== "BUTTON") { - s += text(n); - } - } - return s; - } + if ($ && $(output).resizable) { + $(output).resizable({ + handles: "n,w,nw", + minHeight: 27, + minWidth: 135, + maxHeight: 608, + maxWidth: 990 + }); + } - function init(code) { - var id = getId(); + function onKill() { + if (running) running.Kill(); + } - var output = document.createElement('div'); - var outpre = document.createElement('pre'); - var stopFunc; + function onRun(e) { + onKill(); + output.style.display = "block"; + outpre.innerHTML = ""; + run1.style.display = "none"; + var options = {Race: e.shiftKey}; + running = transport.Run(text(code), PlaygroundOutput(outpre), options); + } - function onKill() { - if (stopFunc) { - stopFunc(); - } - } + function onClose() { + onKill(); + output.style.display = "none"; + run1.style.display = "inline-block"; + } - function onRun(e) { - onKill(); - outpre.innerHTML = ""; - output.style.display = "block"; - run.style.display = "none"; - var options = {Race: e.shiftKey}; - stopFunc = runFunc(text(code), outpre, options); - } + var run1 = document.createElement('button'); + run1.innerHTML = 'Run'; + run1.className = 'run'; + run1.addEventListener("click", onRun, false); + var run2 = document.createElement('button'); + run2.className = 'run'; + run2.innerHTML = 'Run'; + run2.addEventListener("click", onRun, false); + var kill = document.createElement('button'); + kill.className = 'kill'; + kill.innerHTML = 'Kill'; + kill.addEventListener("click", onKill, false); + var close = document.createElement('button'); + close.className = 'close'; + close.innerHTML = 'Close'; + close.addEventListener("click", onClose, false); - function onClose() { - onKill(); - output.style.display = "none"; - run.style.display = "inline-block"; - } + var button = document.createElement('div'); + button.classList.add('buttons'); + button.appendChild(run1); + // Hack to simulate insertAfter + code.parentNode.insertBefore(button, code.nextSibling); - var run = document.createElement('button'); - run.innerHTML = 'Run'; - run.className = 'run'; - run.addEventListener("click", onRun, false); - var run2 = document.createElement('button'); - run2.className = 'run'; - run2.innerHTML = 'Run'; - run2.addEventListener("click", onRun, false); - var kill = document.createElement('button'); - kill.className = 'kill'; - kill.innerHTML = 'Kill'; - kill.addEventListener("click", onKill, false); - var close = document.createElement('button'); - close.className = 'close'; - close.innerHTML = 'Close'; - close.addEventListener("click", onClose, false); + var buttons = document.createElement('div'); + buttons.classList.add('buttons'); + buttons.appendChild(run2); + buttons.appendChild(kill); + buttons.appendChild(close); - var button = document.createElement('div'); - button.classList.add('buttons'); - button.appendChild(run); - // Hack to simulate insertAfter - code.parentNode.insertBefore(button, code.nextSibling); + output.classList.add('output'); + output.appendChild(buttons); + output.appendChild(outpre); + output.style.display = "none"; + code.parentNode.insertBefore(output, button.nextSibling); + } - var buttons = document.createElement('div'); - buttons.classList.add('buttons'); - buttons.appendChild(run2); - buttons.appendChild(kill); - buttons.appendChild(close); + var play = document.querySelectorAll('div.playground'); + for (var i = 0; i < play.length; i++) { + init(play[i]); + } +} - output.classList.add('output'); - output.appendChild(buttons); - output.appendChild(outpre); - output.style.display = "none"; - code.parentNode.insertBefore(output, button.nextSibling); - } - - var play = document.querySelectorAll('div.playground'); - for (var i = 0; i < play.length; i++) { - init(play[i]); - } - if (play.length > 0) { - if (window.connectPlayground) { - runFunc = window.connectPlayground("ws://" + window.location.host + "/socket"); - } else { - // If this message is logged, - // we have neglected to include socket.js or playground.js. - console.log("No playground transport available."); - } - } -})(); diff --git a/static/play/playground.js b/static/play/playground.js index 67961c2..d2be24c 100644 --- a/static/play/playground.js +++ b/static/play/playground.js @@ -2,9 +2,210 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -(function() { +/* +In the absence of any formal way to specify interfaces in JavaScript, +here's a skeleton implementation of a playground transport. + + function Transport() { + // Set up any transport state (eg, make a websocket connnection). + return { + Run: function(body, output, options) { + // Compile and run the program 'body' with 'options'. + // Call the 'output' callback to display program output. + return { + Kill: function() { + // Kill the running program. + } + }; + } + }; + } + + // The output callback is called multiple times, and each time it is + // passed an object of this form. + var write = { + Kind: 'string', // 'start', 'stdout', 'stderr', 'end' + Body: 'string' // content of write or end status message + } + + // The first call must be of Kind 'start' with no body. + // Subsequent calls may be of Kind 'stdout' or 'stderr' + // and must have a non-null Body string. + // The final call should be of Kind 'end' with an optional + // Body string, signifying a failure ("killed", for example). + + // The output callback must be of this form. + // See PlaygroundOutput (below) for an implementation. + function outputCallback(write) { + // Append writes to + } +*/ + +function HTTPTransport() { + 'use strict'; + + // TODO(adg): support stderr + + function playback(output, events) { + var timeout; + output({Kind: 'start'}); + function next() { + if (events.length === 0) { + output({Kind: 'end'}); + return; + } + var e = events.shift(); + if (e.Delay === 0) { + output({Kind: 'stdout', Body: e.Message}); + next(); + return; + } + timeout = setTimeout(function() { + output({Kind: 'stdout', Body: e.Message}); + next(); + }, e.Delay / 1000000); + } + next(); + return { + Stop: function() { + clearTimeout(timeout); + } + } + } + + function error(output, msg) { + output({Kind: 'start'}); + output({Kind: 'stderr', Body: msg}); + output({Kind: 'end'}); + } + + var seq = 0; + return { + Run: function(body, output, options) { + seq++; + var cur = seq; + var playing; + $.ajax('/compile', { + type: 'POST', + data: {'version': 2, 'body': body}, + dataType: 'json', + success: function(data) { + if (seq != cur) return; + if (!data) return; + if (playing != null) playing.Stop(); + if (data.Errors) { + error(output, data.Errors); + return; + } + playing = playback(output, data.Events); + }, + error: function() { + error(output, 'Error communicating with remote server.'); + } + }); + return { + Kill: function() { + if (playing != null) playing.Stop(); + output({Kind: 'end', Body: 'killed'}); + } + }; + } + }; +} + +function SocketTransport() { + 'use strict'; + + var id = 0; + var outputs = {}; + var started = {}; + var websocket = new WebSocket('ws://' + window.location.host + '/socket'); + + websocket.onclose = function() { + console.log('websocket connection closed'); + } + + websocket.onmessage = function(e) { + var m = JSON.parse(e.data); + var output = outputs[m.Id]; + if (output === null) + return; + if (!started[m.Id]) { + output({Kind: 'start'}); + started[m.Id] = true; + } + output({Kind: m.Kind, Body: m.Body}); + } + + function send(m) { + websocket.send(JSON.stringify(m)); + } - // TODO(adg): make these functions operate only on a specific code div + return { + Run: function(body, output, options) { + var thisID = id+''; + id++; + outputs[thisID] = output; + send({Id: thisID, Kind: 'run', Body: body, Options: options}); + return { + Kill: function() { + send({Id: thisID, Kind: 'kill'}); + } + }; + } + }; +} + +function PlaygroundOutput(el) { + 'use strict'; + + return function(write) { + if (write.Kind == 'start') { + el.innerHTML = ''; + return; + } + + var cl = 'system'; + if (write.Kind == 'stdout' || write.Kind == 'stderr') + cl = write.Kind; + + var m = write.Body; + if (write.Kind == 'end') + m = '\nProgram exited' + (m?(': '+m):'.'); + + if (m.indexOf('IMAGE:') === 0) { + // TODO(adg): buffer all writes before creating image + var url = 'data:image/png;base64,' + m.substr(6); + var img = document.createElement('img'); + img.src = url; + el.appendChild(img); + return; + } + + // ^L clears the screen. + var s = m.split('\x0c'); + if (s.length > 1) { + el.innerHTML = ''; + m = s.pop(); + } + + m = m.replace(/&/g, '&'); + m = m.replace(/</g, '<'); + m = m.replace(/>/g, '>'); + + var needScroll = (el.scrollTop + el.offsetHeight) == el.scrollHeight; + + var span = document.createElement('span'); + span.className = cl; + span.innerHTML = m; + el.appendChild(span); + + if (needScroll) + el.scrollTop = el.scrollHeight - el.offsetHeight; + } +} + +(function() { function lineHighlight(error) { var regex = /prog.go:([0-9]+)/g; var r = regex.exec(error); @@ -13,115 +214,16 @@ r = regex.exec(error); } } + function highlightOutput(wrappedOutput) { + return function(write) { + if (write.Body) lineHighlight(write.Body); + wrappedOutput(write); + } + } function lineClear() { $(".lineerror").removeClass("lineerror"); } - function connectPlayground() { - var playbackTimeout; - - function playback(pre, events) { - function show(msg) { - // ^L clears the screen. - var msgs = msg.split("\x0c"); - if (msgs.length == 1) { - pre.text(pre.text() + msg); - return; - } - pre.text(msgs.pop()); - } - function next() { - if (events.length === 0) { - var exit = $('<span class="exit"/>'); - exit.text("\nProgram exited."); - exit.appendTo(pre); - return; - } - var e = events.shift(); - if (e.Delay === 0) { - show(e.Message); - next(); - } else { - playbackTimeout = setTimeout(function() { - show(e.Message); - next(); - }, e.Delay / 1000000); - } - } - next(); - } - - function stopPlayback() { - clearTimeout(playbackTimeout); - } - - function setOutput(output, events, error) { - stopPlayback(); - output.empty(); - lineClear(); - - // Display errors. - if (error) { - lineHighlight(error); - output.addClass("error").text(error); - return; - } - - // Display image output. - if (events.length > 0 && events[0].Message.indexOf("IMAGE:") === 0) { - var out = ""; - for (var i = 0; i < events.length; i++) { - out += events[i].Message; - } - var url = "data:image/png;base64," + out.substr(6); - $("<img/>").attr("src", url).appendTo(output); - return; - } - - // Play back events. - if (events !== null) { - playback(output, events); - } - } - - var seq = 0; - function runFunc(body, output) { - output = $(output); - seq++; - var cur = seq; - var data = { - "version": 2, - "body": body - }; - $.ajax("/compile", { - data: data, - type: "POST", - dataType: "json", - success: function(data) { - if (seq != cur) { - return; - } - if (!data) { - return; - } - if (data.Errors) { - setOutput(output, null, data.Errors); - return; - } - setOutput(output, data.Events, false); - }, - error: function() { - output.addClass("error").text( - "Error communicating with remote server." - ); - } - }); - return stopPlayback; - } - - return runFunc; - } - // opts is an object with these keys // codeEl - code editor element // outputEl - program output element @@ -132,10 +234,11 @@ // shareRedirect - base URL to redirect to on share (optional) // toysEl - toys select element (optional) // enableHistory - enable using HTML5 history API (optional) + // transport - playground transport to use (default is HTTPTransport) function playground(opts) { var code = $(opts.codeEl); - var runFunc = connectPlayground(); - var stopFunc; + var transport = opts['transport'] || new HTTPTransport(); + var running; // autoindent helpers. function insertTabs(n) { @@ -227,18 +330,19 @@ } function setError(error) { - if (stopFunc) stopFunc(); + if (running) running.Kill(); lineClear(); lineHighlight(error); output.empty().addClass("error").text(error); } function loading() { - if (stopFunc) stopFunc(); + lineClear(); + if (running) running.Kill(); output.removeClass("error").text('Waiting for remote server...'); } function run() { loading(); - stopFunc = runFunc(body(), output); + running = transport.Run(body(), highlightOutput(PlaygroundOutput(output[0]))); } function fmt() { loading(); @@ -317,7 +421,5 @@ } } - window.connectPlayground = connectPlayground; window.playground = playground; - })(); diff --git a/static/script.js b/static/script.js deleted file mode 100644 index 8c61b5a..0000000 --- a/static/script.js +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -$(function() { - // Insert line numbers for all playground elements. - $('.playground').each(function() { - var $spans = $(this).find('> pre > span'); - - // Compute width of number column (including trailing space). - var max = 0; - $spans.each(function() { - var n = $(this).attr('num')*1; - if (n > max) max = n; - }); - var width = 2; - while (max > 10) { - max = max / 10; - width++; - } - - // Insert line numbers with space padding. - $spans.each(function() { - var n = $(this).attr('num')+" "; - while (n.length < width) n = " "+n; - $('<span class="number">').text(n).insertBefore(this); - }); - }); -}); |