Skip to content

Commit 15f88b3

Browse files
authored
Merge pull request #107 from google/speaker-notes
Add support for speaker notes
2 parents 28942d7 + d5359fa commit 15f88b3

File tree

7 files changed

+322
-2
lines changed

7 files changed

+322
-2
lines changed

book.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class = "bob"
1313

1414
[output.html]
1515
curly-quotes = true
16-
additional-js = ["ga4.js"]
17-
additional-css = ["svgbob.css"]
16+
additional-js = ["ga4.js", "speaker-notes.js"]
17+
additional-css = ["svgbob.css", "speaker-notes.css"]
1818
git-repository-url = "https://github.com/google/comprehensive-rust"
1919
edit-url-template = "https://github.com/google/comprehensive-rust/edit/main/{path}"
2020

speaker-notes.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.content details {
2+
background: var(--sidebar-bg);
3+
color: var(--sidebar-fg) !important;
4+
border-radius: 0.25em;
5+
padding: 0.25em;
6+
}
7+
8+
.content details summary h4 {
9+
display: inline-block;
10+
list-style: none;
11+
font-weight: normal;
12+
font-style: italic;
13+
margin: 0.5em 0.25em;
14+
cursor: pointer;
15+
}
16+
17+
.content details summary h4:target::before {
18+
margin-left: -40px;
19+
width: 40px;
20+
}
21+
22+
.content details summary a {
23+
margin-left: 0.5em;
24+
}

