mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-04-26 01:04:35 +02:00
Test translations using same source as for publish (#1492)
Before we would run `mdbook test` using the current Markdown sources. This is subtly wrong: we publish the course using back-dated sources, so we should therefore also run the tests using the same sources (this ensures that the code snippets actually work). After this commit, all translatable content lives in exactly two directories: - `src/` - `third_party/` We need to restore both directories when testing and when publishing. This ensures consistency in the Markdown text and in the included source code. A new `.github/workflows/build.sh` script takes care of preparing the two directories according to the date in the PO file (if any). To ensure we can restore all of `third_party/` to an old commit, the non-changing `third_party/mdbook/book.js` file has been moved to `theme/book.js`. The file is generated by `mdbook init --theme`, making it suitable for modification by the user (us). Symlinks have been added to `third_party/mdbook/` to indicate that the files ultimately came from upstream.
This commit is contained in:
parent
4cb12d0073
commit
e2c59ddbb5
2
.github/typos.toml
vendored
2
.github/typos.toml
vendored
@ -13,4 +13,4 @@ check-file = false
|
|||||||
|
|
||||||
[files]
|
[files]
|
||||||
# Typos in third party packages should be fixed upstream.
|
# Typos in third party packages should be fixed upstream.
|
||||||
extend-exclude = ["third_party/*"]
|
extend-exclude = ["third_party/*", "theme/book.js"]
|
||||||
|
37
.github/workflows/build.sh
vendored
Executable file
37
.github/workflows/build.sh
vendored
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Usage: build.sh <book-lang> <dest-dir>
|
||||||
|
#
|
||||||
|
# Build the course as of the date specified specified in the
|
||||||
|
# POT-Creation-Date header of po/$book_lang.po. The output can be
|
||||||
|
# found in $dest_dir.
|
||||||
|
#
|
||||||
|
# The src/ and third_party/ directories are left in a dirty state so
|
||||||
|
# you can run `mdbook test` and other commands afterwards.
|
||||||
|
|
||||||
|
book_lang=${1:?"Usage: $0 <book-lang> <dest-dir>"}
|
||||||
|
dest_dir=${2:?"Usage: $0 <book-lang> <dest-dir>"}
|
||||||
|
|
||||||
|
if [ "$book_lang" = "en" ]; then
|
||||||
|
echo "::group::Building English course"
|
||||||
|
else
|
||||||
|
pot_creation_date=$(grep --max-count 1 '^"POT-Creation-Date:' "po/$book_lang.po" | sed -E 's/".*: (.*)\\n"/\1/')
|
||||||
|
pot_creation_date=${pot_creation_date:-now}
|
||||||
|
echo "::group::Building $book_lang translation as of $pot_creation_date"
|
||||||
|
|
||||||
|
# Back-date the sources to POT-Creation-Date. The content lives in two
|
||||||
|
# directories:
|
||||||
|
rm -r src/ third_party/
|
||||||
|
git restore --source "$(git rev-list -n 1 --before "$pot_creation_date" @)" src/ third_party/
|
||||||
|
# Set language and adjust site URL. Clear the redirects since they are
|
||||||
|
# in sync with the source files, not the translation.
|
||||||
|
export MDBOOK_BOOK__LANGUAGE=$book_lang
|
||||||
|
export MDBOOK_OUTPUT__HTML__SITE_URL=/comprehensive-rust/$book_lang/
|
||||||
|
export MDBOOK_OUTPUT__HTML__REDIRECT='{}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
mdbook build -d "$dest_dir"
|
||||||
|
(cd "$dest_dir/exerciser" && zip --recurse-paths ../html/comprehensive-rust-exercises.zip comprehensive-rust-exercises/)
|
||||||
|
|
||||||
|
echo "::endgroup::"
|
20
.github/workflows/build.yml
vendored
20
.github/workflows/build.yml
vendored
@ -114,11 +114,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
language: ${{ fromJSON(needs.find-languages.outputs.languages) }}
|
language: ${{ fromJSON(needs.find-languages.outputs.languages) }}
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
env:
|
|
||||||
MDBOOK_BOOK__LANGUAGE: ${{ matrix.language }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # We need the full history for build.sh below.
|
||||||
|
|
||||||
- name: Setup Rust cache
|
- name: Setup Rust cache
|
||||||
uses: ./.github/workflows/setup-rust-cache
|
uses: ./.github/workflows/setup-rust-cache
|
||||||
@ -141,23 +141,9 @@ jobs:
|
|||||||
MDBOOK_OUTPUT='{"xgettext": {"pot-file": "messages.pot"}}' mdbook build -d po
|
MDBOOK_OUTPUT='{"xgettext": {"pot-file": "messages.pot"}}' mdbook build -d po
|
||||||
msgfmt -o /dev/null --statistics po/messages.pot
|
msgfmt -o /dev/null --statistics po/messages.pot
|
||||||
|
|
||||||
# Build the translation without redirects just like publish.yml does.
|
|
||||||
- name: Build ${{ matrix.language }} translation
|
- name: Build ${{ matrix.language }} translation
|
||||||
if: matrix.language != 'en'
|
|
||||||
run: MDBOOK_OUTPUT__HTML__REDIRECT='{}' mdbook build
|
|
||||||
|
|
||||||
- name: Build course
|
|
||||||
if: matrix.language == 'en'
|
|
||||||
run: mdbook build
|
|
||||||
|
|
||||||
- name: Zip exercise templates
|
|
||||||
run: zip --recurse-paths ../html/comprehensive-rust-exercises.zip comprehensive-rust-exercises/
|
|
||||||
working-directory: book/exerciser
|
|
||||||
|
|
||||||
- name: Prepare book for upload
|
|
||||||
run: |
|
run: |
|
||||||
mv book/html book/comprehensive-rust-${{ matrix.language }}
|
.github/workflows/build.sh ${{ matrix.language }} book/comprehensive-rust-${{ matrix.language }}
|
||||||
rm -r book/exerciser
|
|
||||||
|
|
||||||
# Upload the book now to retain it in case mdbook test fails.
|
# Upload the book now to retain it in case mdbook test fails.
|
||||||
- name: Upload book
|
- name: Upload book
|
||||||
|
26
.github/workflows/publish.yml
vendored
26
.github/workflows/publish.yml
vendored
@ -31,8 +31,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
# We need the full history below.
|
fetch-depth: 0 # We need the full history for build.sh below.
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Rust cache
|
- name: Setup Rust cache
|
||||||
uses: ./.github/workflows/setup-rust-cache
|
uses: ./.github/workflows/setup-rust-cache
|
||||||
@ -41,32 +40,13 @@ jobs:
|
|||||||
uses: ./.github/workflows/install-mdbook
|
uses: ./.github/workflows/install-mdbook
|
||||||
|
|
||||||
- name: Build course in English
|
- name: Build course in English
|
||||||
run: mdbook build -d book
|
run: .github/workflows/build.sh en book
|
||||||
|
|
||||||
- name: Zip exercise templates
|
|
||||||
run: cd book/exerciser && zip --recurse-paths ../html/comprehensive-rust-exercises.zip comprehensive-rust-exercises/
|
|
||||||
|
|
||||||
- name: Build all translations
|
- name: Build all translations
|
||||||
run: |
|
run: |
|
||||||
for po_lang in ${{ env.LANGUAGES }}; do
|
for po_lang in ${{ env.LANGUAGES }}; do
|
||||||
POT_CREATION_DATE=$(grep --max-count 1 '^"POT-Creation-Date:' po/$po_lang.po | sed -E 's/".*: (.*)\\n"/\1/')
|
.github/workflows/build.sh $po_lang book/$po_lang
|
||||||
if [[ $POT_CREATION_DATE == "" ]]; then
|
|
||||||
POT_CREATION_DATE=now
|
|
||||||
fi
|
|
||||||
echo "::group::Building $po_lang translation as of $POT_CREATION_DATE"
|
|
||||||
rm -r src/
|
|
||||||
git restore --source "$(git rev-list -n 1 --before "$POT_CREATION_DATE" @)" src/
|
|
||||||
|
|
||||||
# Set language and adjust site URL. Clear the redirects
|
|
||||||
# since they are in sync with the source files, not the
|
|
||||||
# translation.
|
|
||||||
MDBOOK_BOOK__LANGUAGE=$po_lang \
|
|
||||||
MDBOOK_OUTPUT__HTML__SITE_URL=/comprehensive-rust/$po_lang/ \
|
|
||||||
MDBOOK_OUTPUT__HTML__REDIRECT='{}' \
|
|
||||||
mdbook build -d book/$po_lang
|
|
||||||
(cd book/$po_lang/exerciser && zip --recurse-paths ../html/comprehensive-rust-exercises.zip comprehensive-rust-exercises/)
|
|
||||||
mv book/$po_lang/html book/html/$po_lang
|
mv book/$po_lang/html book/html/$po_lang
|
||||||
echo "::endgroup::"
|
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Setup Pages
|
- name: Setup Pages
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"excludes": [
|
"excludes": [
|
||||||
"/book/",
|
"/book/",
|
||||||
"/theme/*.hbs",
|
"/theme/*.hbs",
|
||||||
|
"/theme/book.js",
|
||||||
"/third_party/",
|
"/third_party/",
|
||||||
"target/"
|
"target/"
|
||||||
],
|
],
|
||||||
|
82
po/ja.po
82
po/ja.po
@ -7633,36 +7633,6 @@ msgstr ""
|
|||||||
#: src/traits.md:27 src/traits/trait-objects.md:24
|
#: src/traits.md:27 src/traits/trait-objects.md:24
|
||||||
msgid "\"Fido\""
|
msgid "\"Fido\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"```rust,editable\n"
|
|
||||||
"struct Dog { name: String, age: i8 }\n"
|
|
||||||
"struct Cat { lives: i8 } // 猫に名前は必要ありません。どのみち猫は名前に反応"
|
|
||||||
"しないからです。\n"
|
|
||||||
"\n"
|
|
||||||
"trait Pet {\n"
|
|
||||||
" fn talk(&self) -> String;\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"impl Pet for Dog {\n"
|
|
||||||
" fn talk(&self) -> String { format!(\"Woof, my name is {}!\", self."
|
|
||||||
"name) }\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"impl Pet for Cat {\n"
|
|
||||||
" fn talk(&self) -> String { String::from(\"Miau!\") }\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"fn greet<P: Pet>(pet: &P) {\n"
|
|
||||||
" println!(\"Oh you're a cutie! What's your name? {}\", pet.talk());\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"fn main() {\n"
|
|
||||||
" let captain_floof = Cat { lives: 9 };\n"
|
|
||||||
" let fido = Dog { name: String::from(\"Fido\"), age: 5 };\n"
|
|
||||||
"\n"
|
|
||||||
" greet(&captain_floof);\n"
|
|
||||||
" greet(&fido);\n"
|
|
||||||
"}\n"
|
|
||||||
"```"
|
|
||||||
|
|
||||||
#: src/traits/trait-objects.md:3
|
#: src/traits/trait-objects.md:3
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -7675,34 +7645,6 @@ msgstr ""
|
|||||||
#: src/traits/trait-objects.md:27
|
#: src/traits/trait-objects.md:27
|
||||||
msgid "\"Hello, who are you? {}\""
|
msgid "\"Hello, who are you? {}\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"```rust,editable\n"
|
|
||||||
"struct Dog { name: String, age: i8 }\n"
|
|
||||||
"struct Cat { lives: i8 } // 猫に名前は必要ありません。どのみち猫は名前に反応"
|
|
||||||
"しないからです。\n"
|
|
||||||
"\n"
|
|
||||||
"trait Pet {\n"
|
|
||||||
" fn talk(&self) -> String;\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"impl Pet for Dog {\n"
|
|
||||||
" fn talk(&self) -> String { format!(\"Woof, my name is {}!\", self."
|
|
||||||
"name) }\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"impl Pet for Cat {\n"
|
|
||||||
" fn talk(&self) -> String { String::from(\"Miau!\") }\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"fn main() {\n"
|
|
||||||
" let pets: Vec<Box<dyn Pet>> = vec![\n"
|
|
||||||
" Box::new(Cat { lives: 9 }),\n"
|
|
||||||
" Box::new(Dog { name: String::from(\"Fido\"), age: 5 }),\n"
|
|
||||||
" ];\n"
|
|
||||||
" for pet in pets {\n"
|
|
||||||
" println!(\"Hello, who are you? {}\", pet.talk());\n"
|
|
||||||
" }\n"
|
|
||||||
"}\n"
|
|
||||||
"```"
|
|
||||||
|
|
||||||
#: src/traits/trait-objects.md:32
|
#: src/traits/trait-objects.md:32
|
||||||
msgid "Memory layout after allocating `pets`:"
|
msgid "Memory layout after allocating `pets`:"
|
||||||
@ -7940,30 +7882,6 @@ msgstr ""
|
|||||||
#: src/traits/trait-bounds.md:29
|
#: src/traits/trait-bounds.md:29
|
||||||
msgid "\"{many_more}\""
|
msgid "\"{many_more}\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"```rust,editable\n"
|
|
||||||
"fn duplicate<T: Clone>(a: T) -> (T, T) {\n"
|
|
||||||
" (a.clone(), a.clone())\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"// 以下の糖衣構文です:\n"
|
|
||||||
"// fn add_42_millions<T: Into<i32>>(x: T) -> i32 {\n"
|
|
||||||
"fn add_42_millions(x: impl Into<i32>) -> i32 {\n"
|
|
||||||
" x.into() + 42_000_000\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"// struct NotClonable;\n"
|
|
||||||
"\n"
|
|
||||||
"fn main() {\n"
|
|
||||||
" let foo = String::from(\"foo\");\n"
|
|
||||||
" let pair = duplicate(foo);\n"
|
|
||||||
" println!(\"{pair:?}\");\n"
|
|
||||||
"\n"
|
|
||||||
" let many = add_42_millions(42_i8);\n"
|
|
||||||
" println!(\"{many}\");\n"
|
|
||||||
" let many_more = add_42_millions(10_000_000);\n"
|
|
||||||
" println!(\"{many_more}\");\n"
|
|
||||||
"}\n"
|
|
||||||
"```"
|
|
||||||
|
|
||||||
#: src/traits/trait-bounds.md:35
|
#: src/traits/trait-bounds.md:35
|
||||||
msgid "Show a `where` clause, students will encounter it when reading code."
|
msgid "Show a `where` clause, students will encounter it when reading code."
|
||||||
|
@ -3678,7 +3678,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/exercises/day-1/for-loops.md:14
|
#: src/exercises/day-1/for-loops.md:14
|
||||||
msgid "\"array: {array:?}\""
|
msgid "\"array: {array:?}\""
|
||||||
msgstr "\"matriz: {matriz:?}\""
|
msgstr "\"matriz: {array:?}\""
|
||||||
|
|
||||||
#: src/exercises/day-1/for-loops.md:18
|
#: src/exercises/day-1/for-loops.md:18
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -3949,7 +3949,7 @@ msgstr "\"Olá\""
|
|||||||
|
|
||||||
#: src/basic-syntax/static-and-const.md:25
|
#: src/basic-syntax/static-and-const.md:25
|
||||||
msgid "\"Digest: {digest:?}\""
|
msgid "\"Digest: {digest:?}\""
|
||||||
msgstr "\"Resumo: {resumo:?}\""
|
msgstr "\"Resumo: {digest:?}\""
|
||||||
|
|
||||||
#: src/basic-syntax/static-and-const.md:29
|
#: src/basic-syntax/static-and-const.md:29
|
||||||
msgid ""
|
msgid ""
|
||||||
|
11
src/README.md
Normal file
11
src/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Course Content
|
||||||
|
|
||||||
|
The files in this directory make up the content of the course. The files here
|
||||||
|
can include third-party content from `../third_party/` as well.
|
||||||
|
|
||||||
|
When we publish a translation of the course, we `git restore` the `src/` and
|
||||||
|
`third_party/` directories at the repository root back to the date listed in the
|
||||||
|
POT-Creation-Date header of the translation. **It is crucial, that all
|
||||||
|
translatable content lives in those two directories.** The other files (such as
|
||||||
|
`book.toml` and `theme/`) are not restored and we always use the latest version
|
||||||
|
of them.
|
@ -1 +0,0 @@
|
|||||||
../third_party/mdbook/book.js
|
|
724
theme/book.js
Normal file
724
theme/book.js
Normal file
@ -0,0 +1,724 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Fix back button cache problem
|
||||||
|
window.onunload = function () { };
|
||||||
|
|
||||||
|
function isPlaygroundModified(playground) {
|
||||||
|
let code_block = playground.querySelector("code");
|
||||||
|
if (window.ace && code_block.classList.contains("editable")) {
|
||||||
|
let editor = window.ace.edit(code_block);
|
||||||
|
return editor.getValue() != editor.originalCode;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global variable, shared between modules
|
||||||
|
function playground_text(playground, hidden = true) {
|
||||||
|
let code_block = playground.querySelector("code");
|
||||||
|
|
||||||
|
if (window.ace && code_block.classList.contains("editable")) {
|
||||||
|
let editor = window.ace.edit(code_block);
|
||||||
|
return editor.getValue();
|
||||||
|
} else if (hidden) {
|
||||||
|
return code_block.textContent;
|
||||||
|
} else {
|
||||||
|
return code_block.innerText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(function codeSnippets() {
|
||||||
|
function fetch_with_timeout(url, options, timeout = 15000) {
|
||||||
|
return Promise.race([
|
||||||
|
fetch(url, options),
|
||||||
|
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var playgrounds = Array.from(document.querySelectorAll(".playground"));
|
||||||
|
if (playgrounds.length > 0) {
|
||||||
|
fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': "application/json",
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
mode: 'cors',
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => {
|
||||||
|
// get list of crates available in the rust playground
|
||||||
|
let playground_crates = response.crates.map(item => item["id"]);
|
||||||
|
playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_crate_list_update(playground_block, playground_crates) {
|
||||||
|
// update the play buttons after receiving the response
|
||||||
|
update_play_button(playground_block, playground_crates);
|
||||||
|
|
||||||
|
// and install on change listener to dynamically update ACE editors
|
||||||
|
if (window.ace) {
|
||||||
|
let code_block = playground_block.querySelector("code");
|
||||||
|
if (code_block.classList.contains("editable")) {
|
||||||
|
let editor = window.ace.edit(code_block);
|
||||||
|
editor.addEventListener("change", function (e) {
|
||||||
|
update_play_button(playground_block, playground_crates);
|
||||||
|
});
|
||||||
|
// add Ctrl-Enter command to execute rust code
|
||||||
|
editor.commands.addCommand({
|
||||||
|
name: "run",
|
||||||
|
bindKey: {
|
||||||
|
win: "Ctrl-Enter",
|
||||||
|
mac: "Ctrl-Enter"
|
||||||
|
},
|
||||||
|
exec: _editor => run_rust_code(playground_block)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updates the visibility of play button based on `no_run` class and
|
||||||
|
// used crates vs ones available on https://play.rust-lang.org
|
||||||
|
function update_play_button(pre_block, playground_crates) {
|
||||||
|
var play_button = pre_block.querySelector(".play-button");
|
||||||
|
|
||||||
|
// skip if code is `no_run`
|
||||||
|
if (pre_block.querySelector('code').classList.contains("no_run")) {
|
||||||
|
play_button.classList.add("hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get list of `extern crate`'s from snippet
|
||||||
|
var txt = playground_text(pre_block);
|
||||||
|
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
|
||||||
|
var snippet_crates = [];
|
||||||
|
var item;
|
||||||
|
while (item = re.exec(txt)) {
|
||||||
|
snippet_crates.push(item[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if all used crates are available on play.rust-lang.org
|
||||||
|
var all_available = snippet_crates.every(function (elem) {
|
||||||
|
return playground_crates.indexOf(elem) > -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (all_available) {
|
||||||
|
play_button.classList.remove("hidden");
|
||||||
|
} else {
|
||||||
|
play_button.classList.add("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_rust_code(code_block) {
|
||||||
|
var result_block = code_block.querySelector(".result");
|
||||||
|
if (!result_block) {
|
||||||
|
result_block = document.createElement('code');
|
||||||
|
result_block.className = 'result hljs language-bash';
|
||||||
|
|
||||||
|
code_block.append(result_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = playground_text(code_block);
|
||||||
|
let classes = code_block.querySelector('code').classList;
|
||||||
|
let edition = "2015";
|
||||||
|
if(classes.contains("edition2018")) {
|
||||||
|
edition = "2018";
|
||||||
|
} else if(classes.contains("edition2021")) {
|
||||||
|
edition = "2021";
|
||||||
|
}
|
||||||
|
var params = {
|
||||||
|
version: "stable",
|
||||||
|
optimize: "0",
|
||||||
|
code: text,
|
||||||
|
edition: edition
|
||||||
|
};
|
||||||
|
|
||||||
|
if (text.indexOf("#![feature") !== -1) {
|
||||||
|
params.version = "nightly";
|
||||||
|
}
|
||||||
|
|
||||||
|
result_block.innerText = "Running...";
|
||||||
|
|
||||||
|
const playgroundModified = isPlaygroundModified(code_block);
|
||||||
|
const startTime = window.performance.now();
|
||||||
|
fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': "application/json",
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
mode: 'cors',
|
||||||
|
body: JSON.stringify(params)
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => {
|
||||||
|
const endTime = window.performance.now();
|
||||||
|
gtag("event", "playground", {
|
||||||
|
"modified": playgroundModified,
|
||||||
|
"error": (response.error == null) ? null : 'compilation_error',
|
||||||
|
"latency": (endTime - startTime) / 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.result.trim() === '') {
|
||||||
|
result_block.innerText = "No output";
|
||||||
|
result_block.classList.add("result-no-output");
|
||||||
|
} else {
|
||||||
|
result_block.innerText = response.result;
|
||||||
|
result_block.classList.remove("result-no-output");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
const endTime = window.performance.now();
|
||||||
|
gtag("event", "playground", {
|
||||||
|
"modified": playgroundModified,
|
||||||
|
"error": error.message,
|
||||||
|
"latency": (endTime - startTime) / 1000,
|
||||||
|
});
|
||||||
|
result_block.innerText = "Playground Communication: " + error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntax highlighting Configuration
|
||||||
|
hljs.configure({
|
||||||
|
tabReplace: ' ', // 4 spaces
|
||||||
|
languages: [], // Languages used for auto-detection
|
||||||
|
});
|
||||||
|
|
||||||
|
let code_nodes = Array
|
||||||
|
.from(document.querySelectorAll('code'))
|
||||||
|
// Don't highlight `inline code` blocks in headers.
|
||||||
|
.filter(function (node) {return !node.parentElement.classList.contains("header"); });
|
||||||
|
|
||||||
|
if (window.ace) {
|
||||||
|
// language-rust class needs to be removed for editable
|
||||||
|
// blocks or highlightjs will capture events
|
||||||
|
code_nodes
|
||||||
|
.filter(function (node) {return node.classList.contains("editable"); })
|
||||||
|
.forEach(function (block) { block.classList.remove('language-rust'); });
|
||||||
|
|
||||||
|
code_nodes
|
||||||
|
.filter(function (node) {return !node.classList.contains("editable"); })
|
||||||
|
.forEach(function (block) { hljs.highlightBlock(block); });
|
||||||
|
} else {
|
||||||
|
code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding the hljs class gives code blocks the color css
|
||||||
|
// even if highlighting doesn't apply
|
||||||
|
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
|
||||||
|
|
||||||
|
Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) {
|
||||||
|
|
||||||
|
var lines = Array.from(block.querySelectorAll('.boring'));
|
||||||
|
// If no lines were hidden, return
|
||||||
|
if (!lines.length) { return; }
|
||||||
|
block.classList.add("hide-boring");
|
||||||
|
|
||||||
|
var buttons = document.createElement('div');
|
||||||
|
buttons.className = 'buttons';
|
||||||
|
buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
|
||||||
|
|
||||||
|
// add expand button
|
||||||
|
var pre_block = block.parentNode;
|
||||||
|
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||||
|
|
||||||
|
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
|
||||||
|
if (e.target.classList.contains('fa-eye')) {
|
||||||
|
e.target.classList.remove('fa-eye');
|
||||||
|
e.target.classList.add('fa-eye-slash');
|
||||||
|
e.target.title = 'Hide lines';
|
||||||
|
e.target.setAttribute('aria-label', e.target.title);
|
||||||
|
|
||||||
|
block.classList.remove('hide-boring');
|
||||||
|
} else if (e.target.classList.contains('fa-eye-slash')) {
|
||||||
|
e.target.classList.remove('fa-eye-slash');
|
||||||
|
e.target.classList.add('fa-eye');
|
||||||
|
e.target.title = 'Show hidden lines';
|
||||||
|
e.target.setAttribute('aria-label', e.target.title);
|
||||||
|
|
||||||
|
block.classList.add('hide-boring');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (window.playground_copyable) {
|
||||||
|
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
|
||||||
|
var pre_block = block.parentNode;
|
||||||
|
if (!pre_block.classList.contains('playground')) {
|
||||||
|
var buttons = pre_block.querySelector(".buttons");
|
||||||
|
if (!buttons) {
|
||||||
|
buttons = document.createElement('div');
|
||||||
|
buttons.className = 'buttons';
|
||||||
|
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
var clipButton = document.createElement('button');
|
||||||
|
clipButton.className = 'fa fa-copy clip-button';
|
||||||
|
clipButton.title = 'Copy to clipboard';
|
||||||
|
clipButton.setAttribute('aria-label', clipButton.title);
|
||||||
|
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
|
||||||
|
|
||||||
|
buttons.insertBefore(clipButton, buttons.firstChild);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process playground code blocks
|
||||||
|
Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
|
||||||
|
// Add play button
|
||||||
|
var buttons = pre_block.querySelector(".buttons");
|
||||||
|
if (!buttons) {
|
||||||
|
buttons = document.createElement('div');
|
||||||
|
buttons.className = 'buttons';
|
||||||
|
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
var runCodeButton = document.createElement('button');
|
||||||
|
runCodeButton.className = 'fa fa-play play-button';
|
||||||
|
runCodeButton.hidden = true;
|
||||||
|
runCodeButton.title = 'Run this code';
|
||||||
|
runCodeButton.setAttribute('aria-label', runCodeButton.title);
|
||||||
|
|
||||||
|
buttons.insertBefore(runCodeButton, buttons.firstChild);
|
||||||
|
runCodeButton.addEventListener('click', function (e) {
|
||||||
|
run_rust_code(pre_block);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (window.playground_copyable) {
|
||||||
|
var copyCodeClipboardButton = document.createElement('button');
|
||||||
|
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
|
||||||
|
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
|
||||||
|
copyCodeClipboardButton.title = 'Copy to clipboard';
|
||||||
|
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
|
||||||
|
|
||||||
|
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
let code_block = pre_block.querySelector("code");
|
||||||
|
if (window.ace && code_block.classList.contains("editable")) {
|
||||||
|
var undoChangesButton = document.createElement('button');
|
||||||
|
undoChangesButton.className = 'fa fa-history reset-button';
|
||||||
|
undoChangesButton.title = 'Undo changes';
|
||||||
|
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
|
||||||
|
|
||||||
|
buttons.insertBefore(undoChangesButton, buttons.firstChild);
|
||||||
|
|
||||||
|
undoChangesButton.addEventListener('click', function () {
|
||||||
|
let editor = window.ace.edit(code_block);
|
||||||
|
editor.setValue(editor.originalCode);
|
||||||
|
editor.clearSelection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function themes() {
|
||||||
|
var html = document.querySelector('html');
|
||||||
|
var themeToggleButton = document.getElementById('theme-toggle');
|
||||||
|
var themePopup = document.getElementById('theme-list');
|
||||||
|
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
|
||||||
|
var stylesheets = {
|
||||||
|
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
|
||||||
|
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
|
||||||
|
highlight: document.querySelector("[href$='highlight.css']"),
|
||||||
|
};
|
||||||
|
|
||||||
|
function showThemes() {
|
||||||
|
themePopup.style.display = 'block';
|
||||||
|
themeToggleButton.setAttribute('aria-expanded', true);
|
||||||
|
themePopup.querySelector("button#" + get_theme()).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateThemeSelected() {
|
||||||
|
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
|
||||||
|
el.classList.remove('theme-selected');
|
||||||
|
});
|
||||||
|
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideThemes() {
|
||||||
|
themePopup.style.display = 'none';
|
||||||
|
themeToggleButton.setAttribute('aria-expanded', false);
|
||||||
|
themeToggleButton.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_theme() {
|
||||||
|
var theme;
|
||||||
|
try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
|
||||||
|
if (theme === null || theme === undefined) {
|
||||||
|
return default_theme;
|
||||||
|
} else {
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_theme(theme, store = true) {
|
||||||
|
let ace_theme;
|
||||||
|
|
||||||
|
if (theme == 'coal' || theme == 'navy') {
|
||||||
|
stylesheets.ayuHighlight.disabled = true;
|
||||||
|
stylesheets.tomorrowNight.disabled = false;
|
||||||
|
stylesheets.highlight.disabled = true;
|
||||||
|
|
||||||
|
ace_theme = "ace/theme/tomorrow_night";
|
||||||
|
} else if (theme == 'ayu') {
|
||||||
|
stylesheets.ayuHighlight.disabled = false;
|
||||||
|
stylesheets.tomorrowNight.disabled = true;
|
||||||
|
stylesheets.highlight.disabled = true;
|
||||||
|
ace_theme = "ace/theme/tomorrow_night";
|
||||||
|
} else {
|
||||||
|
stylesheets.ayuHighlight.disabled = true;
|
||||||
|
stylesheets.tomorrowNight.disabled = true;
|
||||||
|
stylesheets.highlight.disabled = false;
|
||||||
|
ace_theme = "ace/theme/dawn";
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor;
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
if (window.ace && window.editors) {
|
||||||
|
window.editors.forEach(function (editor) {
|
||||||
|
editor.setTheme(ace_theme);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousTheme = get_theme();
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
html.classList.remove(previousTheme);
|
||||||
|
html.classList.add(theme);
|
||||||
|
updateThemeSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set theme
|
||||||
|
var theme = get_theme();
|
||||||
|
|
||||||
|
set_theme(theme, false);
|
||||||
|
|
||||||
|
themeToggleButton.addEventListener('click', function () {
|
||||||
|
if (themePopup.style.display === 'block') {
|
||||||
|
hideThemes();
|
||||||
|
} else {
|
||||||
|
showThemes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
themePopup.addEventListener('click', function (e) {
|
||||||
|
var theme;
|
||||||
|
if (e.target.className === "theme") {
|
||||||
|
theme = e.target.id;
|
||||||
|
} else if (e.target.parentElement.className === "theme") {
|
||||||
|
theme = e.target.parentElement.id;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set_theme(theme);
|
||||||
|
});
|
||||||
|
|
||||||
|
themePopup.addEventListener('focusout', function(e) {
|
||||||
|
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
|
||||||
|
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
|
||||||
|
hideThemes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
|
||||||
|
hideThemes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function (e) {
|
||||||
|
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||||
|
if (!themePopup.contains(e.target)) { return; }
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case 'Escape':
|
||||||
|
e.preventDefault();
|
||||||
|
hideThemes();
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
e.preventDefault();
|
||||||
|
var li = document.activeElement.parentElement;
|
||||||
|
if (li && li.previousElementSibling) {
|
||||||
|
li.previousElementSibling.querySelector('button').focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
e.preventDefault();
|
||||||
|
var li = document.activeElement.parentElement;
|
||||||
|
if (li && li.nextElementSibling) {
|
||||||
|
li.nextElementSibling.querySelector('button').focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Home':
|
||||||
|
e.preventDefault();
|
||||||
|
themePopup.querySelector('li:first-child button').focus();
|
||||||
|
break;
|
||||||
|
case 'End':
|
||||||
|
e.preventDefault();
|
||||||
|
themePopup.querySelector('li:last-child button').focus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function sidebar() {
|
||||||
|
var body = document.querySelector("body");
|
||||||
|
var sidebar = document.getElementById("sidebar");
|
||||||
|
var sidebarLinks = document.querySelectorAll('#sidebar a');
|
||||||
|
var sidebarToggleButton = document.getElementById("sidebar-toggle");
|
||||||
|
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
|
||||||
|
var firstContact = null;
|
||||||
|
|
||||||
|
function showSidebar() {
|
||||||
|
body.classList.remove('sidebar-hidden')
|
||||||
|
body.classList.add('sidebar-visible');
|
||||||
|
Array.from(sidebarLinks).forEach(function (link) {
|
||||||
|
link.setAttribute('tabIndex', 0);
|
||||||
|
});
|
||||||
|
sidebarToggleButton.setAttribute('aria-expanded', true);
|
||||||
|
sidebar.setAttribute('aria-hidden', false);
|
||||||
|
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
|
||||||
|
|
||||||
|
function toggleSection(ev) {
|
||||||
|
ev.currentTarget.parentElement.classList.toggle('expanded');
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(sidebarAnchorToggles).forEach(function (el) {
|
||||||
|
el.addEventListener('click', toggleSection);
|
||||||
|
});
|
||||||
|
|
||||||
|
function hideSidebar() {
|
||||||
|
body.classList.remove('sidebar-visible')
|
||||||
|
body.classList.add('sidebar-hidden');
|
||||||
|
Array.from(sidebarLinks).forEach(function (link) {
|
||||||
|
link.setAttribute('tabIndex', -1);
|
||||||
|
});
|
||||||
|
sidebarToggleButton.setAttribute('aria-expanded', false);
|
||||||
|
sidebar.setAttribute('aria-hidden', true);
|
||||||
|
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle sidebar
|
||||||
|
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
|
||||||
|
if (body.classList.contains("sidebar-hidden")) {
|
||||||
|
var current_width = parseInt(
|
||||||
|
document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
|
||||||
|
if (current_width < 150) {
|
||||||
|
document.documentElement.style.setProperty('--sidebar-width', '150px');
|
||||||
|
}
|
||||||
|
showSidebar();
|
||||||
|
} else if (body.classList.contains("sidebar-visible")) {
|
||||||
|
hideSidebar();
|
||||||
|
} else {
|
||||||
|
if (getComputedStyle(sidebar)['transform'] === 'none') {
|
||||||
|
hideSidebar();
|
||||||
|
} else {
|
||||||
|
showSidebar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sidebarResizeHandle.addEventListener('mousedown', initResize, false);
|
||||||
|
|
||||||
|
function initResize(e) {
|
||||||
|
window.addEventListener('mousemove', resize, false);
|
||||||
|
window.addEventListener('mouseup', stopResize, false);
|
||||||
|
body.classList.add('sidebar-resizing');
|
||||||
|
}
|
||||||
|
function resize(e) {
|
||||||
|
var pos = (e.clientX - sidebar.offsetLeft);
|
||||||
|
if (pos < 20) {
|
||||||
|
hideSidebar();
|
||||||
|
} else {
|
||||||
|
if (body.classList.contains("sidebar-hidden")) {
|
||||||
|
showSidebar();
|
||||||
|
}
|
||||||
|
pos = Math.min(pos, window.innerWidth - 100);
|
||||||
|
document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//on mouseup remove windows functions mousemove & mouseup
|
||||||
|
function stopResize(e) {
|
||||||
|
body.classList.remove('sidebar-resizing');
|
||||||
|
window.removeEventListener('mousemove', resize, false);
|
||||||
|
window.removeEventListener('mouseup', stopResize, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('touchstart', function (e) {
|
||||||
|
firstContact = {
|
||||||
|
x: e.touches[0].clientX,
|
||||||
|
time: Date.now()
|
||||||
|
};
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
document.addEventListener('touchmove', function (e) {
|
||||||
|
if (!firstContact)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var curX = e.touches[0].clientX;
|
||||||
|
var xDiff = curX - firstContact.x,
|
||||||
|
tDiff = Date.now() - firstContact.time;
|
||||||
|
|
||||||
|
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
|
||||||
|
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
|
||||||
|
showSidebar();
|
||||||
|
else if (xDiff < 0 && curX < 300)
|
||||||
|
hideSidebar();
|
||||||
|
|
||||||
|
firstContact = null;
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function chapterNavigation() {
|
||||||
|
document.addEventListener('keydown', function (e) {
|
||||||
|
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||||
|
if (window.search && window.search.hasFocus()) { return; }
|
||||||
|
var html = document.querySelector('html');
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
var nextButton = document.querySelector('.nav-chapters.next');
|
||||||
|
if (nextButton) {
|
||||||
|
window.location.href = nextButton.href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function prev() {
|
||||||
|
var previousButton = document.querySelector('.nav-chapters.previous');
|
||||||
|
if (previousButton) {
|
||||||
|
window.location.href = previousButton.href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (e.key) {
|
||||||
|
case 'ArrowRight':
|
||||||
|
e.preventDefault();
|
||||||
|
if (html.dir == 'rtl') {
|
||||||
|
prev();
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
e.preventDefault();
|
||||||
|
if (html.dir == 'rtl') {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
prev();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function clipboard() {
|
||||||
|
var clipButtons = document.querySelectorAll('.clip-button');
|
||||||
|
|
||||||
|
function hideTooltip(elem) {
|
||||||
|
elem.firstChild.innerText = "";
|
||||||
|
elem.className = 'fa fa-copy clip-button';
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTooltip(elem, msg) {
|
||||||
|
elem.firstChild.innerText = msg;
|
||||||
|
elem.className = 'fa fa-copy tooltipped';
|
||||||
|
}
|
||||||
|
|
||||||
|
var clipboardSnippets = new ClipboardJS('.clip-button', {
|
||||||
|
text: function (trigger) {
|
||||||
|
hideTooltip(trigger);
|
||||||
|
let playground = trigger.closest("pre");
|
||||||
|
return playground_text(playground, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Array.from(clipButtons).forEach(function (clipButton) {
|
||||||
|
clipButton.addEventListener('mouseout', function (e) {
|
||||||
|
hideTooltip(e.currentTarget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
clipboardSnippets.on('success', function (e) {
|
||||||
|
e.clearSelection();
|
||||||
|
showTooltip(e.trigger, "Copied!");
|
||||||
|
});
|
||||||
|
|
||||||
|
clipboardSnippets.on('error', function (e) {
|
||||||
|
showTooltip(e.trigger, "Clipboard error!");
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function scrollToTop () {
|
||||||
|
var menuTitle = document.querySelector('.menu-title');
|
||||||
|
|
||||||
|
menuTitle.addEventListener('click', function () {
|
||||||
|
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function controllMenu() {
|
||||||
|
var menu = document.getElementById('menu-bar');
|
||||||
|
|
||||||
|
(function controllPosition() {
|
||||||
|
var scrollTop = document.scrollingElement.scrollTop;
|
||||||
|
var prevScrollTop = scrollTop;
|
||||||
|
var minMenuY = -menu.clientHeight - 50;
|
||||||
|
// When the script loads, the page can be at any scroll (e.g. if you reforesh it).
|
||||||
|
menu.style.top = scrollTop + 'px';
|
||||||
|
// Same as parseInt(menu.style.top.slice(0, -2), but faster
|
||||||
|
var topCache = menu.style.top.slice(0, -2);
|
||||||
|
menu.classList.remove('sticky');
|
||||||
|
var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
|
||||||
|
document.addEventListener('scroll', function () {
|
||||||
|
scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
|
||||||
|
// `null` means that it doesn't need to be updated
|
||||||
|
var nextSticky = null;
|
||||||
|
var nextTop = null;
|
||||||
|
var scrollDown = scrollTop > prevScrollTop;
|
||||||
|
var menuPosAbsoluteY = topCache - scrollTop;
|
||||||
|
if (scrollDown) {
|
||||||
|
nextSticky = false;
|
||||||
|
if (menuPosAbsoluteY > 0) {
|
||||||
|
nextTop = prevScrollTop;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (menuPosAbsoluteY > 0) {
|
||||||
|
nextSticky = true;
|
||||||
|
} else if (menuPosAbsoluteY < minMenuY) {
|
||||||
|
nextTop = prevScrollTop + minMenuY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextSticky === true && stickyCache === false) {
|
||||||
|
menu.classList.add('sticky');
|
||||||
|
stickyCache = true;
|
||||||
|
} else if (nextSticky === false && stickyCache === true) {
|
||||||
|
menu.classList.remove('sticky');
|
||||||
|
stickyCache = false;
|
||||||
|
}
|
||||||
|
if (nextTop !== null) {
|
||||||
|
menu.style.top = nextTop + 'px';
|
||||||
|
topCache = nextTop;
|
||||||
|
}
|
||||||
|
prevScrollTop = scrollTop;
|
||||||
|
}, { passive: true });
|
||||||
|
})();
|
||||||
|
(function controllBorder() {
|
||||||
|
function updateBorder() {
|
||||||
|
if (menu.offsetTop === 0) {
|
||||||
|
menu.classList.remove('bordered');
|
||||||
|
} else {
|
||||||
|
menu.classList.add('bordered');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateBorder();
|
||||||
|
document.addEventListener('scroll', updateBorder, { passive: true });
|
||||||
|
})();
|
||||||
|
})();
|
12
third_party/README.md
vendored
Normal file
12
third_party/README.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Third-party Course Content
|
||||||
|
|
||||||
|
The files in this directory are included in the course via the `{{#include ..}}`
|
||||||
|
syntax. All third-party content must be placed here to clearly indicate its
|
||||||
|
origin.
|
||||||
|
|
||||||
|
When we publish a translation of the course, we `git restore` the `src/` and
|
||||||
|
`third_party/` directories at the repository root back to the date listed in the
|
||||||
|
POT-Creation-Date header of the translation. **It is crucial, that all
|
||||||
|
translatable content lives in those two directories.** The other files (such as
|
||||||
|
`book.toml` and `theme/`) are not restored and we always use the latest version
|
||||||
|
of them.
|
724
third_party/mdbook/book.js
vendored
724
third_party/mdbook/book.js
vendored
@ -1,724 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
// Fix back button cache problem
|
|
||||||
window.onunload = function () { };
|
|
||||||
|
|
||||||
function isPlaygroundModified(playground) {
|
|
||||||
let code_block = playground.querySelector("code");
|
|
||||||
if (window.ace && code_block.classList.contains("editable")) {
|
|
||||||
let editor = window.ace.edit(code_block);
|
|
||||||
return editor.getValue() != editor.originalCode;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global variable, shared between modules
|
|
||||||
function playground_text(playground, hidden = true) {
|
|
||||||
let code_block = playground.querySelector("code");
|
|
||||||
|
|
||||||
if (window.ace && code_block.classList.contains("editable")) {
|
|
||||||
let editor = window.ace.edit(code_block);
|
|
||||||
return editor.getValue();
|
|
||||||
} else if (hidden) {
|
|
||||||
return code_block.textContent;
|
|
||||||
} else {
|
|
||||||
return code_block.innerText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(function codeSnippets() {
|
|
||||||
function fetch_with_timeout(url, options, timeout = 15000) {
|
|
||||||
return Promise.race([
|
|
||||||
fetch(url, options),
|
|
||||||
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var playgrounds = Array.from(document.querySelectorAll(".playground"));
|
|
||||||
if (playgrounds.length > 0) {
|
|
||||||
fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': "application/json",
|
|
||||||
},
|
|
||||||
method: 'POST',
|
|
||||||
mode: 'cors',
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(response => {
|
|
||||||
// get list of crates available in the rust playground
|
|
||||||
let playground_crates = response.crates.map(item => item["id"]);
|
|
||||||
playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handle_crate_list_update(playground_block, playground_crates) {
|
|
||||||
// update the play buttons after receiving the response
|
|
||||||
update_play_button(playground_block, playground_crates);
|
|
||||||
|
|
||||||
// and install on change listener to dynamically update ACE editors
|
|
||||||
if (window.ace) {
|
|
||||||
let code_block = playground_block.querySelector("code");
|
|
||||||
if (code_block.classList.contains("editable")) {
|
|
||||||
let editor = window.ace.edit(code_block);
|
|
||||||
editor.addEventListener("change", function (e) {
|
|
||||||
update_play_button(playground_block, playground_crates);
|
|
||||||
});
|
|
||||||
// add Ctrl-Enter command to execute rust code
|
|
||||||
editor.commands.addCommand({
|
|
||||||
name: "run",
|
|
||||||
bindKey: {
|
|
||||||
win: "Ctrl-Enter",
|
|
||||||
mac: "Ctrl-Enter"
|
|
||||||
},
|
|
||||||
exec: _editor => run_rust_code(playground_block)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// updates the visibility of play button based on `no_run` class and
|
|
||||||
// used crates vs ones available on https://play.rust-lang.org
|
|
||||||
function update_play_button(pre_block, playground_crates) {
|
|
||||||
var play_button = pre_block.querySelector(".play-button");
|
|
||||||
|
|
||||||
// skip if code is `no_run`
|
|
||||||
if (pre_block.querySelector('code').classList.contains("no_run")) {
|
|
||||||
play_button.classList.add("hidden");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get list of `extern crate`'s from snippet
|
|
||||||
var txt = playground_text(pre_block);
|
|
||||||
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
|
|
||||||
var snippet_crates = [];
|
|
||||||
var item;
|
|
||||||
while (item = re.exec(txt)) {
|
|
||||||
snippet_crates.push(item[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if all used crates are available on play.rust-lang.org
|
|
||||||
var all_available = snippet_crates.every(function (elem) {
|
|
||||||
return playground_crates.indexOf(elem) > -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (all_available) {
|
|
||||||
play_button.classList.remove("hidden");
|
|
||||||
} else {
|
|
||||||
play_button.classList.add("hidden");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_rust_code(code_block) {
|
|
||||||
var result_block = code_block.querySelector(".result");
|
|
||||||
if (!result_block) {
|
|
||||||
result_block = document.createElement('code');
|
|
||||||
result_block.className = 'result hljs language-bash';
|
|
||||||
|
|
||||||
code_block.append(result_block);
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = playground_text(code_block);
|
|
||||||
let classes = code_block.querySelector('code').classList;
|
|
||||||
let edition = "2015";
|
|
||||||
if(classes.contains("edition2018")) {
|
|
||||||
edition = "2018";
|
|
||||||
} else if(classes.contains("edition2021")) {
|
|
||||||
edition = "2021";
|
|
||||||
}
|
|
||||||
var params = {
|
|
||||||
version: "stable",
|
|
||||||
optimize: "0",
|
|
||||||
code: text,
|
|
||||||
edition: edition
|
|
||||||
};
|
|
||||||
|
|
||||||
if (text.indexOf("#![feature") !== -1) {
|
|
||||||
params.version = "nightly";
|
|
||||||
}
|
|
||||||
|
|
||||||
result_block.innerText = "Running...";
|
|
||||||
|
|
||||||
const playgroundModified = isPlaygroundModified(code_block);
|
|
||||||
const startTime = window.performance.now();
|
|
||||||
fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': "application/json",
|
|
||||||
},
|
|
||||||
method: 'POST',
|
|
||||||
mode: 'cors',
|
|
||||||
body: JSON.stringify(params)
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(response => {
|
|
||||||
const endTime = window.performance.now();
|
|
||||||
gtag("event", "playground", {
|
|
||||||
"modified": playgroundModified,
|
|
||||||
"error": (response.error == null) ? null : 'compilation_error',
|
|
||||||
"latency": (endTime - startTime) / 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.result.trim() === '') {
|
|
||||||
result_block.innerText = "No output";
|
|
||||||
result_block.classList.add("result-no-output");
|
|
||||||
} else {
|
|
||||||
result_block.innerText = response.result;
|
|
||||||
result_block.classList.remove("result-no-output");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
const endTime = window.performance.now();
|
|
||||||
gtag("event", "playground", {
|
|
||||||
"modified": playgroundModified,
|
|
||||||
"error": error.message,
|
|
||||||
"latency": (endTime - startTime) / 1000,
|
|
||||||
});
|
|
||||||
result_block.innerText = "Playground Communication: " + error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Syntax highlighting Configuration
|
|
||||||
hljs.configure({
|
|
||||||
tabReplace: ' ', // 4 spaces
|
|
||||||
languages: [], // Languages used for auto-detection
|
|
||||||
});
|
|
||||||
|
|
||||||
let code_nodes = Array
|
|
||||||
.from(document.querySelectorAll('code'))
|
|
||||||
// Don't highlight `inline code` blocks in headers.
|
|
||||||
.filter(function (node) {return !node.parentElement.classList.contains("header"); });
|
|
||||||
|
|
||||||
if (window.ace) {
|
|
||||||
// language-rust class needs to be removed for editable
|
|
||||||
// blocks or highlightjs will capture events
|
|
||||||
code_nodes
|
|
||||||
.filter(function (node) {return node.classList.contains("editable"); })
|
|
||||||
.forEach(function (block) { block.classList.remove('language-rust'); });
|
|
||||||
|
|
||||||
code_nodes
|
|
||||||
.filter(function (node) {return !node.classList.contains("editable"); })
|
|
||||||
.forEach(function (block) { hljs.highlightBlock(block); });
|
|
||||||
} else {
|
|
||||||
code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adding the hljs class gives code blocks the color css
|
|
||||||
// even if highlighting doesn't apply
|
|
||||||
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
|
|
||||||
|
|
||||||
Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) {
|
|
||||||
|
|
||||||
var lines = Array.from(block.querySelectorAll('.boring'));
|
|
||||||
// If no lines were hidden, return
|
|
||||||
if (!lines.length) { return; }
|
|
||||||
block.classList.add("hide-boring");
|
|
||||||
|
|
||||||
var buttons = document.createElement('div');
|
|
||||||
buttons.className = 'buttons';
|
|
||||||
buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
|
|
||||||
|
|
||||||
// add expand button
|
|
||||||
var pre_block = block.parentNode;
|
|
||||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
|
||||||
|
|
||||||
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
|
|
||||||
if (e.target.classList.contains('fa-eye')) {
|
|
||||||
e.target.classList.remove('fa-eye');
|
|
||||||
e.target.classList.add('fa-eye-slash');
|
|
||||||
e.target.title = 'Hide lines';
|
|
||||||
e.target.setAttribute('aria-label', e.target.title);
|
|
||||||
|
|
||||||
block.classList.remove('hide-boring');
|
|
||||||
} else if (e.target.classList.contains('fa-eye-slash')) {
|
|
||||||
e.target.classList.remove('fa-eye-slash');
|
|
||||||
e.target.classList.add('fa-eye');
|
|
||||||
e.target.title = 'Show hidden lines';
|
|
||||||
e.target.setAttribute('aria-label', e.target.title);
|
|
||||||
|
|
||||||
block.classList.add('hide-boring');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (window.playground_copyable) {
|
|
||||||
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
|
|
||||||
var pre_block = block.parentNode;
|
|
||||||
if (!pre_block.classList.contains('playground')) {
|
|
||||||
var buttons = pre_block.querySelector(".buttons");
|
|
||||||
if (!buttons) {
|
|
||||||
buttons = document.createElement('div');
|
|
||||||
buttons.className = 'buttons';
|
|
||||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
var clipButton = document.createElement('button');
|
|
||||||
clipButton.className = 'fa fa-copy clip-button';
|
|
||||||
clipButton.title = 'Copy to clipboard';
|
|
||||||
clipButton.setAttribute('aria-label', clipButton.title);
|
|
||||||
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
|
|
||||||
|
|
||||||
buttons.insertBefore(clipButton, buttons.firstChild);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process playground code blocks
|
|
||||||
Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
|
|
||||||
// Add play button
|
|
||||||
var buttons = pre_block.querySelector(".buttons");
|
|
||||||
if (!buttons) {
|
|
||||||
buttons = document.createElement('div');
|
|
||||||
buttons.className = 'buttons';
|
|
||||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
var runCodeButton = document.createElement('button');
|
|
||||||
runCodeButton.className = 'fa fa-play play-button';
|
|
||||||
runCodeButton.hidden = true;
|
|
||||||
runCodeButton.title = 'Run this code';
|
|
||||||
runCodeButton.setAttribute('aria-label', runCodeButton.title);
|
|
||||||
|
|
||||||
buttons.insertBefore(runCodeButton, buttons.firstChild);
|
|
||||||
runCodeButton.addEventListener('click', function (e) {
|
|
||||||
run_rust_code(pre_block);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (window.playground_copyable) {
|
|
||||||
var copyCodeClipboardButton = document.createElement('button');
|
|
||||||
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
|
|
||||||
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
|
|
||||||
copyCodeClipboardButton.title = 'Copy to clipboard';
|
|
||||||
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
|
|
||||||
|
|
||||||
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
let code_block = pre_block.querySelector("code");
|
|
||||||
if (window.ace && code_block.classList.contains("editable")) {
|
|
||||||
var undoChangesButton = document.createElement('button');
|
|
||||||
undoChangesButton.className = 'fa fa-history reset-button';
|
|
||||||
undoChangesButton.title = 'Undo changes';
|
|
||||||
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
|
|
||||||
|
|
||||||
buttons.insertBefore(undoChangesButton, buttons.firstChild);
|
|
||||||
|
|
||||||
undoChangesButton.addEventListener('click', function () {
|
|
||||||
let editor = window.ace.edit(code_block);
|
|
||||||
editor.setValue(editor.originalCode);
|
|
||||||
editor.clearSelection();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
(function themes() {
|
|
||||||
var html = document.querySelector('html');
|
|
||||||
var themeToggleButton = document.getElementById('theme-toggle');
|
|
||||||
var themePopup = document.getElementById('theme-list');
|
|
||||||
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
|
|
||||||
var stylesheets = {
|
|
||||||
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
|
|
||||||
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
|
|
||||||
highlight: document.querySelector("[href$='highlight.css']"),
|
|
||||||
};
|
|
||||||
|
|
||||||
function showThemes() {
|
|
||||||
themePopup.style.display = 'block';
|
|
||||||
themeToggleButton.setAttribute('aria-expanded', true);
|
|
||||||
themePopup.querySelector("button#" + get_theme()).focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateThemeSelected() {
|
|
||||||
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
|
|
||||||
el.classList.remove('theme-selected');
|
|
||||||
});
|
|
||||||
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideThemes() {
|
|
||||||
themePopup.style.display = 'none';
|
|
||||||
themeToggleButton.setAttribute('aria-expanded', false);
|
|
||||||
themeToggleButton.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_theme() {
|
|
||||||
var theme;
|
|
||||||
try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
|
|
||||||
if (theme === null || theme === undefined) {
|
|
||||||
return default_theme;
|
|
||||||
} else {
|
|
||||||
return theme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_theme(theme, store = true) {
|
|
||||||
let ace_theme;
|
|
||||||
|
|
||||||
if (theme == 'coal' || theme == 'navy') {
|
|
||||||
stylesheets.ayuHighlight.disabled = true;
|
|
||||||
stylesheets.tomorrowNight.disabled = false;
|
|
||||||
stylesheets.highlight.disabled = true;
|
|
||||||
|
|
||||||
ace_theme = "ace/theme/tomorrow_night";
|
|
||||||
} else if (theme == 'ayu') {
|
|
||||||
stylesheets.ayuHighlight.disabled = false;
|
|
||||||
stylesheets.tomorrowNight.disabled = true;
|
|
||||||
stylesheets.highlight.disabled = true;
|
|
||||||
ace_theme = "ace/theme/tomorrow_night";
|
|
||||||
} else {
|
|
||||||
stylesheets.ayuHighlight.disabled = true;
|
|
||||||
stylesheets.tomorrowNight.disabled = true;
|
|
||||||
stylesheets.highlight.disabled = false;
|
|
||||||
ace_theme = "ace/theme/dawn";
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor;
|
|
||||||
}, 1);
|
|
||||||
|
|
||||||
if (window.ace && window.editors) {
|
|
||||||
window.editors.forEach(function (editor) {
|
|
||||||
editor.setTheme(ace_theme);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var previousTheme = get_theme();
|
|
||||||
|
|
||||||
if (store) {
|
|
||||||
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
html.classList.remove(previousTheme);
|
|
||||||
html.classList.add(theme);
|
|
||||||
updateThemeSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set theme
|
|
||||||
var theme = get_theme();
|
|
||||||
|
|
||||||
set_theme(theme, false);
|
|
||||||
|
|
||||||
themeToggleButton.addEventListener('click', function () {
|
|
||||||
if (themePopup.style.display === 'block') {
|
|
||||||
hideThemes();
|
|
||||||
} else {
|
|
||||||
showThemes();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
themePopup.addEventListener('click', function (e) {
|
|
||||||
var theme;
|
|
||||||
if (e.target.className === "theme") {
|
|
||||||
theme = e.target.id;
|
|
||||||
} else if (e.target.parentElement.className === "theme") {
|
|
||||||
theme = e.target.parentElement.id;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
set_theme(theme);
|
|
||||||
});
|
|
||||||
|
|
||||||
themePopup.addEventListener('focusout', function(e) {
|
|
||||||
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
|
|
||||||
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
|
|
||||||
hideThemes();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
|
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
|
|
||||||
hideThemes();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('keydown', function (e) {
|
|
||||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
|
||||||
if (!themePopup.contains(e.target)) { return; }
|
|
||||||
|
|
||||||
switch (e.key) {
|
|
||||||
case 'Escape':
|
|
||||||
e.preventDefault();
|
|
||||||
hideThemes();
|
|
||||||
break;
|
|
||||||
case 'ArrowUp':
|
|
||||||
e.preventDefault();
|
|
||||||
var li = document.activeElement.parentElement;
|
|
||||||
if (li && li.previousElementSibling) {
|
|
||||||
li.previousElementSibling.querySelector('button').focus();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'ArrowDown':
|
|
||||||
e.preventDefault();
|
|
||||||
var li = document.activeElement.parentElement;
|
|
||||||
if (li && li.nextElementSibling) {
|
|
||||||
li.nextElementSibling.querySelector('button').focus();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'Home':
|
|
||||||
e.preventDefault();
|
|
||||||
themePopup.querySelector('li:first-child button').focus();
|
|
||||||
break;
|
|
||||||
case 'End':
|
|
||||||
e.preventDefault();
|
|
||||||
themePopup.querySelector('li:last-child button').focus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
(function sidebar() {
|
|
||||||
var body = document.querySelector("body");
|
|
||||||
var sidebar = document.getElementById("sidebar");
|
|
||||||
var sidebarLinks = document.querySelectorAll('#sidebar a');
|
|
||||||
var sidebarToggleButton = document.getElementById("sidebar-toggle");
|
|
||||||
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
|
|
||||||
var firstContact = null;
|
|
||||||
|
|
||||||
function showSidebar() {
|
|
||||||
body.classList.remove('sidebar-hidden')
|
|
||||||
body.classList.add('sidebar-visible');
|
|
||||||
Array.from(sidebarLinks).forEach(function (link) {
|
|
||||||
link.setAttribute('tabIndex', 0);
|
|
||||||
});
|
|
||||||
sidebarToggleButton.setAttribute('aria-expanded', true);
|
|
||||||
sidebar.setAttribute('aria-hidden', false);
|
|
||||||
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
|
|
||||||
|
|
||||||
function toggleSection(ev) {
|
|
||||||
ev.currentTarget.parentElement.classList.toggle('expanded');
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.from(sidebarAnchorToggles).forEach(function (el) {
|
|
||||||
el.addEventListener('click', toggleSection);
|
|
||||||
});
|
|
||||||
|
|
||||||
function hideSidebar() {
|
|
||||||
body.classList.remove('sidebar-visible')
|
|
||||||
body.classList.add('sidebar-hidden');
|
|
||||||
Array.from(sidebarLinks).forEach(function (link) {
|
|
||||||
link.setAttribute('tabIndex', -1);
|
|
||||||
});
|
|
||||||
sidebarToggleButton.setAttribute('aria-expanded', false);
|
|
||||||
sidebar.setAttribute('aria-hidden', true);
|
|
||||||
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle sidebar
|
|
||||||
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
|
|
||||||
if (body.classList.contains("sidebar-hidden")) {
|
|
||||||
var current_width = parseInt(
|
|
||||||
document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
|
|
||||||
if (current_width < 150) {
|
|
||||||
document.documentElement.style.setProperty('--sidebar-width', '150px');
|
|
||||||
}
|
|
||||||
showSidebar();
|
|
||||||
} else if (body.classList.contains("sidebar-visible")) {
|
|
||||||
hideSidebar();
|
|
||||||
} else {
|
|
||||||
if (getComputedStyle(sidebar)['transform'] === 'none') {
|
|
||||||
hideSidebar();
|
|
||||||
} else {
|
|
||||||
showSidebar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sidebarResizeHandle.addEventListener('mousedown', initResize, false);
|
|
||||||
|
|
||||||
function initResize(e) {
|
|
||||||
window.addEventListener('mousemove', resize, false);
|
|
||||||
window.addEventListener('mouseup', stopResize, false);
|
|
||||||
body.classList.add('sidebar-resizing');
|
|
||||||
}
|
|
||||||
function resize(e) {
|
|
||||||
var pos = (e.clientX - sidebar.offsetLeft);
|
|
||||||
if (pos < 20) {
|
|
||||||
hideSidebar();
|
|
||||||
} else {
|
|
||||||
if (body.classList.contains("sidebar-hidden")) {
|
|
||||||
showSidebar();
|
|
||||||
}
|
|
||||||
pos = Math.min(pos, window.innerWidth - 100);
|
|
||||||
document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//on mouseup remove windows functions mousemove & mouseup
|
|
||||||
function stopResize(e) {
|
|
||||||
body.classList.remove('sidebar-resizing');
|
|
||||||
window.removeEventListener('mousemove', resize, false);
|
|
||||||
window.removeEventListener('mouseup', stopResize, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('touchstart', function (e) {
|
|
||||||
firstContact = {
|
|
||||||
x: e.touches[0].clientX,
|
|
||||||
time: Date.now()
|
|
||||||
};
|
|
||||||
}, { passive: true });
|
|
||||||
|
|
||||||
document.addEventListener('touchmove', function (e) {
|
|
||||||
if (!firstContact)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var curX = e.touches[0].clientX;
|
|
||||||
var xDiff = curX - firstContact.x,
|
|
||||||
tDiff = Date.now() - firstContact.time;
|
|
||||||
|
|
||||||
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
|
|
||||||
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
|
|
||||||
showSidebar();
|
|
||||||
else if (xDiff < 0 && curX < 300)
|
|
||||||
hideSidebar();
|
|
||||||
|
|
||||||
firstContact = null;
|
|
||||||
}
|
|
||||||
}, { passive: true });
|
|
||||||
})();
|
|
||||||
|
|
||||||
(function chapterNavigation() {
|
|
||||||
document.addEventListener('keydown', function (e) {
|
|
||||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
|
||||||
if (window.search && window.search.hasFocus()) { return; }
|
|
||||||
var html = document.querySelector('html');
|
|
||||||
|
|
||||||
function next() {
|
|
||||||
var nextButton = document.querySelector('.nav-chapters.next');
|
|
||||||
if (nextButton) {
|
|
||||||
window.location.href = nextButton.href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function prev() {
|
|
||||||
var previousButton = document.querySelector('.nav-chapters.previous');
|
|
||||||
if (previousButton) {
|
|
||||||
window.location.href = previousButton.href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (e.key) {
|
|
||||||
case 'ArrowRight':
|
|
||||||
e.preventDefault();
|
|
||||||
if (html.dir == 'rtl') {
|
|
||||||
prev();
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'ArrowLeft':
|
|
||||||
e.preventDefault();
|
|
||||||
if (html.dir == 'rtl') {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
prev();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
(function clipboard() {
|
|
||||||
var clipButtons = document.querySelectorAll('.clip-button');
|
|
||||||
|
|
||||||
function hideTooltip(elem) {
|
|
||||||
elem.firstChild.innerText = "";
|
|
||||||
elem.className = 'fa fa-copy clip-button';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showTooltip(elem, msg) {
|
|
||||||
elem.firstChild.innerText = msg;
|
|
||||||
elem.className = 'fa fa-copy tooltipped';
|
|
||||||
}
|
|
||||||
|
|
||||||
var clipboardSnippets = new ClipboardJS('.clip-button', {
|
|
||||||
text: function (trigger) {
|
|
||||||
hideTooltip(trigger);
|
|
||||||
let playground = trigger.closest("pre");
|
|
||||||
return playground_text(playground, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Array.from(clipButtons).forEach(function (clipButton) {
|
|
||||||
clipButton.addEventListener('mouseout', function (e) {
|
|
||||||
hideTooltip(e.currentTarget);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
clipboardSnippets.on('success', function (e) {
|
|
||||||
e.clearSelection();
|
|
||||||
showTooltip(e.trigger, "Copied!");
|
|
||||||
});
|
|
||||||
|
|
||||||
clipboardSnippets.on('error', function (e) {
|
|
||||||
showTooltip(e.trigger, "Clipboard error!");
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
(function scrollToTop () {
|
|
||||||
var menuTitle = document.querySelector('.menu-title');
|
|
||||||
|
|
||||||
menuTitle.addEventListener('click', function () {
|
|
||||||
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
(function controllMenu() {
|
|
||||||
var menu = document.getElementById('menu-bar');
|
|
||||||
|
|
||||||
(function controllPosition() {
|
|
||||||
var scrollTop = document.scrollingElement.scrollTop;
|
|
||||||
var prevScrollTop = scrollTop;
|
|
||||||
var minMenuY = -menu.clientHeight - 50;
|
|
||||||
// When the script loads, the page can be at any scroll (e.g. if you reforesh it).
|
|
||||||
menu.style.top = scrollTop + 'px';
|
|
||||||
// Same as parseInt(menu.style.top.slice(0, -2), but faster
|
|
||||||
var topCache = menu.style.top.slice(0, -2);
|
|
||||||
menu.classList.remove('sticky');
|
|
||||||
var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
|
|
||||||
document.addEventListener('scroll', function () {
|
|
||||||
scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
|
|
||||||
// `null` means that it doesn't need to be updated
|
|
||||||
var nextSticky = null;
|
|
||||||
var nextTop = null;
|
|
||||||
var scrollDown = scrollTop > prevScrollTop;
|
|
||||||
var menuPosAbsoluteY = topCache - scrollTop;
|
|
||||||
if (scrollDown) {
|
|
||||||
nextSticky = false;
|
|
||||||
if (menuPosAbsoluteY > 0) {
|
|
||||||
nextTop = prevScrollTop;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (menuPosAbsoluteY > 0) {
|
|
||||||
nextSticky = true;
|
|
||||||
} else if (menuPosAbsoluteY < minMenuY) {
|
|
||||||
nextTop = prevScrollTop + minMenuY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nextSticky === true && stickyCache === false) {
|
|
||||||
menu.classList.add('sticky');
|
|
||||||
stickyCache = true;
|
|
||||||
} else if (nextSticky === false && stickyCache === true) {
|
|
||||||
menu.classList.remove('sticky');
|
|
||||||
stickyCache = false;
|
|
||||||
}
|
|
||||||
if (nextTop !== null) {
|
|
||||||
menu.style.top = nextTop + 'px';
|
|
||||||
topCache = nextTop;
|
|
||||||
}
|
|
||||||
prevScrollTop = scrollTop;
|
|
||||||
}, { passive: true });
|
|
||||||
})();
|
|
||||||
(function controllBorder() {
|
|
||||||
function updateBorder() {
|
|
||||||
if (menu.offsetTop === 0) {
|
|
||||||
menu.classList.remove('bordered');
|
|
||||||
} else {
|
|
||||||
menu.classList.add('bordered');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateBorder();
|
|
||||||
document.addEventListener('scroll', updateBorder, { passive: true });
|
|
||||||
})();
|
|
||||||
})();
|
|
1
third_party/mdbook/book.js
vendored
Symbolic link
1
third_party/mdbook/book.js
vendored
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../theme/book.js
|
1
third_party/mdbook/index.hbs
vendored
Symbolic link
1
third_party/mdbook/index.hbs
vendored
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../theme/index.hbs
|
Loading…
x
Reference in New Issue
Block a user