Fix website history interactions (#16060)
cc @GuillaumeGomez - Searching/filtering no longer creates a new history entry per keystroke/change - Loading a URL with a specified search query now works. The search is now stored as `?search=foo` instead of `#/foo`, not a breaking change since this didn't work before - The browser back/forward actions now update the filters/search and displayed lints The bulk of the changes are to support that last one, previously the filter state was stored both in the DOM and as JS objects. The DOM is now the single source of truth changelog: none
This commit is contained in:
commit
801e5b3cc0
3 changed files with 244 additions and 388 deletions
|
|
@ -28,7 +28,7 @@ Otherwise, have a great day =^.^=
|
|||
<script src="script.js" defer></script> {# #}
|
||||
</head> {# #}
|
||||
<body> {# #}
|
||||
<div id="settings-dropdown"> {# #}
|
||||
<div id="settings-dropdown" class="dropdown"> {# #}
|
||||
<button class="settings-icon" tabindex="-1"></button> {# #}
|
||||
<div class="settings-menu" tabindex="-1"> {# #}
|
||||
<div class="setting-radio-name">Theme</div> {# #}
|
||||
|
|
@ -59,64 +59,96 @@ Otherwise, have a great day =^.^=
|
|||
<div id="menu-filters"> {# #}
|
||||
<div class="panel-body row"> {# #}
|
||||
<div id="upper-filters"> {# #}
|
||||
<div id="lint-levels" tabindex="-1"> {# #}
|
||||
<div class="dropdown" data-filter="levels" tabindex="-1"> {# #}
|
||||
<button type="button" class="btn-default dropdown-toggle"> {# #}
|
||||
Lint levels <span class="badge">4</span> <span class="caret"></span> {# #}
|
||||
Lint levels <span id="levels-count" class="badge">4</span> <span class="caret"></span> {# #}
|
||||
</button> {# #}
|
||||
<ul class="dropdown-menu" id="lint-levels-selector"> {# #}
|
||||
<ul class="dropdown-menu"> {# #}
|
||||
<li class="checkbox"> {# #}
|
||||
<button onclick="toggleElements('levels_filter', true)">All</button> {# #}
|
||||
<button class="reset-all">All</button> {# #}
|
||||
</li> {# #}
|
||||
<li class="checkbox"> {# #}
|
||||
<button onclick="toggleElements('levels_filter', false)">None</button> {# #}
|
||||
<button class="reset-none">None</button> {# #}
|
||||
</li> {# #}
|
||||
<li role="separator" class="divider"></li> {# #}
|
||||
{% for level in ["allow", "warn", "deny", "none"] %}
|
||||
<li class="checkbox"> {# #}
|
||||
<label> {# #}
|
||||
<input type="checkbox" name="{{ level }}" checked />
|
||||
{{ level | capitalize }}
|
||||
</label> {# #}
|
||||
</li> {# #}
|
||||
{% endfor %}
|
||||
</ul> {# #}
|
||||
</div> {# #}
|
||||
<div id="lint-groups" tabindex="-1"> {# #}
|
||||
<div class="dropdown" data-filter="groups" tabindex="-1"> {# #}
|
||||
<button type="button" class="btn-default dropdown-toggle"> {# #}
|
||||
Lint groups <span class="badge">9</span> <span class="caret"></span> {# #}
|
||||
Lint groups <span id="groups-count" class="badge">9</span> <span class="caret"></span> {# #}
|
||||
</button> {# #}
|
||||
<ul class="dropdown-menu" id="lint-groups-selector"> {# #}
|
||||
<ul class="dropdown-menu"> {# #}
|
||||
<li class="checkbox"> {# #}
|
||||
<button onclick="toggleElements('groups_filter', true)">All</button> {# #}
|
||||
<button class="reset-all">All</button> {# #}
|
||||
</li> {# #}
|
||||
<li class="checkbox"> {# #}
|
||||
<button onclick="resetGroupsToDefault()">Default</button> {# #}
|
||||
<button class="reset-default">Default</button> {# #}
|
||||
</li> {# #}
|
||||
<li class="checkbox"> {# #}
|
||||
<button onclick="toggleElements('groups_filter', false)">None</button> {# #}
|
||||
<button class="reset-none">None</button> {# #}
|
||||
</li> {# #}
|
||||
<li role="separator" class="divider"></li> {# #}
|
||||
{% for group in ["cargo", "complexity", "correctness", "nursery", "pedantic", "perf", "restriction", "style", "suspicious", "deprecated"] %}
|
||||
<li class="checkbox"> {# #}
|
||||
<label> {# #}
|
||||
<input type="checkbox" name="{{ group }}" {% if *group != "deprecated" +%} checked {% endif %} />
|
||||
{{ group | capitalize }}
|
||||
</label> {# #}
|
||||
</li> {# #}
|
||||
{% endfor %}
|
||||
</ul> {# #}
|
||||
</div> {# #}
|
||||
<div id="version-filter" tabindex="-1"> {# #}
|
||||
<div id="version-filter" class="dropdown" tabindex="-1"> {# #}
|
||||
<button type="button" class="btn-default dropdown-toggle"> {# #}
|
||||
Version {#+ #}
|
||||
<span id="version-filter-count" class="badge">0</span> {#+ #}
|
||||
<span id="versions-count" class="badge">0</span> {#+ #}
|
||||
<span class="caret"></span> {# #}
|
||||
</button> {# #}
|
||||
<ul id="version-filter-selector" class="dropdown-menu"> {# #}
|
||||
<ul class="dropdown-menu"> {# #}
|
||||
<li class="checkbox"> {# #}
|
||||
<button onclick="clearVersionFilters()">Clear filters</button> {# #}
|
||||
<button id="reset-versions">Clear filters</button> {# #}
|
||||
</li> {# #}
|
||||
<li role="separator" class="divider"></li> {# #}
|
||||
{% for (sym, name) in [("≥", "gte"), ("≤", "lte"), ("=", "eq")] %}
|
||||
<li class="checkbox"> {# #}
|
||||
<label>{{ sym }}</label> {#+ #}
|
||||
<span>1.</span> {# #}
|
||||
<input type="number" name="{{ name }}" min="29" maxlength="2" class="version-filter-input form-control filter-input" /> {# #}
|
||||
<span>.0</span> {# #}
|
||||
</li> {# #}
|
||||
{% endfor %}
|
||||
</ul> {# #}
|
||||
</div> {# #}
|
||||
<div id="lint-applicabilities" tabindex="-1"> {# #}
|
||||
<div class="dropdown" data-filter="applicabilities" tabindex="-1"> {# #}
|
||||
<button type="button" class="btn-default dropdown-toggle"> {# #}
|
||||
Applicability {#+ #}
|
||||
<span class="badge">4</span> {#+ #}
|
||||
<span id="applicabilities-count" class="badge">4</span> {#+ #}
|
||||
<span class="caret"></span> {# #}
|
||||
</button> {# #}
|
||||
<ul class="dropdown-menu" id="lint-applicabilities-selector"> {# #}
|
||||
<ul class="dropdown-menu"> {# #}
|
||||
<li class="checkbox"> {# #}
|
||||
<button onclick="toggleElements('applicabilities_filter', true)">All</button> {# #}
|
||||
<button class="reset-all">All</button> {# #}
|
||||
</li> {# #}
|
||||
<li class="checkbox"> {# #}
|
||||
<button onclick="toggleElements('applicabilities_filter', false)">None</button> {# #}
|
||||
<button class="reset-none">None</button> {# #}
|
||||
</li> {# #}
|
||||
<li role="separator" class="divider"></li> {# #}
|
||||
{% for applicability in ["Unspecified", "MachineApplicable", "MaybeIncorrect", "HasPlaceholders"] %}
|
||||
<li class="checkbox"> {# #}
|
||||
<label> {# #}
|
||||
<input type="checkbox" name="{{ applicability }}" checked />
|
||||
{{ applicability }}
|
||||
</label> {# #}
|
||||
</li> {# #}
|
||||
{% endfor %}
|
||||
</ul> {# #}
|
||||
</div> {# #}
|
||||
</div> {# #}
|
||||
|
|
@ -124,7 +156,7 @@ Otherwise, have a great day =^.^=
|
|||
<div class="input-group"> {# #}
|
||||
<label class="input-group-addon" id="filter-label" for="search-input">Filter:</label> {# #}
|
||||
<input type="text" class="form-control filter-input" placeholder="Keywords or search string (`S` or `/` to focus)" id="search-input" /> {# #}
|
||||
<button class="filter-clear" type="button" onclick="searchState.clearInput(event)"> {# #}
|
||||
<button id="filter-clear" type="button"> {# #}
|
||||
Clear {# #}
|
||||
</button> {# #}
|
||||
</div> {# #}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
"use strict";
|
||||
|
||||
window.searchState = {
|
||||
inputElem: document.getElementById("search-input"),
|
||||
const searchState = {
|
||||
lastSearch: '',
|
||||
clearInput: () => {
|
||||
searchState.inputElem.value = "";
|
||||
searchState.filterLints();
|
||||
},
|
||||
filterLints: () => {
|
||||
filterLints: (updateURL = true) => {
|
||||
function matchesSearch(lint, terms, searchStr) {
|
||||
// Search by id
|
||||
if (lint.elem.id.indexOf(searchStr) !== -1) {
|
||||
|
|
@ -31,7 +26,7 @@ window.searchState = {
|
|||
return true;
|
||||
}
|
||||
|
||||
let searchStr = searchState.inputElem.value.trim().toLowerCase();
|
||||
let searchStr = elements.search.value.trim().toLowerCase();
|
||||
if (searchStr.startsWith("clippy::")) {
|
||||
searchStr = searchStr.slice(8);
|
||||
}
|
||||
|
|
@ -44,21 +39,16 @@ window.searchState = {
|
|||
|
||||
for (const lint of filters.getAllLints()) {
|
||||
lint.searchFilteredOut = !matchesSearch(lint, terms, cleanedSearchStr);
|
||||
if (lint.filteredOut) {
|
||||
continue;
|
||||
}
|
||||
if (lint.searchFilteredOut) {
|
||||
if (lint.searchFilteredOut || lint.filteredOut) {
|
||||
lint.elem.style.display = "none";
|
||||
} else {
|
||||
lint.elem.style.display = "";
|
||||
}
|
||||
}
|
||||
if (searchStr.length > 0) {
|
||||
window.location.hash = `/${searchStr}`;
|
||||
} else {
|
||||
window.location.hash = '';
|
||||
|
||||
if (updateURL) {
|
||||
setURL();
|
||||
}
|
||||
updateLintCount();
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -84,7 +74,7 @@ function handleShortcut(ev) {
|
|||
case "S":
|
||||
case "/":
|
||||
ev.preventDefault(); // To prevent the key to be put into the input.
|
||||
document.getElementById("search-input").focus();
|
||||
elements.search.focus();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -92,25 +82,13 @@ function handleShortcut(ev) {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleElements(filter, value) {
|
||||
let needsUpdate = false;
|
||||
let count = 0;
|
||||
|
||||
const element = document.getElementById(filters[filter].id);
|
||||
onEachLazy(
|
||||
element.querySelectorAll("ul input"),
|
||||
el => {
|
||||
if (el.checked !== value) {
|
||||
el.checked = value;
|
||||
filters[filter][el.getAttribute("data-value")] = value;
|
||||
needsUpdate = true;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
);
|
||||
element.querySelector(".badge").innerText = value ? count : 0;
|
||||
if (needsUpdate) {
|
||||
filters.filterLints();
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,17 +101,9 @@ function onEachLazy(lazyArray, func) {
|
|||
|
||||
function expandLint(lintId) {
|
||||
const elem = document.querySelector(`#${lintId} > input[type="checkbox"]`);
|
||||
elem.checked = true;
|
||||
}
|
||||
|
||||
function lintAnchor(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const id = event.target.getAttribute("href").replace("#", "");
|
||||
window.location.hash = id;
|
||||
|
||||
expandLint(id);
|
||||
if (elem) {
|
||||
elem.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
const clipboardTimeouts = new Map();
|
||||
|
|
@ -157,71 +127,13 @@ function copyToClipboard(event) {
|
|||
);
|
||||
}
|
||||
|
||||
function handleBlur(event, elementId) {
|
||||
const parent = document.getElementById(elementId);
|
||||
if (!parent.contains(document.activeElement) &&
|
||||
!parent.contains(event.relatedTarget)
|
||||
) {
|
||||
parent.classList.remove("open");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleExpansion(expand) {
|
||||
for (const checkbox of document.querySelectorAll("article input[type=checkbox]")) {
|
||||
checkbox.checked = expand;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the current URL without any query parameter or hash.
|
||||
function getNakedUrl() {
|
||||
return window.location.href.split("?")[0].split("#")[0];
|
||||
}
|
||||
|
||||
const GROUPS_FILTER_DEFAULT = {
|
||||
cargo: true,
|
||||
complexity: true,
|
||||
correctness: true,
|
||||
nursery: true,
|
||||
pedantic: true,
|
||||
perf: true,
|
||||
restriction: true,
|
||||
style: true,
|
||||
suspicious: true,
|
||||
deprecated: false,
|
||||
};
|
||||
const LEVEL_FILTERS_DEFAULT = {
|
||||
allow: true,
|
||||
warn: true,
|
||||
deny: true,
|
||||
none: true,
|
||||
};
|
||||
const APPLICABILITIES_FILTER_DEFAULT = {
|
||||
Unspecified: true,
|
||||
MachineApplicable: true,
|
||||
MaybeIncorrect: true,
|
||||
HasPlaceholders: true,
|
||||
};
|
||||
const URL_PARAMS_CORRESPONDENCE = {
|
||||
"groups_filter": "groups",
|
||||
"levels_filter": "levels",
|
||||
"applicabilities_filter": "applicabilities",
|
||||
"version_filter": "versions",
|
||||
};
|
||||
const VERSIONS_CORRESPONDENCE = {
|
||||
"lte": "≤",
|
||||
"gte": "≥",
|
||||
"eq": "=",
|
||||
};
|
||||
|
||||
window.filters = {
|
||||
groups_filter: { id: "lint-groups", ...GROUPS_FILTER_DEFAULT },
|
||||
levels_filter: { id: "lint-levels", ...LEVEL_FILTERS_DEFAULT },
|
||||
applicabilities_filter: { id: "lint-applicabilities", ...APPLICABILITIES_FILTER_DEFAULT },
|
||||
version_filter: {
|
||||
"≥": null,
|
||||
"≤": null,
|
||||
"=": null,
|
||||
},
|
||||
const filters = {
|
||||
allLints: null,
|
||||
getAllLints: () => {
|
||||
if (filters.allLints === null) {
|
||||
|
|
@ -246,72 +158,27 @@ window.filters = {
|
|||
}
|
||||
return filters.allLints;
|
||||
},
|
||||
regenerateURLparams: () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
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)
|
||||
));
|
||||
|
||||
function compareObjects(obj1, obj2) {
|
||||
return (JSON.stringify(obj1) === JSON.stringify({ id: obj1.id, ...obj2 }));
|
||||
}
|
||||
function updateIfNeeded(filterName, obj2) {
|
||||
const obj1 = filters[filterName];
|
||||
const name = URL_PARAMS_CORRESPONDENCE[filterName];
|
||||
if (!compareObjects(obj1, obj2)) {
|
||||
urlParams.set(
|
||||
name,
|
||||
Object.entries(obj1).filter(
|
||||
([key, value]) => value && key !== "id"
|
||||
).map(
|
||||
([key, _]) => key
|
||||
).join(","),
|
||||
);
|
||||
} else {
|
||||
urlParams.delete(name);
|
||||
}
|
||||
}
|
||||
const [lte, gte, eq] = ["lte", "gte", "eq"].map(key => Number(elements.versions[key].value));
|
||||
|
||||
updateIfNeeded("groups_filter", GROUPS_FILTER_DEFAULT);
|
||||
updateIfNeeded("levels_filter", LEVEL_FILTERS_DEFAULT);
|
||||
updateIfNeeded(
|
||||
"applicabilities_filter", APPLICABILITIES_FILTER_DEFAULT);
|
||||
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;
|
||||
|
||||
const versions = [];
|
||||
if (filters.version_filter["="] !== null) {
|
||||
versions.push(`eq:${filters.version_filter["="]}`);
|
||||
}
|
||||
if (filters.version_filter["≥"] !== null) {
|
||||
versions.push(`gte:${filters.version_filter["≥"]}`);
|
||||
}
|
||||
if (filters.version_filter["≤"] !== null) {
|
||||
versions.push(`lte:${filters.version_filter["≤"]}`);
|
||||
}
|
||||
if (versions.length !== 0) {
|
||||
urlParams.set(URL_PARAMS_CORRESPONDENCE["version_filter"], versions.join(","));
|
||||
} else {
|
||||
urlParams.delete(URL_PARAMS_CORRESPONDENCE["version_filter"]);
|
||||
}
|
||||
|
||||
let params = urlParams.toString();
|
||||
if (params.length !== 0) {
|
||||
params = `?${params}`;
|
||||
}
|
||||
|
||||
const url = getNakedUrl() + params + window.location.hash
|
||||
if (!history.state) {
|
||||
history.pushState(null, "", url);
|
||||
} else {
|
||||
history.replaceState(null, "", url);
|
||||
}
|
||||
},
|
||||
filterLints: () => {
|
||||
// First we regenerate the URL parameters.
|
||||
filters.regenerateURLparams();
|
||||
for (const lint of filters.getAllLints()) {
|
||||
lint.filteredOut = (!filters.groups_filter[lint.group]
|
||||
|| !filters.levels_filter[lint.level]
|
||||
|| !filters.applicabilities_filter[lint.applicability]
|
||||
|| !(filters.version_filter["="] === null || lint.version === filters.version_filter["="])
|
||||
|| !(filters.version_filter["≥"] === null || lint.version >= filters.version_filter["≥"])
|
||||
|| !(filters.version_filter["≤"] === null || lint.version <= filters.version_filter["≤"])
|
||||
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";
|
||||
|
|
@ -319,120 +186,25 @@ window.filters = {
|
|||
lint.elem.style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (updateURL) {
|
||||
setURL();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function updateFilter(elem, filter, skipLintsFiltering) {
|
||||
const value = elem.getAttribute("data-value");
|
||||
if (filters[filter][value] !== elem.checked) {
|
||||
filters[filter][value] = elem.checked;
|
||||
const counter = document.querySelector(`#${filters[filter].id} .badge`);
|
||||
counter.innerText = parseInt(counter.innerText) + (elem.checked ? 1 : -1);
|
||||
if (!skipLintsFiltering) {
|
||||
filters.filterLints();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateVersionFilters(elem, skipLintsFiltering) {
|
||||
let value = elem.value.trim();
|
||||
if (value.length === 0) {
|
||||
value = null;
|
||||
} else if (/^\d+$/.test(value)) {
|
||||
value = parseInt(value);
|
||||
} else {
|
||||
console.error(`Failed to get version number from "${value}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
const counter = document.querySelector("#version-filter .badge");
|
||||
let count = 0;
|
||||
onEachLazy(document.querySelectorAll("#version-filter input"), el => {
|
||||
if (el.value.trim().length !== 0) {
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
counter.innerText = count;
|
||||
|
||||
const comparisonKind = elem.getAttribute("data-value");
|
||||
if (filters.version_filter[comparisonKind] !== value) {
|
||||
filters.version_filter[comparisonKind] = value;
|
||||
if (!skipLintsFiltering) {
|
||||
filters.filterLints();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearVersionFilters() {
|
||||
let needsUpdate = false;
|
||||
|
||||
onEachLazy(document.querySelectorAll("#version-filter input"), el => {
|
||||
el.value = "";
|
||||
const comparisonKind = el.getAttribute("data-value");
|
||||
if (filters.version_filter[comparisonKind] !== null) {
|
||||
needsUpdate = true;
|
||||
filters.version_filter[comparisonKind] = null;
|
||||
}
|
||||
});
|
||||
document.querySelector("#version-filter .badge").innerText = 0;
|
||||
if (needsUpdate) {
|
||||
filters.filterLints();
|
||||
}
|
||||
}
|
||||
|
||||
function resetGroupsToDefault() {
|
||||
let needsUpdate = false;
|
||||
let count = 0;
|
||||
|
||||
onEachLazy(document.querySelectorAll("#lint-groups-selector input"), el => {
|
||||
const key = el.getAttribute("data-value");
|
||||
const value = GROUPS_FILTER_DEFAULT[key];
|
||||
if (filters.groups_filter[key] !== value) {
|
||||
filters.groups_filter[key] = value;
|
||||
el.checked = value;
|
||||
needsUpdate = true;
|
||||
}
|
||||
if (value) {
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
document.querySelector("#lint-groups .badge").innerText = count;
|
||||
if (needsUpdate) {
|
||||
filters.filterLints();
|
||||
}
|
||||
}
|
||||
|
||||
function generateListOfOptions(list, elementId, filter) {
|
||||
let html = '';
|
||||
let nbEnabled = 0;
|
||||
for (const [key, value] of Object.entries(list)) {
|
||||
const attr = value ? " checked" : "";
|
||||
html += `\
|
||||
<li class="checkbox">\
|
||||
<label class="text-capitalize">\
|
||||
<input type="checkbox" data-value="${key}" \
|
||||
onchange="updateFilter(this, '${filter}')"${attr}/>${key}\
|
||||
</label>\
|
||||
</li>`;
|
||||
if (value) {
|
||||
nbEnabled += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const elem = document.getElementById(`${elementId}-selector`);
|
||||
elem.previousElementSibling.querySelector(".badge").innerText = `${nbEnabled}`;
|
||||
elem.innerHTML += html;
|
||||
|
||||
setupDropdown(elementId);
|
||||
}
|
||||
|
||||
function setupDropdown(elementId) {
|
||||
const elem = document.getElementById(elementId);
|
||||
const button = document.querySelector(`#${elementId} > button`);
|
||||
function setupDropdown(elem) {
|
||||
const button = elem.querySelector("button");
|
||||
button.onclick = () => elem.classList.toggle("open");
|
||||
|
||||
const setBlur = child => {
|
||||
child.onblur = event => handleBlur(event, elementId);
|
||||
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);
|
||||
|
|
@ -440,87 +212,100 @@ function setupDropdown(elementId) {
|
|||
onEachLazy(elem.querySelectorAll("ul button"), setBlur);
|
||||
}
|
||||
|
||||
function generateSettings() {
|
||||
setupDropdown("settings-dropdown");
|
||||
function setURL() {
|
||||
const url = new URL(location);
|
||||
|
||||
generateListOfOptions(LEVEL_FILTERS_DEFAULT, "lint-levels", "levels_filter");
|
||||
generateListOfOptions(GROUPS_FILTER_DEFAULT, "lint-groups", "groups_filter");
|
||||
generateListOfOptions(
|
||||
APPLICABILITIES_FILTER_DEFAULT, "lint-applicabilities", "applicabilities_filter");
|
||||
|
||||
let html = '';
|
||||
for (const kind of ["≥", "≤", "="]) {
|
||||
html += `\
|
||||
<li class="checkbox">\
|
||||
<label>${kind}</label>\
|
||||
<span>1.</span> \
|
||||
<input type="number" \
|
||||
min="29" \
|
||||
class="version-filter-input form-control filter-input" \
|
||||
maxlength="2" \
|
||||
data-value="${kind}" \
|
||||
onchange="updateVersionFilters(this)" \
|
||||
oninput="updateVersionFilters(this)" \
|
||||
onkeydown="updateVersionFilters(this)" \
|
||||
onkeyup="updateVersionFilters(this)" \
|
||||
onpaste="updateVersionFilters(this)" \
|
||||
/>
|
||||
<span>.0</span>\
|
||||
</li>`;
|
||||
function nonDefault(filter) {
|
||||
return elements.checkboxes[filter]
|
||||
.some(element => element.checked === (element.name === "deprecated"));
|
||||
}
|
||||
document.getElementById("version-filter-selector").innerHTML += html;
|
||||
setupDropdown("version-filter");
|
||||
}
|
||||
|
||||
function scrollToLint(lintId) {
|
||||
const target = document.getElementById(lintId);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
target.scrollIntoView();
|
||||
expandLint(lintId);
|
||||
}
|
||||
|
||||
// If the page we arrive on has link to a given lint, we scroll to it.
|
||||
function scrollToLintByURL() {
|
||||
const lintId = window.location.hash.substring(1);
|
||||
if (lintId.length > 0) {
|
||||
scrollToLint(lintId);
|
||||
}
|
||||
}
|
||||
|
||||
function parseURLFilters() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
for (const [key, value] of urlParams.entries()) {
|
||||
for (const [corres_key, corres_value] of Object.entries(URL_PARAMS_CORRESPONDENCE)) {
|
||||
if (corres_value === key) {
|
||||
if (key !== "versions") {
|
||||
const settings = new Set(value.split(","));
|
||||
onEachLazy(document.querySelectorAll(`#lint-${key} ul input`), elem => {
|
||||
elem.checked = settings.has(elem.getAttribute("data-value"));
|
||||
updateFilter(elem, corres_key, true);
|
||||
});
|
||||
} else {
|
||||
const settings = value.split(",").map(elem => elem.split(":"));
|
||||
|
||||
for (const [kind, value] of settings) {
|
||||
const elem = document.querySelector(
|
||||
`#version-filter input[data-value="${VERSIONS_CORRESPONDENCE[kind]}"]`);
|
||||
elem.value = value;
|
||||
updateVersionFilters(elem, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
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() {
|
||||
searchState.inputElem.addEventListener("input", handleInputChanged);
|
||||
document.getElementById("upper-filters").addEventListener("input", () => {
|
||||
filters.filterLints();
|
||||
});
|
||||
elements.search.addEventListener("input", handleInputChanged);
|
||||
|
||||
disableShortcutsButton.addEventListener("change", () => {
|
||||
disableShortcuts = disableShortcutsButton.checked;
|
||||
elements.disableShortcuts.addEventListener("change", () => {
|
||||
disableShortcuts = elements.disableShortcuts.checked;
|
||||
storeValue("disable-shortcuts", disableShortcuts);
|
||||
});
|
||||
|
||||
|
|
@ -536,12 +321,34 @@ function addListeners() {
|
|||
if (event.target.classList.contains("copy-to-clipboard")) {
|
||||
copyToClipboard(event);
|
||||
} else if (event.target.classList.contains("anchor")) {
|
||||
lintAnchor(event);
|
||||
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"
|
||||
|
|
@ -565,14 +372,35 @@ function highlightLazily() {
|
|||
}
|
||||
}
|
||||
|
||||
function findCheckboxes(filter) {
|
||||
return [...document.querySelectorAll(`.dropdown[data-filter="${filter}"] input[type="checkbox"]`)];
|
||||
}
|
||||
|
||||
let disableShortcuts = loadValue("disable-shortcuts") === "true";
|
||||
|
||||
const disableShortcutsButton = document.getElementById("disable-shortcuts");
|
||||
disableShortcutsButton.checked = disableShortcuts;
|
||||
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();
|
||||
generateSettings();
|
||||
parseURLFilters();
|
||||
scrollToLintByURL();
|
||||
filters.filterLints();
|
||||
parseURL();
|
||||
|
|
|
|||
|
|
@ -97,10 +97,6 @@ label {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
|
|
@ -372,7 +368,7 @@ article:hover .panel-title-name .anchor { display: inline;}
|
|||
|
||||
@media (max-width: 430px) {
|
||||
/* Turn the version filter list to the left */
|
||||
#version-filter-selector {
|
||||
#version-filter .dropdown-menu {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
|
@ -532,7 +528,7 @@ summary {
|
|||
}
|
||||
|
||||
html:not(.js) #settings-dropdown,
|
||||
html:not(.js)#menu-filters {
|
||||
html:not(.js) #menu-filters {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
@ -634,7 +630,7 @@ pre, hr {
|
|||
border: 1px solid var(--theme-popup-border);
|
||||
}
|
||||
|
||||
#version-filter-selector .checkbox {
|
||||
#version-filter .checkbox {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
|
@ -667,13 +663,13 @@ ul.dropdown-menu li.checkbox > button:hover {
|
|||
border-bottom: 1px solid #000000;
|
||||
}
|
||||
|
||||
#filter-label, .filter-clear {
|
||||
#filter-label, #filter-clear {
|
||||
background: var(--searchbar-bg);
|
||||
color: var(--searchbar-fg);
|
||||
border-color: var(--theme-popup-border);
|
||||
filter: brightness(95%);
|
||||
}
|
||||
#filter-label:hover, .filter-clear:hover {
|
||||
#filter-label:hover, #filter-clear:hover {
|
||||
filter: brightness(90%);
|
||||
}
|
||||
.filter-input {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue