From bd4ddf8b58fa61281086f04ae7fab2b5c1f7cfed Mon Sep 17 00:00:00 2001 From: darkweak Date: Sun, 17 Dec 2023 21:13:51 +0100 Subject: [PATCH] feat(redesign): synchronize new download page to upstream --- src/download.html | 119 +++++++------ src/includes/card.html | 48 ++++++ src/resources/css/download.css | 298 +++++++++++++++++++++++++++++++++ src/resources/js/download.js | 206 +++++++++++++++++++++++ 4 files changed, 623 insertions(+), 48 deletions(-) create mode 100644 src/includes/card.html create mode 100644 src/resources/css/download.css create mode 100644 src/resources/js/download.js diff --git a/src/download.html b/src/download.html index cb25d893..4c8cfa67 100644 --- a/src/download.html +++ b/src/download.html @@ -2,32 +2,33 @@ Download Caddy - {{import "/old/includes/head.html"}} - {{template "head"}} - - - - + {{import "/includes/head.html"}} + {{template "head" .}} + + + -
-
-
- -
a project
+
+ {{include "/includes/header.html" "dark-header"}} + +
+
+

+ Download +
+ Build your own caddy binary with modules. +
+

- {{include "/old/includes/header-nav.html"}} -
- -
- ⚠️ Due to multiple outstanding bugs in the go command, we are aware that some downloads may hang or fail. In the meantime, you can download Caddy from the latest release on GitHub, or use xcaddy for custom builds. (Remember, this download page comes with no guarantees or SLAs.) Sorry for the inconvenience.
- -
-
-
- Platform: - @@ -56,39 +57,61 @@ + + +
-
-
-
- Standard features: ☑️ + +
+
+							xcaddy build
+						
+ + + + +
-
-
- Extra features: 0 -
-
-
- Download -
- - - -
- ⚠️ Only choose plugins you need and trust + +
+ +
-
- ⚠️ Run the following against the downloaded binary: - xattr -d com.apple.quarantine - + +
+
+
+
+
+ -
- -
-
+ {{include "/includes/footer.html"}} - {{include "/old/includes/footer.html"}} + + diff --git a/src/includes/card.html b/src/includes/card.html new file mode 100644 index 00000000..e332d0af --- /dev/null +++ b/src/includes/card.html @@ -0,0 +1,48 @@ +
+
+
+

+ ${item.name} +

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

+ ${item.description} +

