diff --git a/.eleventy.js b/.eleventy.js index 26bda7a09..300f53926 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -1,9 +1,8 @@ const lodashGet = require("lodash/get"); +const yaml = require("js-yaml"); module.exports = function(eleventyConfig) { - // Support yaml data files - const yaml = require("js-yaml"); eleventyConfig.addDataExtension("yaml", contents => yaml.safeLoad(contents)) // pass images directly through to the output @@ -69,9 +68,37 @@ module.exports = function(eleventyConfig) { // filter a data array based on the value of a property eleventyConfig.addFilter('select', (array, clause) => { - const property = clause.split("=")[0]; - const value = clause.split("=")[1]; - return array.filter(item => item[property].includes(value)); + if(clause.indexOf("=") > -1) { + const property = clause.split("=")[0]; + const value = clause.split("=")[1]; + return array.filter(item => lodashGet(item, property).includes(value)); + } else { + return array.map(item => lodashGet(item, clause)); + } + }); + + eleventyConfig.addFilter('flatten', (array) => { + let results = []; + for(let result of array) { + if(result) { + if(Array.isArray(result)) { + results = [...results, ...result]; + } else { + results.push(result); + } + } + } + return results; + }); + + eleventyConfig.addFilter('unique', (array) => { + let caseInsensitive = {}; + for(let val of array) { + if(typeof val === "string") { + caseInsensitive[val.toLowerCase()] = val; + } + } + return Object.values(caseInsensitive); }); // Get a random selection of items from an array diff --git a/package.json b/package.json index 5f0e30a5b..945679297 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "postcss": "^8.1.10", "postcss-cli": "^8.3.0", "postcss-import": "^13.0.0", + "spdx-correct": "^3.1.1", "tailwindcss": "^2.0.1" }, "devDependencies": { diff --git a/src/11ty/normalizeLicense.js b/src/11ty/normalizeLicense.js new file mode 100644 index 000000000..1d873bdd7 --- /dev/null +++ b/src/11ty/normalizeLicense.js @@ -0,0 +1,10 @@ +const correct = require("spdx-correct"); + +module.exports = function(license, title) { + let correctedLicenseName = correct(license); + if(correctedLicenseName) { + return correctedLicenseName; + } + console.log( `WARN License name (${license}) for ${title} not found on the approved SPDX License List: https://spdx.org/licenses/` ); + return license; +}; \ No newline at end of file diff --git a/src/css/tailwind.css b/src/css/tailwind.css index 9e8288968..834955303 100644 --- a/src/css/tailwind.css +++ b/src/css/tailwind.css @@ -314,6 +314,15 @@ details[open] .summary-swap-open { .tool-content img { @apply my-8; } + +/* Filters */ +.filter-opensource--hide, +.filter-typeofcms--hide, +.filter-language--hide, +.filter-template--hide, +.filter-license--hide { + display: none !important; +} /* purgecss end ignore */ @tailwind utilities; diff --git a/src/js/filter-container.js b/src/js/filter-container.js new file mode 100644 index 000000000..77d11517a --- /dev/null +++ b/src/js/filter-container.js @@ -0,0 +1,114 @@ +class FilterContainer extends HTMLElement { + constructor() { + super(); + this.attrs = { + bind: "data-filter-bind", + delimiter: "data-filter-delimiter", + results: "data-filter-results", + }; + this.classes = { + hidden: "filter--hide", + } + } + + connectedCallback() { + this.results = this.querySelector(`[${this.attrs.results}]`); + let formElements = this.getAllFormElements(); + this.bindEvents(formElements); + this.filterAll(formElements); + } + + getAllFormElements() { + return this.querySelectorAll(`[${this.attrs.bind}]`); + } + + getAllKeys() { + let keys = new Set(); + for(let formEl of this.getAllFormElements()) { + keys.add(formEl.getAttribute(this.attrs.bind)); + } + return Array.from(keys); + } + + getElementSelector(key) { + return `data-filter-${key}` + } + + getAllFilterableElements() { + let keys = this.getAllKeys(); + let selector = keys.map(key => { + return `[${this.getElementSelector(key)}]`; + }).join(","); + return this.querySelectorAll(selector); + } + + bindEvents(formElements) { + for(let el of formElements) { + el.addEventListener("change", e => { + this.filter(e.target); + requestAnimationFrame(() => { + this.renderResultCount(); + }) + }, false); + } + } + + filterAll(formElements) { + for(let el of formElements) { + this.filter(el); + } + this.renderResultCount(); + } + + filter(formElement) { + let key = formElement.getAttribute(this.attrs.bind); + let delimiter = formElement.getAttribute(this.attrs.delimiter); + + let value = formElement.value; + let elementsSelectorAttr = this.getElementSelector(key); + + let elements = this.querySelectorAll(`[${elementsSelectorAttr}]`); + let count = 0; + let cls = `filter-${key}--hide`; + for(let element of Array.from(elements)) { + if(this.elementIsValid(element, elementsSelectorAttr, value, delimiter)) { + element.classList.remove(cls); + } else { + element.classList.add(cls); + } + } + } + + elementIsValid(element, attributeName, value, delimiter) { + if(!value && element.hasAttribute(attributeName)) { + return true; + } + let attrValue = element.getAttribute(attributeName); + if(delimiter && attrValue.split(delimiter).indexOf(value) > -1) { + return true; + } + if(!delimiter && attrValue === value) { + return true; + } + return false; + } + + elementIsVisible(element) { + for(let cls of element.classList) { + if(cls.startsWith("filter-") && cls.endsWith("--hide")) { + return false; + } + } + return true; + } + + renderResultCount() { + let count = Array.from(this.getAllFilterableElements()) + .filter(entry => this.elementIsVisible(entry)) + .length; + + this.results.innerHTML = `${count} Result${count !== 1 ? "s" : ""}`; + } +} + +window.customElements.define("filter-container", FilterContainer); \ No newline at end of file diff --git a/src/js/sort-container.js b/src/js/sort-container.js new file mode 100644 index 000000000..5428a372d --- /dev/null +++ b/src/js/sort-container.js @@ -0,0 +1,54 @@ +class SortContainer extends HTMLElement { + constructor() { + super(); + this.attrs = { + select: "data-sort", + children: "data-sort-children", + }; + } + + connectedCallback() { + this.select = this.querySelector(`[${this.attrs.select}]`); + this.bindEvents(); + } + + bindEvents() { + this.select.addEventListener("change", e => { + this.sort(e.target.value); + }, false); + } + + sort(key) { + let container = this.querySelector(`[${this.attrs.children}]`); + // Thanks https://github.com/component/sort (MIT License) + let arr = [].slice.call(container.children).sort((a, b) => { + let aVal = a.getAttribute(`data-sort-${key}`); + let bVal = b.getAttribute(`data-sort-${key}`); + + // numeric sorts + if(key.endsWith("-numeric") || key.endsWith("-numeric-ascending") || key.endsWith("-numeric-descending")) { + aVal = parseFloat(aVal) || 0; + bVal = parseFloat(bVal) || 0; + + if(key.endsWith("-numeric-descending")) { + [aVal, bVal] = [bVal, aVal]; + } + return aVal - bVal; + } + + if(bVal < aVal) { + return 1; + } else if(aVal < bVal) { + return -1; + } + return 0; + }); + let frag = document.createDocumentFragment(); + for (let i = 0; i < arr.length; i++) { + frag.appendChild(arr[i]); + } + container.appendChild(frag); + } +} + +window.customElements.define("sort-container", SortContainer); \ No newline at end of file diff --git a/src/site/_data/github.js b/src/site/_data/github.js index 4f7c7a44e..db1f962da 100644 --- a/src/site/_data/github.js +++ b/src/site/_data/github.js @@ -102,11 +102,13 @@ async function getReposFromMarkdown(glob) { let split = fullRepo.split("/"); let user = split[0]; let repo = split[1]; - if(!matter.data.repohost || matter.data.repohost === "github") - repos.push({ user, repo }); - } else { - // TODO maybe just log this in production? - // console.log( "GitHub full repo not found for", ssg ); + + if(!matter.data.repohost || matter.data.repohost === "github") { + if(matter.data.disabled) { + continue; + } + repos.push({ user, repo }); + } } } diff --git a/src/site/_includes/components/cards.njk b/src/site/_includes/components/cards.njk index 5294fb626..82588d2c6 100644 --- a/src/site/_includes/components/cards.njk +++ b/src/site/_includes/components/cards.njk @@ -1,7 +1,14 @@ {% macro repo(item, loopIndex, githubData) %} -