| "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(); |