mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00: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:
parent
6eef0b4fad
commit
19c2ab042a
32
cmd/present/static/notes.css
Normal file
32
cmd/present/static/notes.css
Normal 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
158
cmd/present/static/notes.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
@ -14,17 +14,17 @@ 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"))) {
|
||||
if (typeof document !== 'undefined' && !('classList' in document.createElement('a'))) {
|
||||
|
||||
(function (view) {
|
||||
|
||||
var
|
||||
classListProp = "classList"
|
||||
, protoProp = "prototype"
|
||||
classListProp = 'classList'
|
||||
, protoProp = 'prototype'
|
||||
, elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
|
||||
, objCtr = Object
|
||||
strTrim = String[protoProp].trim || function () {
|
||||
return this.replace(/^\s+|\s+$/g, "");
|
||||
return this.replace(/^\s+|\s+$/g, '');
|
||||
}
|
||||
, arrIndexOf = Array[protoProp].indexOf || function (item) {
|
||||
for (var i = 0, len = this.length; i < len; i++) {
|
||||
@ -41,16 +41,16 @@ var
|
||||
this.message = message;
|
||||
}
|
||||
, checkTokenAndGetIndex = function (classList, token) {
|
||||
if (token === "") {
|
||||
if (token === '') {
|
||||
throw new DOMEx(
|
||||
"SYNTAX_ERR"
|
||||
, "An invalid or illegal string was specified"
|
||||
'SYNTAX_ERR'
|
||||
, 'An invalid or illegal string was specified'
|
||||
);
|
||||
}
|
||||
if (/\s/.test(token)) {
|
||||
throw new DOMEx(
|
||||
"INVALID_CHARACTER_ERR"
|
||||
, "String contains an invalid character"
|
||||
'INVALID_CHARACTER_ERR'
|
||||
, 'String contains an invalid character'
|
||||
);
|
||||
}
|
||||
return arrIndexOf.call(classList, token);
|
||||
@ -79,18 +79,18 @@ classListProto.item = function (i) {
|
||||
return this[i] || null;
|
||||
};
|
||||
classListProto.contains = function (token) {
|
||||
token += "";
|
||||
token += '';
|
||||
return checkTokenAndGetIndex(this, token) !== -1;
|
||||
};
|
||||
classListProto.add = function (token) {
|
||||
token += "";
|
||||
token += '';
|
||||
if (checkTokenAndGetIndex(this, token) === -1) {
|
||||
this.push(token);
|
||||
this._updateClassName();
|
||||
}
|
||||
};
|
||||
classListProto.remove = function (token) {
|
||||
token += "";
|
||||
token += '';
|
||||
var index = checkTokenAndGetIndex(this, token);
|
||||
if (index !== -1) {
|
||||
this.splice(index, 1);
|
||||
@ -98,7 +98,7 @@ classListProto.remove = function (token) {
|
||||
}
|
||||
};
|
||||
classListProto.toggle = function (token) {
|
||||
token += "";
|
||||
token += '';
|
||||
if (checkTokenAndGetIndex(this, token) === -1) {
|
||||
this.add(token);
|
||||
} else {
|
||||
@ -106,7 +106,7 @@ classListProto.toggle = function (token) {
|
||||
}
|
||||
};
|
||||
classListProto.toString = function () {
|
||||
return this.join(" ");
|
||||
return this.join(' ');
|
||||
};
|
||||
|
||||
if (objCtr.defineProperty) {
|
||||
@ -211,6 +211,8 @@ function prevSlide() {
|
||||
|
||||
updateSlides();
|
||||
}
|
||||
|
||||
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
|
||||
};
|
||||
|
||||
function nextSlide() {
|
||||
@ -220,6 +222,8 @@ function nextSlide() {
|
||||
|
||||
updateSlides();
|
||||
}
|
||||
|
||||
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
|
||||
};
|
||||
|
||||
/* Slide events */
|
||||
@ -395,9 +399,12 @@ function updateHash() {
|
||||
|
||||
function handleBodyKeyDown(event) {
|
||||
// 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) {
|
||||
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();
|
||||
@ -481,11 +488,13 @@ function handleDomLoaded() {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
document.body.classList.add('loaded');
|
||||
|
||||
setupNotesSync();
|
||||
};
|
||||
|
||||
function initialize() {
|
||||
@ -521,3 +530,56 @@ if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) {
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
|
@ -6,7 +6,18 @@
|
||||
<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}};
|
||||
</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") {
|
||||
|
@ -3,16 +3,16 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
function initPlayground(transport) {
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
function text(node) {
|
||||
var s = "";
|
||||
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") {
|
||||
if (n.tagName === 'BUTTON') continue
|
||||
if (n.tagName === 'SPAN' && n.className === 'number') continue;
|
||||
if (n.tagName === 'DIV' || n.tagName == 'BR') {
|
||||
s += "\n";
|
||||
}
|
||||
s += text(n);
|
||||
@ -22,41 +22,53 @@ function initPlayground(transport) {
|
||||
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 outpre = document.createElement('pre');
|
||||
var running;
|
||||
|
||||
if ($ && $(output).resizable) {
|
||||
$(output).resizable({
|
||||
handles: "n,w,nw",
|
||||
handles: 'n,w,nw',
|
||||
minHeight: 27,
|
||||
minWidth: 135,
|
||||
maxHeight: 608,
|
||||
maxHeight: 608,
|
||||
maxWidth: 990
|
||||
});
|
||||
}
|
||||
|
||||
function onKill() {
|
||||
if (running) running.Kill();
|
||||
if (notesEnabled) updatePlayStorage('onKill', index);
|
||||
}
|
||||
|
||||
function onRun(e) {
|
||||
onKill();
|
||||
output.style.display = "block";
|
||||
outpre.innerHTML = "";
|
||||
run1.style.display = "none";
|
||||
var options = {Race: e.shiftKey};
|
||||
var sk = e.shiftKey || localStorage.getItem('play-shiftKey') === 'true';
|
||||
if (running) running.Kill();
|
||||
output.style.display = 'block';
|
||||
outpre.innerHTML = '';
|
||||
run1.style.display = 'none';
|
||||
var options = {Race: sk};
|
||||
running = transport.Run(text(code), PlaygroundOutput(outpre), options);
|
||||
if (notesEnabled) updatePlayStorage('onRun', index, e);
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
onKill();
|
||||
output.style.display = "none";
|
||||
run1.style.display = "inline-block";
|
||||
if (running) running.Kill();
|
||||
output.style.display = 'none';
|
||||
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');
|
||||
@ -91,13 +103,12 @@ function initPlayground(transport) {
|
||||
output.classList.add('output');
|
||||
output.appendChild(buttons);
|
||||
output.appendChild(outpre);
|
||||
output.style.display = "none";
|
||||
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]);
|
||||
init(play[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2043,16 +2043,16 @@ perl -i -pe 'chomp if eof' package.txt
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
function initPlayground(transport) {
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
function text(node) {
|
||||
var s = "";
|
||||
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") {
|
||||
if (n.tagName === 'BUTTON') continue
|
||||
if (n.tagName === 'SPAN' && n.className === 'number') continue;
|
||||
if (n.tagName === 'DIV' || n.tagName == 'BR') {
|
||||
s += "\n";
|
||||
}
|
||||
s += text(n);
|
||||
@ -2062,41 +2062,53 @@ function initPlayground(transport) {
|
||||
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 outpre = document.createElement('pre');
|
||||
var running;
|
||||
|
||||
if ($ && $(output).resizable) {
|
||||
$(output).resizable({
|
||||
handles: "n,w,nw",
|
||||
handles: 'n,w,nw',
|
||||
minHeight: 27,
|
||||
minWidth: 135,
|
||||
maxHeight: 608,
|
||||
maxHeight: 608,
|
||||
maxWidth: 990
|
||||
});
|
||||
}
|
||||
|
||||
function onKill() {
|
||||
if (running) running.Kill();
|
||||
if (notesEnabled) updatePlayStorage('onKill', index);
|
||||
}
|
||||
|
||||
function onRun(e) {
|
||||
onKill();
|
||||
output.style.display = "block";
|
||||
outpre.innerHTML = "";
|
||||
run1.style.display = "none";
|
||||
var options = {Race: e.shiftKey};
|
||||
var sk = e.shiftKey || localStorage.getItem('play-shiftKey') === 'true';
|
||||
if (running) running.Kill();
|
||||
output.style.display = 'block';
|
||||
outpre.innerHTML = '';
|
||||
run1.style.display = 'none';
|
||||
var options = {Race: sk};
|
||||
running = transport.Run(text(code), PlaygroundOutput(outpre), options);
|
||||
if (notesEnabled) updatePlayStorage('onRun', index, e);
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
onKill();
|
||||
output.style.display = "none";
|
||||
run1.style.display = "inline-block";
|
||||
if (running) running.Kill();
|
||||
output.style.display = 'none';
|
||||
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');
|
||||
@ -2131,16 +2143,15 @@ function initPlayground(transport) {
|
||||
output.classList.add('output');
|
||||
output.appendChild(buttons);
|
||||
output.appendChild(outpre);
|
||||
output.style.display = "none";
|
||||
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]);
|
||||
init(play[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
`,
|
||||
|
||||
"playground.js": `// Copyright 2012 The Go Authors. All rights reserved.
|
||||
|
Loading…
x
Reference in New Issue
Block a user