+
+ +
+
+
\ No newline at end of file diff --git a/src/resources/css/download.css b/src/resources/css/download.css new file mode 100644 index 00000000..8c1c6268 --- /dev/null +++ b/src/resources/css/download.css @@ -0,0 +1,298 @@ +* { + --border-download: 1px solid rgba(226, 232, 240, 0.8); + --radius: 0.5rem; +} + +.card { + border: var(--border-download); + 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); +} + +.shadow-lg { + box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); +} + +.wrapper.list { + display: flex; +} + +#side-panel-packages { + padding-bottom: 32px; +} + +#side-panel-packages>div { + padding: 32px 1rem 1rem 0; + overflow-y: scroll; + width: 250px; + max-height: 100vh; + display: flex; + flex-direction: column; + position: sticky; + top: 100px; +} + +#download-link>button { + font-size: 1rem; +} + +#platform { + width: unset; + min-height: unset; + height: unset; + line-height: unset; +} + +#packages { + width: 100%; + padding-top: 32px; + padding-bottom: 32px; + display: grid; + gap: 48px; +} + +#download { + position: sticky; + top: 0; + padding-bottom: 32px; + z-index: 10; +} + +#download>div { + border: var(--border-download); + background-color: var(--body-bg); + border-radius: var(--radius); + padding: 1rem; +} + +#download>div { + border: var(--border-download); + background-color: var(--body-bg); + border-radius: var(--radius); + padding: 1rem; +} + +#downloader>button { + margin: 0 auto; + min-width: fit-content; + white-space: nowrap; + font-size: 80%; + font-weight: bold; +} + +#downloader { + padding-bottom: 16px; +} + +#command { + border-radius: var(--radius); + border: 1px solid var(--button-border-color); + padding: 0.5rem 0.75rem; + display: inline-flex; + width: 100%; +} + +#command>pre { + width: 100%; + overflow-x: scroll; + display: inline-flex; + white-space: nowrap; + font-size: 1rem; +} + +#command>svg { + height: 1em; + cursor: pointer; +} + +#command-builder::before { + content: '$'; + color: var(--text-color-muted); + margin-right: 0.25rem; +} + +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-name>a { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow-x: hidden; +} + +.card-title-info { + display: inline-flex; + gap: 8px; + 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-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; + } + + .wrapper.list { + flex-direction: column; + } + + #side-panel-packages>div { + overflow: unset; + width: 100%; + } +} + +input, +select { + padding: 0.5rem; + height: 3rem; + font-size: 100%; + font-weight: 600; + border: 1px solid var(--button-border-color); + color: var(--text-color); + background-color: var(--body-bg); + width: 100%; + border-radius: var(--radius); +} + +.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: 1px solid var(--button-border-color); + 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; +} + +.package-version { + height: 50%; + font-size: 80%; + width: 4rem; + padding-top: unset; + padding-bottom: unset; +} + +section { + padding: unset; + background-color: unset; +} + +h3.blue { + padding-left: unset; + border: unset; +} \ No newline at end of file diff --git a/src/resources/js/download.js b/src/resources/js/download.js new file mode 100644 index 00000000..7e4d5306 --- /dev/null +++ b/src/resources/js/download.js @@ -0,0 +1,206 @@ +const BASE_API_PATH = '/api'; +const pkgURL = `${BASE_API_PATH}/packages`; +const downloadURL = `${BASE_API_PATH}/download`; + +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 {ReadonlyArray} + */ + packages = []; + + /** + * @type {string} + */ + filter = ''; + + /** + * @returns Promise<> + */ + getPackages() { + return fetch(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 || item.path).split('/').pop().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(); + +const params = new URLSearchParams(window.location.search?.slice(1)); +let versions = params.getAll('p').reduce((acc, current) => { + [p, v] = current.split('@'); + + acc[p] = v ?? ''; + + return acc; +}, {}); + +function togglePackage({ target: { dataset: { module } } }) { + const element = document.getElementById('packages').querySelector(`button[data-module="${module}"]`); + if (module in versions) { + delete versions[module]; + const countVersions = Object.keys(versions).length; + if (!countVersions) { + modulesCount.innerHTML = ''; + } else { + modulesCount.innerHTML = `with ${countVersions} extra module${countVersions > 1 ? 's' : ''}`; + } + + element.innerHTML = "Add this module"; + } else { + versions[module] = ''; + const countVersions = Object.keys(versions).length; + element.innerHTML = "Remove this module"; + modulesCount.innerHTML = `with ${countVersions} extra module${countVersions > 1 ? 's' : ''}`; + } + + setDownloadLink(); +} + +function setDownloadLink() { + document.getElementById('command-builder').innerText = getCommand(); + document + .getElementById('download-link') + .setAttribute('href', `${downloadURL}?${new URLSearchParams(Object.entries(versions).map(([p, v]) => ['p', `${p}${!!v ? `@${v}` : ''}`])).toString()}`); +} + +function getCommand() { + return `xcaddy build${Object.entries(versions ?? {}).map(([p, v]) => ` --with ${p}${!!v ? `@${v}` : ''}`).join('')}` +} + +function copyCommand() { + navigator.clipboard.writeText(getCommand()) +} + +function renderList(list) { + if (groupBy === 'type') { + const groupedData = Object.entries(packageManager.group(groupBy)).filter(([_, items]) => !!items.length) + document.getElementById('side-panel-packages').innerHTML = ` +
+

Namespaces

+${groupedData.map(([k]) => ` ${k}`).join('')} +
`; + document.getElementById('packages').innerHTML = groupedData.map(([category, items]) => ` +
+

${category}

+
${items.map(item => getCardTemplate({ ...item, state: versions[item.path] })).join('')}
+
`).join('') + return; + } + + document.getElementById('side-panel-packages').innerHTML = ''; + document.getElementById('packages').innerHTML = ` +
+${list.map(item => getCardTemplate({ ...item, state: versions[item.path] })).join('')} +
`; +}; + +packageManager.getPackages().then(() => { + renderList(packageManager.group(groupBy)); + const countVersions = Object.keys(versions).length; + modulesCount.innerHTML = countVersions ? `with ${countVersions} extra module${countVersions > 1 ? 's' : ''}` : ''; + setDownloadLink(); +}) + +function updateVersion({ target: { value } }, pkg) { + versions[pkg] = value; + setDownloadLink(); +} \ No newline at end of file