1
0
mirror of https://github.com/golang/go synced 2024-11-18 11:04:42 -07:00

x/tools/present: display presenter notes and synchronize browser windows

Change-Id: If7d5cc52f7594c141060d40e8393ac69cb7ba9ad
Reviewed-on: https://go-review.googlesource.com/21488
Reviewed-by: Andrew Gerrand <adg@golang.org>
This commit is contained in:
Audrey Lim 2016-04-03 16:36:34 -07:00 committed by Chris Broadfoot
parent 6eef0b4fad
commit 19c2ab042a
6 changed files with 341 additions and 56 deletions

View File

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

158
cmd/present/static/notes.js Normal file
View File

@ -0,0 +1,158 @@
// 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();
};
function initNotes() {
notesWindow = window.open('', '', 'width=1000,height=700');
var w = notesWindow;
var slidesUrl = window.location.href;
var curSlide = parseInt(localStorage.getItem('destSlide'), 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);
}
// 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);
// 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('destSlide'), 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 {
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);
}
}
};

View File

@ -14,17 +14,17 @@ var curSlide;
/* classList polyfill by Eli Grey /* classList polyfill by Eli Grey
* (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */ * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */
if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) { if (typeof document !== 'undefined' && !('classList' in document.createElement('a'))) {
(function (view) { (function (view) {
var var
classListProp = "classList" classListProp = 'classList'
, protoProp = "prototype" , protoProp = 'prototype'
, elemCtrProto = (view.HTMLElement || view.Element)[protoProp] , elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
, objCtr = Object , objCtr = Object
strTrim = String[protoProp].trim || function () { strTrim = String[protoProp].trim || function () {
return this.replace(/^\s+|\s+$/g, ""); return this.replace(/^\s+|\s+$/g, '');
} }
, arrIndexOf = Array[protoProp].indexOf || function (item) { , arrIndexOf = Array[protoProp].indexOf || function (item) {
for (var i = 0, len = this.length; i < len; i++) { for (var i = 0, len = this.length; i < len; i++) {
@ -41,16 +41,16 @@ var
this.message = message; this.message = message;
} }
, checkTokenAndGetIndex = function (classList, token) { , checkTokenAndGetIndex = function (classList, token) {
if (token === "") { if (token === '') {
throw new DOMEx( throw new DOMEx(
"SYNTAX_ERR" 'SYNTAX_ERR'
, "An invalid or illegal string was specified" , 'An invalid or illegal string was specified'
); );
} }
if (/\s/.test(token)) { if (/\s/.test(token)) {
throw new DOMEx( throw new DOMEx(
"INVALID_CHARACTER_ERR" 'INVALID_CHARACTER_ERR'
, "String contains an invalid character" , 'String contains an invalid character'
); );
} }
return arrIndexOf.call(classList, token); return arrIndexOf.call(classList, token);
@ -79,18 +79,18 @@ classListProto.item = function (i) {
return this[i] || null; return this[i] || null;
}; };
classListProto.contains = function (token) { classListProto.contains = function (token) {
token += ""; token += '';
return checkTokenAndGetIndex(this, token) !== -1; return checkTokenAndGetIndex(this, token) !== -1;
}; };
classListProto.add = function (token) { classListProto.add = function (token) {
token += ""; token += '';
if (checkTokenAndGetIndex(this, token) === -1) { if (checkTokenAndGetIndex(this, token) === -1) {
this.push(token); this.push(token);
this._updateClassName(); this._updateClassName();
} }
}; };
classListProto.remove = function (token) { classListProto.remove = function (token) {
token += ""; token += '';
var index = checkTokenAndGetIndex(this, token); var index = checkTokenAndGetIndex(this, token);
if (index !== -1) { if (index !== -1) {
this.splice(index, 1); this.splice(index, 1);
@ -98,7 +98,7 @@ classListProto.remove = function (token) {
} }
}; };
classListProto.toggle = function (token) { classListProto.toggle = function (token) {
token += ""; token += '';
if (checkTokenAndGetIndex(this, token) === -1) { if (checkTokenAndGetIndex(this, token) === -1) {
this.add(token); this.add(token);
} else { } else {
@ -106,7 +106,7 @@ classListProto.toggle = function (token) {
} }
}; };
classListProto.toString = function () { classListProto.toString = function () {
return this.join(" "); return this.join(' ');
}; };
if (objCtr.defineProperty) { if (objCtr.defineProperty) {
@ -211,6 +211,8 @@ function prevSlide() {
updateSlides(); updateSlides();
} }
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
}; };
function nextSlide() { function nextSlide() {
@ -220,6 +222,8 @@ function nextSlide() {
updateSlides(); updateSlides();
} }
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
}; };
/* Slide events */ /* Slide events */
@ -395,9 +399,12 @@ function updateHash() {
function handleBodyKeyDown(event) { function handleBodyKeyDown(event) {
// If we're in a code element, only handle pgup/down. // If we're in a code element, only handle pgup/down.
var inCode = event.target.classList.contains("code"); var inCode = event.target.classList.contains('code');
switch (event.keyCode) { switch (event.keyCode) {
case 78: // 'N' opens presenter notes window
if (!inCode && notesEnabled) toggleNotesWindow();
break;
case 72: // 'H' hides the help text case 72: // 'H' hides the help text
case 27: // escape key case 27: // escape key
if (!inCode) hideHelpText(); if (!inCode) hideHelpText();
@ -481,11 +488,13 @@ function handleDomLoaded() {
setupInteraction(); setupInteraction();
if (window.location.hostname == "localhost" || window.location.hostname == "127.0.0.1" || window.location.hostname == "::1") { if (window.location.hostname == 'localhost' || window.location.hostname == '127.0.0.1' || window.location.hostname == '::1') {
hideHelpText(); hideHelpText();
} }
document.body.classList.add('loaded'); document.body.classList.add('loaded');
setupNotesSync();
}; };
function initialize() { function initialize() {
@ -521,3 +530,56 @@ if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) {
} else { } else {
initialize(); initialize();
} }
/* Synchronize windows when notes are enabled */
function setupNotesSync() {
if (!notesEnabled) return;
function setupPlayResizeSync() {
var out = document.getElementsByClassName('output');
for (let 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 (let 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('destSlide', 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('destSlide');
while (destSlide > curSlide) {
nextSlide();
}
while (destSlide < curSlide) {
prevSlide();
}
updatePlay(e);
updateNotes();
}

View File

@ -6,7 +6,18 @@
<head> <head>
<title>{{.Title}}</title> <title>{{.Title}}</title>
<meta charset='utf-8'> <meta charset='utf-8'>
<script>
var notesEnabled = {{.NotesEnabled}};
</script>
<script src='/static/slides.js'></script> <script src='/static/slides.js'></script>
{{if .NotesEnabled}}
<script>
var sections = {{.Sections}};
</script>
<script src='/static/notes.js'></script>
{{end}}
<script> <script>
// Initialize Google Analytics tracking code on production site only. // Initialize Google Analytics tracking code on production site only.
if (window["location"] && window["location"]["hostname"] == "talks.golang.org") { if (window["location"] && window["location"]["hostname"] == "talks.golang.org") {

View File

@ -3,16 +3,16 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
function initPlayground(transport) { function initPlayground(transport) {
"use strict"; 'use strict';
function text(node) { function text(node) {
var s = ""; var s = '';
for (var i = 0; i < node.childNodes.length; i++) { for (var i = 0; i < node.childNodes.length; i++) {
var n = node.childNodes[i]; var n = node.childNodes[i];
if (n.nodeType === 1) { if (n.nodeType === 1) {
if (n.tagName === "BUTTON") continue if (n.tagName === 'BUTTON') continue
if (n.tagName === "SPAN" && n.className === "number") continue; if (n.tagName === 'SPAN' && n.className === 'number') continue;
if (n.tagName === "DIV" || n.tagName == "BR") { if (n.tagName === 'DIV' || n.tagName == 'BR') {
s += "\n"; s += "\n";
} }
s += text(n); s += text(n);
@ -22,41 +22,53 @@ function initPlayground(transport) {
s += n.nodeValue; s += n.nodeValue;
} }
} }
return s.replace("\xA0", " "); // replace non-breaking spaces return s.replace('\xA0', ' '); // replace non-breaking spaces
} }
function init(code) { // 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 output = document.createElement('div');
var outpre = document.createElement('pre'); var outpre = document.createElement('pre');
var running; var running;
if ($ && $(output).resizable) { if ($ && $(output).resizable) {
$(output).resizable({ $(output).resizable({
handles: "n,w,nw", handles: 'n,w,nw',
minHeight: 27, minHeight: 27,
minWidth: 135, minWidth: 135,
maxHeight: 608, maxHeight: 608,
maxWidth: 990 maxWidth: 990
}); });
} }
function onKill() { function onKill() {
if (running) running.Kill(); if (running) running.Kill();
if (notesEnabled) updatePlayStorage('onKill', index);
} }
function onRun(e) { function onRun(e) {
onKill(); var sk = e.shiftKey || localStorage.getItem('play-shiftKey') === 'true';
output.style.display = "block"; if (running) running.Kill();
outpre.innerHTML = ""; output.style.display = 'block';
run1.style.display = "none"; outpre.innerHTML = '';
var options = {Race: e.shiftKey}; run1.style.display = 'none';
var options = {Race: sk};
running = transport.Run(text(code), PlaygroundOutput(outpre), options); running = transport.Run(text(code), PlaygroundOutput(outpre), options);
if (notesEnabled) updatePlayStorage('onRun', index, e);
} }
function onClose() { function onClose() {
onKill(); if (running) running.Kill();
output.style.display = "none"; output.style.display = 'none';
run1.style.display = "inline-block"; run1.style.display = 'inline-block';
if (notesEnabled) updatePlayStorage('onClose', index);
}
if (notesEnabled) {
playgroundHandlers.onRun.push(onRun);
playgroundHandlers.onClose.push(onClose);
playgroundHandlers.onKill.push(onKill);
} }
var run1 = document.createElement('button'); var run1 = document.createElement('button');
@ -91,13 +103,12 @@ function initPlayground(transport) {
output.classList.add('output'); output.classList.add('output');
output.appendChild(buttons); output.appendChild(buttons);
output.appendChild(outpre); output.appendChild(outpre);
output.style.display = "none"; output.style.display = 'none';
code.parentNode.insertBefore(output, button.nextSibling); code.parentNode.insertBefore(output, button.nextSibling);
} }
var play = document.querySelectorAll('div.playground'); var play = document.querySelectorAll('div.playground');
for (var i = 0; i < play.length; i++) { for (var i = 0; i < play.length; i++) {
init(play[i]); init(play[i], i);
} }
} }

View File

@ -2043,16 +2043,16 @@ perl -i -pe 'chomp if eof' package.txt
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
function initPlayground(transport) { function initPlayground(transport) {
"use strict"; 'use strict';
function text(node) { function text(node) {
var s = ""; var s = '';
for (var i = 0; i < node.childNodes.length; i++) { for (var i = 0; i < node.childNodes.length; i++) {
var n = node.childNodes[i]; var n = node.childNodes[i];
if (n.nodeType === 1) { if (n.nodeType === 1) {
if (n.tagName === "BUTTON") continue if (n.tagName === 'BUTTON') continue
if (n.tagName === "SPAN" && n.className === "number") continue; if (n.tagName === 'SPAN' && n.className === 'number') continue;
if (n.tagName === "DIV" || n.tagName == "BR") { if (n.tagName === 'DIV' || n.tagName == 'BR') {
s += "\n"; s += "\n";
} }
s += text(n); s += text(n);
@ -2062,41 +2062,53 @@ function initPlayground(transport) {
s += n.nodeValue; s += n.nodeValue;
} }
} }
return s.replace("\xA0", " "); // replace non-breaking spaces return s.replace('\xA0', ' '); // replace non-breaking spaces
} }
function init(code) { // 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 output = document.createElement('div');
var outpre = document.createElement('pre'); var outpre = document.createElement('pre');
var running; var running;
if ($ && $(output).resizable) { if ($ && $(output).resizable) {
$(output).resizable({ $(output).resizable({
handles: "n,w,nw", handles: 'n,w,nw',
minHeight: 27, minHeight: 27,
minWidth: 135, minWidth: 135,
maxHeight: 608, maxHeight: 608,
maxWidth: 990 maxWidth: 990
}); });
} }
function onKill() { function onKill() {
if (running) running.Kill(); if (running) running.Kill();
if (notesEnabled) updatePlayStorage('onKill', index);
} }
function onRun(e) { function onRun(e) {
onKill(); var sk = e.shiftKey || localStorage.getItem('play-shiftKey') === 'true';
output.style.display = "block"; if (running) running.Kill();
outpre.innerHTML = ""; output.style.display = 'block';
run1.style.display = "none"; outpre.innerHTML = '';
var options = {Race: e.shiftKey}; run1.style.display = 'none';
var options = {Race: sk};
running = transport.Run(text(code), PlaygroundOutput(outpre), options); running = transport.Run(text(code), PlaygroundOutput(outpre), options);
if (notesEnabled) updatePlayStorage('onRun', index, e);
} }
function onClose() { function onClose() {
onKill(); if (running) running.Kill();
output.style.display = "none"; output.style.display = 'none';
run1.style.display = "inline-block"; run1.style.display = 'inline-block';
if (notesEnabled) updatePlayStorage('onClose', index);
}
if (notesEnabled) {
playgroundHandlers.onRun.push(onRun);
playgroundHandlers.onClose.push(onClose);
playgroundHandlers.onKill.push(onKill);
} }
var run1 = document.createElement('button'); var run1 = document.createElement('button');
@ -2131,16 +2143,15 @@ function initPlayground(transport) {
output.classList.add('output'); output.classList.add('output');
output.appendChild(buttons); output.appendChild(buttons);
output.appendChild(outpre); output.appendChild(outpre);
output.style.display = "none"; output.style.display = 'none';
code.parentNode.insertBefore(output, button.nextSibling); code.parentNode.insertBefore(output, button.nextSibling);
} }
var play = document.querySelectorAll('div.playground'); var play = document.querySelectorAll('div.playground');
for (var i = 0; i < play.length; i++) { for (var i = 0; i < play.length; i++) {
init(play[i]); init(play[i], i);
} }
} }
`, `,
"playground.js": `// Copyright 2012 The Go Authors. All rights reserved. "playground.js": `// Copyright 2012 The Go Authors. All rights reserved.