diff --git a/public/components/icon/icon.js b/public/components/icon/icon.js index 23b8ce28..8573a850 100644 --- a/public/components/icon/icon.js +++ b/public/components/icon/icon.js @@ -141,12 +141,41 @@ c0-20.25-0.75-88.25 31-88.25c5.25 0 31.25 21.25 65 21.25c11.5 0 22.5-2 33.25-5.7 `, lock: html` +`, + eye: html` + +`, + "eye-off": html` + ` }; diff --git a/public/components/package/package.css b/public/components/package/package.css index 70fbdd47..3513f741 100644 --- a/public/components/package/package.css +++ b/public/components/package/package.css @@ -1,5 +1,4 @@ @import url("./header/header.css"); -@import url("./pannels/scripts/scripts.css"); @import url("./pannels/overview/overview.css"); @import url("./pannels/scorecard/scorecard.css"); @import url("./pannels/warnings/warnings.css"); diff --git a/public/components/package/package.js b/public/components/package/package.js index 81ff1083..918580e7 100644 --- a/public/components/package/package.js +++ b/public/components/package/package.js @@ -109,6 +109,14 @@ export class PackageInfo { vulns.id = "pan-vulnerabilities"; vulns.classList.add("package-container", "hidden"); panVulns.parentElement.replaceChild(vulns, panVulns); + + const panDependencies = packageHTMLElement.querySelector("#pan-dependencies"); + const scripts = document.createElement("package-scripts"); + scripts.package = this; + scripts.isHidden = this.dependencyVersion.hidden; + scripts.id = "pan-dependencies"; + scripts.classList.add("package-container", "hidden"); + panDependencies.parentElement.replaceChild(scripts, panDependencies); } /** @@ -126,6 +134,20 @@ export class PackageInfo { } } + /** + * @param {!HTMLTemplateElement} clone + */ + setupSignal(clone) { + const { flags } = this.dependencyVersion; + + if (flags.includes("hasScript")) { + this.addNavigationSignal( + clone.getElementById("dependencies-nav-menu"), + "!" + ); + } + } + /** * @param {!string} name * @returns {void} @@ -170,7 +192,7 @@ export class PackageInfo { new Pannels.Overview(this).generate(clone); new Pannels.Warnings(this).generate(clone); - new Pannels.Scripts(this).generate(clone); + this.setupSignal(clone); this.addNavigationSignal(clone.getElementById("vulnerabilities-nav-menu"), this.dependency.vulnerabilities.length); if (window.settings.config.disableExternalRequests === false) { diff --git a/public/components/package/pannels/scripts/scripts.js b/public/components/package/pannels/scripts/scripts.js index ceb69c1d..b2971eca 100644 --- a/public/components/package/pannels/scripts/scripts.js +++ b/public/components/package/pannels/scripts/scripts.js @@ -1,6 +1,14 @@ +// Import Third-party Dependencies +import { LitElement, html, css, nothing } from "lit"; +import { when } from "lit/directives/when.js"; +import { repeat } from "lit/directives/repeat.js"; +import { classMap } from "lit/directives/class-map.js"; + // Import Internal Dependencies import * as utils from "../../../../common/utils.js"; import "../../../expandable/expandable.js"; +import "../../../items-list/items-list.js"; +import "../../../icon/icon.js"; // CONSTANTS const kUnsafeNpmScripts = new Set([ @@ -11,163 +19,304 @@ const kUnsafeNpmScripts = new Set([ "postuninstall" ]); -export class Scripts { - static SimulationTimeout = null; +class Scripts extends LitElement { + static styles = css` +.package-scripts { + display: flex; + flex-direction: column; + margin: 10px 0; +} - constructor(pkg) { - this.package = pkg; - } +.package-scripts .script { + display: flex; + flex-direction: column; + padding: 5px; + border-radius: 4px; + box-sizing: border-box; +} - /** - * @param {!HTMLTemplateElement} clone - */ - generate(clone) { - this.setupSignal(clone); - - const packageScriptsElement = clone.querySelector(".package-scripts"); - const fragment = this.renderScripts(packageScriptsElement); - if (fragment === null) { - clone.getElementById("script-title").style.display = "none"; - packageScriptsElement.style.display = "none"; - } - else { - packageScriptsElement.appendChild(this.renderScripts(packageScriptsElement)); - } - this.renderDependencies(clone); - this.showHideDependenciesInTree(clone); - } +.package-scripts .script.suspicious { + background: #8776464f; +} - /** - * @param {!HTMLTemplateElement} clone - */ - setupSignal(clone) { - const { flags } = this.package.dependencyVersion; - - if (flags.includes("hasScript")) { - this.package.addNavigationSignal( - clone.getElementById("dependencies-nav-menu"), - "!" - ); - } +.package-scripts .script:nth-child(even) { + background: rgb(150 100 150 / 5%); +} + +.package-scripts .script+.script { + margin-top: 5px; +} + +.package-scripts .script.suspicious p.name { + color: #efe493; +} + +.package-scripts .script p.name { + color: #B3E5FC; + height: 26px; + display: flex; + align-items: center; + letter-spacing: 0.3px; +} + +.package-scripts .script p.value { + font-size: 13px; + color: #f3e4f8; + font-family: mononoki; + margin-top: 5px; +} + +.head-title { + background: var(--primary-darker); + height: 28px; + flex-shrink: 0; + display: flex; + align-items: center; + border-bottom: 2px solid var(--primary-lighter); + border-radius: 2px 2px 0 0; +} + +.head-title.no-margin { + margin-top: 0; +} + +.head-title>p { + text-shadow: 1px 1px 5px rgb(20 20 20 / 50%); + font-size: 18px; + font-variant: small-caps; + + /* lowercase is needed with small-caps font variant */ + text-transform: lowercase; + font-family: mononoki; + font-weight: bold; + letter-spacing: 1px; + padding: 0 10px; +} + +.head-title>span { + margin-left: auto; + background: #0068ff; + margin-right: 10px; + padding: 2px 5px; + border-radius: 4px; + font-size: 12px; + font-family: mononoki; + text-shadow: 2px 2px 5px #000000d4; + transition: all 0.5s ease; + cursor: pointer; +} + +.head-title>span.disabled { + background: #3b3b3b; + cursor: default; + opacity: 0.3; +} + +.head-title>span.active { + background: #7900ff; +} + +.head-title>span.active:not(.disabled):hover { + background: #0068ff; +} + +.head-title>span:not(.disabled, .active):hover { + background: #7900ff; +} + +#show-hide-dependency nsecure-icon { + transform: translateY(2px); +} +`; + + static properties = { + package: { type: Object }, + isClosed: { type: Boolean }, + isHidden: { type: Boolean } + }; + + static SimulationTimeout = null; + + constructor() { + super(); + this.isClosed = true; } - renderScripts(packageScriptsElement) { - const fragment = document.createDocumentFragment(); - function createPElement(className, text) { - return utils.createDOMElement("p", { className, text }); - } + render() { + return html` + ${this.#renderScripts()} + ${this.#renderDependencies()} + `; + } - const scripts = Object.entries(this.package.dependencyVersion.scripts); - if (scripts.length === 0) { - return null; - } + #renderScripts() { const hideItemsLength = 4; + const scripts = Object.entries(this.package.dependencyVersion.scripts); const hideItems = scripts.length > hideItemsLength; + const scriptsToDisplay = this.isClosed ? scripts.slice(0, hideItemsLength) : scripts; - for (let id = 0; id < scripts.length; id++) { - const [scriptName, scriptContent] = scripts[id]; - - const isSuspicious = kUnsafeNpmScripts.has(scriptName); - const script = utils.createDOMElement("div", { - className: "script", - childs: [ - createPElement("name", (isSuspicious ? "⚠️ " : "") + scriptName), - createPElement("value", scriptContent) - ] - }); - if (isSuspicious) { - script.classList.add("suspicious"); - } - if (hideItems && id >= hideItemsLength) { - script.classList.add("hidden"); - } - - fragment.appendChild(script); + if (scripts.length === 0) { + return nothing; } - if (hideItems) { - const expandableSpan = document.createElement("expandable-span"); - expandableSpan.onToggle = (expandable) => utils.toggle(expandable, packageScriptsElement, hideItemsLength); - fragment.appendChild(expandableSpan); - } + return html` +
npm scripts
+${`⚠️ ${scriptName}`}
`, + () => html`${scriptName}
` + )} +${scriptContent}
+${i18n.package_info.title.unused_deps}
+${i18n.package_info.title.missing_deps}
+${i18n.package_info.title.node_deps}
+${i18n.package_info.title.third_party_deps}
+ ${this.#showHideDependenciesInTree()} +