speaker-notes.js

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
(function() {
16+
let notes = document.querySelector("details");
17+
// Create an unattached DOM node for the code below.
18+
if (!notes) {
19+
notes = document.createElement("details");
20+
}
21+
let popIn = document.createElement("button");
22+
23+
// Mark the speaker note window defunct. This means that it will no longer
24+
// show the notes.
25+
function markDefunct() {
26+
const main = document.querySelector("main");
27+
const h4 = document.createElement("h4");
28+
h4.append("(You can close this window now.)");
29+
main.replaceChildren(h4);
30+
window.location.hash = "#speaker-notes-defunct";
31+
}
32+
33+
// Update the window. This shows/hides controls as necessary for regular and
34+
// speaker note pages.
35+
function applyState() {
36+
if (window.location.hash == "#speaker-notes-open") {
37+
if (getState() != "popup") {
38+
markDefunct();
39+
}
40+
return;
41+
}
42+
43+
switch (getState()) {
44+
case "popup":
45+
popIn.classList.remove("hidden");
46+
notes.classList.add("hidden");
47+
break;
48+
case "inline-open":
49+
popIn.classList.add("hidden");
50+
notes.open = true;
51+
notes.classList.remove("hidden");
52+
break;
53+
case "inline-closed":
54+
popIn.classList.add("hidden");
55+
notes.open = false;
56+
notes.classList.remove("hidden");
57+
break;
58+
}
59+
}
60+
61+
// Get the state of the speaker note window: "inline-open", "inline-closed",
62+
// or "popup".
63+
function getState() {
64+
return window.localStorage["speakerNotes"] || "inline-open";
65+
}
66+
67+
// Set the state of the speaker note window. Call applyState as needed
68+
// afterwards.
69+
function setState(state) {
70+
window.localStorage["speakerNotes"] = state;
71+
}
72+
73+
// Create controls for a regular page.
74+
function setupRegularPage() {
75+
// Create pop-in button.
76+
popIn.setAttribute("id", "speaker-notes-toggle");
77+
popIn.setAttribute("type", "button");
78+
popIn.setAttribute("title", "Close speaker notes");
79+
popIn.setAttribute("aria-label", "Close speaker notes");
80+
popIn.classList.add("icon-button");
81+
let i = document.createElement("i");
82+
i.classList.add("fa", "fa-window-close-o");
83+
popIn.append(i);
84+
popIn.addEventListener("click", (event) => {
85+
setState("inline-open");
86+
applyState();
87+
});
88+
document.querySelector(".left-buttons").append(popIn);
89+
90+
// Create speaker notes.
91+
notes.addEventListener("toggle", (event) => {
92+
setState(notes.open ? "inline-open" : "inline-closed");
93+
});
94+
95+
let summary = document.createElement("summary");
96+
notes.insertBefore(summary, notes.firstChild);
97+
98+
let h4 = document.createElement("h4");
99+
h4.setAttribute("id", "speaker-notes");
100+
h4.append("Speaker Notes");
101+
h4.addEventListener("click", (event) => {
102+
// Update fragment as if we had clicked a link. A regular a element would
103+
// result in double-fire of the event.
104+
window.location.hash = "#speaker-notes";
105+
});
106+
summary.append(h4);
107+
108+
// Create pop-out button.
109+
let popOutLocation = new URL(window.location.href);
110+
popOutLocation.hash = "#speaker-notes-open";
111+
let popOut = document.createElement("a");
112+
popOut.setAttribute("href", popOutLocation.href);
113+
popOut.setAttribute("target", "speakerNotes");
114+
popOut.classList.add("fa", "fa-external-link");
115+
summary.append(popOut);
116+
}
117+
118+
// Create controls for a speaker note window.
119+
function setupSpeakerNotes() {
120+
// Show the notes inline again when the window is closed.
121+
window.addEventListener("pagehide", (event) => {
122+
setState("inline-open");
123+
});
124+
125+
// Hide sidebar and buttons.
126+
document.querySelector("html").classList.remove("sidebar-visible");
127+
document.querySelector("html").classList.add("sidebar-hidden");
128+
document.querySelector(".left-buttons").classList.add("hidden");
129+
document.querySelector(".right-buttons").classList.add("hidden");
130+
131+
// Hide content except for the speaker notes and h1 elements.
132+
const main = document.querySelector("main");
133+
let children = main.childNodes;
134+
let i = 0;
135+
while (i < children.length) {
136+
const node = children[i];
137+
switch (node.tagName) {
138+
case "DETAILS":
139+
// We found the speaker notes: extract their content.
140+
let div = document.createElement("div");
141+
div.replaceChildren(...node.childNodes);
142+
node.replaceWith(div);
143+
i += 1;
144+
break;
145+
case "H1":
146+
// We found a header: turn it into a smaller header for the speaker
147+
// note window.
148+
let h4 = document.createElement("h4");
149+
let pageLocation = new URL(window.location.href);
150+
pageLocation.hash = "";
151+
let a = document.createElement("a");
152+
a.setAttribute("href", pageLocation.href);
153+
a.append(node.innerText);
154+
h4.append("Speaker Notes for ", a);
155+
node.replaceWith(h4);
156+
i += 1;
157+
break;
158+
default:
159+
// We found something else: remove it.
160+
main.removeChild(node);
161+
}
162+
}
163+
164+
// Update prev/next buttons to keep speaker note state.
165+
document.querySelectorAll('a[rel="prev"], a[rel="next"]').forEach((elem) => {
166+
elem.href += "#speaker-notes-open";
167+
});
168+
}
169+
170+
let timeout = null;
171+
// This will fire on _other_ open windows when we change window.localStorage.
172+
window.addEventListener("storage", (event) => {
173+
switch (event.key) {
174+
case "currentPage":
175+
if (getState() == "popup") {
176+
// We link all windows when we are showing speaker notes.
177+
window.location.pathname = event.newValue;
178+
}
179+
break;
180+
case "speakerNotes":
181+
// When nagigating to another page, we see two state changes in rapid
182+
// succession:
183+
//
184+
// - "popup" -> "inline-open"
185+
// - "inline-open" -> "popup"
186+
//
187+
// When the page is closed, we only see:
188+
//
189+
// - "popup" -> "inline-open"
190+
//
191+
// We can use a timeout to detect the difference. The effect is that
192+
// showing the speaker notes is delayed by 500 ms when closing the
193+
// speaker notes window.
194+
if (timeout) {
195+
clearTimeout(timeout);
196+
}
197+
timeout = setTimeout(applyState, 500);
198+
break;
199+
}
200+
});
201+
window.localStorage["currentPage"] = window.location.pathname;
202+
203+
// We encode the kind of page in the location hash:
204+
switch (window.location.hash) {
205+
case "#speaker-notes-open":
206+
// We are on a page in the speaker notes. We need to re-establish the
207+
// popup state so that the main window will hide the notes.
208+
setState("popup");
209+
setupSpeakerNotes();
210+
break;
211+
case "#speaker-notes-defunct":
212+
// We are on a page in a defunct speaker note window. We keep the state
213+
// unchanged and mark the window defunct.
214+
setupSpeakerNotes();
215+
markDefunct();
216+
break;
217+
default:
218+
// We are on a regular page. We force the state to "inline-open" if this
219+
// looks like a direct link to the speaker notes.
220+
if (window.location.hash == "#speaker-notes") {
221+
setState("inline-open");
222+
}
223+
applyState();
224+
setupRegularPage();
225+
}
226+
})();

