blob: 56cd245101b21f81dc5e8905eb0ed1a0ff3461b6 [file] [log] [blame]
"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();