diff --git a/new/download.html b/new/download.html index bc5a259..535d39c 100644 --- a/new/download.html +++ b/new/download.html @@ -4,21 +4,73 @@ Download Caddy {{include "/includes/head.html"}} + + -
- {{include "/includes/header.html" "light-header"}} - - + {{include "/includes/header.html" "light-header"}}
- -
- Hello, world! +
+

Download

+
+ +
+ + +
+ +
+
+
+
+
-
{{include "/includes/footer.html"}} + \ No newline at end of file diff --git a/new/includes/card.html b/new/includes/card.html new file mode 100644 index 0000000..e4c8998 --- /dev/null +++ b/new/includes/card.html @@ -0,0 +1,46 @@ +
+
+
+

+ ${item.name} +

+ + ${item.path} + +
+
+ + + + + + + + ${item.downloads} + + + + + + + + + latest + +
+
+
+

+ ${item.description} +

+
+ +
+
+
\ No newline at end of file diff --git a/new/resources/css/download.css b/new/resources/css/download.css new file mode 100644 index 0000000..d1090c0 --- /dev/null +++ b/new/resources/css/download.css @@ -0,0 +1,189 @@ +.card { + border: 1px solid rgba(226, 232, 240, 0.8); + border-radius: 16px; + padding: 16px; + width: 100%; + height: 100%; + background-color: var(--body-bg); + display: flex; + flex-direction: column; + gap: 16px; +} + +html { + scroll-behavior: smooth; +} + +.shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06); +} + +.wrapper.list { + display: flex; +} + +#side-panel-packages { + padding-top: 32px; + padding-bottom: 32px; +} + +#side-panel-packages>div { + padding: 1rem 1rem 1rem 0; + overflow-y: scroll; + width: 300px; + max-height: 100vh; + display: flex; + flex-direction: column; + position: sticky; + top: 0; +} + +#packages { + width: 100%; + padding-top: 32px; + padding-bottom: 32px; + display: grid; + gap: 48px; +} + +#packages>section>h2 { + padding-bottom: 16px; + color: rgb(14, 110, 189); + border-color: rgb(14, 110, 189); +} + +.card-list { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 32px; +} + +.card-header { + width: 100%; + display: flex; + justify-content: space-between; +} + +.card-title { + width: 100%; + display: flex; + padding-left: 16px; + justify-content: space-between; +} + +.card-title-name { + display: flex; + flex-direction: column; + font-weight: lighter; + color: var(--text-color); +} + +.card-title-info { + display: inline-flex; + gap: 16px; + font-size: 90%; + color: var(--text-color-muted); +} + +.card-title-info>span>svg { + margin-right: -4px; +} + +.card-header>:first-child>span:first-child { + font-weight: bold; +} + +.card-icon { + border-radius: 8px; +} + +.card-description { + display: flex; + color: var(--text-color-muted); + justify-content: space-between; + height: 100%; + gap: 8px; +} + +.card-description>p { + font-weight: 400; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 4; + overflow: hidden; + word-break: break-word; +} + +.card-actions { + margin-top: auto; +} + +.card-button { + margin: 0 auto; + min-width: fit-content; + white-space: nowrap; + font-size: 80%; + font-weight: bold; +} + +article { + margin-left: auto; + margin-right: auto; +} + +.filters { + display: grid; + grid-template-columns: 2fr 1fr 2fr; + gap: 16px; +} + +@media (max-width: 1400px) { + .card-description { + flex-direction: column; + } + + .card-list { + grid-template-columns: 1fr; + } + + .filters { + grid-template-columns: 1fr; + } +} + +input, +select { + padding: 0.5rem; + height: 3rem; + font-size: 100%; + font-weight: 600; + background-color: white; + width: 100%; + border-radius: 0.5rem; + border: none; +} + +.filters>div { + flex-direction: column; +} + +select { + cursor: pointer; + -webkit-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding-left: 1rem; + padding-right: 2.5rem; + font-size: .875rem; + line-height: 1.25rem; + line-height: 2; + min-height: 3rem; + border-width: 1px; + border-color: var(--text-color-muted); + background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%); + background-position: calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%); + background-size: 4px 4px, 4px 4px; + background-repeat: no-repeat; +} \ No newline at end of file diff --git a/new/resources/js/download.js b/new/resources/js/download.js new file mode 100644 index 0000000..71941de --- /dev/null +++ b/new/resources/js/download.js @@ -0,0 +1,120 @@ +class Package { + /** + * @typedef {Object} Module + * @property {string} docs + * @property {string} name + * @property {string} package + * @property {string} repo + */ + + /** + * @typedef {Object} Pkg + * @property {string} id + * @property {string} path + * @property {string} published + * @property {boolean} listed + * @property {boolean} available + * @property {number} downloads + * @property {ReadonlyArray} modules + * @property {string} repo + * @property {string} name + */ + + /** @type {string} */ + pkgURL = "https://localhost/api/packages"; + + /** + * @type {ReadonlyArray} + */ + packages = []; + + /** + * @type {string} + */ + filter = ''; + + /** + * @returns Promise<> + */ + getPackages() { + return fetch(this.pkgURL, { headers: { 'X-Requested-With': 'XMLHttpRequest', Origin: 'https://caddyserver.com' } }) + .then(res => res.json()) + .then(({ result }) => this.packages = result.sort((a, b) => a.downloads - b.downloads).map(item => ({ ...item, description: item.modules?.map(m => m.docs ?? m.name).join('\n') ?? '', name: item.repo.split('/')[4].toLowerCase() }))); + } + + setFilterValue(value) { + this.filter = value; + } + + getSearchPackages(pkgs) { + if (!this.filter) { + return pkgs; + } + + return pkgs.filter(pkg => pkg.name.includes(this.filter) || pkg.repo.includes(this.filter) || pkg.description.includes(this.filter)); + } + + /** + * @param {'alphabetically' | 'type' | 'download'} groupBy + * @return { + * Record | ReadonlyArray a.name.localeCompare(b.name)); + case 'download': + return pkgs.sort((a,b) => b.downloads - a.downloads); + case 'type': + return pkgs.reduce((acc, current) => { + if (!current?.modules?.length) { + + return acc; + } + + current.modules.forEach(module => { + let moduleName = module.name + if (module.name.includes('.')) { + const splitted = module.name.split('.') + moduleName = `${splitted[0]}.${splitted[1]}` + } + if (acc[moduleName]) { + acc[moduleName] = [...acc[moduleName], current]; + } + }) + + return acc; + }, { + "http.handlers": [], + "http.matchers": [], + "dns.providers": [], + "http.encoders": [], + "caddy.config_loaders": [], + "caddy.fs": [], + "caddy.listeners": [], + "caddy.logging.encoders": [], + "caddy.logging.encoders.filter": [], + "caddy.logging.writers": [], + "caddy.storage": [], + "events.handlers": [], + "http.authentication.hashes": [], + "http.authentication.providers": [], + "http.ip_sources": [], + "http.precompressed": [], + "http.reverse_proxy.circuit_breakers": [], + "http.reverse_proxy.selection_policies": [], + "http.reverse_proxy.transport": [], + "http.reverse_proxy.upstreams": [], + "tls.certificates": [], + "tls.client_auth": [], + "tls.handshake_match": [], + "tls.issuance": [], + "tls.get_certificate": [], + "tls.stek": [], + }) + } + } +} + +const packageManager = new Package();