ref: 5f020655d08a351cb5b74d0ba7f6a998eb5be528
parent: f8c9bd648275f2b20c6b2656dfcc5a1800cde026
author: Philip Silva <[email protected]>
date: Fri Feb 5 14:44:24 EST 2021
Call document.close at the end
--- a/browser/experimental.go
+++ b/browser/experimental.go
@@ -132,6 +132,10 @@
initialized = true
}
+ if err = d.CloseDoc(); err != nil {
+ return "", fmt.Errorf("close doc: %w", err)
+ }
+
resHtm, changed, err := d.TrackChanges()
if err != nil {
return "", fmt.Errorf("track changes: %w", err)
--- a/domino/domino.go
+++ b/domino/domino.go
@@ -116,6 +116,7 @@
window.top = window;
window.self = window;
addEventListener = function() {};
+ removeEventListener = function() {};
window.location.href = 'http://example.com';
var ___fq;
___fq = function(pre, el) {
@@ -151,8 +152,6 @@
userAgent: 'opossum'
};
HTMLElement = domino.impl.HTMLElement;
- // Fire DOMContentLoaded to trigger $(document).ready(..)
- document.close();
` + script
if !initial {
SCRIPT = script
@@ -271,6 +270,12 @@
return d.Exec(string(buf), true)
}
+// CloseDoc fires DOMContentLoaded to trigger $(document).ready(..)
+func (d *Domino) CloseDoc() (err error) {
+ _, err = d.Exec("document.close();", false)
+ return
+}
+
// TriggerClick, and return the result html
// ...then HTML5 parse it, diff the node tree
// (probably faster and cleaner than anything else)
@@ -281,7 +286,10 @@
console.log('query ' + sel);
- if (el._listeners && el._listeners.click) {
+ if (!el) {
+ console.log('el is null/undefined');
+ null;
+ } else if (el._listeners && el._listeners.click) {
var fn = el.click.bind(el);
if (fn) {
--- a/domino/domino_test.go
+++ b/domino/domino_test.go
@@ -109,6 +109,9 @@
if err != nil {
t.Fatalf("%v", err)
}
+ if err = d.CloseDoc(); err != nil {
+ t.Fatalf("%v", err)
+ }
res, err := d.Exec("$('h1').attr('style')", false)
t.Logf("res=%v", res)
if err != nil {
@@ -160,10 +163,6 @@
}
d := NewDomino(string(buf), nil)
d.Start()
- script := `
- Object.assign(this, window);
- `
- _ = script
for i, fn := range []string{"initfuncs.js", "jquery-1.8.2.js", "goversion.js", "godocs.js"} {
buf, err := ioutil.ReadFile("godoc/"+fn)
if err != nil {
@@ -177,6 +176,37 @@
d.Stop()
}
+func TestGoplayground(t *testing.T) {
+ buf, err := ioutil.ReadFile("godoc/golang.html")
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ d := NewDomino(string(buf), nil)
+ d.Start()
+ for i, fn := range []string{"initfuncs.js", "jquery-1.8.2.js", "playground.js", "goversion.js", "godocs.js", "golang.js"} {
+ buf, err := ioutil.ReadFile("godoc/"+fn)
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ _, err = d.Exec(string(buf) /*+ ";" + script*/, i == 0)
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ }
+ res, err := d.Exec("window.playground", false)
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ if !strings.Contains(res, "function playground(opts) {") {
+ t.Fatalf("%v", res)
+ }
+ if err = d.CloseDoc(); err != nil {
+ t.Fatalf("%v", err)
+ }
+
+ d.Stop()
+}
+
func TestJqueryUI(t *testing.T) {
buf, err := ioutil.ReadFile("jqueryui/tabs.html")
if err != nil {
@@ -250,16 +280,10 @@
if err != nil {
t.Fatalf("%v", err)
}
- //t.Parallel()
SCRIPT := string(jQuery) + `
- ;;;
- Object.assign(this, window);
- console.log("Started");
var clicked = false;
$(document).ready(function() {
- console.log('READDDYYYYY!!!!!!!');
$('h1').click(function() {
- console.log('CLICKED!!!!');
clicked = true;
});
});
@@ -269,6 +293,9 @@
_, err = d.Exec(SCRIPT, true)
if err != nil {
t.Fatalf(err.Error())
+ }
+ if err = d.CloseDoc(); err != nil {
+ t.Fatalf("%v", err)
}
res, err := d.Exec("$('h1').html()", false)
--- /dev/null
+++ b/domino/godoc/golang.html
@@ -1,0 +1,299 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<meta name="description" content="Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="theme-color" content="#00ADD8">
+
+ <title>The Go Programming Language</title>
+
+<link href="https://fonts.googleapis.com/css?family=Work+Sans:600|Roboto:400,700" rel="stylesheet">
+<link href="https://fonts.googleapis.com/css?family=Product+Sans&text=Supported%20by%20Google&display=swap" rel="stylesheet">
+<link type="text/css" rel="stylesheet" href="/lib/godoc/style.css">
+
+<link rel="search" type="application/opensearchdescription+xml" title="godoc" href="/opensearch.xml" />
+
+<script>window.initFuncs = [];</script>
+
+<script>
+var _gaq = _gaq || [];
+_gaq.push(["_setAccount", "UA-11222381-2"]);
+window.trackPageview = function() {
+ _gaq.push(["_trackPageview", location.pathname+location.hash]);
+};
+window.trackPageview();
+window.trackEvent = function(category, action, opt_label, opt_value, opt_noninteraction) {
+ _gaq.push(["_trackEvent", category, action, opt_label, opt_value, opt_noninteraction]);
+};
+</script>
+
+<script src="/lib/godoc/jquery.js" defer></script>
+
+
+<script src="/lib/godoc/playground.js" defer></script>
+
+<script>var goVersion = "go1.15.8";</script>
+<script src="/lib/godoc/godocs.js" defer></script>
+
+<body class="Site">
+<header class="Header js-header">
+ <div class="Header-banner">
+ Black Lives Matter.
+ <a href="https://support.eji.org/give/153413/#!/donation/checkout"
+ target="_blank"
+ rel="noopener">Support the Equal Justice Initiative.</a>
+ </div>
+ <nav class="Header-nav ">
+ <a href="/"><img class="Header-logo" src="/lib/godoc/images/go-logo-blue.svg" alt="Go"></a>
+ <button class="Header-menuButton js-headerMenuButton" aria-label="Main menu" aria-expanded="false">
+ <div class="Header-menuButtonInner"></div>
+ </button>
+ <ul class="Header-menu">
+ <li class="Header-menuItem"><a href="/doc/">Documents</a></li>
+ <li class="Header-menuItem"><a href="/pkg/">Packages</a></li>
+ <li class="Header-menuItem"><a href="/project/">The Project</a></li>
+ <li class="Header-menuItem"><a href="/help/">Help</a></li>
+
+ <li class="Header-menuItem"><a href="/blog/">Blog</a></li>
+
+ <li class="Header-menuItem"><a href="https://play.golang.org/">Play</a></li>
+
+
+ <li class="Header-menuItem Header-menuItem--search">
+ <form class="HeaderSearch" role="search" action="/search">
+ <input class="HeaderSearch-input"
+ type="search"
+ name="q"
+ placeholder="Search"
+ aria-label="Search"
+ autocapitalize="off"
+ autocomplete="off"
+ autocorrect="off"
+ spellcheck="false"
+ required>
+ <button class="HeaderSearch-submit">
+ <!-- magnifying glass: --><svg class="HeaderSearch-icon" width="24" height="24" viewBox="0 0 24 24"><title>Search</title><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
+ </button>
+ </form>
+ </li>
+ </ul>
+ </nav>
+</header>
+
+<main id="page" class="Site-content">
+<div class="container">
+
+
+
+
+
+
+
+
+<div id="nav"></div>
+
+
+
+
+<div class="HomeContainer">
+ <section class="HomeSection Hero">
+ <h1 class="Hero-header">
+ Go is an open source programming language that makes it easy to build
+ <strong>simple</strong>, <strong>reliable</strong>, and <strong>efficient</strong> software.
+ </h1>
+ <i class="Hero-gopher"></i>
+ <a href="/dl/" class="Button Button--big HeroDownloadButton">
+ <img class="HeroDownloadButton-image" src="/lib/godoc/images/cloud-download.svg" alt="">
+ Download Go
+ </a>
+ <p class="Hero-description">
+ Binary distributions available for<br>
+ Linux, macOS, Windows, and more.
+ </p>
+ </section>
+
+ <section class="HomeSection Playground">
+ <div class="Playground-headerContainer">
+ <h2 class="HomeSection-header">Try Go</h2>
+
+ <a class="Playground-popout js-playgroundShareEl">Open in Playground</a>
+
+ </div>
+ <div class="Playground-inputContainer">
+ <textarea class="Playground-input js-playgroundCodeEl" spellcheck="false" aria-label="Try Go">// You can edit this code!
+// Click here and start typing.
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("Hello, 世界")
+}
+</textarea>
+ </div>
+ <div class="Playground-outputContainer js-playgroundOutputEl">
+ <pre class="Playground-output"><noscript>Hello, 世界</noscript></pre>
+ </div>
+ <div class="Playground-controls">
+ <select class="Playground-selectExample js-playgroundToysEl" aria-label="Code examples">
+ <option value="hello.go">Hello, World!</option>
+ <option value="life.go">Conway's Game of Life</option>
+ <option value="fib.go">Fibonacci Closure</option>
+ <option value="peano.go">Peano Integers</option>
+ <option value="pi.go">Concurrent pi</option>
+ <option value="sieve.go">Concurrent Prime Sieve</option>
+ <option value="solitaire.go">Peg Solitaire Solver</option>
+ <option value="tree.go">Tree Comparison</option>
+ </select>
+ <div class="Playground-buttons">
+ <button class="Button Button--primary js-playgroundRunEl" title="Run this code [shift-enter]">Run</button>
+ <div class="Playground-secondaryButtons">
+
+ <button class="Button js-playgroundShareEl" title="Share this code">Share</button>
+ <a class="Button tour" href="https://tour.golang.org/" title="Playground Go from your browser">Tour</a>
+
+ </div>
+ </div>
+ </div>
+ </section>
+
+
+ <section class="HomeSection Blog js-blogContainerEl">
+ <h2 class="HomeSection-header">Featured articles</h2>
+ <div class="Blog-footer js-blogFooterEl"><a class="Button Button--primary" href="https://blog.golang.org/">Read more ></a></div>
+ </section>
+
+ <section class="HomeSection">
+ <h2 class="HomeSection-header">Featured video</h2>
+ <div class="js-videoContainer" style="--aspect-ratio-padding: 58.07%;">
+ <iframe width="415" height="241" src="https://www.youtube.com/embed/rFejpH_tAHM" frameborder="0" allowfullscreen></iframe>
+ </div>
+ </section>
+
+</div>
+<script>
+(function() {
+ 'use strict';
+
+ window.initFuncs.push(function() {
+ // Set up playground if enabled.
+ if (window.playground) {
+ window.playground({
+ "codeEl": ".js-playgroundCodeEl",
+ "outputEl": ".js-playgroundOutputEl",
+ "runEl": ".js-playgroundRunEl",
+ "shareEl": ".js-playgroundShareEl",
+ "shareRedirect": "//play.golang.org/p/",
+ "toysEl": ".js-playgroundToysEl"
+ });
+
+ // The pre matched below is added by the code above. Style it appropriately.
+ document.querySelector(".js-playgroundOutputEl pre").classList.add("Playground-output");
+ } else {
+ $(".Playground").hide();
+ }
+ });
+
+
+ function readableTime(t) {
+ var m = ["January", "February", "March", "April", "May", "June", "July",
+ "August", "September", "October", "November", "December"];
+ var p = t.substring(0, t.indexOf("T")).split("-");
+ var d = new Date(p[0], p[1]-1, p[2]);
+ return d.getDate() + " " + m[d.getMonth()] + " " + d.getFullYear();
+ }
+
+ window.feedLoaded = function(result) {
+ var read = document.querySelector(".js-blogFooterEl");
+ for (var i = 0; i < result.length && i < 2; i++) {
+ var entry = result[i];
+ var header = document.createElement("h3");
+ header.className = "Blog-title";
+ var titleLink = document.createElement("a");
+ titleLink.href = entry.Link;
+ titleLink.rel = "noopener";
+ titleLink.textContent = entry.Title;
+ header.appendChild(titleLink);
+ read.parentNode.insertBefore(header, read);
+ var extract = document.createElement("div");
+ extract.className = "Blog-extract";
+ extract.innerHTML = entry.Summary;
+ // Ensure any cross-origin links have rel=noopener set.
+ var links = extract.querySelectorAll("a");
+ for (var j = 0; j < links.length; j++) {
+ links[j].rel = "noopener";
+ links[j].classList.add("Blog-link");
+ }
+ read.parentNode.insertBefore(extract, read);
+ var when = document.createElement("div");
+ when.className = "Blog-when";
+ when.textContent = "Published " + readableTime(entry.Time);
+ read.parentNode.insertBefore(when, read);
+ }
+ }
+
+ window.initFuncs.push(function() {
+ // Load blog feed.
+ $("<script/>")
+ .attr("src", "//blog.golang.org/.json?jsonp=feedLoaded")
+ .appendTo("body");
+
+ // Set the video at random.
+ var videos = [
+ {
+ s: "https://www.youtube.com/embed/rFejpH_tAHM",
+ title: "dotGo 2015 - Rob Pike - Simplicity is Complicated",
+ },
+ {
+ s: "https://www.youtube.com/embed/0ReKdcpNyQg",
+ title: "GopherCon 2015: Robert Griesemer - The Evolution of Go",
+ },
+ {
+ s: "https://www.youtube.com/embed/sX8r6zATHGU",
+ title: "Steve Francia - Go: building on the shoulders of giants and stepping on a few toes",
+ },
+ {
+ s: "https://www.youtube.com/embed/rWJHbh6qO_Y",
+ title: "Brad Fitzpatrick Go 1.11 and beyond",
+ },
+ {
+ s: "https://www.youtube.com/embed/bmZNaUcwBt4",
+ title: "The Why of Go",
+ },
+ {
+ s: "https://www.youtube.com/embed/0Zbh_vmAKvk",
+ title: "GopherCon 2017: Russ Cox - The Future of Go",
+ },
+ ];
+ var v = videos[Math.floor(Math.random()*videos.length)];
+ $(".js-videoContainer iframe").attr("src", v.s).attr("title", v.title);
+ });
+
+})();
+</script>
+
+
+</div><!-- .container -->
+</main><!-- #page -->
+<footer>
+ <div class="Footer ">
+ <img class="Footer-gopher" src="/lib/godoc/images/footer-gopher.jpg" alt="The Go Gopher">
+ <ul class="Footer-links">
+ <li class="Footer-link"><a href="/doc/copyright.html">Copyright</a></li>
+ <li class="Footer-link"><a href="/doc/tos.html">Terms of Service</a></li>
+ <li class="Footer-link"><a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a></li>
+ <li class="Footer-link"><a href="http://golang.org/issues/new?title=x/website:" target="_blank" rel="noopener">Report a website issue</a></li>
+ </ul>
+ <a class="Footer-supportedBy" href="https://google.com">Supported by Google</a>
+ </div>
+</footer>
+
+<script>
+(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>
+
+
--- /dev/null
+++ b/domino/godoc/golang.js
@@ -1,0 +1,97 @@
+(function() {
+ 'use strict';
+ window.initFuncs.push(function() {
+ // Set up playground if enabled.
+ if (window.playground) {
+ window.playground({
+ "codeEl": ".js-playgroundCodeEl",
+ "outputEl": ".js-playgroundOutputEl",
+ "runEl": ".js-playgroundRunEl",
+ "shareEl": ".js-playgroundShareEl",
+ "shareRedirect": "//play.golang.org/p/",
+ "toysEl": ".js-playgroundToysEl"
+ });
+
+ // The pre matched below is added by the code above. Style it appropriately.
+ document.querySelector(".js-playgroundOutputEl pre").classList.add("Playground-output");
+ } else {
+ $(".Playground").hide();
+ }
+ });
+
+
+ function readableTime(t) {
+ var m = ["January", "February", "March", "April", "May", "June", "July",
+ "August", "September", "October", "November", "December"];
+ var p = t.substring(0, t.indexOf("T")).split("-");
+ var d = new Date(p[0], p[1]-1, p[2]);
+ return d.getDate() + " " + m[d.getMonth()] + " " + d.getFullYear();
+ }
+
+ window.feedLoaded = function(result) {
+ var read = document.querySelector(".js-blogFooterEl");
+ for (var i = 0; i < result.length && i < 2; i++) {
+ var entry = result[i];
+ var header = document.createElement("h3");
+ header.className = "Blog-title";
+ var titleLink = document.createElement("a");
+ titleLink.href = entry.Link;
+ titleLink.rel = "noopener";
+ titleLink.textContent = entry.Title;
+ header.appendChild(titleLink);
+ read.parentNode.insertBefore(header, read);
+ var extract = document.createElement("div");
+ extract.className = "Blog-extract";
+ extract.innerHTML = entry.Summary;
+ // Ensure any cross-origin links have rel=noopener set.
+ var links = extract.querySelectorAll("a");
+ for (var j = 0; j < links.length; j++) {
+ links[j].rel = "noopener";
+ links[j].classList.add("Blog-link");
+ }
+ read.parentNode.insertBefore(extract, read);
+ var when = document.createElement("div");
+ when.className = "Blog-when";
+ when.textContent = "Published " + readableTime(entry.Time);
+ read.parentNode.insertBefore(when, read);
+ }
+ }
+
+ window.initFuncs.push(function() {
+ // Load blog feed.
+ $("<script/>")
+ .attr("src", "//blog.golang.org/.json?jsonp=feedLoaded")
+ .appendTo("body");
+
+ // Set the video at random.
+ var videos = [
+ {
+ s: "https://www.youtube.com/embed/rFejpH_tAHM",
+ title: "dotGo 2015 - Rob Pike - Simplicity is Complicated",
+ },
+ {
+ s: "https://www.youtube.com/embed/0ReKdcpNyQg",
+ title: "GopherCon 2015: Robert Griesemer - The Evolution of Go",
+ },
+ {
+ s: "https://www.youtube.com/embed/sX8r6zATHGU",
+ title: "Steve Francia - Go: building on the shoulders of giants and stepping on a few toes",
+ },
+ {
+ s: "https://www.youtube.com/embed/rWJHbh6qO_Y",
+ title: "Brad Fitzpatrick Go 1.11 and beyond",
+ },
+ {
+ s: "https://www.youtube.com/embed/bmZNaUcwBt4",
+ title: "The Why of Go",
+ },
+ {
+ s: "https://www.youtube.com/embed/0Zbh_vmAKvk",
+ title: "GopherCon 2017: Russ Cox - The Future of Go",
+ },
+ ];
+ var v = videos[Math.floor(Math.random()*videos.length)];
+ $(".js-videoContainer iframe").attr("src", v.s).attr("title", v.title);
+ });
+
+})();
\ No newline at end of file
--- /dev/null
+++ b/domino/godoc/playground.js
@@ -1,0 +1,575 @@
+// Copyright 2012 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.
+
+/*
+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 connection).
+ 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) {
+ }
+*/
+
+// HTTPTransport is the default transport.
+// enableVet enables running vet if a program was compiled and ran successfully.
+// If vet returned any errors, display them before the output of a program.
+function HTTPTransport(enableVet) {
+ 'use strict';
+
+ function playback(output, data) {
+ // Backwards compatibility: default values do not affect the output.
+ var events = data.Events || [];
+ var errors = data.Errors || '';
+ var status = data.Status || 0;
+ var isTest = data.IsTest || false;
+ var testsFailed = data.TestsFailed || 0;
+
+ var timeout;
+ output({ Kind: 'start' });
+ function next() {
+ if (!events || events.length === 0) {
+ if (isTest) {
+ if (testsFailed > 0) {
+ output({
+ Kind: 'system',
+ Body:
+ '\n' +
+ testsFailed +
+ ' test' +
+ (testsFailed > 1 ? 's' : '') +
+ ' failed.',
+ });
+ } else {
+ output({ Kind: 'system', Body: '\nAll tests passed.' });
+ }
+ } else {
+ if (status > 0) {
+ output({ Kind: 'end', Body: 'status ' + status + '.' });
+ } else {
+ if (errors !== '') {
+ // errors are displayed only in the case of timeout.
+ output({ Kind: 'end', Body: errors + '.' });
+ } else {
+ output({ Kind: 'end' });
+ }
+ }
+ }
+ return;
+ }
+ var e = events.shift();
+ if (e.Delay === 0) {
+ output({ Kind: e.Kind, Body: e.Message });
+ next();
+ return;
+ }
+ timeout = setTimeout(function() {
+ output({ Kind: e.Kind, 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' });
+ }
+
+ function buildFailed(output, msg) {
+ output({ Kind: 'start' });
+ output({ Kind: 'stderr', Body: msg });
+ output({ Kind: 'system', Body: '\nGo build failed.' });
+ }
+
+ 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) {
+ if (data.Errors === 'process took too long') {
+ // Playback the output that was captured before the timeout.
+ playing = playback(output, data);
+ } else {
+ buildFailed(output, data.Errors);
+ }
+ return;
+ }
+
+ if (!enableVet) {
+ playing = playback(output, data);
+ return;
+ }
+
+ $.ajax('/vet', {
+ data: { body: body },
+ type: 'POST',
+ dataType: 'json',
+ success: function(dataVet) {
+ if (dataVet.Errors) {
+ if (!data.Events) {
+ data.Events = [];
+ }
+ // inject errors from the vet as the first events in the output
+ data.Events.unshift({
+ Message: 'Go vet exited.\n\n',
+ Kind: 'system',
+ Delay: 0,
+ });
+ data.Events.unshift({
+ Message: dataVet.Errors,
+ Kind: 'stderr',
+ Delay: 0,
+ });
+ }
+ playing = playback(output, data);
+ },
+ error: function() {
+ playing = playback(output, data);
+ },
+ });
+ },
+ 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;
+ if (window.location.protocol == 'http:') {
+ websocket = new WebSocket('ws://' + window.location.host + '/socket');
+ } else if (window.location.protocol == 'https:') {
+ websocket = new WebSocket('wss://' + 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));
+ }
+
+ 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);
+ while (r) {
+ $('.lines div')
+ .eq(r[1] - 1)
+ .addClass('lineerror');
+ r = regex.exec(error);
+ }
+ }
+ function highlightOutput(wrappedOutput) {
+ return function(write) {
+ if (write.Body) lineHighlight(write.Body);
+ wrappedOutput(write);
+ };
+ }
+ function lineClear() {
+ $('.lineerror').removeClass('lineerror');
+ }
+
+ // opts is an object with these keys
+ // codeEl - code editor element
+ // outputEl - program output element
+ // runEl - run button element
+ // fmtEl - fmt button element (optional)
+ // fmtImportEl - fmt "imports" checkbox element (optional)
+ // shareEl - share button element (optional)
+ // shareURLEl - share URL text input element (optional)
+ // 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)
+ // enableShortcuts - whether to enable shortcuts (Ctrl+S/Cmd+S to save) (default is false)
+ // enableVet - enable running vet and displaying its errors
+ function playground(opts) {
+ var code = $(opts.codeEl);
+ var transport = opts['transport'] || new HTTPTransport(opts['enableVet']);
+ var running;
+
+ // autoindent helpers.
+ function insertTabs(n) {
+ // find the selection start and end
+ var start = code[0].selectionStart;
+ var end = code[0].selectionEnd;
+ // split the textarea content into two, and insert n tabs
+ var v = code[0].value;
+ var u = v.substr(0, start);
+ for (var i = 0; i < n; i++) {
+ u += '\t';
+ }
+ u += v.substr(end);
+ // set revised content
+ code[0].value = u;
+ // reset caret position after inserted tabs
+ code[0].selectionStart = start + n;
+ code[0].selectionEnd = start + n;
+ }
+ function autoindent(el) {
+ var curpos = el.selectionStart;
+ var tabs = 0;
+ while (curpos > 0) {
+ curpos--;
+ if (el.value[curpos] == '\t') {
+ tabs++;
+ } else if (tabs > 0 || el.value[curpos] == '\n') {
+ break;
+ }
+ }
+ setTimeout(function() {
+ insertTabs(tabs);
+ }, 1);
+ }
+
+ // NOTE(cbro): e is a jQuery event, not a DOM event.
+ function handleSaveShortcut(e) {
+ if (e.isDefaultPrevented()) return false;
+ if (!e.metaKey && !e.ctrlKey) return false;
+ if (e.key != 'S' && e.key != 's') return false;
+
+ e.preventDefault();
+
+ // Share and save
+ share(function(url) {
+ window.location.href = url + '.go?download=true';
+ });
+
+ return true;
+ }
+
+ function keyHandler(e) {
+ if (opts.enableShortcuts && handleSaveShortcut(e)) return;
+
+ if (e.keyCode == 9 && !e.ctrlKey) {
+ // tab (but not ctrl-tab)
+ insertTabs(1);
+ e.preventDefault();
+ return false;
+ }
+ if (e.keyCode == 13) {
+ // enter
+ if (e.shiftKey) {
+ // +shift
+ run();
+ e.preventDefault();
+ return false;
+ }
+ if (e.ctrlKey) {
+ // +control
+ fmt();
+ e.preventDefault();
+ } else {
+ autoindent(e.target);
+ }
+ }
+ return true;
+ }
+ code.unbind('keydown').bind('keydown', keyHandler);
+ var outdiv = $(opts.outputEl).empty();
+ var output = $('<pre/>').appendTo(outdiv);
+
+ function body() {
+ return $(opts.codeEl).val();
+ }
+ function setBody(text) {
+ $(opts.codeEl).val(text);
+ }
+ function origin(href) {
+ return ('' + href)
+ .split('/')
+ .slice(0, 3)
+ .join('/');
+ }
+
+ var pushedEmpty = window.location.pathname == '/';
+ function inputChanged() {
+ if (pushedEmpty) {
+ return;
+ }
+ pushedEmpty = true;
+ $(opts.shareURLEl).hide();
+ window.history.pushState(null, '', '/');
+ }
+ function popState(e) {
+ if (e === null) {
+ return;
+ }
+ if (e && e.state && e.state.code) {
+ setBody(e.state.code);
+ }
+ }
+ var rewriteHistory = false;
+ if (
+ window.history &&
+ window.history.pushState &&
+ window.addEventListener &&
+ opts.enableHistory
+ ) {
+ rewriteHistory = true;
+ code[0].addEventListener('input', inputChanged);
+ window.addEventListener('popstate', popState);
+ }
+
+ function setError(error) {
+ if (running) running.Kill();
+ lineClear();
+ lineHighlight(error);
+ output
+ .empty()
+ .addClass('error')
+ .text(error);
+ }
+ function loading() {
+ lineClear();
+ if (running) running.Kill();
+ output.removeClass('error').text('Waiting for remote server...');
+ }
+ function run() {
+ loading();
+ running = transport.Run(
+ body(),
+ highlightOutput(PlaygroundOutput(output[0]))
+ );
+ }
+
+ function fmt() {
+ loading();
+ var data = { body: body() };
+ if ($(opts.fmtImportEl).is(':checked')) {
+ data['imports'] = 'true';
+ }
+ $.ajax('/fmt', {
+ data: data,
+ type: 'POST',
+ dataType: 'json',
+ success: function(data) {
+ if (data.Error) {
+ setError(data.Error);
+ } else {
+ setBody(data.Body);
+ setError('');
+ }
+ },
+ });
+ }
+
+ var shareURL; // jQuery element to show the shared URL.
+ var sharing = false; // true if there is a pending request.
+ var shareCallbacks = [];
+ function share(opt_callback) {
+ if (opt_callback) shareCallbacks.push(opt_callback);
+
+ if (sharing) return;
+ sharing = true;
+
+ var sharingData = body();
+ $.ajax('/share', {
+ processData: false,
+ data: sharingData,
+ type: 'POST',
+ contentType: 'text/plain; charset=utf-8',
+ complete: function(xhr) {
+ sharing = false;
+ if (xhr.status != 200) {
+ alert('Server error; try again.');
+ return;
+ }
+ if (opts.shareRedirect) {
+ window.location = opts.shareRedirect + xhr.responseText;
+ }
+ var path = '/p/' + xhr.responseText;
+ var url = origin(window.location) + path;
+
+ for (var i = 0; i < shareCallbacks.length; i++) {
+ shareCallbacks[i](url);
+ }
+ shareCallbacks = [];
+
+ if (shareURL) {
+ shareURL
+ .show()
+ .val(url)
+ .focus()
+ .select();
+
+ if (rewriteHistory) {
+ var historyData = { code: sharingData };
+ window.history.pushState(historyData, '', path);
+ pushedEmpty = false;
+ }
+ }
+ },
+ });
+ }
+
+ $(opts.runEl).click(run);
+ $(opts.fmtEl).click(fmt);
+
+ if (
+ opts.shareEl !== null &&
+ (opts.shareURLEl !== null || opts.shareRedirect !== null)
+ ) {
+ if (opts.shareURLEl) {
+ shareURL = $(opts.shareURLEl).hide();
+ }
+ $(opts.shareEl).click(function() {
+ share();
+ });
+ }
+
+ if (opts.toysEl !== null) {
+ $(opts.toysEl).bind('change', function() {
+ var toy = $(this).val();
+ $.ajax('/doc/play/' + toy, {
+ processData: false,
+ type: 'GET',
+ complete: function(xhr) {
+ if (xhr.status != 200) {
+ alert('Server error; try again.');
+ return;
+ }
+ setBody(xhr.responseText);
+ },
+ });
+ });
+ }
+ }
+
+ window.playground = playground;
+})();
--- a/domino/playground.js
+++ /dev/null
@@ -1,575 +1,0 @@
-// Copyright 2012 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.
-
-/*
-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 connection).
- 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) {
- }
-*/
-
-// HTTPTransport is the default transport.
-// enableVet enables running vet if a program was compiled and ran successfully.
-// If vet returned any errors, display them before the output of a program.
-function HTTPTransport(enableVet) {
- 'use strict';
-
- function playback(output, data) {
- // Backwards compatibility: default values do not affect the output.
- var events = data.Events || [];
- var errors = data.Errors || '';
- var status = data.Status || 0;
- var isTest = data.IsTest || false;
- var testsFailed = data.TestsFailed || 0;
-
- var timeout;
- output({ Kind: 'start' });
- function next() {
- if (!events || events.length === 0) {
- if (isTest) {
- if (testsFailed > 0) {
- output({
- Kind: 'system',
- Body:
- '\n' +
- testsFailed +
- ' test' +
- (testsFailed > 1 ? 's' : '') +
- ' failed.',
- });
- } else {
- output({ Kind: 'system', Body: '\nAll tests passed.' });
- }
- } else {
- if (status > 0) {
- output({ Kind: 'end', Body: 'status ' + status + '.' });
- } else {
- if (errors !== '') {
- // errors are displayed only in the case of timeout.
- output({ Kind: 'end', Body: errors + '.' });
- } else {
- output({ Kind: 'end' });
- }
- }
- }
- return;
- }
- var e = events.shift();
- if (e.Delay === 0) {
- output({ Kind: e.Kind, Body: e.Message });
- next();
- return;
- }
- timeout = setTimeout(function() {
- output({ Kind: e.Kind, 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' });
- }
-
- function buildFailed(output, msg) {
- output({ Kind: 'start' });
- output({ Kind: 'stderr', Body: msg });
- output({ Kind: 'system', Body: '\nGo build failed.' });
- }
-
- 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) {
- if (data.Errors === 'process took too long') {
- // Playback the output that was captured before the timeout.
- playing = playback(output, data);
- } else {
- buildFailed(output, data.Errors);
- }
- return;
- }
-
- if (!enableVet) {
- playing = playback(output, data);
- return;
- }
-
- $.ajax('/vet', {
- data: { body: body },
- type: 'POST',
- dataType: 'json',
- success: function(dataVet) {
- if (dataVet.Errors) {
- if (!data.Events) {
- data.Events = [];
- }
- // inject errors from the vet as the first events in the output
- data.Events.unshift({
- Message: 'Go vet exited.\n\n',
- Kind: 'system',
- Delay: 0,
- });
- data.Events.unshift({
- Message: dataVet.Errors,
- Kind: 'stderr',
- Delay: 0,
- });
- }
- playing = playback(output, data);
- },
- error: function() {
- playing = playback(output, data);
- },
- });
- },
- 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;
- if (window.location.protocol == 'http:') {
- websocket = new WebSocket('ws://' + window.location.host + '/socket');
- } else if (window.location.protocol == 'https:') {
- websocket = new WebSocket('wss://' + 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));
- }
-
- 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);
- while (r) {
- $('.lines div')
- .eq(r[1] - 1)
- .addClass('lineerror');
- r = regex.exec(error);
- }
- }
- function highlightOutput(wrappedOutput) {
- return function(write) {
- if (write.Body) lineHighlight(write.Body);
- wrappedOutput(write);
- };
- }
- function lineClear() {
- $('.lineerror').removeClass('lineerror');
- }
-
- // opts is an object with these keys
- // codeEl - code editor element
- // outputEl - program output element
- // runEl - run button element
- // fmtEl - fmt button element (optional)
- // fmtImportEl - fmt "imports" checkbox element (optional)
- // shareEl - share button element (optional)
- // shareURLEl - share URL text input element (optional)
- // 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)
- // enableShortcuts - whether to enable shortcuts (Ctrl+S/Cmd+S to save) (default is false)
- // enableVet - enable running vet and displaying its errors
- function playground(opts) {
- var code = $(opts.codeEl);
- var transport = opts['transport'] || new HTTPTransport(opts['enableVet']);
- var running;
-
- // autoindent helpers.
- function insertTabs(n) {
- // find the selection start and end
- var start = code[0].selectionStart;
- var end = code[0].selectionEnd;
- // split the textarea content into two, and insert n tabs
- var v = code[0].value;
- var u = v.substr(0, start);
- for (var i = 0; i < n; i++) {
- u += '\t';
- }
- u += v.substr(end);
- // set revised content
- code[0].value = u;
- // reset caret position after inserted tabs
- code[0].selectionStart = start + n;
- code[0].selectionEnd = start + n;
- }
- function autoindent(el) {
- var curpos = el.selectionStart;
- var tabs = 0;
- while (curpos > 0) {
- curpos--;
- if (el.value[curpos] == '\t') {
- tabs++;
- } else if (tabs > 0 || el.value[curpos] == '\n') {
- break;
- }
- }
- setTimeout(function() {
- insertTabs(tabs);
- }, 1);
- }
-
- // NOTE(cbro): e is a jQuery event, not a DOM event.
- function handleSaveShortcut(e) {
- if (e.isDefaultPrevented()) return false;
- if (!e.metaKey && !e.ctrlKey) return false;
- if (e.key != 'S' && e.key != 's') return false;
-
- e.preventDefault();
-
- // Share and save
- share(function(url) {
- window.location.href = url + '.go?download=true';
- });
-
- return true;
- }
-
- function keyHandler(e) {
- if (opts.enableShortcuts && handleSaveShortcut(e)) return;
-
- if (e.keyCode == 9 && !e.ctrlKey) {
- // tab (but not ctrl-tab)
- insertTabs(1);
- e.preventDefault();
- return false;
- }
- if (e.keyCode == 13) {
- // enter
- if (e.shiftKey) {
- // +shift
- run();
- e.preventDefault();
- return false;
- }
- if (e.ctrlKey) {
- // +control
- fmt();
- e.preventDefault();
- } else {
- autoindent(e.target);
- }
- }
- return true;
- }
- code.unbind('keydown').bind('keydown', keyHandler);
- var outdiv = $(opts.outputEl).empty();
- var output = $('<pre/>').appendTo(outdiv);
-
- function body() {
- return $(opts.codeEl).val();
- }
- function setBody(text) {
- $(opts.codeEl).val(text);
- }
- function origin(href) {
- return ('' + href)
- .split('/')
- .slice(0, 3)
- .join('/');
- }
-
- var pushedEmpty = window.location.pathname == '/';
- function inputChanged() {
- if (pushedEmpty) {
- return;
- }
- pushedEmpty = true;
- $(opts.shareURLEl).hide();
- window.history.pushState(null, '', '/');
- }
- function popState(e) {
- if (e === null) {
- return;
- }
- if (e && e.state && e.state.code) {
- setBody(e.state.code);
- }
- }
- var rewriteHistory = false;
- if (
- window.history &&
- window.history.pushState &&
- window.addEventListener &&
- opts.enableHistory
- ) {
- rewriteHistory = true;
- code[0].addEventListener('input', inputChanged);
- window.addEventListener('popstate', popState);
- }
-
- function setError(error) {
- if (running) running.Kill();
- lineClear();
- lineHighlight(error);
- output
- .empty()
- .addClass('error')
- .text(error);
- }
- function loading() {
- lineClear();
- if (running) running.Kill();
- output.removeClass('error').text('Waiting for remote server...');
- }
- function run() {
- loading();
- running = transport.Run(
- body(),
- highlightOutput(PlaygroundOutput(output[0]))
- );
- }
-
- function fmt() {
- loading();
- var data = { body: body() };
- if ($(opts.fmtImportEl).is(':checked')) {
- data['imports'] = 'true';
- }
- $.ajax('/fmt', {
- data: data,
- type: 'POST',
- dataType: 'json',
- success: function(data) {
- if (data.Error) {
- setError(data.Error);
- } else {
- setBody(data.Body);
- setError('');
- }
- },
- });
- }
-
- var shareURL; // jQuery element to show the shared URL.
- var sharing = false; // true if there is a pending request.
- var shareCallbacks = [];
- function share(opt_callback) {
- if (opt_callback) shareCallbacks.push(opt_callback);
-
- if (sharing) return;
- sharing = true;
-
- var sharingData = body();
- $.ajax('/share', {
- processData: false,
- data: sharingData,
- type: 'POST',
- contentType: 'text/plain; charset=utf-8',
- complete: function(xhr) {
- sharing = false;
- if (xhr.status != 200) {
- alert('Server error; try again.');
- return;
- }
- if (opts.shareRedirect) {
- window.location = opts.shareRedirect + xhr.responseText;
- }
- var path = '/p/' + xhr.responseText;
- var url = origin(window.location) + path;
-
- for (var i = 0; i < shareCallbacks.length; i++) {
- shareCallbacks[i](url);
- }
- shareCallbacks = [];
-
- if (shareURL) {
- shareURL
- .show()
- .val(url)
- .focus()
- .select();
-
- if (rewriteHistory) {
- var historyData = { code: sharingData };
- window.history.pushState(historyData, '', path);
- pushedEmpty = false;
- }
- }
- },
- });
- }
-
- $(opts.runEl).click(run);
- $(opts.fmtEl).click(fmt);
-
- if (
- opts.shareEl !== null &&
- (opts.shareURLEl !== null || opts.shareRedirect !== null)
- ) {
- if (opts.shareURLEl) {
- shareURL = $(opts.shareURLEl).hide();
- }
- $(opts.shareEl).click(function() {
- share();
- });
- }
-
- if (opts.toysEl !== null) {
- $(opts.toysEl).bind('change', function() {
- var toy = $(this).val();
- $.ajax('/doc/play/' + toy, {
- processData: false,
- type: 'GET',
- complete: function(xhr) {
- if (xhr.status != 200) {
- alert('Server error; try again.');
- return;
- }
- setBody(xhr.responseText);
- },
- });
- });
- }
- }
-
- window.playground = playground;
-})();
--- a/nodes/nodes.go
+++ b/nodes/nodes.go
@@ -190,13 +190,13 @@
return ref, true
}
- i := 0
+ i := 1
for _, c := range n.Parent.Children {
- if c.Type() == html.ElementNode {
- i++
- }
if c == n {
break
+ }
+ if c.Type() == html.ElementNode {
+ i++
}
}
ref += fmt.Sprintf(":nth-child(%v)", i)