diff --git a/_includes/appendix-filter.html b/_includes/appendix-filter.html new file mode 100644 index 000000000..9b031bb53 --- /dev/null +++ b/_includes/appendix-filter.html @@ -0,0 +1,29 @@ +
+

Explore by rules — select attributes, then choose how non-matching licenses appear:

+
+ + +
+
+ {% assign types = "permissions|conditions|limitations" | split: "|" %} + {% assign seen_tags = '' %} + {% for type in types %} +
+ {{ type | capitalize }} + {% assign rules = site.data.rules[type] | sort: "label" %} + {% for rule_obj in rules %} + {% if seen_tags contains rule_obj.tag or rule_obj.tag contains '--' %} + {% continue %} + {% endif %} + {% capture seen_tags %}{{ seen_tags | append:rule_obj.tag }}{% endcapture %} + + {% endfor %} +
+ {% endfor %} +
+ +

+
diff --git a/_includes/css/responsive.css b/_includes/css/responsive.css index 64524f36b..40d9a2e31 100644 --- a/_includes/css/responsive.css +++ b/_includes/css/responsive.css @@ -170,3 +170,68 @@ padding: 0 10px; } } + +.appendix-table-wrapper { + overflow-x: auto; + max-width: 100%; + margin-bottom: 2rem; +} + +.appendix-filter { + margin: 1.5rem 0; + padding: 1rem; + border: 1px solid #ddd; + border-radius: 6px; + background: color-mix(in srgb, var(--backgroundColor) 92%, #149ad4 8%); +} + +.appendix-filter-mode { + display: flex; + gap: 1.25rem; + margin: 0.75rem 0; + font-size: 0.9rem; +} + +.appendix-filter-mode label { + font-weight: normal; +} + +.appendix-filter-groups { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +.appendix-filter-group { + border: none; + margin: 0; + padding: 0; + min-width: 12rem; +} + +.appendix-filter-chip { + display: inline-flex; + align-items: center; + gap: 0.35rem; + margin: 0.15rem 0.5rem 0.15rem 0; + font-size: 0.85rem; + font-weight: normal; +} + +.appendix-filter-clear { + margin-top: 0.75rem; +} + +.appendix-filter-status { + margin: 0.5rem 0 0; + font-size: 0.85rem; +} + +.appendix-row-dimmed { + opacity: 0.35; +} + +.appendix-row-match th, +.appendix-row-match td { + background: color-mix(in srgb, var(--backgroundColor) 80%, #3dc637 20%); +} diff --git a/appendix.md b/appendix.md index 62c119d98..fea52f37c 100644 --- a/appendix.md +++ b/appendix.md @@ -9,8 +9,12 @@ For reference, here is a table of every license described in the [choosealicense If you're here to choose a license, **[start from the home page](/)** to see a few licenses that will work for most cases. - +{% include appendix-filter.html %} + +
+
{% assign types = "permissions|conditions|limitations" | split: "|" %} + {% assign seen_tags = '' %} @@ -25,9 +29,12 @@ If you're here to choose a license, **[start from the home page](/)** to see a f {% endfor %} {% endfor %} + + {% assign licenses = site.licenses | sort: "path" %} {% for license in licenses %} - + {% capture rule_tags %}{% for t in types %}{% for r in license[t] %}{{ r }},{% endfor %}{% endfor %}{% endcapture %} + {% assign seen_tags = '' %} {% for type in types %} {% assign rules = site.data.rules[type] | sort: "label" %} @@ -62,7 +69,9 @@ If you're here to choose a license, **[start from the home page](/)** to see a f {% endfor %} {% endfor %} +
License
{{ license.title }}
{{ license.title }}
+ ## Legend @@ -93,3 +102,5 @@ If you're here to choose a license, **[start from the home page](/)** to see a f {% endfor %} {% endfor %} + + diff --git a/assets/js/appendix-explorer.js b/assets/js/appendix-explorer.js new file mode 100644 index 000000000..e0b67b325 --- /dev/null +++ b/assets/js/appendix-explorer.js @@ -0,0 +1,80 @@ +class AppendixExplorer { + constructor(root) { + this.root = root; + this.table = document.querySelector('.appendix-table'); + this.tbody = this.table?.querySelector('tbody'); + this.rows = Array.from(this.tbody?.querySelectorAll('tr') || []); + this.checkboxes = Array.from(root.querySelectorAll('input[type="checkbox"]')); + this.modeInputs = Array.from(root.querySelectorAll('input[name="appendix-filter-mode"]')); + this.statusEl = document.querySelector('#appendix-filter-status'); + this.clearBtn = document.querySelector('#appendix-filter-clear'); + + this.checkboxes.forEach((cb) => cb.addEventListener('change', () => this.apply())); + this.modeInputs.forEach((input) => input.addEventListener('change', () => this.apply())); + this.clearBtn?.addEventListener('click', () => { + this.checkboxes.forEach((cb) => { cb.checked = false; }); + this.apply(); + }); + + this.apply(); + } + + selectedRules() { + return this.checkboxes.filter((cb) => cb.checked).map((cb) => cb.value); + } + + mode() { + return this.modeInputs.find((input) => input.checked)?.value || 'hide'; + } + + rowMatches(row, rules) { + if (!rules.length) return true; + const rowTags = (row.dataset.rules || '').split(',').filter(Boolean); + return rules.every((rule) => rowTags.includes(rule)); + } + + apply() { + const rules = this.selectedRules(); + const mode = this.mode(); + let matches = 0; + + this.rows.forEach((row) => { + row.classList.remove('appendix-row-dimmed', 'appendix-row-match'); + row.hidden = false; + }); + + if (!rules.length) { + if (this.statusEl) this.statusEl.textContent = `Showing all ${this.rows.length} licenses.`; + return; + } + + const matching = []; + const nonMatching = []; + + this.rows.forEach((row) => { + if (this.rowMatches(row, rules)) { + matching.push(row); + row.classList.add('appendix-row-match'); + matches += 1; + } else { + nonMatching.push(row); + } + }); + + if (mode === 'hide') { + nonMatching.forEach((row) => { row.hidden = true; }); + } else { + [...matching, ...nonMatching].forEach((row) => this.tbody.appendChild(row)); + nonMatching.forEach((row) => row.classList.add('appendix-row-dimmed')); + } + + if (this.statusEl) { + this.statusEl.textContent = `${matches} license(s) match; ${nonMatching.length} do not (${mode === 'hide' ? 'hidden' : 'dimmed'}).`; + } + } +} + +document.addEventListener('DOMContentLoaded', () => { + const root = document.querySelector('#appendix-filter'); + if (root) new AppendixExplorer(root); +});