diff --git a/gcp/website/frontend3/package-lock.json b/gcp/website/frontend3/package-lock.json
index 2d4d1fce000..b481d27e83f 100644
--- a/gcp/website/frontend3/package-lock.json
+++ b/gcp/website/frontend3/package-lock.json
@@ -14,8 +14,7 @@
"@material/layout-grid": "14.0.0",
"@material/theme": "14.0.0",
"@material/web": "^2.0.0",
- "lit": "3.3.2",
- "spicy-sections": "git+https://github.com/tabvengers/spicy-sections.git#c3aae99dbf1e627cdf03a35c913d7f6e970de22b"
+ "lit": "3.3.2"
},
"devDependencies": {
"copy-webpack-plugin": "^13.0.0",
@@ -5558,12 +5557,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/spicy-sections": {
- "version": "0.9.0",
- "resolved": "git+ssh://git@github.com/tabvengers/spicy-sections.git#c3aae99dbf1e627cdf03a35c913d7f6e970de22b",
- "integrity": "sha512-YyjgiaF9vhgpUL1NlXXrRljXJLeWs473YxVGoKWI/yAmimdqxFNrZ0eYqLHfsDjK1h0bzuyAVUkQZLuIg0UoUA==",
- "license": "W3C"
- },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
diff --git a/gcp/website/frontend3/package.json b/gcp/website/frontend3/package.json
index 0d0bdedd568..3d89a4763a7 100644
--- a/gcp/website/frontend3/package.json
+++ b/gcp/website/frontend3/package.json
@@ -15,8 +15,7 @@
"@material/layout-grid": "14.0.0",
"@material/theme": "14.0.0",
"@material/web": "^2.0.0",
- "lit": "3.3.2",
- "spicy-sections": "git+https://github.com/tabvengers/spicy-sections.git#c3aae99dbf1e627cdf03a35c913d7f6e970de22b"
+ "lit": "3.3.2"
},
"devDependencies": {
"copy-webpack-plugin": "^13.0.0",
diff --git a/gcp/website/frontend3/src/index.js b/gcp/website/frontend3/src/index.js
index 47a442816fc..e0f6ada973b 100644
--- a/gcp/website/frontend3/src/index.js
+++ b/gcp/website/frontend3/src/index.js
@@ -4,7 +4,7 @@ import '@material/web/icon/icon.js';
import '@material/web/iconbutton/icon-button.js';
import '@material/web/progress/circular-progress.js';
import '@hotwired/turbo';
-import 'spicy-sections/src/SpicySections';
+import './osv-tabs.js';
import { MdFilledTextField } from '@material/web/textfield/filled-text-field.js';
import { LitElement, html } from 'lit';
import { ExpandableSearch, SearchSuggestionsManager } from './search.js';
diff --git a/gcp/website/frontend3/src/osv-tabs.js b/gcp/website/frontend3/src/osv-tabs.js
new file mode 100644
index 00000000000..0f13df4faaa
--- /dev/null
+++ b/gcp/website/frontend3/src/osv-tabs.js
@@ -0,0 +1,222 @@
+class OsvTabs extends HTMLElement {
+ constructor() {
+ super();
+ this.breakpoint = 500;
+ this.mediaQuery = null;
+ this.headers = [];
+ this.panels = [];
+ this.activeIndex = 0;
+ this.headerListeners = null;
+
+ // shadow DOM for tab-bar layout
+ this.attachShadow({ mode: "open" });
+ this.shadowRoot.innerHTML = `
+
+
+
+
+
+
+
+
+ `;
+ }
+
+ connectedCallback() {
+ this.breakpoint = parseInt(this.getAttribute("breakpoint")) || 500;
+ this.collectHeadersAndPanels();
+ this.setupMediaQuery();
+ this.updateAffordance();
+ this.setupEventListeners();
+ }
+
+ disconnectedCallback() {
+ if (this.mediaQuery) {
+ this.mediaQuery.removeEventListener("change", this.boundUpdateAffordance);
+ }
+ this.removeEventListeners();
+ }
+
+ collectHeadersAndPanels() {
+ this.headers = [];
+ this.panels = [];
+
+ const children = Array.from(this.children);
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ if (child.matches("h2, h3")) {
+ const nextSibling = children[i + 1];
+ if (nextSibling && nextSibling.matches("div")) {
+ this.headers.push(child);
+ this.panels.push(nextSibling);
+ }
+ }
+ }
+ }
+
+ setupMediaQuery() {
+ this.mediaQuery = window.matchMedia(
+ `(min-width: ${this.breakpoint + 1}px)`
+ );
+ this.boundUpdateAffordance = () => this.updateAffordance();
+ this.mediaQuery.addEventListener("change", this.boundUpdateAffordance);
+ }
+
+ setupEventListeners() {
+ this.headerListeners = this.headers.map((header, index) => {
+ const clickListener = (e) => this.handleHeaderClick(index, e);
+ const keydownListener = (e) => this.handleKeydown(index, e);
+
+ header.addEventListener("click", clickListener);
+ header.addEventListener("keydown", keydownListener);
+
+ return { header, clickListener, keydownListener };
+ });
+ }
+
+ removeEventListeners() {
+ if (this.headerListeners) {
+ this.headerListeners.forEach(({ header, clickListener, keydownListener }) => {
+ header.removeEventListener("click", clickListener);
+ header.removeEventListener("keydown", keydownListener);
+ });
+ this.headerListeners = null;
+ }
+ }
+
+ updateAffordance() {
+ const isDesktop = this.mediaQuery.matches;
+ const affordance = isDesktop ? "tab-bar" : "collapse";
+ this.setAttribute("affordance", affordance);
+
+ if (isDesktop) {
+ this.renderTabs();
+ } else {
+ this.renderAccordion();
+ }
+ }
+
+ renderTabs() {
+ this.headers.forEach((header, index) => {
+ const isActive = index === this.activeIndex;
+ header.setAttribute("slot", "tab");
+ header.setAttribute("tabindex", isActive ? "0" : "-1");
+ header.setAttribute("role", "tab");
+ header.setAttribute("aria-selected", isActive ? "true" : "false");
+ header.removeAttribute("aria-expanded");
+ header.removeAttribute("expanded");
+ });
+
+ this.panels.forEach((panel, index) => {
+ const isActive = index === this.activeIndex;
+ panel.setAttribute("slot", "panel");
+ panel.setAttribute("role", "tabpanel");
+ if (isActive) {
+ panel.setAttribute("data-panel-active", "");
+ } else {
+ panel.removeAttribute("data-panel-active");
+ }
+ });
+ }
+
+ renderAccordion() {
+ this.headers.forEach((header, index) => {
+ const panel = this.panels[index];
+
+ header.removeAttribute("slot");
+ header.removeAttribute("role");
+ header.removeAttribute("aria-selected");
+ header.setAttribute("tabindex", "0");
+ header.setAttribute("aria-expanded", "true");
+ header.setAttribute("expanded", "");
+
+ panel.removeAttribute("slot");
+ panel.removeAttribute("data-panel-active");
+ panel.style.display = "";
+ });
+
+ this.panels.forEach((panel) => {
+ panel.setAttribute("role", "region");
+ });
+ }
+
+ handleHeaderClick(index, event) {
+ if (!this.headers[index].contains(event.target)) {
+ return;
+ }
+
+ const affordance = this.getAttribute("affordance");
+
+ if (affordance === "tab-bar") {
+ this.activeIndex = index;
+ this.renderTabs();
+ } else {
+ const panel = this.panels[index];
+ const header = this.headers[index];
+ const isExpanded = panel.style.display !== "none";
+
+ panel.style.display = isExpanded ? "none" : "";
+ header.setAttribute("aria-expanded", isExpanded ? "false" : "true");
+ if (isExpanded) {
+ header.removeAttribute("expanded");
+ } else {
+ header.setAttribute("expanded", "");
+ }
+ }
+ }
+
+ handleKeydown(index, event) {
+ const affordance = this.getAttribute("affordance");
+
+ if (event.key === "Enter" || event.key === " ") {
+ event.preventDefault();
+ this.handleHeaderClick(index, { target: this.headers[index] });
+ }
+
+ if (affordance === "tab-bar") {
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
+ event.preventDefault();
+ const nextIndex = (index + 1) % this.headers.length;
+ this.activeIndex = nextIndex;
+ this.renderTabs();
+ this.headers[nextIndex].focus();
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
+ event.preventDefault();
+ const prevIndex =
+ (index - 1 + this.headers.length) % this.headers.length;
+ this.activeIndex = prevIndex;
+ this.renderTabs();
+ this.headers[prevIndex].focus();
+ }
+ }
+ }
+}
+
+customElements.define("osv-tabs", OsvTabs);
diff --git a/gcp/website/frontend3/src/styles.scss b/gcp/website/frontend3/src/styles.scss
index 10740b59234..4ec2a395078 100644
--- a/gcp/website/frontend3/src/styles.scss
+++ b/gcp/website/frontend3/src/styles.scss
@@ -39,6 +39,37 @@ $osv-border-color: #555;
margin-right: -50vw;
}
+@mixin hide-details-marker {
+ list-style: none;
+ &::-webkit-details-marker {
+ display: none;
+ }
+ &::marker {
+ display: none;
+ content: '';
+ }
+}
+
+// Indicator for expand/collapse UI
+@mixin chevron-indicator($size: 12px, $margin-right: 8px, $color: $osv-grey-800) {
+ content: '';
+ display: inline-block;
+ width: $size;
+ height: $size;
+ margin-right: $margin-right;
+ -webkit-mask-image: url(/static/img/filled-triangle.svg);
+ mask-image: url(/static/img/filled-triangle.svg);
+ -webkit-mask-size: contain;
+ mask-size: contain;
+ -webkit-mask-repeat: no-repeat;
+ mask-repeat: no-repeat;
+ -webkit-mask-position: center;
+ mask-position: center;
+ background-color: $color;
+ transform: rotate(0deg);
+ transition: transform 0.2s ease-in-out, background-color 0.2s ease-in-out;
+}
+
/** Reset */
*,
@@ -391,65 +422,58 @@ pre {
gap: 10px;
align-items: center;
- // We use to collapse the ecosystem list only on mobile.
- // On desktop, display the full list.
- --const-mq-affordances: [screen and (max-width: #{$osv-mobile-breakpoint})] collapse;
+ .ecosystem-label-all {
+ position: relative;
+ margin-right: 20px;
- &::part(tab-bar) {
- // The tab bar affordance isn't used, so hide it.
- display: none;
+ &::after {
+ content: '';
+ position: absolute;
+ right: -16px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 1px;
+ height: 30px;
+ background-color: $osv-grey-600;
+ }
}
- &::part(content-panels) {
- // Because the shadow root of actually wraps our content
- // we need to use display: contents; in order for flex to apply as if the
- // child elements are direct descendants of the parent flex container.
- display: contents;
- }
+ @media (max-width: #{$osv-mobile-breakpoint}) {
+ gap: 8px;
- .spicy-content {
- // Expanded contents also should act as if they were direct descendants
- // of our flex container.
- display: contents;
- }
+ .ecosystem-label {
+ padding: 8px 14px;
+ height: 32px;
+ gap: 12px;
+ font-size: 14px;
+ }
- // Customizing the collapsing header for . We can select it
- // by looking for the element with affordance="collapse".
- [affordance=collapse] {
- display: flex;
- align-items: center;
+ .ecosystem-label-all {
+ margin-right: 14px;
- // The ::before on the collapsible is used to display the chevron.
- &::before {
- display: block;
- padding: 0 8px;
- // HACK: Invert the black chevron to white. The chevron is set using
- // background-image, so we can't use the CSS fill property on it.
- filter: invert(100%);
+ &::after {
+ height: 20px;
+ right: -12px;
+ }
}
}
.ecosystem-label {
- display: flex;
+ display: inline-flex;
gap: 16px;
font-family: $osv-heading-font-family;
padding: 10px 20px;
background: #696969;
border-radius: 999px;
height: 38px;
+ cursor: pointer;
+ align-items: center;
.ecosystem-count {
font-weight: bold;
}
}
- .ecosystems-divider {
- display: block;
- margin: 0 10px;
- height: 30px;
- border-right: 1px solid $osv-grey-600;
- }
-
input[type=radio]:checked+.ecosystem-label {
background: $osv-text-color;
color: $osv-accent-color;
@@ -879,12 +903,28 @@ dl.vulnerability-details,
margin-bottom: 16px;
font-weight: bold;
}
+}
- // Tab bar styling.
- --const-mq-affordances: [screen and (max-width: #{$osv-mobile-breakpoint})] collapse | [screen and (min-width: #{$osv-mobile-breakpoint + 1})] tab-bar;
+osv-tabs.vulnerability-packages[affordance="tab-bar"] {
+ display: block;
+}
+
+osv-tabs.vulnerability-packages[affordance="collapse"] {
+ display: block;
+
+ > h2.package-header {
+ display: block;
+ width: 100%;
+ cursor: pointer;
- .force-collapse {
- --const-mq-affordances: [screen] collapse;
+ &::before {
+ @include chevron-indicator;
+ }
+
+ &[expanded]::before {
+ transform: rotate(90deg);
+ background-color: $osv-accent-color;
+ }
}
}
@@ -893,6 +933,7 @@ dl.vulnerability-details,
background: #aaa;
color: #000;
display: inline-block;
+ font-family: $osv-heading-font-family;
font-size: 14px;
padding: 16px;
}
@@ -912,28 +953,25 @@ dl.vulnerability-details,
}
.versions-section {
- --const-mq-affordances: [screen] collapse;
+ > summary {
+ @include hide-details-marker;
+ }
- h2.version-header::before {
- content: '';
- margin-right: 16px;
- background: url(/static/img/filled-triangle.svg);
- background-position: center;
- transform: rotate(0deg);
- // Make the filled triangle white.
- filter: invert(100%);
+ summary.version-header::before {
+ @include chevron-indicator($margin-right: 16px, $color: #fff);
}
- h2.version-header[expanded]::before {
+ &[open] > summary.version-header::before {
transform: rotate(90deg);
}
- h2.version-header {
+ summary.version-header {
background: none;
font-family: $osv-heading-font-family;
color: #fff;
- padding-left: 0px;
+ padding: 16px 0;
font-size: 16px;
+ cursor: pointer;
}
}
@@ -942,6 +980,7 @@ dl.vulnerability-details,
overflow: auto;
flex-wrap: wrap;
gap: 8px;
+ margin-top: 8px;
padding-bottom: 12px;
}
@@ -969,32 +1008,27 @@ dl.vulnerability-details,
}
.database-specific-section {
- --const-mq-affordances: [screen] collapse;
+ > summary {
+ @include hide-details-marker;
+ }
- h2.database-specific-header {
+ summary.database-specific-header {
background: none;
font-family: $osv-heading-font-family;
color: #fff;
- padding-left: 0;
+ padding: 16px 0;
font-size: 16px;
display: flex;
align-items: center;
+ cursor: pointer;
&::before {
- content: '';
- width: 12px;
- height: 12px;
- margin-right: 8px;
- background: url(/static/img/filled-triangle.svg);
- background-position: center;
- background-repeat: no-repeat;
- transform: rotate(0deg);
- filter: invert(100%);
+ @include chevron-indicator($color: #fff);
}
+ }
- &[expanded]::before {
- transform: rotate(90deg);
- }
+ &[open] > summary.database-specific-header::before {
+ transform: rotate(90deg);
}
}
@@ -1002,56 +1036,46 @@ dl.vulnerability-details,
padding: 8px 0 0 24px;
}
- .spicy-sections-workaround {
- // https://github.com/tabvengers/spicy-sections/issues/64.
- pointer-events: none;
- }
&.force-collapse {
- h2.package-header {
+ .ecosystem-accordion > summary {
+ @include hide-details-marker;
+ }
+
+ summary.package-header {
width: 100%;
background: #393939;
color: #fff;
padding: 16px;
margin-bottom: 2px;
border-radius: 0;
+ font-family: $osv-heading-font-family;
font-weight: normal;
transition: background-color 0.2s ease-in-out;
cursor: pointer;
+ display: block;
&::before {
- content: '';
- width: 12px;
- height: 12px;
- margin-right: 8px;
- background-image: url(/static/img/filled-triangle.svg);
- background-position: center;
- background-repeat: no-repeat;
- transition: transform 0.2s ease-in-out;
- transform: rotate(0deg);
- filter: invert(100%);
- }
-
- &[expanded]::before {
- transform: rotate(90deg);
+ @include chevron-indicator($color: #fff);
}
&:hover {
background: #4F4F4F;
}
+ }
- &[expanded] {
- background: #fff;
- color: $osv-accent-color;
- font-weight: bold;
-
- &::before {
- filter: invert(24%) sepia(89%) saturate(2293%) hue-rotate(345deg) brightness(81%) contrast(107%);
- }
-
- &:hover {
- background: #f0f0f0;
- }
+ .ecosystem-accordion[open] > summary.package-header {
+ background: #fff;
+ color: $osv-accent-color;
+ font-weight: bold;
+
+ &::before {
+ transform: rotate(90deg);
+ background-color: $osv-accent-color;
+ }
+
+ &:hover {
+ background: #f0f0f0;
}
}
@@ -1095,7 +1119,10 @@ dl.vulnerability-details,
.package-accordion {
position: relative;
- margin-bottom: 8px;
+
+ > summary {
+ @include hide-details-marker;
+ }
// The horizontal "connector" line
&::before {
@@ -1114,37 +1141,29 @@ dl.vulnerability-details,
padding-bottom: $last-ecosystem-border-gap;
}
- .package-accordion h3.package-name-title {
+ .package-accordion summary.package-name-title {
font-family: $osv-heading-font-family;
font-size: 1.1rem;
+ font-weight: bold;
color: #f1f1f1;
padding: 12px 16px;
cursor: pointer;
background: #333333;
border: 1px solid #444;
border-radius: 0;
+ display: block;
&::before {
- content: '';
- width: 12px;
- height: 12px;
- margin-right: 8px;
- background-image: url(/static/img/filled-triangle.svg);
- background-position: center;
- background-repeat: no-repeat;
- filter: invert(100%);
- transition: transform 0.2s ease-in-out;
- display: inline-block;
+ @include chevron-indicator($color: #fff);
vertical-align: middle;
- transform: rotate(0deg);
}
+ }
- &[expanded] {
- border-bottom: 1px dashed #fff;
-
- &::before {
- transform: rotate(90deg);
- }
+ .package-accordion[open] > summary.package-name-title {
+ border-bottom: 1px dashed #fff;
+
+ &::before {
+ transform: rotate(90deg);
}
}
@@ -1214,7 +1233,6 @@ dl.vulnerability-details,
&[tabindex="0"] {
background: #fff;
color: $osv-accent-color;
- // Override spicy-sections default blue bottom border.
border-bottom: 1px solid $osv-accent-color;
}
diff --git a/gcp/website/frontend3/src/templates/list.html b/gcp/website/frontend3/src/templates/list.html
index 7218905be6d..77c2e20d99f 100644
--- a/gcp/website/frontend3/src/templates/list.html
+++ b/gcp/website/frontend3/src/templates/list.html
@@ -43,27 +43,22 @@ Vulnerabilities
{% if ecosystem_counts %}
-
-
-
-
-
-
-
- {% for ecosystem in ecosystem_counts %}
-
-
- {% endfor %}
-
-
+
+
+
+ {% for ecosystem in ecosystem_counts %}
+
+
+ {% endfor %}
+
{% endif %}
diff --git a/gcp/website/frontend3/src/templates/vulnerability.html b/gcp/website/frontend3/src/templates/vulnerability.html
index 3b7a1d58b48..3a37f434249 100644
--- a/gcp/website/frontend3/src/templates/vulnerability.html
+++ b/gcp/website/frontend3/src/templates/vulnerability.html
@@ -177,16 +177,17 @@ Affected packages
{% if vulnerability.affected|should_collapse %}
{% set ecosystems = vulnerability.affected | group_by_ecosystem %}
-
+
{% for ecosystem_name, packages in ecosystems.items() -%}
{% set is_last_ecosystem = loop.last %}
-
-
- {% for affected in packages -%}
-
-
+
+
+
+ {% for affected in packages -%}
+
+
{% if 'package' in affected %}
{{ affected.package.name }}
{% else %}
@@ -195,7 +196,7 @@
{{ affected_repo | strip_scheme }}
{% endif %}
{% endif %}
-
+
{%- if 'package' in affected -%}
@@ -285,14 +286,14 @@
{% for group, versions in (affected.versions|group_versions(ecosystem_name)).items() -%}
-
-
+
+
{% for version in versions -%}
{{ version }}
{% endfor -%}
-
+
{% endfor -%}
@@ -311,12 +312,12 @@
{% else %}
@@ -327,15 +328,16 @@
{% endif -%}
-
+
{% endfor -%}
-
+
+
{% endfor -%}
-
+
{% else %}
-
+
{% for affected in vulnerability.affected -%}
{% if 'package' in affected %}
{% set ecosystem = affected.package.ecosystem %}
@@ -348,9 +350,9 @@
{% endif %}
{% endif %}
{%- if 'package' in affected -%}
@@ -469,14 +471,14 @@
{% for group, versions in (affected.versions|group_versions(ecosystem)).items() -%}
-
-
+
+
{% for version in versions -%}
{{ version }}
{% endfor -%}
-
+
{% endfor -%}
@@ -505,12 +507,12 @@
{% if db_specific is mapping %}
{% for key, value in db_specific.items() %}
-
-
+
+
{{ value | display_json }}
-
+
{% endfor %}
{% else %}
@@ -521,7 +523,7 @@
{% endif -%}
{% endfor -%}
-
+
{% endif %}
@@ -544,75 +546,33 @@
* - Within each ecosystem, all package headers are expanded if their count is below `PACKAGE_EXPAND_THRESHOLD`.
*/
function setupVerticalLayout() {
- const ecosystemHeaders = document.querySelectorAll('.vulnerability-packages.force-collapse .package-header');
- if (!ecosystemHeaders.length) return;
+ const ecosystemAccordions = document.querySelectorAll('.vulnerability-packages.force-collapse .ecosystem-accordion');
+ if (!ecosystemAccordions.length) return;
- const shouldExpandEcosystems = ecosystemHeaders.length < ECOSYSTEM_EXPAND_THRESHOLD;
+ const shouldExpandEcosystems = ecosystemAccordions.length < ECOSYSTEM_EXPAND_THRESHOLD;
- ecosystemHeaders.forEach(header => {
- const panel = header.nextElementSibling;
- const packageHeaders = panel ? panel.querySelectorAll('.package-name-title') : [];
+ ecosystemAccordions.forEach(accordion => {
+ const panel = accordion.querySelector('.ecosystem-content-panel');
+ const packageAccordions = panel ? panel.querySelectorAll('.package-accordion') : [];
const hasManyPackages =
- ecosystemHeaders.length > 1 && packageHeaders.length > ECOSYSTEM_PACKAGE_COLLAPSE_THRESHOLD;
+ ecosystemAccordions.length > 1 && packageAccordions.length > ECOSYSTEM_PACKAGE_COLLAPSE_THRESHOLD;
- const shouldExpandHeader =
+ const shouldExpandAccordion =
shouldExpandEcosystems &&
- header.getAttribute('aria-expanded') === 'false' &&
panel !== null &&
!hasManyPackages;
- if (shouldExpandHeader) {
- header.click();
- }
- });
+ accordion.open = shouldExpandAccordion;
- const ecosystemPanels = document.querySelectorAll('.ecosystem-content-panel');
- ecosystemPanels.forEach(panel => {
- const packageHeaders = panel.querySelectorAll('.package-name-title');
- if (packageHeaders.length <= PACKAGE_EXPAND_THRESHOLD) {
- packageHeaders.forEach(header => {
- if (header.getAttribute('aria-expanded') === 'false') {
- header.click();
- }
- });
- }
+ const shouldExpandPackages = packageAccordions.length <= PACKAGE_EXPAND_THRESHOLD;
+ packageAccordions.forEach(packageAccordion => {
+ packageAccordion.open = shouldExpandPackages;
+ });
});
}
- /**
- * Sets up the default layout,(tabs on desktop, accordion on mobile).
- * This function ensures that on the mobile accordion view,
- * all package headers are expanded by default if their count is
- * below a defined threshold. This has no effect on the desktop tab view.
- */
- function setupDefaultLayout() {
- const packageHeaders = document.querySelectorAll('.vulnerability-packages:not(.force-collapse) .package-header');
- if (!packageHeaders.length) return;
-
- /**
- * Expands collapsed package headers.
- * We use `spicy-section` to make the packages content collapsible for mobile view,
- * but it collapses the content by default. We want it expanded after the page
- * is loaded, so we programmatically click on the header of collapsed packages
- * to make the content visible.
- */
- function expandPackageHeaders() {
- packageHeaders.forEach((header) => {
- if (header.getAttribute('aria-expanded') === 'false') {
- header.click();
- }
- });
- }
-
- if (packageHeaders.length < ECOSYSTEM_EXPAND_THRESHOLD) {
- expandPackageHeaders();
- }
- }
-
if (document.querySelector('.vulnerability-packages.force-collapse')) {
setupVerticalLayout();
- } else {
- setupDefaultLayout();
}
/**