1
0
mirror of https://github.com/golang/go synced 2024-11-05 17:46:16 -07:00

go.tools/cmd/present: add present tool

LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/97030045
This commit is contained in:
Andrew Gerrand 2014-05-05 14:55:27 -07:00
parent 9f1412014f
commit ca18093fea
16 changed files with 2052 additions and 0 deletions

24
cmd/present/appengine.go Normal file
View File

@ -0,0 +1,24 @@
// 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.
// +build appengine
package main
import (
"code.google.com/p/go.tools/present"
_ "code.google.com/p/go.tools/playground"
)
var basePath = "./present/"
func init() {
playScript(basePath, "HTTPTransport")
present.PlayEnabled = true
}
func playable(c present.Code) bool {
return present.PlayEnabled && c.Play && c.Ext == ".go"
}

281
cmd/present/dir.go Normal file
View File

@ -0,0 +1,281 @@
// 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 (
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"path/filepath"
"sort"
"code.google.com/p/go.tools/present"
)
func init() {
http.HandleFunc("/", dirHandler)
}
// dirHandler serves a directory listing for the requested path, rooted at basePath.
func dirHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/favicon.ico" {
http.Error(w, "not found", 404)
return
}
const base = "."
name := filepath.Join(base, r.URL.Path)
if isDoc(name) {
err := renderDoc(w, basePath, name)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), 500)
}
return
}
if isDir, err := dirList(w, name); err != nil {
log.Println(err)
http.Error(w, err.Error(), 500)
return
} else if isDir {
return
}
http.FileServer(http.Dir(base)).ServeHTTP(w, r)
}
// extensions maps the presentable file extensions to the name of the
// template to be executed.
var extensions = map[string]string{
".slide": "slides.tmpl",
".article": "article.tmpl",
}
func isDoc(path string) bool {
_, ok := extensions[filepath.Ext(path)]
return ok
}
// renderDoc reads the present file, builds its template representation,
// and executes the template, sending output to w.
func renderDoc(w io.Writer, base, 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.
ext := filepath.Ext(docFile)
contentTmpl, ok := extensions[ext]
if !ok {
return fmt.Errorf("no template for extension %v", ext)
}
// Locate the template file.
actionTmpl := filepath.Join(base, "templates/action.tmpl")
contentTmpl = filepath.Join(base, "templates", contentTmpl)
// Read and parse the input.
tmpl := present.Template()
tmpl = tmpl.Funcs(template.FuncMap{"playable": playable})
if _, err := tmpl.ParseFiles(actionTmpl, contentTmpl); err != nil {
return err
}
// 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, 0)
}
// 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
}
d := &dirListData{Path: name}
for _, fi := range fis {
// skip the pkg directory
if name == "." && fi.Name() == "pkg" {
continue
}
e := dirEntry{
Name: fi.Name(),
Path: filepath.ToSlash(filepath.Join(name, fi.Name())),
}
if fi.IsDir() && showDir(e.Name) {
d.Dirs = append(d.Dirs, e)
continue
}
if isDoc(e.Name) {
if p, err := parse(e.Path, present.TitlesOnly); err != nil {
log.Println(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 }
var dirListTemplate = template.Must(template.New("").Parse(dirListHTML))
const dirListHTML = `<!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>
</head>
<body>
<div id="topbar"><div class="container">
<form method="GET" action="http://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>
</body>
</html>`

29
cmd/present/doc.go Normal file
View File

@ -0,0 +1,29 @@
// 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.
The stand-alone version permits the execution of programs from within a
presentation. The App Engine version does not provide this functionality.
Usage of present:
-base="": base path for slide template and static resources
-http="127.0.0.1:3999": host:port to listen on
You may use the app.yaml file provided in the root of the go.talks repository
to deploy present to App Engine:
appcfg.py update -A your-app-id -V your-app-version /path/to/go.talks
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:
http://godoc.org/code.google.com/p/go.tools/present
*/
package main

6
cmd/present/js/jquery-ui.js vendored Normal file

File diff suppressed because one or more lines are too long

122
cmd/present/local.go Normal file
View File

@ -0,0 +1,122 @@
// 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.
// +build !appengine
package main
import (
"flag"
"fmt"
"go/build"
"log"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"code.google.com/p/go.tools/playground/socket"
"code.google.com/p/go.tools/present"
)
const basePkg = "code.google.com/p/go.tools/cmd/present"
var (
basePath string
nativeClient bool
)
func main() {
httpListen := flag.String("http", "127.0.0.1:3999", "host:port to listen on")
flag.StringVar(&basePath, "base", "", "base path for slide template and static resources")
flag.BoolVar(&present.PlayEnabled, "play", true, "enable playground (permit execution of arbitrary user code)")
flag.BoolVar(&nativeClient, "nacl", false, "use Native Client environment playground (prevents non-Go code execution)")
flag.Parse()
if basePath == "" {
p, err := build.Default.Import(basePkg, "", build.FindOnly)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't find gopresent files: %v\n", err)
fmt.Fprintf(os.Stderr, basePathMessage, basePkg)
os.Exit(1)
}
basePath = p.Dir
}
if present.PlayEnabled {
if nativeClient {
socket.RunScripts = false
socket.Environ = func() []string {
if runtime.GOARCH == "amd64" {
return environ("GOOS=nacl", "GOARCH=amd64p32")
}
return environ("GOOS=nacl")
}
}
playScript(basePath, "SocketTransport")
host, port, err := net.SplitHostPort(*httpListen)
if err != nil {
log.Fatal(err)
}
origin := &url.URL{Scheme: "http", Host: host + ":" + port}
http.Handle("/socket", socket.NewHandler(origin))
}
http.Handle("/static/", http.FileServer(http.Dir(basePath)))
if !strings.HasPrefix(*httpListen, "127.0.0.1") &&
!strings.HasPrefix(*httpListen, "localhost") &&
present.PlayEnabled && !nativeClient {
log.Print(localhostWarning)
}
log.Printf("Open your web browser and visit http://%s/", *httpListen)
log.Fatal(http.ListenAndServe(*httpListen, nil))
}
func playable(c present.Code) bool {
return present.PlayEnabled && c.Play
}
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.
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 or run with -play=false.
If you don't understand this message, hit Control-C to terminate this process.
WARNING! WARNING! WARNING!
`

43
cmd/present/play.go Normal file
View File

@ -0,0 +1,43 @@
// 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/ioutil"
"net/http"
"path/filepath"
"time"
"code.google.com/p/go.tools/godoc/static"
)
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(root, transport string) {
modTime := time.Now()
var buf bytes.Buffer
for _, p := range scripts {
if s, ok := static.Files[p]; ok {
buf.WriteString(s)
continue
}
b, err := ioutil.ReadFile(filepath.Join(root, "js", 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))
})
}

View File

@ -0,0 +1,136 @@
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: 24px;
}
h2 {
font-size: 20px;
background: #E0EBF5;
padding: 2px 5px;
}
h3 {
font-size: 20px;
}
h3, h4 {
margin: 20px 5px;
}
h4 {
font-size: 16px;
}
div#heading {
float: left;
margin: 0 0 10px 0;
padding: 21px 0;
font-size: 20px;
font-weight: normal;
}
div#topbar {
background: #E0EBF5;
height: 64px;
overflow: hidden;
}
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: 20, 0, 0, 0px;
}
div.code,
div.output {
margin: 20px;
padding: 10px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
div.code { background: #e9e9e9; }
div.output { background: black; }
div.output .stdout { color: #e6e6e6; }
div.output .stderr { color: rgb(244, 74, 63); }
div.output .system { 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: white;
max-width: 33%;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
#toc ul, #toc a {
list-style-type: none;
padding-left: 10px;
color: black;
margin: 0px;
}

186
cmd/present/static/dir.css Normal file
View File

@ -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;
}

41
cmd/present/static/dir.js Normal file
View File

@ -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

View File

@ -0,0 +1,51 @@
/* set page layout */
@page {
size: A4 landscape;
}
body {
display: block !important;
}
.slides {
left: 0;
top: 0;
}
.slides > article {
position: relative;
left: 0;
top: 0;
margin: 0 !important;
page-break-inside: avoid;
text-shadow: none; /* disable shadow */
display: block !important;
transform: translate(0) !important;
-o-transform: translate(0) !important;
-moz-transform: translate(0) !important;
-webkit-transform: translate3d(0, 0, 0) !important;
}
div.code {
background: rgb(240, 240, 240);
}
/* hide click areas */
.slide-area, #prev-slide-area, #next-slide-area {
display: none;
}
/* add explicit links */
a:link:after, a:visited:after {
content: " (" attr(href) ") ";
font-size: 50%;
}
/* white background */
body {
background: rgb(255,255,255) !important;
}

View File

@ -0,0 +1,516 @@
// 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 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() {
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() {
if (curSlide > 0) {
curSlide--;
updateSlides();
}
};
function nextSlide() {
if (curSlide < slideEls.length - 1) {
curSlide++;
updateSlides();
}
};
/* 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 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 addEventListeners() {
document.addEventListener('keydown', handleBodyKeyDown, false);
};
/* 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=1100,height=750';
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);
};
function addPrintStyle() {
var el = document.createElement('link');
el.rel = 'stylesheet';
el.type = 'text/css';
el.media = "print";
el.href = PERMANENT_URL_PREFIX + 'print.css';
document.body.appendChild(el);
};
function handleDomLoaded() {
slideEls = document.querySelectorAll('section.slides > article');
setupFrames();
addFontStyle();
addGeneralStyle();
addPrintStyle();
addEventListeners();
updateSlides();
setupInteraction();
document.body.classList.add('loaded');
};
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();
}

View File

@ -0,0 +1,455 @@
/* Framework */
html {
height: 100%;
}
body {
margin: 0;
padding: 0;
display: block !important;
height: 100%;
min-height: 740px;
overflow-x: hidden;
overflow-y: auto;
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, .3);
/*
transition: transform .3s ease-out;
-o-transition: -o-transform .3s ease-out;
-moz-transition: -moz-transform .3s ease-out;
-webkit-transition: -webkit-transform .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);
}
/* Styles for slides */
.slides > article {
font-family: 'Open Sans', Arial, sans-serif;
color: black;
text-shadow: 0 1px 1px rgba(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, .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 .5em 0;
}
div.code {
padding: 5px 10px;
margin-top: 20px;
margin-bottom: 20px;
overflow: hidden;
background: rgb(240, 240, 240);
border: 1px solid rgb(224, 224, 224);
}
pre {
margin: 0;
padding: 0;
font-family: 'Droid Sans Mono', 'Courier New', monospace;
font-size: 18px;
line-height: 24px;
letter-spacing: -1px;
color: black;
}
code {
font-size: 95%;
font-family: 'Droid Sans Mono', 'Courier New', monospace;
color: black;
}
article > .image {
text-align: center;
margin-top: 40px;
}
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;
}
/* Code */
div.code {
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;
}

View File

@ -0,0 +1,46 @@
{/*
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}}" contenteditable="true" spellcheck="false">{{.Text}}</div>
{{end}}
{{define "image"}}
<div class="image">
<img src="{{.URL}}"{{with .Height}} height="{{.}}"{{end}}{{with .Width}} width="{{.}}"{{end}}>
</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}}

View File

@ -0,0 +1,58 @@
{/* 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'>
</head>
<body>
<div id="topbar" class="wide">
<div class="container">
<div id="heading">{{.Title}}
{{with .Subtitle}}{{.}}{{end}}
</div>
</div>
</div>
<div id="page" class="wide">
<div class="container">
{{with .Sections}}
<div id="toc">
{{template "TOC" .}}
</div>
{{end}}
{{range .Sections}}
{{elem $.Template .}}
{{end}}{{/* of Section block */}}
{{if .Authors}}
<h2>Authors</h2>
{{range .Authors}}
<div class="author">
{{range .Elem}}{{elem $.Template .}}{{end}}
</div>
{{end}}
{{end}}
</div>
</div>
<script src='/play.js'></script>
</body>
</html>
{{end}}
{{define "TOC"}}
<ul>
{{range .}}
<li><a href="#TOC_{{.FormattedNumber}}">{{.Title}}</a></li>
{{with .Sections}}{{template "TOC" .}}{{end}}
{{end}}
</ul>
{{end}}
{{define "newline"}}
{{/* No automatic line break. Paragraphs are free-form. */}}
{{end}}

View File

@ -0,0 +1,58 @@
{/* 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 src='/static/slides.js'></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>
{{if $s.Elem}}
<h3>{{$s.Title}}</h3>
{{range $s.Elem}}{{elem $.Template .}}{{end}}
{{else}}
<h2>{{$s.Title}}</h2>
{{end}}
</article>
<!-- end of slide {{$i}} -->
{{end}}{{/* of Slide block */}}
<article>
<h3>Thank you</h1>
{{range .Authors}}
<div class="presenter">
{{range .Elem}}{{elem $.Template .}}{{end}}
</div>
{{end}}
</article>
</body>
{{if .PlayEnabled}}
<script src='/play.js'></script>
{{end}}
</html>
{{end}}
{{define "newline"}}
<br>
{{end}}