mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-28 20:16:58 +03:00
407 lines
13 KiB
JavaScript
407 lines
13 KiB
JavaScript
"use strict";
|
|
|
|
const searchState = {
|
|
lastSearch: '',
|
|
filterLints: (updateURL = true) => {
|
|
function matchesSearch(lint, terms, searchStr) {
|
|
// Search by id
|
|
if (lint.elem.id.indexOf(searchStr) !== -1) {
|
|
return true;
|
|
}
|
|
// Search the description
|
|
// The use of `for`-loops instead of `foreach` enables us to return early
|
|
const docsLowerCase = lint.elem.textContent.toLowerCase();
|
|
for (const term of terms) {
|
|
// This is more likely and will therefore be checked first
|
|
if (docsLowerCase.indexOf(term) !== -1) {
|
|
return true;
|
|
}
|
|
|
|
if (lint.elem.id.indexOf(term) !== -1) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
let searchStr = elements.search.value.trim().toLowerCase();
|
|
if (searchStr.startsWith("clippy::")) {
|
|
searchStr = searchStr.slice(8);
|
|
}
|
|
if (searchState.lastSearch === searchStr) {
|
|
return;
|
|
}
|
|
searchState.lastSearch = searchStr;
|
|
const terms = searchStr.split(" ");
|
|
const cleanedSearchStr = searchStr.replaceAll("-", "_");
|
|
|
|
for (const lint of filters.getAllLints()) {
|
|
lint.searchFilteredOut = !matchesSearch(lint, terms, cleanedSearchStr);
|
|
if (lint.searchFilteredOut || lint.filteredOut) {
|
|
lint.elem.style.display = "none";
|
|
} else {
|
|
lint.elem.style.display = "";
|
|
}
|
|
}
|
|
|
|
if (updateURL) {
|
|
setURL();
|
|
}
|
|
},
|
|
};
|
|
|
|
function handleInputChanged(event) {
|
|
if (event.target !== document.activeElement) {
|
|
return;
|
|
}
|
|
searchState.filterLints();
|
|
}
|
|
|
|
function handleShortcut(ev) {
|
|
if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) {
|
|
return;
|
|
}
|
|
|
|
if (document.activeElement.tagName === "INPUT") {
|
|
if (ev.key === "Escape") {
|
|
document.activeElement.blur();
|
|
}
|
|
} else {
|
|
switch (ev.key) {
|
|
case "s":
|
|
case "S":
|
|
case "/":
|
|
ev.preventDefault(); // To prevent the key to be put into the input.
|
|
elements.search.focus();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When `value` is `null` the default value is used - true for all but the `deprecated` lint group
|
|
*/
|
|
function resetCheckboxes(filter, value) {
|
|
for (const element of elements.checkboxes[filter]) {
|
|
const checked = value ?? element.name !== "deprecated";
|
|
element.checked = checked;
|
|
}
|
|
}
|
|
|
|
function onEachLazy(lazyArray, func) {
|
|
const arr = Array.prototype.slice.call(lazyArray);
|
|
for (const el of arr) {
|
|
func(el);
|
|
}
|
|
}
|
|
|
|
function expandLint(lintId) {
|
|
const elem = document.querySelector(`#${lintId} > input[type="checkbox"]`);
|
|
if (elem) {
|
|
elem.checked = true;
|
|
}
|
|
}
|
|
|
|
const clipboardTimeouts = new Map();
|
|
function copyToClipboard(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
const clipboard = event.target;
|
|
|
|
navigator.clipboard.writeText("clippy::" + clipboard.parentElement.id.slice(5));
|
|
|
|
clipboard.textContent = "✓";
|
|
|
|
clearTimeout(clipboardTimeouts.get(clipboard));
|
|
clipboardTimeouts.set(
|
|
clipboard,
|
|
setTimeout(() => {
|
|
clipboard.textContent = "📋";
|
|
clipboardTimeouts.delete(clipboard);
|
|
}, 1000)
|
|
);
|
|
}
|
|
|
|
function toggleExpansion(expand) {
|
|
for (const checkbox of document.querySelectorAll("article input[type=checkbox]")) {
|
|
checkbox.checked = expand;
|
|
}
|
|
}
|
|
|
|
const filters = {
|
|
allLints: null,
|
|
getAllLints: () => {
|
|
if (filters.allLints === null) {
|
|
filters.allLints = Array.prototype.slice.call(
|
|
document.getElementsByTagName("article"),
|
|
).map(elem => {
|
|
let version = elem.querySelector(".label-version").innerText;
|
|
// Strip the "pre " prefix for pre 1.29.0 lints
|
|
if (version.startsWith("pre ")) {
|
|
version = version.slice(4);
|
|
}
|
|
return {
|
|
elem: elem,
|
|
group: elem.querySelector(".lint-group").innerText,
|
|
level: elem.querySelector(".lint-level").innerText,
|
|
version: parseInt(version.split(".")[1]),
|
|
applicability: elem.querySelector(".applicability").innerText,
|
|
filteredOut: false,
|
|
searchFilteredOut: false,
|
|
};
|
|
});
|
|
}
|
|
return filters.allLints;
|
|
},
|
|
filterLints: (updateURL = true) => {
|
|
const [levels, groups, applicabilities] = ["levels", "groups", "applicabilities"].map(key => new Set(
|
|
elements.checkboxes[key]
|
|
.filter(checkbox => checkbox.checked)
|
|
.map(checkbox => checkbox.name)
|
|
));
|
|
|
|
const [lte, gte, eq] = ["lte", "gte", "eq"].map(key => Number(elements.versions[key].value));
|
|
|
|
elements.counts.versions.textContent = (lte > 0) + (gte > 0) + (eq > 0);
|
|
elements.counts.groups.textContent = groups.size;
|
|
elements.counts.levels.textContent = levels.size;
|
|
elements.counts.applicabilities.textContent = applicabilities.size;
|
|
|
|
for (const lint of filters.getAllLints()) {
|
|
lint.filteredOut = (!groups.has(lint.group)
|
|
|| !levels.has(lint.level)
|
|
|| !applicabilities.has(lint.applicability)
|
|
|| !(eq === 0 || lint.version === eq)
|
|
|| !(gte === 0 || lint.version >= gte)
|
|
|| !(lte === 0 || lint.version <= lte)
|
|
);
|
|
if (lint.filteredOut || lint.searchFilteredOut) {
|
|
lint.elem.style.display = "none";
|
|
} else {
|
|
lint.elem.style.display = "";
|
|
}
|
|
}
|
|
|
|
if (updateURL) {
|
|
setURL();
|
|
}
|
|
},
|
|
};
|
|
|
|
function setupDropdown(elem) {
|
|
const button = elem.querySelector("button");
|
|
button.onclick = () => elem.classList.toggle("open");
|
|
|
|
const setBlur = child => {
|
|
child.onblur = event => {
|
|
if (!elem.contains(document.activeElement) &&
|
|
!elem.contains(event.relatedTarget)
|
|
) {
|
|
elem.classList.remove("open");
|
|
}
|
|
}
|
|
};
|
|
onEachLazy(elem.children, setBlur);
|
|
onEachLazy(elem.querySelectorAll("select"), setBlur);
|
|
onEachLazy(elem.querySelectorAll("input"), setBlur);
|
|
onEachLazy(elem.querySelectorAll("ul button"), setBlur);
|
|
}
|
|
|
|
function setURL() {
|
|
const url = new URL(location);
|
|
|
|
function nonDefault(filter) {
|
|
return elements.checkboxes[filter]
|
|
.some(element => element.checked === (element.name === "deprecated"));
|
|
}
|
|
function setBoolean(filter) {
|
|
if (nonDefault(filter)) {
|
|
const value = elements.checkboxes[filter]
|
|
.filter(el => el.checked)
|
|
.map(el => el.name)
|
|
.join(",");
|
|
url.searchParams.set(filter, value);
|
|
}
|
|
}
|
|
|
|
url.search = "";
|
|
setBoolean("groups");
|
|
setBoolean("levels");
|
|
setBoolean("applicabilities");
|
|
|
|
const versions = ["eq", "gte", "lte"]
|
|
.filter(op => elements.versions[op].value)
|
|
.map(op => `${op}:${elements.versions[op].value}`)
|
|
.join(",");
|
|
if (versions) {
|
|
url.searchParams.set("versions", versions);
|
|
}
|
|
|
|
const search = elements.search.value;
|
|
if (search) {
|
|
url.searchParams.set("search", search)
|
|
}
|
|
|
|
url.hash = "";
|
|
|
|
if (!history.state) {
|
|
history.pushState(true, "", url);
|
|
} else {
|
|
history.replaceState(true, "", url);
|
|
}
|
|
}
|
|
|
|
function parseURL() {
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
for (const [filter, checkboxes] of Object.entries(elements.checkboxes)) {
|
|
if (params.has(filter)) {
|
|
const settings = new Set(params.get(filter).split(","));
|
|
|
|
for (const checkbox of checkboxes) {
|
|
checkbox.checked = settings.has(checkbox.name);
|
|
}
|
|
} else {
|
|
resetCheckboxes(filter, null);
|
|
}
|
|
}
|
|
|
|
const versionStr = params.get("versions") ?? "";
|
|
const versions = new Map(versionStr.split(",").map(elem => elem.split(":")));
|
|
for (const element of Object.values(elements.versions)) {
|
|
element.value = versions.get(element.name) ?? "";
|
|
}
|
|
|
|
elements.search.value = params.get("search");
|
|
|
|
if (location.hash) {
|
|
expandLint(location.hash.slice(1));
|
|
}
|
|
|
|
filters.filterLints(false);
|
|
searchState.filterLints(false);
|
|
}
|
|
|
|
function addResetListeners(selector, value) {
|
|
for (const button of document.querySelectorAll(selector)) {
|
|
button.addEventListener("click", event => {
|
|
const container = event.target.closest("[data-filter]");
|
|
const filter = container.dataset.filter;
|
|
resetCheckboxes(filter, value);
|
|
filters.filterLints();
|
|
})
|
|
}
|
|
}
|
|
|
|
function addListeners() {
|
|
document.getElementById("upper-filters").addEventListener("input", () => {
|
|
filters.filterLints();
|
|
});
|
|
elements.search.addEventListener("input", handleInputChanged);
|
|
|
|
elements.disableShortcuts.addEventListener("change", () => {
|
|
disableShortcuts = elements.disableShortcuts.checked;
|
|
storeValue("disable-shortcuts", disableShortcuts);
|
|
});
|
|
|
|
document.getElementById("expand-all").addEventListener("click", () => toggleExpansion(true));
|
|
document.getElementById("collapse-all").addEventListener("click", () => toggleExpansion(false));
|
|
|
|
// A delegated listener to avoid the upfront cost of >1000 listeners
|
|
document.addEventListener("click", event => {
|
|
if (!event.target instanceof HTMLAnchorElement) {
|
|
return;
|
|
}
|
|
|
|
if (event.target.classList.contains("copy-to-clipboard")) {
|
|
copyToClipboard(event);
|
|
} else if (event.target.classList.contains("anchor")) {
|
|
event.target.closest("article")
|
|
.querySelector(`input[type="checkbox"]`)
|
|
.checked = true;
|
|
}
|
|
});
|
|
|
|
document.getElementById("filter-clear").addEventListener("click", () => {
|
|
elements.search.value = "";
|
|
searchState.filterLints();
|
|
})
|
|
|
|
addResetListeners(".reset-all", true);
|
|
addResetListeners(".reset-none", false);
|
|
addResetListeners(".reset-default", null);
|
|
|
|
document.getElementById("reset-versions").addEventListener("click", () => {
|
|
for (const input of Object.values(elements.versions)) {
|
|
input.value = "";
|
|
}
|
|
filters.filterLints();
|
|
});
|
|
|
|
document.addEventListener("keypress", handleShortcut);
|
|
document.addEventListener("keydown", handleShortcut);
|
|
|
|
document.querySelectorAll(".dropdown").forEach(setupDropdown);
|
|
|
|
addEventListener("popstate", parseURL);
|
|
}
|
|
|
|
// Highlight code blocks only when they approach the viewport so that clicking the "Expand All"
|
|
// button doesn't take a long time
|
|
function highlightLazily() {
|
|
if (!'IntersectionObserver' in window) {
|
|
return;
|
|
}
|
|
const observer = new IntersectionObserver((entries) => {
|
|
for (const entry of entries) {
|
|
if (entry.isIntersecting) {
|
|
observer.unobserve(entry.target);
|
|
for (const code of entry.target.querySelectorAll("pre code")) {
|
|
hljs.highlightElement(code);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
for (const docs of document.querySelectorAll(".lint-docs")) {
|
|
observer.observe(docs);
|
|
}
|
|
}
|
|
|
|
function findCheckboxes(filter) {
|
|
return [...document.querySelectorAll(`.dropdown[data-filter="${filter}"] input[type="checkbox"]`)];
|
|
}
|
|
|
|
let disableShortcuts = loadValue("disable-shortcuts") === "true";
|
|
|
|
const elements = {
|
|
search: document.getElementById("search-input"),
|
|
disableShortcuts: document.getElementById("disable-shortcuts"),
|
|
checkboxes: {
|
|
levels: findCheckboxes("levels"),
|
|
groups: findCheckboxes("groups"),
|
|
applicabilities: findCheckboxes("applicabilities"),
|
|
},
|
|
versions: {
|
|
gte: document.querySelector(`input[name="gte"]`),
|
|
lte: document.querySelector(`input[name="lte"]`),
|
|
eq: document.querySelector(`input[name="eq"]`),
|
|
},
|
|
counts: {
|
|
levels: document.getElementById("levels-count"),
|
|
groups: document.getElementById("groups-count"),
|
|
applicabilities: document.getElementById("applicabilities-count"),
|
|
versions: document.getElementById("versions-count"),
|
|
},
|
|
};
|
|
|
|
elements.disableShortcuts.checked = disableShortcuts;
|
|
|
|
addListeners();
|
|
highlightLazily();
|
|
parseURL();
|