src/hello-world.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,21 @@ What you see:
1616
* The `main` function is the entry point of the program.
1717
* Rust has hygienic macros, `println!` is an example of this.
1818
* Rust strings are UTF-8 encoded and can contain any Unicode character.
19+
20+
<details>
21+
22+
This slide tries to make the students comfortable with Rust code. They will see
23+
a ton of it over the next four days so we start small with something familiar.
24+
25+
Key points:
26+
27+
* Rust is very much like other languages in the C/C++/Java tradition. It is
28+
imperative (not functional) and it doesn't try to reinvent things unless
29+
absolutely necessary.
30+
31+
* Rust is modern with full support for things like Unicode.
32+
33+
* Rust uses macros for situations where you want to have a variable number of
34+
arguments (no function [overloading](basic-syntax/functions-interlude.md)).
35+
36+
</details>

src/hello-world/small-example.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,27 @@ fn main() { // Program entry point
1818
}
1919
```
2020

21+
<details>
22+
23+
The code implements the Collatz conjecture: it is believed that the loop will
24+
always end, but this is not yet proved. Edit the code and play with different
25+
inputs.
26+
27+
Key points:
28+
29+
* Explain that all variables are statically typed. Try removing `i32` to trigger
30+
type inference. Try with `i8` instead and trigger a runtime integer overflow.
31+
32+
* Change `let mut x` to `let x`, discuss the compiler error.
33+
34+
* Show how `print!` gives a compilation error if the arguments don't match the
35+
format string.
36+
37+
* Show how you need to use `{}` as a placeholder if you want to print an
38+
expression which is more complex than just a single variable.
39+
40+
* Show the students the standard library, show them how to search for `std::fmt`
41+
which has the rules of the formatting mini-language. It's important that the
42+
students become familiar with searching in the standard library.
43+
44+
</details>

src/welcome-day-1.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,20 @@ today:
1010
management, and garbage collection.
1111

1212
* Ownership: move semantics, copying and cloning, borrowing, and lifetimes.
13+
14+
<details>
15+
16+
The idea for the first day is to show _just enough_ of Rust to be able to speak
17+
about the famous borrow checker. The way Rust handles memory is a major feature
18+
and we should show students this right away.
19+
20+
If you're teaching this in a classroom, this is a good place to go over the
21+
schedule. We suggest splitting the day into two parts (following the slides):
22+
23+
* Morning: 9:00 to 12:00,
24+
* Afternoon: 13:00 to 16:00.
25+
26+
You can of course adjust this as necessary. Please make sure to include breaks,
27+
we recommend a break every hour!
28+
29+
</details>

src/welcome-day-1/what-is-rust.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,14 @@ Rust is a new programming language which had its 1.0 release in 2015:
1414
* mobile phones,
1515
* desktops,
1616
* servers.
17+
18+
19+
<details>
20+
21+
Rust fits in the same area as C++:
22+
23+
* High flexibility.
24+
* High level of control.
25+
* Can be scaled down to very constrained devices like mobile phones.
26+
27+
</details>

0 commit comments

Comments
 (0)