Merge pull request #2554 from GuillaumeGomez/eslint

Fix eslint warnings and add eslint check in CI
This commit is contained in:
Eric Huss 2025-03-23 19:50:44 +00:00 committed by GitHub
commit b47d1cff33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 636 additions and 417 deletions

95
.eslintrc.json Normal file
View file

@ -0,0 +1,95 @@
{
"env": {
"browser": true,
"node": true,
"es6": true
},
"extends": "eslint:recommended",
"globals": {
"module": "readonly",
"require": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"requireConfigFile": false,
"sourceType": "module"
},
"ignorePatterns": ["**min.js", "**/highlight.js", "**/playground_editor/*"],
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"brace-style": [
"error",
"1tbs",
{ "allowSingleLine": false }
],
"curly": "error",
"no-trailing-spaces": "error",
"no-multi-spaces": "error",
"keyword-spacing": [
"error",
{ "before": true, "after": true }
],
"comma-spacing": [
"error",
{ "before": false, "after": true }
],
"arrow-spacing": [
"error",
{ "before": true, "after": true }
],
"key-spacing": [
"error",
{ "beforeColon": false, "afterColon": true, "mode": "strict" }
],
"func-call-spacing": ["error", "never"],
"space-infix-ops": "error",
"space-before-function-paren": ["error", "never"],
"space-before-blocks": "error",
"no-console": [
"error",
{ "allow": ["warn", "error"] }
],
"comma-dangle": ["error", "always-multiline"],
"comma-style": ["error", "last"],
"max-len": ["error", { "code": 100, "tabWidth": 2 }],
"eol-last": ["error", "always"],
"no-extra-parens": "error",
"arrow-parens": ["error", "as-needed"],
"no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"prefer-const": ["error"],
"no-var": "error",
"eqeqeq": "error"
},
"overrides": [
{
"files": [
"tests/**/*.js"
],
"env": {
"jest": true,
"node": true
}
}
]
}

View file

@ -3,9 +3,6 @@ on:
pull_request: pull_request:
merge_group: merge_group:
env:
BROWSER_UI_TEST_VERSION: '0.19.0'
jobs: jobs:
test: test:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -85,7 +82,9 @@ jobs:
with: with:
node-version: 20 node-version: 20
- name: Install browser-ui-test - name: Install browser-ui-test
run: npm install browser-ui-test@"${BROWSER_UI_TEST_VERSION}" run: npm install
- name: Run eslint
run: npm run lint
- name: Build and run tests (+ GUI) - name: Build and run tests (+ GUI)
run: cargo test --locked --target x86_64-unknown-linux-gnu --test gui run: cargo test --locked --target x86_64-unknown-linux-gnu --test gui

View file

