whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/present"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/", dirHandler)
|
||||
}
|
||||
|
||||
// dirHandler serves a directory listing for the requested path, rooted at *contentPath.
|
||||
func dirHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/favicon.ico" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
name := filepath.Join(*contentPath, r.URL.Path)
|
||||
if isDoc(name) {
|
||||
err := renderDoc(w, name)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
if isDir, err := dirList(w, name); err != nil {
|
||||
addr, _, e := net.SplitHostPort(r.RemoteAddr)
|
||||
if e != nil {
|
||||
addr = r.RemoteAddr
|
||||
}
|
||||
log.Printf("request from %s: %s", addr, err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
} else if isDir {
|
||||
return
|
||||
}
|
||||
http.FileServer(http.Dir(*contentPath)).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func isDoc(path string) bool {
|
||||
_, ok := contentTemplate[filepath.Ext(path)]
|
||||
return ok
|
||||
}
|
||||
|
||||
var (
|
||||
// dirListTemplate holds the front page template.
|
||||
dirListTemplate *template.Template
|
||||
|
||||
// contentTemplate maps the presentable file extensions to the
|
||||
// template to be executed.
|
||||
contentTemplate map[string]*template.Template
|
||||
)
|
||||
|
||||
func initTemplates(fsys fs.FS) error {
|
||||
// Locate the template file.
|
||||
actionTmpl := "templates/action.tmpl"
|
||||
|
||||
contentTemplate = make(map[string]*template.Template)
|
||||
|
||||
for ext, contentTmpl := range map[string]string{
|
||||
".slide": "slides.tmpl",
|
||||
".article": "article.tmpl",
|
||||
} {
|
||||
contentTmpl = "templates/" + contentTmpl
|
||||
|
||||
// Read and parse the input.
|
||||
tmpl := present.Template()
|
||||
tmpl = tmpl.Funcs(template.FuncMap{"playable": playable})
|
||||
if _, err := tmpl.ParseFS(fsys, actionTmpl, contentTmpl); err != nil {
|
||||
return err
|
||||
}
|
||||
contentTemplate[ext] = tmpl
|
||||
}
|
||||
|
||||
var err error
|
||||
dirListTemplate, err = template.ParseFS(fsys, "templates/dir.tmpl")
|
||||
return err
|
||||
}
|
||||
|
||||
// renderDoc reads the present file, gets its template representation,
|
||||
// and executes the template, sending output to w.
|
||||
func renderDoc(w io.Writer, docFile string) error {
|
||||
// Read the input and build the doc structure.
|
||||
doc, err := parse(docFile, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find which template should be executed.
|
||||
tmpl := contentTemplate[filepath.Ext(docFile)]
|
||||
|
||||
// Execute the template.
|
||||
return doc.Render(w, tmpl)
|
||||
}
|
||||
|
||||
func parse(name string, mode present.ParseMode) (*present.Doc, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return present.Parse(f, name, mode)
|
||||
}
|
||||
|
||||
// dirList scans the given path and writes a directory listing to w.
|
||||
// It parses the first part of each .slide file it encounters to display the
|
||||
// presentation title in the listing.
|
||||
// If the given path is not a directory, it returns (isDir == false, err == nil)
|
||||
// and writes nothing to w.
|
||||
func dirList(w io.Writer, name string) (isDir bool, err error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if isDir = fi.IsDir(); !isDir {
|
||||
return false, nil
|
||||
}
|
||||
fis, err := f.Readdir(0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
strippedPath := strings.TrimPrefix(name, filepath.Clean(*contentPath))
|
||||
strippedPath = strings.TrimPrefix(strippedPath, "/")
|
||||
d := &dirListData{Path: strippedPath}
|
||||
for _, fi := range fis {
|
||||
// skip the golang.org directory
|
||||
if name == "." && fi.Name() == "golang.org" {
|
||||
continue
|
||||
}
|
||||
e := dirEntry{
|
||||
Name: fi.Name(),
|
||||
Path: filepath.ToSlash(filepath.Join(strippedPath, fi.Name())),
|
||||
}
|
||||
if fi.IsDir() && showDir(e.Name) {
|
||||
d.Dirs = append(d.Dirs, e)
|
||||
continue
|
||||
}
|
||||
if isDoc(e.Name) {
|
||||
fn := filepath.ToSlash(filepath.Join(name, fi.Name()))
|
||||
if p, err := parse(fn, present.TitlesOnly); err != nil {
|
||||
log.Printf("parse(%q, present.TitlesOnly): %v", fn, err)
|
||||
} else {
|
||||
e.Title = p.Title
|
||||
}
|
||||
switch filepath.Ext(e.Path) {
|
||||
case ".article":
|
||||
d.Articles = append(d.Articles, e)
|
||||
case ".slide":
|
||||
d.Slides = append(d.Slides, e)
|
||||
}
|
||||
} else if showFile(e.Name) {
|
||||
d.Other = append(d.Other, e)
|
||||
}
|
||||
}
|
||||
if d.Path == "." {
|
||||
d.Path = ""
|
||||
}
|
||||
sort.Sort(d.Dirs)
|
||||
sort.Sort(d.Slides)
|
||||
sort.Sort(d.Articles)
|
||||
sort.Sort(d.Other)
|
||||
return true, dirListTemplate.Execute(w, d)
|
||||
}
|
||||
|
||||
// showFile reports whether the given file should be displayed in the list.
|
||||
func showFile(n string) bool {
|
||||
switch filepath.Ext(n) {
|
||||
case ".pdf":
|
||||
case ".html":
|
||||
case ".go":
|
||||
default:
|
||||
return isDoc(n)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// showDir reports whether the given directory should be displayed in the list.
|
||||
func showDir(n string) bool {
|
||||
if len(n) > 0 && (n[0] == '.' || n[0] == '_') || n == "present" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type dirListData struct {
|
||||
Path string
|
||||
Dirs, Slides, Articles, Other dirEntrySlice
|
||||
}
|
||||
|
||||
type dirEntry struct {
|
||||
Name, Path, Title string
|
||||
}
|
||||
|
||||
type dirEntrySlice []dirEntry
|
||||
|
||||
func (s dirEntrySlice) Len() int { return len(s) }
|
||||
func (s dirEntrySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s dirEntrySlice) Less(i, j int) bool { return s[i].Name < s[j].Name }
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
/*
|
||||
Present displays slide presentations and articles. It runs a web server that
|
||||
presents slide and article files from the current directory.
|
||||
|
||||
It may be run as a stand-alone command or an App Engine app.
|
||||
|
||||
To use with App Engine, copy the files in the tools/cmd/present directory to the
|
||||
root of your application and create an app.yaml file similar to this:
|
||||
|
||||
runtime: go111
|
||||
|
||||
handlers:
|
||||
- url: /favicon.ico
|
||||
static_files: static/favicon.ico
|
||||
upload: static/favicon.ico
|
||||
- url: /static
|
||||
static_dir: static
|
||||
- url: /.*
|
||||
script: auto
|
||||
|
||||
# nobuild_files is a regexp that identifies which files to not build. It
|
||||
# is useful for embedding static assets like code snippets and preventing
|
||||
# them from producing build errors for your project.
|
||||
nobuild_files: [path regexp for talk materials]
|
||||
|
||||
When running on App Engine, content will be served from the ./content/
|
||||
subdirectory.
|
||||
|
||||
Present then can be tested in a local App Engine environment with
|
||||
|
||||
GAE_ENV=standard go run .
|
||||
|
||||
And deployed using
|
||||
|
||||
gcloud app deploy
|
||||
|
||||
Input files are named foo.extension, where "extension" defines the format of
|
||||
the generated output. The supported formats are:
|
||||
|
||||
.slide // HTML5 slide presentation
|
||||
.article // article format, such as a blog post
|
||||
|
||||
The present file format is documented by the present package:
|
||||
https://pkg.go.dev/golang.org/x/tools/present
|
||||
*/
|
||||
package main
|
||||
@@ -0,0 +1,151 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/present"
|
||||
)
|
||||
|
||||
var (
|
||||
httpAddr = flag.String("http", "127.0.0.1:3999", "HTTP service address (e.g., '127.0.0.1:3999')")
|
||||
originHost = flag.String("orighost", "", "host component of web origin URL (e.g., 'localhost')")
|
||||
basePath = flag.String("base", "", "base path for slide template and static resources")
|
||||
contentPath = flag.String("content", ".", "base path for presentation content")
|
||||
usePlayground = flag.Bool("use_playground", false, "run code snippets using play.golang.org; if false, run them locally and deliver results by WebSocket transport")
|
||||
)
|
||||
|
||||
//go:embed static templates
|
||||
var embedFS embed.FS
|
||||
|
||||
func main() {
|
||||
flag.BoolVar(&present.PlayEnabled, "play", true, "enable playground (permit execution of arbitrary user code)")
|
||||
flag.BoolVar(&present.NotesEnabled, "notes", false, "enable presenter notes (press 'N' from the browser to display them)")
|
||||
flag.Parse()
|
||||
|
||||
if os.Getenv("GAE_ENV") == "standard" {
|
||||
log.Print("Configuring for App Engine Standard")
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
*httpAddr = fmt.Sprintf("0.0.0.0:%s", port)
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't get pwd: %v\n", err)
|
||||
}
|
||||
*basePath = pwd
|
||||
*usePlayground = true
|
||||
*contentPath = "./content/"
|
||||
}
|
||||
|
||||
var fsys fs.FS = embedFS
|
||||
if *basePath != "" {
|
||||
fsys = os.DirFS(*basePath)
|
||||
}
|
||||
err := initTemplates(fsys)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse templates: %v", err)
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", *httpAddr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
_, port, err := net.SplitHostPort(ln.Addr().String())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
origin := &url.URL{Scheme: "http"}
|
||||
if *originHost != "" {
|
||||
if strings.HasPrefix(*originHost, "https://") {
|
||||
*originHost = strings.TrimPrefix(*originHost, "https://")
|
||||
origin.Scheme = "https"
|
||||
}
|
||||
*originHost = strings.TrimPrefix(*originHost, "http://")
|
||||
origin.Host = net.JoinHostPort(*originHost, port)
|
||||
} else if ln.Addr().(*net.TCPAddr).IP.IsUnspecified() {
|
||||
name, _ := os.Hostname()
|
||||
origin.Host = net.JoinHostPort(name, port)
|
||||
} else {
|
||||
reqHost, reqPort, err := net.SplitHostPort(*httpAddr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if reqPort == "0" {
|
||||
origin.Host = net.JoinHostPort(reqHost, port)
|
||||
} else {
|
||||
origin.Host = *httpAddr
|
||||
}
|
||||
}
|
||||
|
||||
initPlayground(fsys, origin)
|
||||
http.Handle("/static/", http.FileServer(http.FS(fsys)))
|
||||
|
||||
if !ln.Addr().(*net.TCPAddr).IP.IsLoopback() &&
|
||||
present.PlayEnabled && !*usePlayground {
|
||||
log.Print(localhostWarning)
|
||||
}
|
||||
|
||||
log.Printf("Open your web browser and visit %s", origin.String())
|
||||
if present.NotesEnabled {
|
||||
log.Println("Notes are enabled, press 'N' from the browser to display them.")
|
||||
}
|
||||
log.Fatal(http.Serve(ln, nil))
|
||||
}
|
||||
|
||||
func environ(vars ...string) []string {
|
||||
env := os.Environ()
|
||||
for _, r := range vars {
|
||||
k := strings.SplitAfter(r, "=")[0]
|
||||
var found bool
|
||||
for i, v := range env {
|
||||
if strings.HasPrefix(v, k) {
|
||||
env[i] = r
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
env = append(env, r)
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
const basePathMessage = `
|
||||
By default, gopresent locates the slide template files and associated
|
||||
static content by looking for a %q package
|
||||
in your Go workspaces (GOPATH).
|
||||
|
||||
You may use the -base flag to specify an alternate location.
|
||||
`
|
||||
|
||||
const localhostWarning = `
|
||||
WARNING! WARNING! WARNING!
|
||||
|
||||
The present server appears to be listening on an address that is not localhost
|
||||
and is configured to run code snippets locally. Anyone with access to this address
|
||||
and port will have access to this machine as the user running present.
|
||||
|
||||
To avoid this message, listen on localhost, run with -play=false, or run with
|
||||
-play_socket=false.
|
||||
|
||||
If you don't understand this message, hit Control-C to terminate this process.
|
||||
|
||||
WARNING! WARNING! WARNING!
|
||||
`
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/playground/socket"
|
||||
"golang.org/x/tools/present"
|
||||
|
||||
// This will register a handler at /compile that will proxy to the
|
||||
// respective endpoints at play.golang.org. This allows the frontend to call
|
||||
// these endpoints without needing cross-origin request sharing (CORS).
|
||||
// Note that this is imported regardless of whether the endpoints are used or
|
||||
// not (in the case of a local socket connection, they are not called).
|
||||
_ "golang.org/x/tools/playground"
|
||||
)
|
||||
|
||||
var scripts = []string{"jquery.js", "jquery-ui.js", "playground.js", "play.js"}
|
||||
|
||||
// playScript registers an HTTP handler at /play.js that serves all the
|
||||
// scripts specified by the variable above, and appends a line that
|
||||
// initializes the playground with the specified transport.
|
||||
func playScript(fsys fs.FS, transport string) {
|
||||
modTime := time.Now()
|
||||
var buf bytes.Buffer
|
||||
for _, p := range scripts {
|
||||
b, err := fs.ReadFile(fsys, "static/"+p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
buf.Write(b)
|
||||
}
|
||||
fmt.Fprintf(&buf, "\ninitPlayground(new %v());\n", transport)
|
||||
b := buf.Bytes()
|
||||
http.HandleFunc("/play.js", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-type", "application/javascript")
|
||||
http.ServeContent(w, r, "", modTime, bytes.NewReader(b))
|
||||
})
|
||||
}
|
||||
|
||||
func initPlayground(fsys fs.FS, origin *url.URL) {
|
||||
if !present.PlayEnabled {
|
||||
return
|
||||
}
|
||||
if *usePlayground {
|
||||
playScript(fsys, "HTTPTransport")
|
||||
return
|
||||
}
|
||||
|
||||
playScript(fsys, "SocketTransport")
|
||||
http.Handle("/socket", socket.NewHandler(origin))
|
||||
}
|
||||
|
||||
func playable(c present.Code) bool {
|
||||
play := present.PlayEnabled && c.Play
|
||||
|
||||
// Restrict playable files to only Go source files when using play.golang.org,
|
||||
// since there is no method to execute shell scripts there.
|
||||
if *usePlayground {
|
||||
return play && c.Ext == ".go"
|
||||
}
|
||||
return play
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
pre,
|
||||
code {
|
||||
font-family: Menlo, monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
pre {
|
||||
line-height: 18px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
a {
|
||||
color: #375eab;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 20px 0;
|
||||
padding: 0;
|
||||
color: #375eab;
|
||||
font-weight: bold;
|
||||
}
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
h3,
|
||||
h4 {
|
||||
margin: 20px 5px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
div#heading {
|
||||
margin: 0 0 10px 0;
|
||||
padding: 21px 0;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div#heading .author {
|
||||
padding-top: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
div#topbar {
|
||||
}
|
||||
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
div#page {
|
||||
width: 100%;
|
||||
}
|
||||
div#page > .container,
|
||||
div#topbar > .container {
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 0 20px;
|
||||
width: 900px;
|
||||
}
|
||||
div#page.wide > .container,
|
||||
div#topbar.wide > .container {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
div#footer {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
.author p {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
div.code,
|
||||
div.output {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 20px 20px 20px 40px;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
div.output pre {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: white;
|
||||
}
|
||||
div.output pre {
|
||||
background: black;
|
||||
}
|
||||
div.output .stdout pre {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
div.output .stderr pre {
|
||||
color: rgb(244, 74, 63);
|
||||
}
|
||||
div.output .system pre {
|
||||
color: rgb(255, 209, 77);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-left: 20px;
|
||||
}
|
||||
div.output .buttons {
|
||||
margin-left: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#toc {
|
||||
float: right;
|
||||
margin: 0px 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid #e5ecf9;
|
||||
background-color: #eee;
|
||||
box-shadow: 3px 3px 2px #888888;
|
||||
|
||||
max-width: 33%;
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#tochead {
|
||||
font-weight: bold;
|
||||
font-variant: small-caps;
|
||||
font-size: 100%;
|
||||
text-align: center;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
#toc ul,
|
||||
#toc a {
|
||||
list-style-type: none;
|
||||
padding-left: 0px;
|
||||
color: black;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
ul.toc-inner a {
|
||||
padding-left: 10px !important;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.no-print,
|
||||
.no-print * {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/* copied from $GOROOT/doc/style.css */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
pre,
|
||||
code {
|
||||
font-family: Menlo, monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
pre {
|
||||
line-height: 18px;
|
||||
}
|
||||
pre .comment {
|
||||
color: #375eab;
|
||||
}
|
||||
pre .highlight,
|
||||
pre .highlight-comment,
|
||||
pre .selection-highlight,
|
||||
pre .selection-highlight-comment {
|
||||
background: #ffff00;
|
||||
}
|
||||
pre .selection,
|
||||
pre .selection-comment {
|
||||
background: #ff9632;
|
||||
}
|
||||
pre .ln {
|
||||
color: #999;
|
||||
}
|
||||
body {
|
||||
color: #222;
|
||||
}
|
||||
a,
|
||||
.exampleHeading .text {
|
||||
color: #375eab;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover,
|
||||
.exampleHeading .text:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
p,
|
||||
pre,
|
||||
ul,
|
||||
ol {
|
||||
margin: 20px;
|
||||
}
|
||||
pre {
|
||||
background: #e9e9e9;
|
||||
padding: 10px;
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
.rootHeading {
|
||||
margin: 20px 0;
|
||||
padding: 0;
|
||||
color: #375eab;
|
||||
font-weight: bold;
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
background: #e0ebf5;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
h3,
|
||||
h4 {
|
||||
margin: 20px 5px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 20px;
|
||||
}
|
||||
dd {
|
||||
margin: 2px 20px;
|
||||
}
|
||||
dl,
|
||||
dd {
|
||||
font-size: 14px;
|
||||
}
|
||||
div#nav table td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
div#heading {
|
||||
float: left;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 21px 0;
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
}
|
||||
div#heading a {
|
||||
color: #222;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div#topbar {
|
||||
background: #e0ebf5;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
div#page,
|
||||
div#topbar > .container {
|
||||
clear: both;
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 0 20px;
|
||||
width: 900px;
|
||||
}
|
||||
div#page.wide,
|
||||
div#topbar > .wide {
|
||||
width: auto;
|
||||
}
|
||||
div#plusone {
|
||||
float: right;
|
||||
}
|
||||
|
||||
div#footer {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
div#menu > a,
|
||||
div#menu > input {
|
||||
padding: 10px;
|
||||
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
div#menu > a,
|
||||
div#menu > input {
|
||||
border: 1px solid #375eab;
|
||||
}
|
||||
div#menu > a {
|
||||
color: white;
|
||||
background: #375eab;
|
||||
}
|
||||
|
||||
div#menu {
|
||||
float: right;
|
||||
min-width: 590px;
|
||||
padding: 10px 0;
|
||||
text-align: right;
|
||||
}
|
||||
div#menu > a {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
div#menu > input {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
width: 60px;
|
||||
background: white;
|
||||
color: #222;
|
||||
}
|
||||
div#menu > input.inactive {
|
||||
color: #999;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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.
|
||||
|
||||
// copied from $GOROOT/doc/godocs.js
|
||||
|
||||
function bindEvent(el, e, fn) {
|
||||
if (el.addEventListener) {
|
||||
el.addEventListener(e, fn, false);
|
||||
} else if (el.attachEvent) {
|
||||
el.attachEvent('on' + e, fn);
|
||||
}
|
||||
}
|
||||
|
||||
function godocs_bindSearchEvents() {
|
||||
var search = document.getElementById('search');
|
||||
if (!search) {
|
||||
// no search box (index disabled)
|
||||
return;
|
||||
}
|
||||
function clearInactive() {
|
||||
if (search.className == 'inactive') {
|
||||
search.value = '';
|
||||
search.className = '';
|
||||
}
|
||||
}
|
||||
function restoreInactive() {
|
||||
if (search.value !== '') {
|
||||
return;
|
||||
}
|
||||
if (search.type != 'search') {
|
||||
search.value = search.getAttribute('placeholder');
|
||||
}
|
||||
search.className = 'inactive';
|
||||
}
|
||||
restoreInactive();
|
||||
bindEvent(search, 'focus', clearInactive);
|
||||
bindEvent(search, 'blur', restoreInactive);
|
||||
}
|
||||
|
||||
bindEvent(window, 'load', godocs_bindSearchEvents);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 785 B |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
|
||||
p {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#presenter-slides {
|
||||
display: block;
|
||||
margin-top: -10px;
|
||||
margin-left: -17px;
|
||||
position: fixed;
|
||||
border: 0;
|
||||
width: 146%;
|
||||
height: 750px;
|
||||
|
||||
transform: scale(0.7, 0.7);
|
||||
transform-origin: top left;
|
||||
-moz-transform: scale(0.7);
|
||||
-moz-transform-origin: top left;
|
||||
-o-transform: scale(0.7);
|
||||
-o-transform-origin: top left;
|
||||
-webkit-transform: scale(0.7);
|
||||
-webkit-transform-origin: top left;
|
||||
}
|
||||
|
||||
#presenter-notes {
|
||||
margin-top: -180px;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
height: 30%;
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
position: fixed;
|
||||
top: 706px;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2016 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.
|
||||
|
||||
// Store child window object which will display slides with notes
|
||||
var notesWindow = null;
|
||||
|
||||
var isParentWindow = window.parent == window;
|
||||
|
||||
// When parent window closes, clear storage and close child window
|
||||
if (isParentWindow) {
|
||||
window.onbeforeunload = function() {
|
||||
localStorage.clear();
|
||||
if (notesWindow) notesWindow.close();
|
||||
};
|
||||
}
|
||||
|
||||
function toggleNotesWindow() {
|
||||
if (!isParentWindow) return;
|
||||
if (notesWindow) {
|
||||
notesWindow.close();
|
||||
notesWindow = null;
|
||||
return;
|
||||
}
|
||||
|
||||
initNotes();
|
||||
}
|
||||
|
||||
// Create a unique key for the local storage so we don't mix the
|
||||
// destSlide of different presentations. For golang.org/issue/24688.
|
||||
function destSlideKey() {
|
||||
var key = '';
|
||||
if (notesWindow) {
|
||||
var slides = notesWindow.document.getElementById('presenter-slides');
|
||||
key = slides.src.split('#')[0];
|
||||
} else {
|
||||
key = window.location.href.split('#')[0];
|
||||
}
|
||||
return 'destSlide:' + key;
|
||||
}
|
||||
|
||||
function initNotes() {
|
||||
notesWindow = window.open('', '', 'width=1000,height=700');
|
||||
var w = notesWindow;
|
||||
var slidesUrl = window.location.href;
|
||||
|
||||
// Hack to apply css. Requires existing html on notesWindow.
|
||||
w.document.write("<div style='display:none;'></div>");
|
||||
|
||||
w.document.title = window.document.title;
|
||||
|
||||
var slides = w.document.createElement('iframe');
|
||||
slides.id = 'presenter-slides';
|
||||
slides.src = slidesUrl;
|
||||
w.document.body.appendChild(slides);
|
||||
|
||||
var curSlide = parseInt(localStorage.getItem(destSlideKey()), 10);
|
||||
var formattedNotes = '';
|
||||
var section = sections[curSlide - 1];
|
||||
// curSlide is 0 when initialized from the first page of slides.
|
||||
// Check if section is valid before retrieving Notes.
|
||||
if (section) {
|
||||
formattedNotes = formatNotes(section.Notes);
|
||||
} else if (curSlide == 0) {
|
||||
formattedNotes = formatNotes(titleNotes);
|
||||
}
|
||||
|
||||
// setTimeout needed for Firefox
|
||||
setTimeout(function() {
|
||||
slides.focus();
|
||||
}, 100);
|
||||
|
||||
var notes = w.document.createElement('div');
|
||||
notes.id = 'presenter-notes';
|
||||
notes.innerHTML = formattedNotes;
|
||||
w.document.body.appendChild(notes);
|
||||
|
||||
w.document.close();
|
||||
|
||||
function addPresenterNotesStyle() {
|
||||
var el = w.document.createElement('link');
|
||||
el.rel = 'stylesheet';
|
||||
el.type = 'text/css';
|
||||
el.href = PERMANENT_URL_PREFIX + 'notes.css';
|
||||
w.document.body.appendChild(el);
|
||||
w.document.querySelector('head').appendChild(el);
|
||||
}
|
||||
|
||||
addPresenterNotesStyle();
|
||||
|
||||
// Add listener on notesWindow to update notes when triggered from
|
||||
// parent window
|
||||
w.addEventListener('storage', updateNotes, false);
|
||||
}
|
||||
|
||||
function formatNotes(notes) {
|
||||
var formattedNotes = '';
|
||||
if (notes) {
|
||||
for (var i = 0; i < notes.length; i++) {
|
||||
formattedNotes = formattedNotes + '<p>' + notes[i] + '</p>';
|
||||
}
|
||||
}
|
||||
return formattedNotes;
|
||||
}
|
||||
|
||||
function updateNotes() {
|
||||
// When triggered from parent window, notesWindow is null
|
||||
// The storage event listener on notesWindow will update notes
|
||||
if (!notesWindow) return;
|
||||
var destSlide = parseInt(localStorage.getItem(destSlideKey()), 10);
|
||||
var section = sections[destSlide - 1];
|
||||
var el = notesWindow.document.getElementById('presenter-notes');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
if (section && section.Notes) {
|
||||
el.innerHTML = formatNotes(section.Notes);
|
||||
} else if (destSlide == 0) {
|
||||
el.innerHTML = formatNotes(titleNotes);
|
||||
} else {
|
||||
el.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
/* Playground syncing */
|
||||
|
||||
// When presenter notes are enabled, playground click handlers are
|
||||
// stored here to sync click events on the correct playground
|
||||
var playgroundHandlers = { onRun: [], onKill: [], onClose: [] };
|
||||
|
||||
function updatePlay(e) {
|
||||
var i = localStorage.getItem('play-index');
|
||||
|
||||
switch (e.key) {
|
||||
case 'play-index':
|
||||
return;
|
||||
case 'play-action':
|
||||
// Sync 'run', 'kill', 'close' actions
|
||||
var action = localStorage.getItem('play-action');
|
||||
playgroundHandlers[action][i](e);
|
||||
return;
|
||||
case 'play-code':
|
||||
// Sync code editing
|
||||
var play = document.querySelectorAll('div.playground')[i];
|
||||
play.innerHTML = localStorage.getItem('play-code');
|
||||
return;
|
||||
case 'output-style':
|
||||
// Sync resizing of playground output
|
||||
var out = document.querySelectorAll('.output')[i];
|
||||
out.style = localStorage.getItem('output-style');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset 'run', 'kill', 'close' storage items when synced
|
||||
// so that successive actions can be synced correctly
|
||||
function updatePlayStorage(action, index, e) {
|
||||
localStorage.setItem('play-index', index);
|
||||
|
||||
if (localStorage.getItem('play-action') === action) {
|
||||
// We're the receiving window, and the message has been received
|
||||
localStorage.removeItem('play-action');
|
||||
} else {
|
||||
// We're the triggering window, send the message
|
||||
localStorage.setItem('play-action', action);
|
||||
}
|
||||
|
||||
if (action === 'onRun') {
|
||||
if (localStorage.getItem('play-shiftKey') === 'true') {
|
||||
localStorage.removeItem('play-shiftKey');
|
||||
} else if (e.shiftKey) {
|
||||
localStorage.setItem('play-shiftKey', e.shiftKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// 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.
|
||||
|
||||
function initPlayground(transport) {
|
||||
'use strict';
|
||||
|
||||
function text(node) {
|
||||
var s = '';
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
var n = node.childNodes[i];
|
||||
if (n.nodeType === 1) {
|
||||
if (n.tagName === 'BUTTON') continue;
|
||||
if (n.tagName === 'SPAN' && n.className === 'number') continue;
|
||||
if (n.tagName === 'DIV' || n.tagName === 'BR' || n.tagName === 'PRE') {
|
||||
s += '\n';
|
||||
}
|
||||
s += text(n);
|
||||
continue;
|
||||
}
|
||||
if (n.nodeType === 3) {
|
||||
s += n.nodeValue;
|
||||
}
|
||||
}
|
||||
return s.replace('\xA0', ' '); // replace non-breaking spaces
|
||||
}
|
||||
|
||||
// When presenter notes are enabled, the index passed
|
||||
// here will identify the playground to be synced
|
||||
function init(code, index) {
|
||||
var output = document.createElement('div');
|
||||
var outpre = document.createElement('pre');
|
||||
var running;
|
||||
|
||||
if ($ && $(output).resizable) {
|
||||
$(output).resizable({
|
||||
handles: 'n,w,nw',
|
||||
minHeight: 27,
|
||||
minWidth: 135,
|
||||
maxHeight: 608,
|
||||
maxWidth: 990,
|
||||
});
|
||||
}
|
||||
|
||||
function onKill() {
|
||||
if (running) running.Kill();
|
||||
if (window.notesEnabled) updatePlayStorage('onKill', index);
|
||||
}
|
||||
|
||||
function onRun(e) {
|
||||
var sk = e.shiftKey || localStorage.getItem('play-shiftKey') === 'true';
|
||||
if (running) running.Kill();
|
||||
output.style.display = 'block';
|
||||
outpre.textContent = '';
|
||||
run1.style.display = 'none';
|
||||
var options = { Race: sk };
|
||||
running = transport.Run(text(code), PlaygroundOutput(outpre), options);
|
||||
if (window.notesEnabled) updatePlayStorage('onRun', index, e);
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
if (running) running.Kill();
|
||||
output.style.display = 'none';
|
||||
run1.style.display = 'inline-block';
|
||||
if (window.notesEnabled) updatePlayStorage('onClose', index);
|
||||
}
|
||||
|
||||
if (window.notesEnabled) {
|
||||
playgroundHandlers.onRun.push(onRun);
|
||||
playgroundHandlers.onClose.push(onClose);
|
||||
playgroundHandlers.onKill.push(onKill);
|
||||
}
|
||||
|
||||
var run1 = document.createElement('button');
|
||||
run1.textContent = 'Run';
|
||||
run1.className = 'run';
|
||||
run1.addEventListener('click', onRun, false);
|
||||
var run2 = document.createElement('button');
|
||||
run2.className = 'run';
|
||||
run2.textContent = 'Run';
|
||||
run2.addEventListener('click', onRun, false);
|
||||
var kill = document.createElement('button');
|
||||
kill.className = 'kill';
|
||||
kill.textContent = 'Kill';
|
||||
kill.addEventListener('click', onKill, false);
|
||||
var close = document.createElement('button');
|
||||
close.className = 'close';
|
||||
close.textContent = 'Close';
|
||||
close.addEventListener('click', onClose, false);
|
||||
|
||||
var button = document.createElement('div');
|
||||
button.classList.add('buttons');
|
||||
button.appendChild(run1);
|
||||
// Hack to simulate insertAfter
|
||||
code.parentNode.insertBefore(button, code.nextSibling);
|
||||
|
||||
var buttons = document.createElement('div');
|
||||
buttons.classList.add('buttons');
|
||||
buttons.appendChild(run2);
|
||||
buttons.appendChild(kill);
|
||||
buttons.appendChild(close);
|
||||
|
||||
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], i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
// 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, withVet: enableVet },
|
||||
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 (!data.Events) {
|
||||
data.Events = [];
|
||||
}
|
||||
if (data.VetErrors) {
|
||||
// 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: data.VetErrors,
|
||||
Kind: 'stderr',
|
||||
Delay: 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (!enableVet || data.VetOK || data.VetErrors) {
|
||||
playing = playback(output, data);
|
||||
return;
|
||||
}
|
||||
|
||||
// In case the server support doesn't support
|
||||
// compile+vet in same request signaled by the
|
||||
// 'withVet' parameter above, also try the old way.
|
||||
// TODO: remove this when it falls out of use.
|
||||
// It is 2019-05-13 now.
|
||||
$.ajax('/vet', {
|
||||
data: { body: body },
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function(dataVet) {
|
||||
if (dataVet.Errors) {
|
||||
// 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('https://play.golang.org/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;
|
||||
})();
|
||||
@@ -0,0 +1,635 @@
|
||||
// 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.
|
||||
|
||||
var PERMANENT_URL_PREFIX = '/static/';
|
||||
|
||||
var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next'];
|
||||
|
||||
var PM_TOUCH_SENSITIVITY = 15;
|
||||
|
||||
var curSlide;
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* classList polyfill by Eli Grey
|
||||
* (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */
|
||||
|
||||
if (
|
||||
typeof document !== 'undefined' &&
|
||||
!('classList' in document.createElement('a'))
|
||||
) {
|
||||
(function(view) {
|
||||
var classListProp = 'classList',
|
||||
protoProp = 'prototype',
|
||||
elemCtrProto = (view.HTMLElement || view.Element)[protoProp],
|
||||
objCtr = Object;
|
||||
(strTrim =
|
||||
String[protoProp].trim ||
|
||||
function() {
|
||||
return this.replace(/^\s+|\s+$/g, '');
|
||||
}),
|
||||
(arrIndexOf =
|
||||
Array[protoProp].indexOf ||
|
||||
function(item) {
|
||||
for (var i = 0, len = this.length; i < len; i++) {
|
||||
if (i in this && this[i] === item) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}),
|
||||
// Vendors: please allow content code to instantiate DOMExceptions
|
||||
(DOMEx = function(type, message) {
|
||||
this.name = type;
|
||||
this.code = DOMException[type];
|
||||
this.message = message;
|
||||
}),
|
||||
(checkTokenAndGetIndex = function(classList, token) {
|
||||
if (token === '') {
|
||||
throw new DOMEx(
|
||||
'SYNTAX_ERR',
|
||||
'An invalid or illegal string was specified'
|
||||
);
|
||||
}
|
||||
if (/\s/.test(token)) {
|
||||
throw new DOMEx(
|
||||
'INVALID_CHARACTER_ERR',
|
||||
'String contains an invalid character'
|
||||
);
|
||||
}
|
||||
return arrIndexOf.call(classList, token);
|
||||
}),
|
||||
(ClassList = function(elem) {
|
||||
var trimmedClasses = strTrim.call(elem.className),
|
||||
classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [];
|
||||
for (var i = 0, len = classes.length; i < len; i++) {
|
||||
this.push(classes[i]);
|
||||
}
|
||||
this._updateClassName = function() {
|
||||
elem.className = this.toString();
|
||||
};
|
||||
}),
|
||||
(classListProto = ClassList[protoProp] = []),
|
||||
(classListGetter = function() {
|
||||
return new ClassList(this);
|
||||
});
|
||||
// Most DOMException implementations don't allow calling DOMException's toString()
|
||||
// on non-DOMExceptions. Error's toString() is sufficient here.
|
||||
DOMEx[protoProp] = Error[protoProp];
|
||||
classListProto.item = function(i) {
|
||||
return this[i] || null;
|
||||
};
|
||||
classListProto.contains = function(token) {
|
||||
token += '';
|
||||
return checkTokenAndGetIndex(this, token) !== -1;
|
||||
};
|
||||
classListProto.add = function(token) {
|
||||
token += '';
|
||||
if (checkTokenAndGetIndex(this, token) === -1) {
|
||||
this.push(token);
|
||||
this._updateClassName();
|
||||
}
|
||||
};
|
||||
classListProto.remove = function(token) {
|
||||
token += '';
|
||||
var index = checkTokenAndGetIndex(this, token);
|
||||
if (index !== -1) {
|
||||
this.splice(index, 1);
|
||||
this._updateClassName();
|
||||
}
|
||||
};
|
||||
classListProto.toggle = function(token) {
|
||||
token += '';
|
||||
if (checkTokenAndGetIndex(this, token) === -1) {
|
||||
this.add(token);
|
||||
} else {
|
||||
this.remove(token);
|
||||
}
|
||||
};
|
||||
classListProto.toString = function() {
|
||||
return this.join(' ');
|
||||
};
|
||||
|
||||
if (objCtr.defineProperty) {
|
||||
var classListPropDesc = {
|
||||
get: classListGetter,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
};
|
||||
try {
|
||||
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
|
||||
} catch (ex) {
|
||||
// IE 8 doesn't support enumerable:true
|
||||
if (ex.number === -0x7ff5ec54) {
|
||||
classListPropDesc.enumerable = false;
|
||||
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
|
||||
}
|
||||
}
|
||||
} else if (objCtr[protoProp].__defineGetter__) {
|
||||
elemCtrProto.__defineGetter__(classListProp, classListGetter);
|
||||
}
|
||||
})(self);
|
||||
}
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* Slide movement */
|
||||
|
||||
function hideHelpText() {
|
||||
document.getElementById('help').style.display = 'none';
|
||||
}
|
||||
|
||||
function getSlideEl(no) {
|
||||
if (no < 0 || no >= slideEls.length) {
|
||||
return null;
|
||||
} else {
|
||||
return slideEls[no];
|
||||
}
|
||||
}
|
||||
|
||||
function updateSlideClass(slideNo, className) {
|
||||
var el = getSlideEl(slideNo);
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (className) {
|
||||
el.classList.add(className);
|
||||
}
|
||||
|
||||
for (var i in SLIDE_CLASSES) {
|
||||
if (className != SLIDE_CLASSES[i]) {
|
||||
el.classList.remove(SLIDE_CLASSES[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateSlides() {
|
||||
if (window.trackPageview) window.trackPageview();
|
||||
|
||||
for (var i = 0; i < slideEls.length; i++) {
|
||||
switch (i) {
|
||||
case curSlide - 2:
|
||||
updateSlideClass(i, 'far-past');
|
||||
break;
|
||||
case curSlide - 1:
|
||||
updateSlideClass(i, 'past');
|
||||
break;
|
||||
case curSlide:
|
||||
updateSlideClass(i, 'current');
|
||||
break;
|
||||
case curSlide + 1:
|
||||
updateSlideClass(i, 'next');
|
||||
break;
|
||||
case curSlide + 2:
|
||||
updateSlideClass(i, 'far-next');
|
||||
break;
|
||||
default:
|
||||
updateSlideClass(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
triggerLeaveEvent(curSlide - 1);
|
||||
triggerEnterEvent(curSlide);
|
||||
|
||||
window.setTimeout(function() {
|
||||
// Hide after the slide
|
||||
disableSlideFrames(curSlide - 2);
|
||||
}, 301);
|
||||
|
||||
enableSlideFrames(curSlide - 1);
|
||||
enableSlideFrames(curSlide + 2);
|
||||
|
||||
updateHash();
|
||||
}
|
||||
|
||||
function prevSlide() {
|
||||
hideHelpText();
|
||||
if (curSlide > 0) {
|
||||
curSlide--;
|
||||
|
||||
updateSlides();
|
||||
}
|
||||
|
||||
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
hideHelpText();
|
||||
if (curSlide < slideEls.length - 1) {
|
||||
curSlide++;
|
||||
|
||||
updateSlides();
|
||||
}
|
||||
|
||||
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
|
||||
}
|
||||
|
||||
/* Slide events */
|
||||
|
||||
function triggerEnterEvent(no) {
|
||||
var el = getSlideEl(no);
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
var onEnter = el.getAttribute('onslideenter');
|
||||
if (onEnter) {
|
||||
new Function(onEnter).call(el);
|
||||
}
|
||||
|
||||
var evt = document.createEvent('Event');
|
||||
evt.initEvent('slideenter', true, true);
|
||||
evt.slideNumber = no + 1; // Make it readable
|
||||
|
||||
el.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
function triggerLeaveEvent(no) {
|
||||
var el = getSlideEl(no);
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
var onLeave = el.getAttribute('onslideleave');
|
||||
if (onLeave) {
|
||||
new Function(onLeave).call(el);
|
||||
}
|
||||
|
||||
var evt = document.createEvent('Event');
|
||||
evt.initEvent('slideleave', true, true);
|
||||
evt.slideNumber = no + 1; // Make it readable
|
||||
|
||||
el.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
/* Touch events */
|
||||
|
||||
function handleTouchStart(event) {
|
||||
if (event.touches.length == 1) {
|
||||
touchDX = 0;
|
||||
touchDY = 0;
|
||||
|
||||
touchStartX = event.touches[0].pageX;
|
||||
touchStartY = event.touches[0].pageY;
|
||||
|
||||
document.body.addEventListener('touchmove', handleTouchMove, true);
|
||||
document.body.addEventListener('touchend', handleTouchEnd, true);
|
||||
}
|
||||
}
|
||||
|
||||
function handleTouchMove(event) {
|
||||
if (event.touches.length > 1) {
|
||||
cancelTouch();
|
||||
} else {
|
||||
touchDX = event.touches[0].pageX - touchStartX;
|
||||
touchDY = event.touches[0].pageY - touchStartY;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function handleTouchEnd(event) {
|
||||
var dx = Math.abs(touchDX);
|
||||
var dy = Math.abs(touchDY);
|
||||
|
||||
if (dx > PM_TOUCH_SENSITIVITY && dy < (dx * 2) / 3) {
|
||||
if (touchDX > 0) {
|
||||
prevSlide();
|
||||
} else {
|
||||
nextSlide();
|
||||
}
|
||||
}
|
||||
|
||||
cancelTouch();
|
||||
}
|
||||
|
||||
function cancelTouch() {
|
||||
document.body.removeEventListener('touchmove', handleTouchMove, true);
|
||||
document.body.removeEventListener('touchend', handleTouchEnd, true);
|
||||
}
|
||||
|
||||
/* Preloading frames */
|
||||
|
||||
function disableSlideFrames(no) {
|
||||
var el = getSlideEl(no);
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
var frames = el.getElementsByTagName('iframe');
|
||||
for (var i = 0, frame; (frame = frames[i]); i++) {
|
||||
disableFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
function enableSlideFrames(no) {
|
||||
var el = getSlideEl(no);
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
var frames = el.getElementsByTagName('iframe');
|
||||
for (var i = 0, frame; (frame = frames[i]); i++) {
|
||||
enableFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
function disableFrame(frame) {
|
||||
frame.src = 'about:blank';
|
||||
}
|
||||
|
||||
function enableFrame(frame) {
|
||||
var src = frame._src;
|
||||
|
||||
if (frame.src != src && src != 'about:blank') {
|
||||
frame.src = src;
|
||||
}
|
||||
}
|
||||
|
||||
function setupFrames() {
|
||||
var frames = document.querySelectorAll('iframe');
|
||||
for (var i = 0, frame; (frame = frames[i]); i++) {
|
||||
frame._src = frame.src;
|
||||
disableFrame(frame);
|
||||
}
|
||||
|
||||
enableSlideFrames(curSlide);
|
||||
enableSlideFrames(curSlide + 1);
|
||||
enableSlideFrames(curSlide + 2);
|
||||
}
|
||||
|
||||
function setupInteraction() {
|
||||
/* Clicking and tapping */
|
||||
|
||||
var el = document.createElement('div');
|
||||
el.className = 'slide-area';
|
||||
el.id = 'prev-slide-area';
|
||||
el.addEventListener('click', prevSlide, false);
|
||||
document.querySelector('section.slides').appendChild(el);
|
||||
|
||||
var el = document.createElement('div');
|
||||
el.className = 'slide-area';
|
||||
el.id = 'next-slide-area';
|
||||
el.addEventListener('click', nextSlide, false);
|
||||
document.querySelector('section.slides').appendChild(el);
|
||||
|
||||
/* Swiping */
|
||||
|
||||
document.body.addEventListener('touchstart', handleTouchStart, false);
|
||||
}
|
||||
|
||||
/* Hash functions */
|
||||
|
||||
function getCurSlideFromHash() {
|
||||
var slideNo = parseInt(location.hash.substr(1));
|
||||
|
||||
if (slideNo) {
|
||||
curSlide = slideNo - 1;
|
||||
} else {
|
||||
curSlide = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function updateHash() {
|
||||
location.replace('#' + (curSlide + 1));
|
||||
}
|
||||
|
||||
/* Event listeners */
|
||||
|
||||
function handleBodyKeyDown(event) {
|
||||
// If we're in a code element, only handle pgup/down.
|
||||
var inCode = event.target.classList.contains('code');
|
||||
|
||||
switch (event.keyCode) {
|
||||
case 78: // 'N' opens presenter notes window
|
||||
if (!inCode && notesEnabled) toggleNotesWindow();
|
||||
break;
|
||||
case 72: // 'H' hides the help text
|
||||
case 27: // escape key
|
||||
if (!inCode) hideHelpText();
|
||||
break;
|
||||
|
||||
case 39: // right arrow
|
||||
case 13: // Enter
|
||||
case 32: // space
|
||||
if (inCode) break;
|
||||
case 34: // PgDn
|
||||
nextSlide();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case 37: // left arrow
|
||||
case 8: // Backspace
|
||||
if (inCode) break;
|
||||
case 33: // PgUp
|
||||
prevSlide();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case 40: // down arrow
|
||||
if (inCode) break;
|
||||
nextSlide();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case 38: // up arrow
|
||||
if (inCode) break;
|
||||
prevSlide();
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function scaleSmallViewports() {
|
||||
var el = document.querySelector('section.slides');
|
||||
var transform = '';
|
||||
var sWidthPx = 1250;
|
||||
var sHeightPx = 750;
|
||||
var sAspectRatio = sWidthPx / sHeightPx;
|
||||
var wAspectRatio = window.innerWidth / window.innerHeight;
|
||||
|
||||
if (wAspectRatio <= sAspectRatio && window.innerWidth < sWidthPx) {
|
||||
transform = 'scale(' + window.innerWidth / sWidthPx + ')';
|
||||
} else if (window.innerHeight < sHeightPx) {
|
||||
transform = 'scale(' + window.innerHeight / sHeightPx + ')';
|
||||
}
|
||||
el.style.transform = transform;
|
||||
}
|
||||
|
||||
function addEventListeners() {
|
||||
document.addEventListener('keydown', handleBodyKeyDown, false);
|
||||
var resizeTimeout;
|
||||
window.addEventListener('resize', function() {
|
||||
// throttle resize events
|
||||
window.clearTimeout(resizeTimeout);
|
||||
resizeTimeout = window.setTimeout(function() {
|
||||
resizeTimeout = null;
|
||||
scaleSmallViewports();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
// Force reset transform property of section.slides when printing page.
|
||||
// Use both onbeforeprint and matchMedia for compatibility with different browsers.
|
||||
var beforePrint = function() {
|
||||
var el = document.querySelector('section.slides');
|
||||
el.style.transform = '';
|
||||
};
|
||||
window.onbeforeprint = beforePrint;
|
||||
if (window.matchMedia) {
|
||||
var mediaQueryList = window.matchMedia('print');
|
||||
mediaQueryList.addListener(function(mql) {
|
||||
if (mql.matches) beforePrint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialization */
|
||||
|
||||
function addFontStyle() {
|
||||
var el = document.createElement('link');
|
||||
el.rel = 'stylesheet';
|
||||
el.type = 'text/css';
|
||||
el.href =
|
||||
'//fonts.googleapis.com/css?family=' +
|
||||
'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono';
|
||||
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
|
||||
function addGeneralStyle() {
|
||||
var el = document.createElement('link');
|
||||
el.rel = 'stylesheet';
|
||||
el.type = 'text/css';
|
||||
el.href = PERMANENT_URL_PREFIX + 'styles.css';
|
||||
document.body.appendChild(el);
|
||||
|
||||
var el = document.createElement('meta');
|
||||
el.name = 'viewport';
|
||||
el.content = 'width=device-width,height=device-height,initial-scale=1';
|
||||
document.querySelector('head').appendChild(el);
|
||||
|
||||
var el = document.createElement('meta');
|
||||
el.name = 'apple-mobile-web-app-capable';
|
||||
el.content = 'yes';
|
||||
document.querySelector('head').appendChild(el);
|
||||
|
||||
scaleSmallViewports();
|
||||
}
|
||||
|
||||
function handleDomLoaded() {
|
||||
slideEls = document.querySelectorAll('section.slides > article');
|
||||
|
||||
setupFrames();
|
||||
|
||||
addFontStyle();
|
||||
addGeneralStyle();
|
||||
addEventListeners();
|
||||
|
||||
updateSlides();
|
||||
|
||||
setupInteraction();
|
||||
|
||||
if (
|
||||
window.location.hostname == 'localhost' ||
|
||||
window.location.hostname == '127.0.0.1' ||
|
||||
window.location.hostname == '::1'
|
||||
) {
|
||||
hideHelpText();
|
||||
}
|
||||
|
||||
document.body.classList.add('loaded');
|
||||
|
||||
setupNotesSync();
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
getCurSlideFromHash();
|
||||
|
||||
if (window['_DEBUG']) {
|
||||
PERMANENT_URL_PREFIX = '../';
|
||||
}
|
||||
|
||||
if (window['_DCL']) {
|
||||
handleDomLoaded();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', handleDomLoaded, false);
|
||||
}
|
||||
}
|
||||
|
||||
// If ?debug exists then load the script relative instead of absolute
|
||||
if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) {
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
function() {
|
||||
// Avoid missing the DomContentLoaded event
|
||||
window['_DCL'] = true;
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
window['_DEBUG'] = true;
|
||||
var script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = '../slides.js';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(script, s);
|
||||
|
||||
// Remove this script
|
||||
s.parentNode.removeChild(s);
|
||||
} else {
|
||||
initialize();
|
||||
}
|
||||
|
||||
/* Synchronize windows when notes are enabled */
|
||||
|
||||
function setupNotesSync() {
|
||||
if (!notesEnabled) return;
|
||||
|
||||
function setupPlayResizeSync() {
|
||||
var out = document.getElementsByClassName('output');
|
||||
for (var i = 0; i < out.length; i++) {
|
||||
$(out[i]).bind('resize', function(event) {
|
||||
if ($(event.target).hasClass('ui-resizable')) {
|
||||
localStorage.setItem('play-index', i);
|
||||
localStorage.setItem('output-style', out[i].style.cssText);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function setupPlayCodeSync() {
|
||||
var play = document.querySelectorAll('div.playground');
|
||||
for (var i = 0; i < play.length; i++) {
|
||||
play[i].addEventListener('input', inputHandler, false);
|
||||
|
||||
function inputHandler(e) {
|
||||
localStorage.setItem('play-index', i);
|
||||
localStorage.setItem('play-code', e.target.innerHTML);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupPlayCodeSync();
|
||||
setupPlayResizeSync();
|
||||
localStorage.setItem(destSlideKey(), curSlide);
|
||||
window.addEventListener('storage', updateOtherWindow, false);
|
||||
}
|
||||
|
||||
// An update to local storage is caught only by the other window
|
||||
// The triggering window does not handle any sync actions
|
||||
function updateOtherWindow(e) {
|
||||
// Ignore remove storage events which are not meant to update the other window
|
||||
var isRemoveStorageEvent = !e.newValue;
|
||||
if (isRemoveStorageEvent) return;
|
||||
|
||||
var destSlide = localStorage.getItem(destSlideKey());
|
||||
while (destSlide > curSlide) {
|
||||
nextSlide();
|
||||
}
|
||||
while (destSlide < curSlide) {
|
||||
prevSlide();
|
||||
}
|
||||
|
||||
updatePlay(e);
|
||||
updateNotes();
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
@media screen {
|
||||
/* Framework */
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
display: block !important;
|
||||
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background: rgb(215, 215, 215);
|
||||
background: -o-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));
|
||||
background: -moz-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));
|
||||
background: -webkit-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));
|
||||
background: -webkit-gradient(
|
||||
radial,
|
||||
50% 50%,
|
||||
0,
|
||||
50% 50%,
|
||||
500,
|
||||
from(rgb(240, 240, 240)),
|
||||
to(rgb(190, 190, 190))
|
||||
);
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.slides {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
position: absolute;
|
||||
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.slides > article {
|
||||
display: block;
|
||||
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
|
||||
width: 900px;
|
||||
height: 700px;
|
||||
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
|
||||
margin-left: -450px;
|
||||
margin-top: -350px;
|
||||
|
||||
padding: 40px 60px;
|
||||
|
||||
box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
|
||||
border-radius: 10px;
|
||||
-o-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
|
||||
background-color: white;
|
||||
|
||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||
|
||||
transition: transform 0.3s ease-out;
|
||||
-o-transition: -o-transform 0.3s ease-out;
|
||||
-moz-transition: -moz-transform 0.3s ease-out;
|
||||
-webkit-transition: -webkit-transform 0.3s ease-out;
|
||||
}
|
||||
.slides.layout-widescreen > article {
|
||||
margin-left: -550px;
|
||||
width: 1100px;
|
||||
}
|
||||
.slides.layout-faux-widescreen > article {
|
||||
margin-left: -550px;
|
||||
width: 1100px;
|
||||
|
||||
padding: 40px 160px;
|
||||
}
|
||||
|
||||
.slides.layout-widescreen > article:not(.nobackground):not(.biglogo),
|
||||
.slides.layout-faux-widescreen > article:not(.nobackground):not(.biglogo) {
|
||||
background-position-x: 0, 840px;
|
||||
}
|
||||
|
||||
/* Clickable/tappable areas */
|
||||
|
||||
.slide-area {
|
||||
z-index: 1000;
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 150px;
|
||||
height: 700px;
|
||||
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
|
||||
cursor: pointer;
|
||||
margin-top: -350px;
|
||||
|
||||
tap-highlight-color: transparent;
|
||||
-o-tap-highlight-color: transparent;
|
||||
-moz-tap-highlight-color: transparent;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
#prev-slide-area {
|
||||
margin-left: -550px;
|
||||
}
|
||||
#next-slide-area {
|
||||
margin-left: 400px;
|
||||
}
|
||||
.slides.layout-widescreen #prev-slide-area,
|
||||
.slides.layout-faux-widescreen #prev-slide-area {
|
||||
margin-left: -650px;
|
||||
}
|
||||
.slides.layout-widescreen #next-slide-area,
|
||||
.slides.layout-faux-widescreen #next-slide-area {
|
||||
margin-left: 500px;
|
||||
}
|
||||
|
||||
/* Slides */
|
||||
|
||||
.slides > article {
|
||||
display: none;
|
||||
}
|
||||
.slides > article.far-past {
|
||||
display: block;
|
||||
transform: translate(-2040px);
|
||||
-o-transform: translate(-2040px);
|
||||
-moz-transform: translate(-2040px);
|
||||
-webkit-transform: translate3d(-2040px, 0, 0);
|
||||
}
|
||||
.slides > article.past {
|
||||
display: block;
|
||||
transform: translate(-1020px);
|
||||
-o-transform: translate(-1020px);
|
||||
-moz-transform: translate(-1020px);
|
||||
-webkit-transform: translate3d(-1020px, 0, 0);
|
||||
}
|
||||
.slides > article.current {
|
||||
display: block;
|
||||
transform: translate(0);
|
||||
-o-transform: translate(0);
|
||||
-moz-transform: translate(0);
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
}
|
||||
.slides > article.next {
|
||||
display: block;
|
||||
transform: translate(1020px);
|
||||
-o-transform: translate(1020px);
|
||||
-moz-transform: translate(1020px);
|
||||
-webkit-transform: translate3d(1020px, 0, 0);
|
||||
}
|
||||
.slides > article.far-next {
|
||||
display: block;
|
||||
transform: translate(2040px);
|
||||
-o-transform: translate(2040px);
|
||||
-moz-transform: translate(2040px);
|
||||
-webkit-transform: translate3d(2040px, 0, 0);
|
||||
}
|
||||
|
||||
.slides.layout-widescreen > article.far-past,
|
||||
.slides.layout-faux-widescreen > article.far-past {
|
||||
display: block;
|
||||
transform: translate(-2260px);
|
||||
-o-transform: translate(-2260px);
|
||||
-moz-transform: translate(-2260px);
|
||||
-webkit-transform: translate3d(-2260px, 0, 0);
|
||||
}
|
||||
.slides.layout-widescreen > article.past,
|
||||
.slides.layout-faux-widescreen > article.past {
|
||||
display: block;
|
||||
transform: translate(-1130px);
|
||||
-o-transform: translate(-1130px);
|
||||
-moz-transform: translate(-1130px);
|
||||
-webkit-transform: translate3d(-1130px, 0, 0);
|
||||
}
|
||||
.slides.layout-widescreen > article.current,
|
||||
.slides.layout-faux-widescreen > article.current {
|
||||
display: block;
|
||||
transform: translate(0);
|
||||
-o-transform: translate(0);
|
||||
-moz-transform: translate(0);
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
}
|
||||
.slides.layout-widescreen > article.next,
|
||||
.slides.layout-faux-widescreen > article.next {
|
||||
display: block;
|
||||
transform: translate(1130px);
|
||||
-o-transform: translate(1130px);
|
||||
-moz-transform: translate(1130px);
|
||||
-webkit-transform: translate3d(1130px, 0, 0);
|
||||
}
|
||||
.slides.layout-widescreen > article.far-next,
|
||||
.slides.layout-faux-widescreen > article.far-next {
|
||||
display: block;
|
||||
transform: translate(2260px);
|
||||
-o-transform: translate(2260px);
|
||||
-moz-transform: translate(2260px);
|
||||
-webkit-transform: translate3d(2260px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* Set page layout */
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
}
|
||||
|
||||
body {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.slides > article {
|
||||
display: block;
|
||||
|
||||
position: relative;
|
||||
|
||||
page-break-inside: never;
|
||||
page-break-after: always;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: static !important;
|
||||
margin-top: 400px !important;
|
||||
margin-bottom: 100px !important;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
/* Add explicit links */
|
||||
a:link:after,
|
||||
a:visited:after {
|
||||
content: ' (' attr(href) ') ';
|
||||
font-size: 50%;
|
||||
}
|
||||
|
||||
#help {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles for slides */
|
||||
|
||||
.slides > article {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
|
||||
color: black;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
|
||||
font-size: 26px;
|
||||
line-height: 36px;
|
||||
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(0, 102, 204);
|
||||
text-decoration: none;
|
||||
}
|
||||
a:visited {
|
||||
color: rgba(0, 102, 204, 0.75);
|
||||
}
|
||||
a:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
margin-top: 20px;
|
||||
}
|
||||
p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 60px;
|
||||
line-height: 60px;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: 200px;
|
||||
margin-bottom: 5px;
|
||||
padding-right: 40px;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
letter-spacing: -3px;
|
||||
|
||||
color: rgb(51, 51, 51);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 45px;
|
||||
line-height: 45px;
|
||||
|
||||
position: absolute;
|
||||
bottom: 150px;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-right: 40px;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
letter-spacing: -2px;
|
||||
|
||||
color: rgb(51, 51, 51);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 30px;
|
||||
line-height: 36px;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-right: 40px;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
letter-spacing: -1px;
|
||||
|
||||
color: rgb(51, 51, 51);
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-top: 20px;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
||||
|
||||
div.code, div.output {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 5px 10px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
|
||||
background: rgb(240, 240, 240);
|
||||
border: 1px solid rgb(224, 224, 224);
|
||||
|
||||
font-family: 'Droid Sans Mono', 'Courier New', monospace;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -1px;
|
||||
|
||||
color: black;
|
||||
}
|
||||
|
||||
pre.numbers span:before {
|
||||
content: attr(num);
|
||||
margin-right: 1em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 95%;
|
||||
font-family: 'Droid Sans Mono', 'Courier New', monospace;
|
||||
|
||||
color: black;
|
||||
}
|
||||
|
||||
pre code {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
article > .image,
|
||||
article > .video {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
article.background {
|
||||
background-size: contain;
|
||||
background-repeat: round;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 40px;
|
||||
}
|
||||
th {
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
}
|
||||
td,
|
||||
th {
|
||||
border: 1px solid rgb(224, 224, 224);
|
||||
padding: 5px 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
p.link {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.pagenumber {
|
||||
color: #8c8c8c;
|
||||
font-size: 75%;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
/* Code */
|
||||
pre {
|
||||
outline: 0px solid transparent;
|
||||
}
|
||||
div.playground {
|
||||
position: relative;
|
||||
}
|
||||
div.output {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
right: 40px;
|
||||
bottom: 40px;
|
||||
background: #202020;
|
||||
padding: 5px 10px;
|
||||
z-index: 2;
|
||||
|
||||
border-radius: 10px;
|
||||
-o-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
}
|
||||
div.output pre {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
div.output .stdout,
|
||||
div.output pre {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
div.output .stderr,
|
||||
div.output .error {
|
||||
color: rgb(255, 200, 200);
|
||||
}
|
||||
div.output .system,
|
||||
div.output .exit {
|
||||
color: rgb(255, 230, 120);
|
||||
}
|
||||
.buttons {
|
||||
position: relative;
|
||||
float: right;
|
||||
top: -60px;
|
||||
right: 10px;
|
||||
}
|
||||
div.output .buttons {
|
||||
position: absolute;
|
||||
float: none;
|
||||
top: auto;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
/* Presenter details */
|
||||
.presenter {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.presenter p,
|
||||
.presenter .link {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
/* Output resize details */
|
||||
.ui-resizable-handle {
|
||||
position: absolute;
|
||||
}
|
||||
.ui-resizable-n {
|
||||
cursor: n-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-resizable-w {
|
||||
cursor: w-resize;
|
||||
width: 7px;
|
||||
left: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable-nw {
|
||||
cursor: nw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
iframe {
|
||||
border: none;
|
||||
}
|
||||
figcaption {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
#help {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
text-align: center;
|
||||
color: white;
|
||||
background: #000;
|
||||
opacity: 0.5;
|
||||
position: fixed;
|
||||
bottom: 25px;
|
||||
left: 50px;
|
||||
right: 50px;
|
||||
padding: 20px;
|
||||
|
||||
border-radius: 10px;
|
||||
-o-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{/*
|
||||
This is the action template.
|
||||
It determines how the formatting actions are rendered.
|
||||
*/}
|
||||
|
||||
{{define "section"}}
|
||||
<h{{len .Number}} id="TOC_{{.FormattedNumber}}">{{.FormattedNumber}} {{.Title}}</h{{len .Number}}>
|
||||
{{range .Elem}}{{elem $.Template .}}{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "list"}}
|
||||
<ul>
|
||||
{{range .Bullet}}
|
||||
<li>{{style .}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
{{define "text"}}
|
||||
{{if .Pre}}
|
||||
<div class="code"><pre>{{range .Lines}}{{.}}{{end}}</pre></div>
|
||||
{{else}}
|
||||
<p>
|
||||
{{range $i, $l := .Lines}}{{if $i}}{{template "newline"}}
|
||||
{{end}}{{style $l}}{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "code"}}
|
||||
<div class="code{{if playable .}} playground{{end}}" {{if .Edit}}contenteditable="true" spellcheck="false"{{end}}>{{.Text}}</div>
|
||||
{{end}}
|
||||
|
||||
{{define "image"}}
|
||||
<div class="image">
|
||||
<img src="{{.URL}}"{{with .Height}} height="{{.}}"{{end}}{{with .Width}} width="{{.}}"{{end}}>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "video"}}
|
||||
<div class="video">
|
||||
<video {{with .Height}} height="{{.}}"{{end}}{{with .Width}} width="{{.}}"{{end}} controls>
|
||||
<source src="{{.URL}}" type="{{.SourceType}}">
|
||||
</video>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "background"}}
|
||||
<div class="background">
|
||||
<img src="{{.URL}}">
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "iframe"}}
|
||||
<iframe src="{{.URL}}"{{with .Height}} height="{{.}}"{{end}}{{with .Width}} width="{{.}}"{{end}}></iframe>
|
||||
{{end}}
|
||||
|
||||
{{define "link"}}<p class="link"><a href="{{.URL}}" target="_blank">{{style .Label}}</a></p>{{end}}
|
||||
|
||||
{{define "html"}}{{.HTML}}{{end}}
|
||||
|
||||
{{define "caption"}}<figcaption>{{style .Text}}</figcaption>{{end}}
|
||||
@@ -0,0 +1,98 @@
|
||||
{/* This is the article template. It defines how articles are formatted. */}
|
||||
|
||||
{{define "root"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
<link type="text/css" rel="stylesheet" href="/static/article.css">
|
||||
<meta charset='utf-8'>
|
||||
<script>
|
||||
// Initialize Google Analytics tracking code on production site only.
|
||||
if (window["location"] && window["location"]["hostname"] == "talks.golang.org") {
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(["_setAccount", "UA-11222381-6"]);
|
||||
_gaq.push(["b._setAccount", "UA-49880327-6"]);
|
||||
window.trackPageview = function() {
|
||||
_gaq.push(["_trackPageview", location.pathname+location.hash]);
|
||||
_gaq.push(["b._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]);
|
||||
_gaq.push(["b._trackEvent", category, action, opt_label, opt_value, opt_noninteraction]);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="topbar" class="wide">
|
||||
<div class="container">
|
||||
<div id="heading">{{.Title}}
|
||||
{{with .Subtitle}}{{.}}{{end}}
|
||||
{{if .Authors}}
|
||||
{{range .Authors}}
|
||||
<div class="author">
|
||||
{{range .Elem}}{{elem $.Template .}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="page" class="wide">
|
||||
<div class="container">
|
||||
{{with .Sections}}
|
||||
<div id="toc" class="no-print">
|
||||
<div id="tochead">Contents</div>
|
||||
{{template "TOC" .}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{range .Sections}}
|
||||
{{elem $.Template .}}
|
||||
{{end}}{{/* of Section block */}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .PlayEnabled}}
|
||||
<script src='/play.js'></script>
|
||||
{{end}}
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// Load Google Analytics tracking code on production site only.
|
||||
if (window["location"] && window["location"]["hostname"] == "talks.golang.org") {
|
||||
var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
|
||||
ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
|
||||
var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
{{define "TOC"}}
|
||||
<ul class="toc-outer">
|
||||
{{range .}}
|
||||
<li><a href="#TOC_{{.FormattedNumber}}">{{.Title}}</a></li>
|
||||
{{with .Sections}}{{template "TOC-Inner" .}}{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
{{define "TOC-Inner"}}
|
||||
<ul class="toc-inner">
|
||||
{{range .}}
|
||||
<li><a href="#TOC_{{.FormattedNumber}}">{{.Title}}</a></li>
|
||||
{{with .Sections}}{{template "TOC-Inner" .}}{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
{{define "newline"}}
|
||||
{{/* No automatic line break. Paragraphs are free-form. */}}
|
||||
{{end}}
|
||||
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Talks - The Go Programming Language</title>
|
||||
<link type="text/css" rel="stylesheet" href="/static/dir.css">
|
||||
<script src="/static/dir.js"></script>
|
||||
<script>
|
||||
// Initialize Google Analytics tracking code on production site only.
|
||||
if (window["location"] && window["location"]["hostname"] == "talks.golang.org") {
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(["_setAccount", "UA-11222381-6"]);
|
||||
_gaq.push(["b._setAccount", "UA-49880327-6"]);
|
||||
window.trackPageview = function() {
|
||||
_gaq.push(["_trackPageview", location.pathname+location.hash]);
|
||||
_gaq.push(["b._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]);
|
||||
_gaq.push(["b._trackEvent", category, action, opt_label, opt_value, opt_noninteraction]);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="topbar"><div class="container">
|
||||
|
||||
<form method="GET" action="//golang.org/search">
|
||||
<div id="menu">
|
||||
<a href="http://golang.org/doc/">Documents</a>
|
||||
<a href="http://golang.org/ref">References</a>
|
||||
<a href="http://golang.org/pkg/">Packages</a>
|
||||
<a href="http://golang.org/project/">The Project</a>
|
||||
<a href="http://golang.org/help/">Help</a>
|
||||
<input type="text" id="search" name="q" class="inactive" value="Search">
|
||||
</div>
|
||||
<div id="heading"><a href="/">The Go Programming Language</a></div>
|
||||
</form>
|
||||
|
||||
</div></div>
|
||||
|
||||
<div id="page">
|
||||
|
||||
<h1>Go talks</h1>
|
||||
|
||||
{{with .Path}}<h2>{{.}}</h2>{{end}}
|
||||
|
||||
{{with .Articles}}
|
||||
<h4>Articles:</h4>
|
||||
<dl>
|
||||
{{range .}}
|
||||
<dd><a href="/{{.Path}}">{{.Name}}</a>: {{.Title}}</dd>
|
||||
{{end}}
|
||||
</dl>
|
||||
{{end}}
|
||||
|
||||
{{with .Slides}}
|
||||
<h4>Slide decks:</h4>
|
||||
<dl>
|
||||
{{range .}}
|
||||
<dd><a href="/{{.Path}}">{{.Name}}</a>: {{.Title}}</dd>
|
||||
{{end}}
|
||||
</dl>
|
||||
{{end}}
|
||||
|
||||
{{with .Other}}
|
||||
<h4>Files:</h4>
|
||||
<dl>
|
||||
{{range .}}
|
||||
<dd><a href="/{{.Path}}">{{.Name}}</a></dd>
|
||||
{{end}}
|
||||
</dl>
|
||||
{{end}}
|
||||
|
||||
{{with .Dirs}}
|
||||
<h4>Sub-directories:</h4>
|
||||
<dl>
|
||||
{{range .}}
|
||||
<dd><a href="/{{.Path}}">{{.Name}}</a></dd>
|
||||
{{end}}
|
||||
</dl>
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
Except as <a href="https://developers.google.com/site-policies#restrictions">noted</a>,
|
||||
the content of this page is licensed under the
|
||||
Creative Commons Attribution 3.0 License,
|
||||
and code is licensed under a <a href="http://golang.org/LICENSE">BSD license</a>.<br>
|
||||
<a href="http://golang.org/doc/tos.html">Terms of Service</a> |
|
||||
<a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// Load Google Analytics tracking code on production site only.
|
||||
if (window["location"] && window["location"]["hostname"] == "talks.golang.org") {
|
||||
var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
|
||||
ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
|
||||
var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,107 @@
|
||||
{/* This is the slide template. It defines how presentations are formatted. */}
|
||||
|
||||
{{define "root"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
<meta charset='utf-8'>
|
||||
<script>
|
||||
var notesEnabled = {{.NotesEnabled}};
|
||||
</script>
|
||||
<script src='/static/slides.js'></script>
|
||||
|
||||
{{if .NotesEnabled}}
|
||||
<script>
|
||||
var sections = {{.Sections}};
|
||||
var titleNotes = {{.TitleNotes}}
|
||||
</script>
|
||||
<script src='/static/notes.js'></script>
|
||||
{{end}}
|
||||
|
||||
<script>
|
||||
// Initialize Google Analytics tracking code on production site only.
|
||||
if (window["location"] && window["location"]["hostname"] == "talks.golang.org") {
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(["_setAccount", "UA-11222381-6"]);
|
||||
_gaq.push(["b._setAccount", "UA-49880327-6"]);
|
||||
window.trackPageview = function() {
|
||||
_gaq.push(["_trackPageview", location.pathname+location.hash]);
|
||||
_gaq.push(["b._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]);
|
||||
_gaq.push(["b._trackEvent", category, action, opt_label, opt_value, opt_noninteraction]);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body style='display: none'>
|
||||
|
||||
<section class='slides layout-widescreen'>
|
||||
|
||||
<article>
|
||||
<h1>{{.Title}}</h1>
|
||||
{{with .Subtitle}}<h3>{{.}}</h3>{{end}}
|
||||
{{if not .Time.IsZero}}<h3>{{.Time.Format "2 January 2006"}}</h3>{{end}}
|
||||
{{range .Authors}}
|
||||
<div class="presenter">
|
||||
{{range .TextElem}}{{elem $.Template .}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</article>
|
||||
|
||||
{{range $i, $s := .Sections}}
|
||||
<!-- start of slide {{$s.Number}} -->
|
||||
<article {{$s.HTMLAttributes}}>
|
||||
{{if $s.Elem}}
|
||||
<h3>{{$s.Title}}</h3>
|
||||
{{range $s.Elem}}{{elem $.Template .}}{{end}}
|
||||
{{else}}
|
||||
<h2>{{$s.Title}}</h2>
|
||||
{{end}}
|
||||
<span class="pagenumber">{{pagenum $s 1}}</span>
|
||||
</article>
|
||||
<!-- end of slide {{$s.Number}} -->
|
||||
{{end}}{{/* of Slide block */}}
|
||||
|
||||
<article>
|
||||
<h3>Thank you</h3>
|
||||
{{range .Authors}}
|
||||
<div class="presenter">
|
||||
{{range .Elem}}{{elem $.Template .}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</article>
|
||||
|
||||
</section>
|
||||
|
||||
<div id="help">
|
||||
Use the left and right arrow keys or click the left and right
|
||||
edges of the page to navigate between slides.<br>
|
||||
(Press 'H' or navigate to hide this message.)
|
||||
</div>
|
||||
|
||||
{{if .PlayEnabled}}
|
||||
<script src='/play.js'></script>
|
||||
{{end}}
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// Load Google Analytics tracking code on production site only.
|
||||
if (window["location"] && window["location"]["hostname"] == "talks.golang.org") {
|
||||
var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
|
||||
ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
|
||||
var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
{{define "newline"}}
|
||||
<br>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user