(function () { var md = window.markdownit({ html: true, linkify: true, typographer: true, highlight: function (str, lang) { if (lang && hljs.getLanguage(lang)) { try { return '
' +
                        hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
                        '
'; } catch (__) {} } return '
' + md.utils.escapeHtml(str) + '
'; } }); function scrollToLint(lintId) { var target = document.getElementById(lintId); if (!target) { return; } target.scrollIntoView(); } function scrollToLintByURL($scope, $location) { var removeListener = $scope.$on('ngRepeatFinished', function (ngRepeatFinishedEvent) { scrollToLint($location.path().substring(1)); removeListener(); }); } function selectGroup($scope, selectedGroup) { var groups = $scope.groups; for (var group in groups) { if (groups.hasOwnProperty(group)) { if (group === selectedGroup) { groups[group] = true; } else { groups[group] = false; } } } } angular.module("clippy", []) .filter('markdown', function ($sce) { return function (text) { return $sce.trustAsHtml( md.render(text || '') // Oh deer, what a hack :O .replace(' [value, key]) ); // An internal URL change occurs when we are modifying the URL parameters in a way // that should not reload parameters from the URL let internalURLChange = false; // loadFromURLParameters retrieves filter settings from the URL parameters and assigns them // to corresponding $scope variables. function loadFromURLParameters() { // Extract parameters from URL const urlParameters = $location.search(); // Define a helper function that assigns URL parameters to a provided scope variable const handleParameter = (parameter, scopeVariable, defaultValues) => { if (urlParameters[parameter]) { const items = urlParameters[parameter].split(','); for (const key in scopeVariable) { if (scopeVariable.hasOwnProperty(key)) { scopeVariable[key] = items.includes(key); } } } else if (defaultValues) { for (const key in defaultValues) { if (scopeVariable.hasOwnProperty(key)) { scopeVariable[key] = defaultValues[key]; } } } }; handleParameter('levels', $scope.levels, LEVEL_FILTERS_DEFAULT); handleParameter('groups', $scope.groups, GROUPS_FILTER_DEFAULT); // Handle 'versions' parameter separately because it needs additional processing if (urlParameters.versions) { const versionFilters = urlParameters.versions.split(','); for (const versionFilter of versionFilters) { const [key, minorVersion] = versionFilter.split(':'); const parsedMinorVersion = parseInt(minorVersion); // Map the key from the URL parameter to its original form const originalKey = reverseVersionFilterKeyMap[key]; if (originalKey in $scope.versionFilters && !isNaN(parsedMinorVersion)) { $scope.versionFilters[originalKey].enabled = true; $scope.versionFilters[originalKey].minorVersion = parsedMinorVersion; } } } // Load the search parameter from the URL path const searchParameter = $location.path().substring(1); // Remove the leading slash if (searchParameter) { $scope.search = searchParameter; $scope.open[searchParameter] = true; scrollToLintByURL($scope, $location); } // If there are any filters in the URL, mark that the filters have been changed if (urlParameters.levels || urlParameters.groups || urlParameters.versions) { $scope.filtersChanged = true; } } // updateURLParameter updates the URL parameter with the given key to the given value function updateURLParameter(filterObj, urlKey, processFilter = filter => filter) { const parameter = Object.keys(filterObj) .filter(filter => filterObj[filter]) .map(processFilter) .filter(Boolean) // Filters out any falsy values, including null .join(','); $location.search(urlKey, parameter || null); } // updateVersionURLParameter updates the version URL parameter with the given version filters function updateVersionURLParameter(versionFilters) { updateURLParameter( versionFilters, 'versions', versionFilter => versionFilters[versionFilter].enabled && versionFilters[versionFilter].minorVersion != null ? `${versionFilterKeyMap[versionFilter]}:${versionFilters[versionFilter].minorVersion}` : null ); } // updateAllURLParameters updates all the URL parameters with the current filter settings function updateAllURLParameters() { updateURLParameter($scope.levels, 'levels'); updateURLParameter($scope.groups, 'groups'); updateVersionURLParameter($scope.versionFilters); } // Add $watches to automatically update URL parameters when the data changes $scope.$watch('levels', function (newVal, oldVal) { if (newVal !== oldVal) { $scope.filtersChanged = true; updateURLParameter(newVal, 'levels'); } }, true); $scope.$watch('groups', function (newVal, oldVal) { if (newVal !== oldVal) { $scope.filtersChanged = true; updateURLParameter(newVal, 'groups'); } }, true); $scope.$watch('versionFilters', function (newVal, oldVal) { if (newVal !== oldVal) { $scope.filtersChanged = true; updateVersionURLParameter(newVal); } }, true); // Watch for changes in the URL path and update the search and lint display $scope.$watch(function () { return $location.path(); }, function (newPath) { const searchParameter = newPath.substring(1); if ($scope.search !== searchParameter) { $scope.search = searchParameter; $scope.open[searchParameter] = true; scrollToLintByURL($scope, $location); } }); let debounceTimeout; $scope.$watch('search', function (newVal, oldVal) { if (newVal !== oldVal) { if (debounceTimeout) { $timeout.cancel(debounceTimeout); } debounceTimeout = $timeout(function () { $location.path(newVal); }, 1000); } }); $scope.$watch(function () { return $location.search(); }, function (newParameters) { if (!internalURLChange) { loadFromURLParameters(); } internalURLChange = false; }, true); $scope.updatePath = function () { if (debounceTimeout) { $timeout.cancel(debounceTimeout); } $location.path($scope.search); } $scope.selectTheme = function (theme) { setTheme(theme, true); } $scope.toggleLevels = function (value) { const levels = $scope.levels; for (const key in levels) { if (levels.hasOwnProperty(key)) { levels[key] = value; } } }; $scope.toggleGroups = function (value) { const groups = $scope.groups; for (const key in groups) { if (groups.hasOwnProperty(key)) { groups[key] = value; } } }; $scope.resetGroupsToDefault = function () { const groups = $scope.groups; for (const [key, value] of Object.entries(GROUPS_FILTER_DEFAULT)) { groups[key] = value; } internalURLChange = true; $location.search('groups', null); }; $scope.selectedValuesCount = function (obj) { return Object.values(obj).filter(x => x).length; } $scope.clearVersionFilters = function () { for (let filter in $scope.versionFilters) { $scope.versionFilters[filter] = { enabled: false, minorVersion: null }; } } $scope.versionFilterCount = function(obj) { return Object.values(obj).filter(x => x.enabled).length; } $scope.updateVersionFilters = function() { for (const filter in $scope.versionFilters) { let minorVersion = $scope.versionFilters[filter].minorVersion; // 1.29.0 and greater if (minorVersion && minorVersion > 28) { $scope.versionFilters[filter].enabled = true; continue; } $scope.versionFilters[filter].enabled = false; } } $scope.byVersion = function(lint) { let filters = $scope.versionFilters; for (const filter in filters) { if (filters[filter].enabled) { let minorVersion = filters[filter].minorVersion; // Strip the "pre " prefix for pre 1.29.0 lints let lintVersion = lint.version.startsWith("pre ") ? lint.version.substring(4, lint.version.length) : lint.version; let lintMinorVersion = lintVersion.substring(2, 4); switch (filter) { // "=" gets the highest priority, since all filters are inclusive case "=": return (lintMinorVersion == minorVersion); case "≥": if (lintMinorVersion < minorVersion) { return false; } break; case "≤": if (lintMinorVersion > minorVersion) { return false; } break; default: return true } } } return true; } $scope.byGroups = function (lint) { return $scope.groups[lint.group]; }; $scope.bySearch = function (lint, index, array) { let searchStr = $scope.search; // It can be `null` I haven't missed this value if (searchStr == null || searchStr.length < 3) { return true; } searchStr = searchStr.toLowerCase(); if (searchStr.startsWith("clippy::")) { searchStr = searchStr.slice(8); } // Search by id if (lint.id.indexOf(searchStr.replace("-", "_")) !== -1) { return true; } // Search the description // The use of `for`-loops instead of `foreach` enables us to return early let terms = searchStr.split(" "); let docsLowerCase = lint.docs.toLowerCase(); for (index = 0; index < terms.length; index++) { // This is more likely and will therefor be checked first if (docsLowerCase.indexOf(terms[index]) !== -1) { continue; } if (lint.id.indexOf(terms[index]) !== -1) { continue; } return false; } return true; } // Show details for one lint $scope.openLint = function (lint) { $scope.open[lint.id] = true; $location.path(lint.id); if ($scope.filtersChanged) { updateAllURLParameters(); $scope.filtersChanged = false; } }; $scope.copyToClipboard = function (lint) { const clipboard = document.getElementById("clipboard-" + lint.id); if (clipboard) { let resetClipboardTimeout = null; let resetClipboardIcon = clipboard.innerHTML; function resetClipboard() { resetClipboardTimeout = null; clipboard.innerHTML = resetClipboardIcon; } navigator.clipboard.writeText("clippy::" + lint.id); clipboard.innerHTML = "✓"; if (resetClipboardTimeout !== null) { clearTimeout(resetClipboardTimeout); } resetClipboardTimeout = setTimeout(resetClipboard, 1000); } } // Get data $scope.open = {}; $scope.loading = true; $scope.filtersChanged = false; // This will be used to jump into the source code of the version that this documentation is for. $scope.docVersion = window.location.pathname.split('/')[2] || "master"; // Set up the filters from the URL parameters before we start loading the data loadFromURLParameters(); $http.get('./lints.json') .success(function (data) { $scope.data = data; $scope.loading = false; var selectedGroup = getQueryVariable("sel"); if (selectedGroup) { selectGroup($scope, selectedGroup.toLowerCase()); } scrollToLintByURL($scope, $location); setTimeout(function () { var el = document.getElementById('filter-input'); if (el) { el.focus() } }, 0); }) .error(function (data) { $scope.error = data; $scope.loading = false; }); }); })(); function getQueryVariable(variable) { var query = window.location.search.substring(1); var vars = query.split('&'); for (var i = 0; i < vars.length; i++) { var pair = vars[i].split('='); if (decodeURIComponent(pair[0]) == variable) { return decodeURIComponent(pair[1]); } } } function setTheme(theme, store) { let enableHighlight = false; let enableNight = false; let enableAyu = false; switch(theme) { case "ayu": enableAyu = true; break; case "coal": case "navy": enableNight = true; break; case "rust": enableHighlight = true; break; default: enableHighlight = true; theme = "light"; break; } document.getElementsByTagName("body")[0].className = theme; document.getElementById("githubLightHighlight").disabled = enableNight || !enableHighlight; document.getElementById("githubDarkHighlight").disabled = !enableNight && !enableAyu; document.getElementById("styleHighlight").disabled = !enableHighlight; document.getElementById("styleNight").disabled = !enableNight; document.getElementById("styleAyu").disabled = !enableAyu; if (store) { try { localStorage.setItem('clippy-lint-list-theme', theme); } catch (e) { } } } // loading the theme after the initial load const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); const theme = localStorage.getItem('clippy-lint-list-theme'); if (prefersDark.matches && !theme) { setTheme("coal", false); } else { setTheme(theme, false); }