@ -150,7 +150,8 @@ If you want to only run some tests, you can filter them by passing (part of) the
cargo test --test gui -- search cargo test --test gui -- search
``` ```
The first time, it'll fail and ask you to install the `browser-ui-test` package. Install it then re-run the tests. The first time, it'll fail and ask you to install the `browser-ui-test` package. Install it with the provided
command then re-run the tests.
If you want to disable the headless mode, use the `--disable-headless-test` option: If you want to disable the headless mode, use the `--disable-headless-test` option:
@ -162,6 +163,21 @@ The GUI tests are in the directory `tests/gui` in text files with the `.goml` ex
using a `node.js` framework called `browser-ui-test`. You can find documentation for this language on its using a `node.js` framework called `browser-ui-test`. You can find documentation for this language on its
[repository](https://github.com/GuillaumeGomez/browser-UI-test/blob/master/goml-script.md). [repository](https://github.com/GuillaumeGomez/browser-UI-test/blob/master/goml-script.md).
### Checking changes in `.js` files
The `.js` files source code is checked using [`eslint`](https://eslint.org/). This is a linter (just like `clippy` in Rust)
for the Javascript language. You can install it with `npm` by running the following command:
```
npm install
```
Then you can run it using:
```
npm run lint
```
## Updating highlight.js ## Updating highlight.js
The following are instructions for updating [highlight.js](https://highlightjs.org/). The following are instructions for updating [highlight.js](https://highlightjs.org/).

10
package.json Normal file
View file

@ -0,0 +1,10 @@
{
"dependencies": {
"browser-ui-test": "0.19.0",
"eslint": "^8.57.1"
},
"scripts": {
"lint": "eslint src/theme/*js src/theme/**/*js",
"lint-fix": "eslint --fix src/theme/*js src/theme/**/*js"
}
}

View file

@ -1,14 +1,16 @@
"use strict"; 'use strict';
/* global default_theme, hljs, ClipboardJS */
// Fix back button cache problem // Fix back button cache problem
window.onunload = function () { }; window.onunload = function() { };
// Global variable, shared between modules // Global variable, shared between modules
function playground_text(playground, hidden = true) { function playground_text(playground, hidden = true) {
let code_block = playground.querySelector("code"); const code_block = playground.querySelector('code');
if (window.ace && code_block.classList.contains("editable")) { if (window.ace && code_block.classList.contains('editable')) {
let editor = window.ace.edit(code_block); const editor = window.ace.edit(code_block);
return editor.getValue(); return editor.getValue();
} else if (hidden) { } else if (hidden) {
return code_block.textContent; return code_block.textContent;
@ -21,25 +23,25 @@ function playground_text(playground, hidden = true) {
function fetch_with_timeout(url, options, timeout = 6000) { function fetch_with_timeout(url, options, timeout = 6000) {
return Promise.race([ return Promise.race([
fetch(url, options), fetch(url, options),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)),
]); ]);
} }
var playgrounds = Array.from(document.querySelectorAll(".playground")); const playgrounds = Array.from(document.querySelectorAll('.playground'));
if (playgrounds.length > 0) { if (playgrounds.length > 0) {
fetch_with_timeout("https://play.rust-lang.org/meta/crates", { fetch_with_timeout('https://play.rust-lang.org/meta/crates', {
headers: { headers: {
'Content-Type': "application/json", 'Content-Type': 'application/json',
}, },
method: 'POST', method: 'POST',
mode: 'cors', mode: 'cors',
}) })
.then(response => response.json()) .then(response => response.json())
.then(response => { .then(response => {
// get list of crates available in the rust playground // get list of crates available in the rust playground
let playground_crates = response.crates.map(item => item["id"]); const playground_crates = response.crates.map(item => item['id']);
playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
}); });
} }
function handle_crate_list_update(playground_block, playground_crates) { function handle_crate_list_update(playground_block, playground_crates) {
@ -48,20 +50,20 @@ function playground_text(playground, hidden = true) {
// and install on change listener to dynamically update ACE editors // and install on change listener to dynamically update ACE editors
if (window.ace) { if (window.ace) {
let code_block = playground_block.querySelector("code"); const code_block = playground_block.querySelector('code');
if (code_block.classList.contains("editable")) { if (code_block.classList.contains('editable')) {
let editor = window.ace.edit(code_block); const editor = window.ace.edit(code_block);
editor.addEventListener("change", function (e) { editor.addEventListener('change', () => {
update_play_button(playground_block, playground_crates); update_play_button(playground_block, playground_crates);
}); });
// add Ctrl-Enter command to execute rust code // add Ctrl-Enter command to execute rust code
editor.commands.addCommand({ editor.commands.addCommand({
name: "run", name: 'run',
bindKey: { bindKey: {
win: "Ctrl-Enter", win: 'Ctrl-Enter',
mac: "Ctrl-Enter" mac: 'Ctrl-Enter',
}, },
exec: _editor => run_rust_code(playground_block) exec: _editor => run_rust_code(playground_block),
}); });
} }
} }
@ -70,37 +72,38 @@ function playground_text(playground, hidden = true) {
// updates the visibility of play button based on `no_run` class and // updates the visibility of play button based on `no_run` class and
// used crates vs ones available on https://play.rust-lang.org // used crates vs ones available on https://play.rust-lang.org
function update_play_button(pre_block, playground_crates) { function update_play_button(pre_block, playground_crates) {
var play_button = pre_block.querySelector(".play-button"); const play_button = pre_block.querySelector('.play-button');
// skip if code is `no_run` // skip if code is `no_run`
if (pre_block.querySelector('code').classList.contains("no_run")) { if (pre_block.querySelector('code').classList.contains('no_run')) {
play_button.classList.add("hidden"); play_button.classList.add('hidden');
return; return;
} }
// get list of `extern crate`'s from snippet // get list of `extern crate`'s from snippet
var txt = playground_text(pre_block); const txt = playground_text(pre_block);
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; const re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
var snippet_crates = []; const snippet_crates = [];
var item; let item;
// eslint-disable-next-line no-cond-assign
while (item = re.exec(txt)) { while (item = re.exec(txt)) {
snippet_crates.push(item[1]); snippet_crates.push(item[1]);
} }
// check if all used crates are available on play.rust-lang.org // check if all used crates are available on play.rust-lang.org
var all_available = snippet_crates.every(function (elem) { const all_available = snippet_crates.every(function(elem) {
return playground_crates.indexOf(elem) > -1; return playground_crates.indexOf(elem) > -1;
}); });
if (all_available) { if (all_available) {
play_button.classList.remove("hidden"); play_button.classList.remove('hidden');
} else { } else {
play_button.classList.add("hidden"); play_button.classList.add('hidden');
} }
} }
function run_rust_code(code_block) { function run_rust_code(code_block) {
var result_block = code_block.querySelector(".result"); let result_block = code_block.querySelector('.result');
if (!result_block) { if (!result_block) {
result_block = document.createElement('code'); result_block = document.createElement('code');
result_block.className = 'result hljs language-bash'; result_block.className = 'result hljs language-bash';
@ -108,93 +111,110 @@ function playground_text(playground, hidden = true) {
code_block.append(result_block); code_block.append(result_block);
} }
let text = playground_text(code_block); const text = playground_text(code_block);
let classes = code_block.querySelector('code').classList; const classes = code_block.querySelector('code').classList;
let edition = "2015"; let edition = '2015';
classes.forEach(className => { classes.forEach(className => {
if (className.startsWith("edition")) { if (className.startsWith('edition')) {
edition = className.slice(7); edition = className.slice(7);
} }
}); });
var params = { const params = {
version: "stable", version: 'stable',
optimize: "0", optimize: '0',
code: text, code: text,
edition: edition edition: edition,
}; };
if (text.indexOf("#![feature") !== -1) { if (text.indexOf('#![feature') !== -1) {
params.version = "nightly"; params.version = 'nightly';
} }
result_block.innerText = "Running..."; result_block.innerText = 'Running...';
fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { fetch_with_timeout('https://play.rust-lang.org/evaluate.json', {
headers: { headers: {
'Content-Type': "application/json", 'Content-Type': 'application/json',
}, },
method: 'POST', method: 'POST',
mode: 'cors', mode: 'cors',
body: JSON.stringify(params) body: JSON.stringify(params),
}) })
.then(response => response.json()) .then(response => response.json())
.then(response => { .then(response => {
if (response.result.trim() === '') { if (response.result.trim() === '') {
result_block.innerText = "No output"; result_block.innerText = 'No output';
result_block.classList.add("result-no-output"); result_block.classList.add('result-no-output');
} else { } else {
result_block.innerText = response.result; result_block.innerText = response.result;
result_block.classList.remove("result-no-output"); result_block.classList.remove('result-no-output');
} }
}) })
.catch(error => result_block.innerText = "Playground Communication: " + error.message); .catch(error => result_block.innerText = 'Playground Communication: ' + error.message);
} }
// Syntax highlighting Configuration // Syntax highlighting Configuration
hljs.configure({ hljs.configure({
tabReplace: ' ', // 4 spaces tabReplace: ' ', // 4 spaces
languages: [], // Languages used for auto-detection languages: [], // Languages used for auto-detection
}); });
let code_nodes = Array const code_nodes = Array
.from(document.querySelectorAll('code')) .from(document.querySelectorAll('code'))
// Don't highlight `inline code` blocks in headers. // Don't highlight `inline code` blocks in headers.
.filter(function (node) {return !node.parentElement.classList.contains("header"); }); .filter(function(node) {
return !node.parentElement.classList.contains('header');
});
if (window.ace) { if (window.ace) {
// language-rust class needs to be removed for editable // language-rust class needs to be removed for editable
// blocks or highlightjs will capture events // blocks or highlightjs will capture events
code_nodes code_nodes
.filter(function (node) {return node.classList.contains("editable"); }) .filter(function(node) {
.forEach(function (block) { block.classList.remove('language-rust'); }); return node.classList.contains('editable');
})
.forEach(function(block) {
block.classList.remove('language-rust');
});
code_nodes code_nodes
.filter(function (node) {return !node.classList.contains("editable"); }) .filter(function(node) {
.forEach(function (block) { hljs.highlightBlock(block); }); return !node.classList.contains('editable');
})
.forEach(function(block) {
hljs.highlightBlock(block);
});
} else { } else {
code_nodes.forEach(function (block) { hljs.highlightBlock(block); }); code_nodes.forEach(function(block) {
hljs.highlightBlock(block);
});
} }
// Adding the hljs class gives code blocks the color css // Adding the hljs class gives code blocks the color css
// even if highlighting doesn't apply // even if highlighting doesn't apply
code_nodes.forEach(function (block) { block.classList.add('hljs'); }); code_nodes.forEach(function(block) {
block.classList.add('hljs');
});
Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) { Array.from(document.querySelectorAll('code.hljs')).forEach(function(block) {
var lines = Array.from(block.querySelectorAll('.boring')); const lines = Array.from(block.querySelectorAll('.boring'));
// If no lines were hidden, return // If no lines were hidden, return
if (!lines.length) { return; } if (!lines.length) {
block.classList.add("hide-boring"); return;
}
block.classList.add('hide-boring');
var buttons = document.createElement('div'); const buttons = document.createElement('div');
buttons.className = 'buttons'; buttons.className = 'buttons';
buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>"; buttons.innerHTML = '<button class="fa fa-eye" title="Show hidden lines" \
aria-label="Show hidden lines"></button>';
// add expand button // add expand button
var pre_block = block.parentNode; const pre_block = block.parentNode;
pre_block.insertBefore(buttons, pre_block.firstChild); pre_block.insertBefore(buttons, pre_block.firstChild);
pre_block.querySelector('.buttons').addEventListener('click', function (e) { pre_block.querySelector('.buttons').addEventListener('click', function(e) {
if (e.target.classList.contains('fa-eye')) { if (e.target.classList.contains('fa-eye')) {
e.target.classList.remove('fa-eye'); e.target.classList.remove('fa-eye');
e.target.classList.add('fa-eye-slash'); e.target.classList.add('fa-eye-slash');
@ -214,21 +234,21 @@ function playground_text(playground, hidden = true) {
}); });
if (window.playground_copyable) { if (window.playground_copyable) {
Array.from(document.querySelectorAll('pre code')).forEach(function (block) { Array.from(document.querySelectorAll('pre code')).forEach(function(block) {
var pre_block = block.parentNode; const pre_block = block.parentNode;
if (!pre_block.classList.contains('playground')) { if (!pre_block.classList.contains('playground')) {
var buttons = pre_block.querySelector(".buttons"); let buttons = pre_block.querySelector('.buttons');
if (!buttons) { if (!buttons) {
buttons = document.createElement('div'); buttons = document.createElement('div');
buttons.className = 'buttons'; buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild); pre_block.insertBefore(buttons, pre_block.firstChild);
} }
var clipButton = document.createElement('button'); const clipButton = document.createElement('button');
clipButton.className = 'clip-button'; clipButton.className = 'clip-button';
clipButton.title = 'Copy to clipboard'; clipButton.title = 'Copy to clipboard';
clipButton.setAttribute('aria-label', clipButton.title); clipButton.setAttribute('aria-label', clipButton.title);
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>'; clipButton.innerHTML = '<i class="tooltiptext"></i>';
buttons.insertBefore(clipButton, buttons.firstChild); buttons.insertBefore(clipButton, buttons.firstChild);
} }
@ -236,28 +256,28 @@ function playground_text(playground, hidden = true) {
} }
// Process playground code blocks // Process playground code blocks
Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) { Array.from(document.querySelectorAll('.playground')).forEach(function(pre_block) {
// Add play button // Add play button
var buttons = pre_block.querySelector(".buttons"); let buttons = pre_block.querySelector('.buttons');
if (!buttons) { if (!buttons) {
buttons = document.createElement('div'); buttons = document.createElement('div');
buttons.className = 'buttons'; buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild); pre_block.insertBefore(buttons, pre_block.firstChild);
} }
var runCodeButton = document.createElement('button'); const runCodeButton = document.createElement('button');
runCodeButton.className = 'fa fa-play play-button'; runCodeButton.className = 'fa fa-play play-button';
runCodeButton.hidden = true; runCodeButton.hidden = true;
runCodeButton.title = 'Run this code'; runCodeButton.title = 'Run this code';
runCodeButton.setAttribute('aria-label', runCodeButton.title); runCodeButton.setAttribute('aria-label', runCodeButton.title);
buttons.insertBefore(runCodeButton, buttons.firstChild); buttons.insertBefore(runCodeButton, buttons.firstChild);
runCodeButton.addEventListener('click', function (e) { runCodeButton.addEventListener('click', () => {
run_rust_code(pre_block); run_rust_code(pre_block);
}); });
if (window.playground_copyable) { if (window.playground_copyable) {
var copyCodeClipboardButton = document.createElement('button'); const copyCodeClipboardButton = document.createElement('button');
copyCodeClipboardButton.className = 'clip-button'; copyCodeClipboardButton.className = 'clip-button';
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>'; copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
copyCodeClipboardButton.title = 'Copy to clipboard'; copyCodeClipboardButton.title = 'Copy to clipboard';
@ -266,17 +286,17 @@ function playground_text(playground, hidden = true) {
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
} }
let code_block = pre_block.querySelector("code"); const code_block = pre_block.querySelector('code');
if (window.ace && code_block.classList.contains("editable")) { if (window.ace && code_block.classList.contains('editable')) {
var undoChangesButton = document.createElement('button'); const undoChangesButton = document.createElement('button');
undoChangesButton.className = 'fa fa-history reset-button'; undoChangesButton.className = 'fa fa-history reset-button';
undoChangesButton.title = 'Undo changes'; undoChangesButton.title = 'Undo changes';
undoChangesButton.setAttribute('aria-label', undoChangesButton.title); undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
buttons.insertBefore(undoChangesButton, buttons.firstChild); buttons.insertBefore(undoChangesButton, buttons.firstChild);
undoChangesButton.addEventListener('click', function () { undoChangesButton.addEventListener('click', function() {
let editor = window.ace.edit(code_block); const editor = window.ace.edit(code_block);
editor.setValue(editor.originalCode); editor.setValue(editor.originalCode);
editor.clearSelection(); editor.clearSelection();
}); });
@ -285,31 +305,31 @@ function playground_text(playground, hidden = true) {
})(); })();
(function themes() { (function themes() {
var html = document.querySelector('html'); const html = document.querySelector('html');
var themeToggleButton = document.getElementById('theme-toggle'); const themeToggleButton = document.getElementById('theme-toggle');
var themePopup = document.getElementById('theme-list'); const themePopup = document.getElementById('theme-list');
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); const themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
var themeIds = []; const themeIds = [];
themePopup.querySelectorAll('button.theme').forEach(function (el) { themePopup.querySelectorAll('button.theme').forEach(function(el) {
themeIds.push(el.id); themeIds.push(el.id);
}); });
var stylesheets = { const stylesheets = {
ayuHighlight: document.querySelector("#ayu-highlight-css"), ayuHighlight: document.querySelector('#ayu-highlight-css'),
tomorrowNight: document.querySelector("#tomorrow-night-css"), tomorrowNight: document.querySelector('#tomorrow-night-css'),
highlight: document.querySelector("#highlight-css"), highlight: document.querySelector('#highlight-css'),
}; };
function showThemes() { function showThemes() {
themePopup.style.display = 'block'; themePopup.style.display = 'block';
themeToggleButton.setAttribute('aria-expanded', true); themeToggleButton.setAttribute('aria-expanded', true);
themePopup.querySelector("button#" + get_theme()).focus(); themePopup.querySelector('button#' + get_theme()).focus();
} }
function updateThemeSelected() { function updateThemeSelected() {
themePopup.querySelectorAll('.theme-selected').forEach(function (el) { themePopup.querySelectorAll('.theme-selected').forEach(function(el) {
el.classList.remove('theme-selected'); el.classList.remove('theme-selected');
}); });
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected'); themePopup.querySelector('button#' + get_theme()).classList.add('theme-selected');
} }
function hideThemes() { function hideThemes() {
@ -319,8 +339,12 @@ function playground_text(playground, hidden = true) {
} }
function get_theme() { function get_theme() {
var theme; let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { } try {
theme = localStorage.getItem('mdbook-theme');
} catch (e) {
// ignore error.
}
if (theme === null || theme === undefined || !themeIds.includes(theme)) { if (theme === null || theme === undefined || !themeIds.includes(theme)) {
return default_theme; return default_theme;
} else { } else {
@ -331,38 +355,42 @@ function playground_text(playground, hidden = true) {
function set_theme(theme, store = true) { function set_theme(theme, store = true) {
let ace_theme; let ace_theme;
if (theme == 'coal' || theme == 'navy') { if (theme === 'coal' || theme === 'navy') {
stylesheets.ayuHighlight.disabled = true; stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = false; stylesheets.tomorrowNight.disabled = false;
stylesheets.highlight.disabled = true; stylesheets.highlight.disabled = true;
ace_theme = "ace/theme/tomorrow_night"; ace_theme = 'ace/theme/tomorrow_night';
} else if (theme == 'ayu') { } else if (theme === 'ayu') {
stylesheets.ayuHighlight.disabled = false; stylesheets.ayuHighlight.disabled = false;
stylesheets.tomorrowNight.disabled = true; stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = true; stylesheets.highlight.disabled = true;
ace_theme = "ace/theme/tomorrow_night"; ace_theme = 'ace/theme/tomorrow_night';
} else { } else {
stylesheets.ayuHighlight.disabled = true; stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = true; stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = false; stylesheets.highlight.disabled = false;
ace_theme = "ace/theme/dawn"; ace_theme = 'ace/theme/dawn';
} }
setTimeout(function () { setTimeout(function() {
themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor; themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor;
}, 1); }, 1);
if (window.ace && window.editors) { if (window.ace && window.editors) {
window.editors.forEach(function (editor) { window.editors.forEach(function(editor) {
editor.setTheme(ace_theme); editor.setTheme(ace_theme);
}); });
} }
var previousTheme = get_theme(); const previousTheme = get_theme();
if (store) { if (store) {
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } try {
localStorage.setItem('mdbook-theme', theme);
} catch (e) {
// ignore error.
}
} }
html.classList.remove(previousTheme); html.classList.remove(previousTheme);
@ -371,11 +399,11 @@ function playground_text(playground, hidden = true) {
} }
// Set theme // Set theme
var theme = get_theme(); const theme = get_theme();
set_theme(theme, false); set_theme(theme, false);
themeToggleButton.addEventListener('click', function () { themeToggleButton.addEventListener('click', function() {
if (themePopup.style.display === 'block') { if (themePopup.style.display === 'block') {
hideThemes(); hideThemes();
} else { } else {
@ -383,11 +411,11 @@ function playground_text(playground, hidden = true) {
} }
}); });
themePopup.addEventListener('click', function (e) { themePopup.addEventListener('click', function(e) {
var theme; let theme;
if (e.target.className === "theme") { if (e.target.className === 'theme') {
theme = e.target.id; theme = e.target.id;
} else if (e.target.parentElement.className === "theme") { } else if (e.target.parentElement.className === 'theme') {
theme = e.target.parentElement.id; theme = e.target.parentElement.id;
} else { } else {
return; return;
@ -397,88 +425,108 @@ function playground_text(playground, hidden = true) {
themePopup.addEventListener('focusout', function(e) { themePopup.addEventListener('focusout', function(e) {
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { if (!!e.relatedTarget &&
!themeToggleButton.contains(e.relatedTarget) &&
!themePopup.contains(e.relatedTarget)
) {
hideThemes(); hideThemes();
} }
}); });
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628 // 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) { document.addEventListener('click', function(e) {
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { if (themePopup.style.display === 'block' &&
!themeToggleButton.contains(e.target) &&
!themePopup.contains(e.target)
) {
hideThemes(); hideThemes();
} }
}); });
document.addEventListener('keydown', function (e) { document.addEventListener('keydown', function(e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
if (!themePopup.contains(e.target)) { return; } return;
}
if (!themePopup.contains(e.target)) {
return;
}
let li;
switch (e.key) { switch (e.key) {
case 'Escape': case 'Escape':
e.preventDefault(); e.preventDefault();
hideThemes(); hideThemes();
break; break;
case 'ArrowUp': case 'ArrowUp':
e.preventDefault(); e.preventDefault();
var li = document.activeElement.parentElement; li = document.activeElement.parentElement;
if (li && li.previousElementSibling) { if (li && li.previousElementSibling) {
li.previousElementSibling.querySelector('button').focus(); li.previousElementSibling.querySelector('button').focus();
} }
break; break;
case 'ArrowDown': case 'ArrowDown':
e.preventDefault(); e.preventDefault();
var li = document.activeElement.parentElement; li = document.activeElement.parentElement;
if (li && li.nextElementSibling) { if (li && li.nextElementSibling) {
li.nextElementSibling.querySelector('button').focus(); li.nextElementSibling.querySelector('button').focus();
} }
break; break;
case 'Home': case 'Home':
e.preventDefault(); e.preventDefault();
themePopup.querySelector('li:first-child button').focus(); themePopup.querySelector('li:first-child button').focus();
break; break;
case 'End': case 'End':
e.preventDefault(); e.preventDefault();
themePopup.querySelector('li:last-child button').focus(); themePopup.querySelector('li:last-child button').focus();
break; break;
} }
}); });
})(); })();
(function sidebar() { (function sidebar() {
var body = document.querySelector("body"); const body = document.querySelector('body');
var sidebar = document.getElementById("sidebar"); const sidebar = document.getElementById('sidebar');
var sidebarLinks = document.querySelectorAll('#sidebar a'); const sidebarLinks = document.querySelectorAll('#sidebar a');
var sidebarToggleButton = document.getElementById("sidebar-toggle"); const sidebarToggleButton = document.getElementById('sidebar-toggle');
var sidebarToggleAnchor = document.getElementById("sidebar-toggle-anchor"); const sidebarToggleAnchor = document.getElementById('sidebar-toggle-anchor');
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle"); const sidebarResizeHandle = document.getElementById('sidebar-resize-handle');
var firstContact = null; let firstContact = null;
function showSidebar() { function showSidebar() {
body.classList.remove('sidebar-hidden') body.classList.remove('sidebar-hidden');
body.classList.add('sidebar-visible'); body.classList.add('sidebar-visible');
Array.from(sidebarLinks).forEach(function (link) { Array.from(sidebarLinks).forEach(function(link) {
link.setAttribute('tabIndex', 0); link.setAttribute('tabIndex', 0);
}); });
sidebarToggleButton.setAttribute('aria-expanded', true); sidebarToggleButton.setAttribute('aria-expanded', true);
sidebar.setAttribute('aria-hidden', false); sidebar.setAttribute('aria-hidden', false);
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } try {
localStorage.setItem('mdbook-sidebar', 'visible');
} catch (e) {
// Ignore error.
}
} }
function hideSidebar() { function hideSidebar() {
body.classList.remove('sidebar-visible') body.classList.remove('sidebar-visible');
body.classList.add('sidebar-hidden'); body.classList.add('sidebar-hidden');
Array.from(sidebarLinks).forEach(function (link) { Array.from(sidebarLinks).forEach(function(link) {
link.setAttribute('tabIndex', -1); link.setAttribute('tabIndex', -1);
}); });
sidebarToggleButton.setAttribute('aria-expanded', false); sidebarToggleButton.setAttribute('aria-expanded', false);
sidebar.setAttribute('aria-hidden', true); sidebar.setAttribute('aria-hidden', true);
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } try {
localStorage.setItem('mdbook-sidebar', 'hidden');
} catch (e) {
// Ignore error.
}
} }
// Toggle sidebar // Toggle sidebar
sidebarToggleAnchor.addEventListener('change', function sidebarToggle() { sidebarToggleAnchor.addEventListener('change', function sidebarToggle() {
if (sidebarToggleAnchor.checked) { if (sidebarToggleAnchor.checked) {
var current_width = parseInt( const current_width = parseInt(
document.documentElement.style.getPropertyValue('--sidebar-width'), 10); document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
if (current_width < 150) { if (current_width < 150) {
document.documentElement.style.setProperty('--sidebar-width', '150px'); document.documentElement.style.setProperty('--sidebar-width', '150px');
@ -491,17 +539,17 @@ function playground_text(playground, hidden = true) {
sidebarResizeHandle.addEventListener('mousedown', initResize, false); sidebarResizeHandle.addEventListener('mousedown', initResize, false);
function initResize(e) { function initResize() {
window.addEventListener('mousemove', resize, false); window.addEventListener('mousemove', resize, false);
window.addEventListener('mouseup', stopResize, false); window.addEventListener('mouseup', stopResize, false);
body.classList.add('sidebar-resizing'); body.classList.add('sidebar-resizing');
} }
function resize(e) { function resize(e) {
var pos = (e.clientX - sidebar.offsetLeft); let pos = e.clientX - sidebar.offsetLeft;
if (pos < 20) { if (pos < 20) {
hideSidebar(); hideSidebar();
} else { } else {
if (body.classList.contains("sidebar-hidden")) { if (body.classList.contains('sidebar-hidden')) {
showSidebar(); showSidebar();
} }
pos = Math.min(pos, window.innerWidth - 100); pos = Math.min(pos, window.innerWidth - 100);
@ -509,32 +557,34 @@ function playground_text(playground, hidden = true) {
} }
} }
//on mouseup remove windows functions mousemove & mouseup //on mouseup remove windows functions mousemove & mouseup
function stopResize(e) { function stopResize() {
body.classList.remove('sidebar-resizing'); body.classList.remove('sidebar-resizing');
window.removeEventListener('mousemove', resize, false); window.removeEventListener('mousemove', resize, false);
window.removeEventListener('mouseup', stopResize, false); window.removeEventListener('mouseup', stopResize, false);
} }
document.addEventListener('touchstart', function (e) { document.addEventListener('touchstart', function(e) {
firstContact = { firstContact = {
x: e.touches[0].clientX, x: e.touches[0].clientX,
time: Date.now() time: Date.now(),
}; };
}, { passive: true }); }, { passive: true });
document.addEventListener('touchmove', function (e) { document.addEventListener('touchmove', function(e) {
if (!firstContact) if (!firstContact) {
return; return;
}
var curX = e.touches[0].clientX; const curX = e.touches[0].clientX;
var xDiff = curX - firstContact.x, const xDiff = curX - firstContact.x,
tDiff = Date.now() - firstContact.time; tDiff = Date.now() - firstContact.time;
if (tDiff < 250 && Math.abs(xDiff) >= 150) { if (tDiff < 250 && Math.abs(xDiff) >= 150) {
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) {
showSidebar(); showSidebar();
else if (xDiff < 0 && curX < 300) } else if (xDiff < 0 && curX < 300) {
hideSidebar(); hideSidebar();
}
firstContact = null; firstContact = null;
} }
@ -542,49 +592,53 @@ function playground_text(playground, hidden = true) {
})(); })();
(function chapterNavigation() { (function chapterNavigation() {
document.addEventListener('keydown', function (e) { document.addEventListener('keydown', function(e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
if (window.search && window.search.hasFocus()) { return; } return;
var html = document.querySelector('html'); }
if (window.search && window.search.hasFocus()) {
return;
}
const html = document.querySelector('html');
function next() { function next() {
var nextButton = document.querySelector('.nav-chapters.next'); const nextButton = document.querySelector('.nav-chapters.next');
if (nextButton) { if (nextButton) {
window.location.href = nextButton.href; window.location.href = nextButton.href;
} }
} }
function prev() { function prev() {
var previousButton = document.querySelector('.nav-chapters.previous'); const previousButton = document.querySelector('.nav-chapters.previous');
if (previousButton) { if (previousButton) {
window.location.href = previousButton.href; window.location.href = previousButton.href;
} }
} }
switch (e.key) { switch (e.key) {
case 'ArrowRight': case 'ArrowRight':
e.preventDefault(); e.preventDefault();
if (html.dir == 'rtl') { if (html.dir === 'rtl') {
prev(); prev();
} else { } else {
next(); next();
} }
break; break;
case 'ArrowLeft': case 'ArrowLeft':
e.preventDefault(); e.preventDefault();
if (html.dir == 'rtl') { if (html.dir === 'rtl') {
next(); next();
} else { } else {
prev(); prev();
} }
break; break;
} }
}); });
})(); })();
(function clipboard() { (function clipboard() {
var clipButtons = document.querySelectorAll('.clip-button'); const clipButtons = document.querySelectorAll('.clip-button');
function hideTooltip(elem) { function hideTooltip(elem) {
elem.firstChild.innerText = ""; elem.firstChild.innerText = '';
elem.className = 'clip-button'; elem.className = 'clip-button';
} }
@ -593,58 +647,58 @@ function playground_text(playground, hidden = true) {
elem.className = 'clip-button tooltipped'; elem.className = 'clip-button tooltipped';
} }
var clipboardSnippets = new ClipboardJS('.clip-button', { const clipboardSnippets = new ClipboardJS('.clip-button', {
text: function (trigger) { text: function(trigger) {
hideTooltip(trigger); hideTooltip(trigger);
let playground = trigger.closest("pre"); const playground = trigger.closest('pre');
return playground_text(playground, false); return playground_text(playground, false);
} },
}); });
Array.from(clipButtons).forEach(function (clipButton) { Array.from(clipButtons).forEach(function(clipButton) {
clipButton.addEventListener('mouseout', function (e) { clipButton.addEventListener('mouseout', function(e) {
hideTooltip(e.currentTarget); hideTooltip(e.currentTarget);
}); });
}); });
clipboardSnippets.on('success', function (e) { clipboardSnippets.on('success', function(e) {
e.clearSelection(); e.clearSelection();
showTooltip(e.trigger, "Copied!"); showTooltip(e.trigger, 'Copied!');
}); });
clipboardSnippets.on('error', function (e) { clipboardSnippets.on('error', function(e) {
showTooltip(e.trigger, "Clipboard error!"); showTooltip(e.trigger, 'Clipboard error!');
}); });
})(); })();
(function scrollToTop () { (function scrollToTop() {
var menuTitle = document.querySelector('.menu-title'); const menuTitle = document.querySelector('.menu-title');
menuTitle.addEventListener('click', function () { menuTitle.addEventListener('click', function() {
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
}); });
})(); })();
(function controllMenu() { (function controllMenu() {
var menu = document.getElementById('menu-bar'); const menu = document.getElementById('menu-bar');
(function controllPosition() { (function controllPosition() {
var scrollTop = document.scrollingElement.scrollTop; let scrollTop = document.scrollingElement.scrollTop;
var prevScrollTop = scrollTop; let prevScrollTop = scrollTop;
var minMenuY = -menu.clientHeight - 50; const minMenuY = -menu.clientHeight - 50;
// When the script loads, the page can be at any scroll (e.g. if you reforesh it). // When the script loads, the page can be at any scroll (e.g. if you reforesh it).
menu.style.top = scrollTop + 'px'; menu.style.top = scrollTop + 'px';
// Same as parseInt(menu.style.top.slice(0, -2), but faster // Same as parseInt(menu.style.top.slice(0, -2), but faster
var topCache = menu.style.top.slice(0, -2); let topCache = menu.style.top.slice(0, -2);
menu.classList.remove('sticky'); menu.classList.remove('sticky');
var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster let stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
document.addEventListener('scroll', function () { document.addEventListener('scroll', function() {
scrollTop = Math.max(document.scrollingElement.scrollTop, 0); scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
// `null` means that it doesn't need to be updated // `null` means that it doesn't need to be updated
var nextSticky = null; let nextSticky = null;
var nextTop = null; let nextTop = null;
var scrollDown = scrollTop > prevScrollTop; const scrollDown = scrollTop > prevScrollTop;
var menuPosAbsoluteY = topCache - scrollTop; const menuPosAbsoluteY = topCache - scrollTop;
if (scrollDown) { if (scrollDown) {
nextSticky = false; nextSticky = false;
if (menuPosAbsoluteY > 0) { if (menuPosAbsoluteY > 0) {

View file

@ -55,8 +55,8 @@
<!-- Provide site root to javascript --> <!-- Provide site root to javascript -->
<script> <script>
var path_to_root = "{{ path_to_root }}"; const path_to_root = "{{ path_to_root }}";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}"; const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
</script> </script>
<!-- Start loading toc.js asap --> <!-- Start loading toc.js asap -->
<script src="{{ resource "toc.js" }}"></script> <script src="{{ resource "toc.js" }}"></script>
@ -66,8 +66,8 @@
<!-- Work around some values being stored in localStorage wrapped in quotes --> <!-- Work around some values being stored in localStorage wrapped in quotes -->
<script> <script>
try { try {
var theme = localStorage.getItem('mdbook-theme'); let theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar'); let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) { if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1)); localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
@ -81,7 +81,7 @@
<!-- Set the theme before any content is loaded, prevents flash --> <!-- Set the theme before any content is loaded, prevents flash -->
<script> <script>
var theme; let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { } try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; } if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement; const html = document.documentElement;
@ -94,8 +94,8 @@
<!-- Hide / unhide sidebar before it is displayed --> <!-- Hide / unhide sidebar before it is displayed -->
<script> <script>
var sidebar = null; let sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor"); const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) { if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { } try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible'; sidebar = sidebar || 'visible';
@ -251,7 +251,7 @@
{{#if google_analytics}} {{#if google_analytics}}
<!-- Google Analytics Tag --> <!-- Google Analytics Tag -->
<script> <script>
var localAddrs = ["localhost", "127.0.0.1", ""]; const localAddrs = ["localhost", "127.0.0.1", ""];
// make sure we don't activate google analytics if the developer is // make sure we don't activate google analytics if the developer is
// inspecting the book locally... // inspecting the book locally...

View file

@ -1,4 +1,7 @@
"use strict"; 'use strict';
/* global Mark, elasticlunr, path_to_root */
window.search = window.search || {}; window.search = window.search || {};
(function search(search) { (function search(search) {
// Search functionality // Search functionality
@ -10,43 +13,26 @@ window.search = window.search || {};
return; return;
} }
//IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith // eslint-disable-next-line max-len
// IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
if (!String.prototype.startsWith) { if (!String.prototype.startsWith) {
String.prototype.startsWith = function(search, pos) { String.prototype.startsWith = function(search, pos) {
return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
}; };
} }
var search_wrap = document.getElementById('search-wrapper'), const search_wrap = document.getElementById('search-wrapper'),
searchbar = document.getElementById('searchbar'), searchbar = document.getElementById('searchbar'),
searchbar_outer = document.getElementById('searchbar-outer'),
searchresults = document.getElementById('searchresults'), searchresults = document.getElementById('searchresults'),
searchresults_outer = document.getElementById('searchresults-outer'), searchresults_outer = document.getElementById('searchresults-outer'),
searchresults_header = document.getElementById('searchresults-header'), searchresults_header = document.getElementById('searchresults-header'),
searchicon = document.getElementById('search-toggle'), searchicon = document.getElementById('search-toggle'),
content = document.getElementById('content'), content = document.getElementById('content'),
searchindex = null,
doc_urls = [],
results_options = {
teaser_word_count: 30,
limit_results: 30,
},
search_options = {
bool: "AND",
expand: true,
fields: {
title: {boost: 1},
body: {boost: 1},
breadcrumbs: {boost: 0}
}
},
mark_exclude = [], mark_exclude = [],
marker = new Mark(content), marker = new Mark(content),
current_searchterm = "",
URL_SEARCH_PARAM = 'search', URL_SEARCH_PARAM = 'search',
URL_MARK_PARAM = 'highlight', URL_MARK_PARAM = 'highlight',
teaser_count = 0,
SEARCH_HOTKEY_KEYCODE = 83, SEARCH_HOTKEY_KEYCODE = 83,
ESCAPE_KEYCODE = 27, ESCAPE_KEYCODE = 27,
@ -54,6 +40,24 @@ window.search = window.search || {};
UP_KEYCODE = 38, UP_KEYCODE = 38,
SELECT_KEYCODE = 13; SELECT_KEYCODE = 13;
let current_searchterm = '',
doc_urls = [],
search_options = {
bool: 'AND',
expand: true,
fields: {
title: {boost: 1},
body: {boost: 1},
breadcrumbs: {boost: 0},
},
},
searchindex = null,
results_options = {
teaser_word_count: 30,
limit_results: 30,
},
teaser_count = 0;
function hasFocus() { function hasFocus() {
return searchbar === document.activeElement; return searchbar === document.activeElement;
} }
@ -66,96 +70,99 @@ window.search = window.search || {};
// Helper to parse a url into its building blocks. // Helper to parse a url into its building blocks.
function parseURL(url) { function parseURL(url) {
var a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
return { return {
source: url, source: url,
protocol: a.protocol.replace(':',''), protocol: a.protocol.replace(':', ''),
host: a.hostname, host: a.hostname,
port: a.port, port: a.port,
params: (function(){ params: (function() {
var ret = {}; const ret = {};
var seg = a.search.replace(/^\?/,'').split('&'); const seg = a.search.replace(/^\?/, '').split('&');
var len = seg.length, i = 0, s; for (const part of seg) {
for (;i<len;i++) { if (!part) {
if (!seg[i]) { continue; } continue;
s = seg[i].split('='); }
const s = part.split('=');
ret[s[0]] = s[1]; ret[s[0]] = s[1];
} }
return ret; return ret;
})(), })(),
file: (a.pathname.match(/\/([^/?#]+)$/i) || [,''])[1], file: (a.pathname.match(/\/([^/?#]+)$/i) || ['', ''])[1],
hash: a.hash.replace('#',''), hash: a.hash.replace('#', ''),
path: a.pathname.replace(/^([^/])/,'/$1') path: a.pathname.replace(/^([^/])/, '/$1'),
}; };
} }
// Helper to recreate a url string from its building blocks. // Helper to recreate a url string from its building blocks.
function renderURL(urlobject) { function renderURL(urlobject) {
var url = urlobject.protocol + "://" + urlobject.host; let url = urlobject.protocol + '://' + urlobject.host;
if (urlobject.port != "") { if (urlobject.port !== '') {
url += ":" + urlobject.port; url += ':' + urlobject.port;
} }
url += urlobject.path; url += urlobject.path;
var joiner = "?"; let joiner = '?';
for(var prop in urlobject.params) { for (const prop in urlobject.params) {
if(urlobject.params.hasOwnProperty(prop)) { if (Object.prototype.hasOwnProperty.call(urlobject.params, prop)) {
url += joiner + prop + "=" + urlobject.params[prop]; url += joiner + prop + '=' + urlobject.params[prop];
joiner = "&"; joiner = '&';
} }
} }
if (urlobject.hash != "") { if (urlobject.hash !== '') {
url += "#" + urlobject.hash; url += '#' + urlobject.hash;
} }
return url; return url;
} }
// Helper to escape html special chars for displaying the teasers // Helper to escape html special chars for displaying the teasers
var escapeHTML = (function() { const escapeHTML = (function() {
var MAP = { const MAP = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;',
'"': '&#34;', '"': '&#34;',
"'": '&#39;' '\'': '&#39;',
};
const repl = function(c) {
return MAP[c];
}; };
var repl = function(c) { return MAP[c]; };
return function(s) { return function(s) {
return s.replace(/[&<>'"]/g, repl); return s.replace(/[&<>'"]/g, repl);
}; };
})(); })();
function formatSearchMetric(count, searchterm) { function formatSearchMetric(count, searchterm) {
if (count == 1) { if (count === 1) {
return count + " search result for '" + searchterm + "':"; return count + ' search result for \'' + searchterm + '\':';
} else if (count == 0) { } else if (count === 0) {
return "No search results for '" + searchterm + "'."; return 'No search results for \'' + searchterm + '\'.';
} else { } else {
return count + " search results for '" + searchterm + "':"; return count + ' search results for \'' + searchterm + '\':';
} }
} }
function formatSearchResult(result, searchterms) { function formatSearchResult(result, searchterms) {
var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); const teaser = makeTeaser(escapeHTML(result.doc.body), searchterms);
teaser_count++; teaser_count++;
// The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor
var url = doc_urls[result.ref].split("#"); const url = doc_urls[result.ref].split('#');
if (url.length == 1) { // no anchor found if (url.length === 1) { // no anchor found
url.push(""); url.push('');
} }
// encodeURIComponent escapes all chars that could allow an XSS except // encodeURIComponent escapes all chars that could allow an XSS except
// for '. Due to that we also manually replace ' with its url-encoded // for '. Due to that we also manually replace ' with its url-encoded
// representation (%27). // representation (%27).
var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27"); const encoded_search = encodeURIComponent(searchterms.join(' ')).replace(/'/g, '%27');
return '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + searchterms + '#' + url[1] return '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + encoded_search
+ '" aria-details="teaser_' + teaser_count + '">' + result.doc.breadcrumbs + '</a>' + '#' + url[1] + '" aria-details="teaser_' + teaser_count + '">'
+ '<span class="teaser" id="teaser_' + teaser_count + '" aria-label="Search Result Teaser">' + result.doc.breadcrumbs + '</a>' + '<span class="teaser" id="teaser_' + teaser_count
+ teaser + '</span>'; + '" aria-label="Search Result Teaser">' + teaser + '</span>';
} }
function makeTeaser(body, searchterms) { function makeTeaser(body, searchterms) {
// The strategy is as follows: // The strategy is as follows:
// First, assign a value to each word in the document: // First, assign a value to each word in the document:
@ -166,88 +173,90 @@ window.search = window.search || {};
// sum of the values of the words within the window. Then use the window that got the // sum of the values of the words within the window. Then use the window that got the
// maximum sum. If there are multiple maximas, then get the last one. // maximum sum. If there are multiple maximas, then get the last one.
// Enclose the terms in <em>. // Enclose the terms in <em>.
var stemmed_searchterms = searchterms.map(function(w) { const stemmed_searchterms = searchterms.map(function(w) {
return elasticlunr.stemmer(w.toLowerCase()); return elasticlunr.stemmer(w.toLowerCase());
}); });
var searchterm_weight = 40; const searchterm_weight = 40;
var weighted = []; // contains elements of ["word", weight, index_in_document] const weighted = []; // contains elements of ["word", weight, index_in_document]
// split in sentences, then words // split in sentences, then words
var sentences = body.toLowerCase().split('. '); const sentences = body.toLowerCase().split('. ');
var index = 0; let index = 0;
var value = 0; let value = 0;
var searchterm_found = false; let searchterm_found = false;
for (var sentenceindex in sentences) { for (const sentenceindex in sentences) {
var words = sentences[sentenceindex].split(' '); const words = sentences[sentenceindex].split(' ');
value = 8; value = 8;
for (var wordindex in words) { for (const wordindex in words) {
var word = words[wordindex]; const word = words[wordindex];
if (word.length > 0) { if (word.length > 0) {
for (var searchtermindex in stemmed_searchterms) { for (const searchtermindex in stemmed_searchterms) {
if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) { if (elasticlunr.stemmer(word).startsWith(
stemmed_searchterms[searchtermindex])
) {
value = searchterm_weight; value = searchterm_weight;
searchterm_found = true; searchterm_found = true;
} }
}; }
weighted.push([word, value, index]); weighted.push([word, value, index]);
value = 2; value = 2;
} }
index += word.length; index += word.length;
index += 1; // ' ' or '.' if last word in sentence index += 1; // ' ' or '.' if last word in sentence
}; }
index += 1; // because we split at a two-char boundary '. ' index += 1; // because we split at a two-char boundary '. '
}; }
if (weighted.length == 0) { if (weighted.length === 0) {
return body; return body;
} }
var window_weight = []; const window_weight = [];
var window_size = Math.min(weighted.length, results_options.teaser_word_count); const window_size = Math.min(weighted.length, results_options.teaser_word_count);
var cur_sum = 0; let cur_sum = 0;
for (var wordindex = 0; wordindex < window_size; wordindex++) { for (let wordindex = 0; wordindex < window_size; wordindex++) {
cur_sum += weighted[wordindex][1]; cur_sum += weighted[wordindex][1];
}; }
window_weight.push(cur_sum); window_weight.push(cur_sum);
for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { for (let wordindex = 0; wordindex < weighted.length - window_size; wordindex++) {
cur_sum -= weighted[wordindex][1]; cur_sum -= weighted[wordindex][1];
cur_sum += weighted[wordindex + window_size][1]; cur_sum += weighted[wordindex + window_size][1];
window_weight.push(cur_sum); window_weight.push(cur_sum);
}; }
let max_sum_window_index = 0;
if (searchterm_found) { if (searchterm_found) {
var max_sum = 0; let max_sum = 0;
var max_sum_window_index = 0;
// backwards // backwards
for (var i = window_weight.length - 1; i >= 0; i--) { for (let i = window_weight.length - 1; i >= 0; i--) {
if (window_weight[i] > max_sum) { if (window_weight[i] > max_sum) {
max_sum = window_weight[i]; max_sum = window_weight[i];
max_sum_window_index = i; max_sum_window_index = i;
} }
}; }
} else { } else {
max_sum_window_index = 0; max_sum_window_index = 0;
} }
// add <em/> around searchterms // add <em/> around searchterms
var teaser_split = []; const teaser_split = [];
var index = weighted[max_sum_window_index][2]; index = weighted[max_sum_window_index][2];
for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) { for (let i = max_sum_window_index; i < max_sum_window_index + window_size; i++) {
var word = weighted[i]; const word = weighted[i];
if (index < word[2]) { if (index < word[2]) {
// missing text from index to start of `word` // missing text from index to start of `word`
teaser_split.push(body.substring(index, word[2])); teaser_split.push(body.substring(index, word[2]));
index = word[2]; index = word[2];
} }
if (word[1] == searchterm_weight) { if (word[1] === searchterm_weight) {
teaser_split.push("<em>") teaser_split.push('<em>');
} }
index = word[2] + word[0].length; index = word[2] + word[0].length;
teaser_split.push(body.substring(word[2], index)); teaser_split.push(body.substring(word[2], index));
if (word[1] == searchterm_weight) { if (word[1] === searchterm_weight) {
teaser_split.push("</em>") teaser_split.push('</em>');
} }
}; }
return teaser_split.join(''); return teaser_split.join('');
} }
@ -255,74 +264,95 @@ window.search = window.search || {};
function init(config) { function init(config) {
results_options = config.results_options; results_options = config.results_options;
search_options = config.search_options; search_options = config.search_options;
searchbar_outer = config.searchbar_outer;
doc_urls = config.doc_urls; doc_urls = config.doc_urls;
searchindex = elasticlunr.Index.load(config.index); searchindex = elasticlunr.Index.load(config.index);
// Set up events // Set up events
searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false); searchicon.addEventListener('click', () => {
searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false); searchIconClickHandler();
document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false); }, false);
searchbar.addEventListener('keyup', () => {
searchbarKeyUpHandler();
}, false);
document.addEventListener('keydown', e => {
globalKeyHandler(e);
}, false);
// If the user uses the browser buttons, do the same as if a reload happened // If the user uses the browser buttons, do the same as if a reload happened
window.onpopstate = function(e) { doSearchOrMarkFromUrl(); }; window.onpopstate = () => {
doSearchOrMarkFromUrl();
};
// Suppress "submit" events so the page doesn't reload when the user presses Enter // Suppress "submit" events so the page doesn't reload when the user presses Enter
document.addEventListener('submit', function(e) { e.preventDefault(); }, false); document.addEventListener('submit', e => {
e.preventDefault();
}, false);
// If reloaded, do the search or mark again, depending on the current url parameters // If reloaded, do the search or mark again, depending on the current url parameters
doSearchOrMarkFromUrl(); doSearchOrMarkFromUrl();
} }
function unfocusSearchbar() { function unfocusSearchbar() {
// hacky, but just focusing a div only works once // hacky, but just focusing a div only works once
var tmp = document.createElement('input'); const tmp = document.createElement('input');
tmp.setAttribute('style', 'position: absolute; opacity: 0;'); tmp.setAttribute('style', 'position: absolute; opacity: 0;');
searchicon.appendChild(tmp); searchicon.appendChild(tmp);
tmp.focus(); tmp.focus();
tmp.remove(); tmp.remove();
} }
// On reload or browser history backwards/forwards events, parse the url and do search or mark // On reload or browser history backwards/forwards events, parse the url and do search or mark
function doSearchOrMarkFromUrl() { function doSearchOrMarkFromUrl() {
// Check current URL for search request // Check current URL for search request
var url = parseURL(window.location.href); const url = parseURL(window.location.href);
if (url.params.hasOwnProperty(URL_SEARCH_PARAM) if (Object.prototype.hasOwnProperty.call(url.params, URL_SEARCH_PARAM)
&& url.params[URL_SEARCH_PARAM] != "") { && url.params[URL_SEARCH_PARAM] !== '') {
showSearch(true); showSearch(true);
searchbar.value = decodeURIComponent( searchbar.value = decodeURIComponent(
(url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); (url.params[URL_SEARCH_PARAM] + '').replace(/\+/g, '%20'));
searchbarKeyUpHandler(); // -> doSearch() searchbarKeyUpHandler(); // -> doSearch()
} else { } else {
showSearch(false); showSearch(false);
} }
if (url.params.hasOwnProperty(URL_MARK_PARAM)) { if (Object.prototype.hasOwnProperty.call(url.params, URL_MARK_PARAM)) {
var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' '); const words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' ');
marker.mark(words, { marker.mark(words, {
exclude: mark_exclude exclude: mark_exclude,
}); });
var markers = document.querySelectorAll("mark"); const markers = document.querySelectorAll('mark');
function hide() { const hide = () => {
for (var i = 0; i < markers.length; i++) { for (let i = 0; i < markers.length; i++) {
markers[i].classList.add("fade-out"); markers[i].classList.add('fade-out');
window.setTimeout(function(e) { marker.unmark(); }, 300); window.setTimeout(() => {
marker.unmark();
}, 300);
} }
} };
for (var i = 0; i < markers.length; i++) {
for (let i = 0; i < markers.length; i++) {
markers[i].addEventListener('click', hide); markers[i].addEventListener('click', hide);
} }
} }
} }
// Eventhandler for keyevents on `document` // Eventhandler for keyevents on `document`
function globalKeyHandler(e) { function globalKeyHandler(e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text' || !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)) { return; } if (e.altKey ||
e.ctrlKey ||
e.metaKey ||
e.shiftKey ||
e.target.type === 'textarea' ||
e.target.type === 'text' ||
!hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)
) {
return;
}
if (e.keyCode === ESCAPE_KEYCODE) { if (e.keyCode === ESCAPE_KEYCODE) {
e.preventDefault(); e.preventDefault();
searchbar.classList.remove("active"); searchbar.classList.remove('active');
setSearchUrlParameters("", setSearchUrlParameters('',
(searchbar.value.trim() !== "") ? "push" : "replace"); searchbar.value.trim() !== '' ? 'push' : 'replace');
if (hasFocus()) { if (hasFocus()) {
unfocusSearchbar(); unfocusSearchbar();
} }
@ -336,25 +366,27 @@ window.search = window.search || {};
} else if (hasFocus() && e.keyCode === DOWN_KEYCODE) { } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) {
e.preventDefault(); e.preventDefault();
unfocusSearchbar(); unfocusSearchbar();
searchresults.firstElementChild.classList.add("focus"); searchresults.firstElementChild.classList.add('focus');
} else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE
|| e.keyCode === UP_KEYCODE || e.keyCode === UP_KEYCODE
|| e.keyCode === SELECT_KEYCODE)) { || e.keyCode === SELECT_KEYCODE)) {
// not `:focus` because browser does annoying scrolling // not `:focus` because browser does annoying scrolling
var focused = searchresults.querySelector("li.focus"); const focused = searchresults.querySelector('li.focus');
if (!focused) return; if (!focused) {
return;
}
e.preventDefault(); e.preventDefault();
if (e.keyCode === DOWN_KEYCODE) { if (e.keyCode === DOWN_KEYCODE) {
var next = focused.nextElementSibling; const next = focused.nextElementSibling;
if (next) { if (next) {
focused.classList.remove("focus"); focused.classList.remove('focus');
next.classList.add("focus"); next.classList.add('focus');
} }
} else if (e.keyCode === UP_KEYCODE) { } else if (e.keyCode === UP_KEYCODE) {
focused.classList.remove("focus"); focused.classList.remove('focus');
var prev = focused.previousElementSibling; const prev = focused.previousElementSibling;
if (prev) { if (prev) {
prev.classList.add("focus"); prev.classList.add('focus');
} else { } else {
searchbar.select(); searchbar.select();
} }
@ -371,9 +403,9 @@ window.search = window.search || {};
} else { } else {
search_wrap.classList.add('hidden'); search_wrap.classList.add('hidden');
searchicon.setAttribute('aria-expanded', 'false'); searchicon.setAttribute('aria-expanded', 'false');
var results = searchresults.children; const results = searchresults.children;
for (var i = 0; i < results.length; i++) { for (let i = 0; i < results.length; i++) {
results[i].classList.remove("focus"); results[i].classList.remove('focus');
} }
} }
} }
@ -396,36 +428,37 @@ window.search = window.search || {};
showSearch(false); showSearch(false);
} }
} }
// Eventhandler for keyevents while the searchbar is focused // Eventhandler for keyevents while the searchbar is focused
function searchbarKeyUpHandler() { function searchbarKeyUpHandler() {
var searchterm = searchbar.value.trim(); const searchterm = searchbar.value.trim();
if (searchterm != "") { if (searchterm !== '') {
searchbar.classList.add("active"); searchbar.classList.add('active');
doSearch(searchterm); doSearch(searchterm);
} else { } else {
searchbar.classList.remove("active"); searchbar.classList.remove('active');
showResults(false); showResults(false);
removeChildren(searchresults); removeChildren(searchresults);
} }
setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); setSearchUrlParameters(searchterm, 'push_if_new_search_else_replace');
// Remove marks // Remove marks
marker.unmark(); marker.unmark();
} }
// Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor . // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and
// `action` can be one of "push", "replace", "push_if_new_search_else_replace" // `#heading-anchor`. `action` can be one of "push", "replace",
// and replaces or pushes a new browser history item. // "push_if_new_search_else_replace" and replaces or pushes a new browser history item.
// "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet.
function setSearchUrlParameters(searchterm, action) { function setSearchUrlParameters(searchterm, action) {
var url = parseURL(window.location.href); const url = parseURL(window.location.href);
var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM); const first_search = !Object.prototype.hasOwnProperty.call(url.params, URL_SEARCH_PARAM);
if (searchterm != "" || action == "push_if_new_search_else_replace") {
if (searchterm !== '' || action === 'push_if_new_search_else_replace') {
url.params[URL_SEARCH_PARAM] = searchterm; url.params[URL_SEARCH_PARAM] = searchterm;
delete url.params[URL_MARK_PARAM]; delete url.params[URL_MARK_PARAM];
url.hash = ""; url.hash = '';
} else { } else {
delete url.params[URL_MARK_PARAM]; delete url.params[URL_MARK_PARAM];
delete url.params[URL_SEARCH_PARAM]; delete url.params[URL_SEARCH_PARAM];
@ -433,32 +466,40 @@ window.search = window.search || {};
// A new search will also add a new history item, so the user can go back // A new search will also add a new history item, so the user can go back
// to the page prior to searching. A updated search term will only replace // to the page prior to searching. A updated search term will only replace
// the url. // the url.
if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { if (action === 'push' || action === 'push_if_new_search_else_replace' && first_search ) {
history.pushState({}, document.title, renderURL(url)); history.pushState({}, document.title, renderURL(url));
} else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { } else if (action === 'replace' ||
action === 'push_if_new_search_else_replace' &&
!first_search
) {
history.replaceState({}, document.title, renderURL(url)); history.replaceState({}, document.title, renderURL(url));
} }
} }
function doSearch(searchterm) { function doSearch(searchterm) {
// Don't search the same twice // Don't search the same twice
if (current_searchterm == searchterm) { return; } if (current_searchterm === searchterm) {
else { current_searchterm = searchterm; } return;
} else {
current_searchterm = searchterm;
}
if (searchindex == null) { return; } if (searchindex === null) {
return;
}
// Do the actual search // Do the actual search
var results = searchindex.search(searchterm, search_options); const results = searchindex.search(searchterm, search_options);
var resultcount = Math.min(results.length, results_options.limit_results); const resultcount = Math.min(results.length, results_options.limit_results);
// Display search metrics // Display search metrics
searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); searchresults_header.innerText = formatSearchMetric(resultcount, searchterm);
// Clear and insert results // Clear and insert results
var searchterms = searchterm.split(' '); const searchterms = searchterm.split(' ');
removeChildren(searchresults); removeChildren(searchresults);
for(var i = 0; i < resultcount ; i++){ for (let i = 0; i < resultcount ; i++) {
var resultElem = document.createElement('li'); const resultElem = document.createElement('li');
resultElem.innerHTML = formatSearchResult(results[i], searchterms); resultElem.innerHTML = formatSearchResult(results[i], searchterms);
searchresults.appendChild(resultElem); searchresults.appendChild(resultElem);
} }
@ -473,7 +514,7 @@ window.search = window.search || {};
script.id = id; script.id = id;
script.onload = () => init(window.search); script.onload = () => init(window.search);
script.onerror = error => { script.onerror = error => {
console.error(`Failed to load \`${url}\`: ${error}`); console.error(`Failed to load \`${url}\`: ${error}`);
}; };
document.head.append(script); document.head.append(script);
} }

View file

@ -1,3 +1,4 @@
use serde_json::Value;
use std::collections::HashSet; use std::collections::HashSet;
use std::env::current_dir; use std::env::current_dir;
use std::fs::{read_dir, read_to_string, remove_dir_all}; use std::fs::{read_dir, read_to_string, remove_dir_all};
@ -27,15 +28,18 @@ fn get_available_browser_ui_test_version() -> Option<String> {
} }
fn expected_browser_ui_test_version() -> String { fn expected_browser_ui_test_version() -> String {
let content = read_to_string(".github/workflows/main.yml") let content = read_to_string("package.json").expect("failed to read `package.json`");
.expect("failed to read `.github/workflows/main.yml`"); let v: Value = serde_json::from_str(&content).expect("failed to parse `package.json`");
for line in content.lines() { let Some(dependencies) = v.get("dependencies") else {
let line = line.trim(); panic!("Missing `dependencies` key in `package.json`");
if let Some(version) = line.strip_prefix("BROWSER_UI_TEST_VERSION:") { };
return version.trim().replace('\'', ""); let Some(browser_ui_test) = dependencies.get("browser-ui-test") else {
} panic!("Missing `browser-ui-test` key in \"dependencies\" object in `package.json`");
} };
panic!("failed to retrieved `browser-ui-test` version"); let Value::String(version) = browser_ui_test else {
panic!("`browser-ui-test` version is not a string");
};
version.trim().to_string()
} }
fn main() { fn main() {