From 741322be5dd5b4f34615f8e51039dacfb5c40746 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jun 2026 14:23:29 -0400 Subject: [PATCH 1/8] Redesign site to match dprint.dev style Reworks plugins.dprint.dev to share the redesigned dprint.dev design system: IBM Plex Mono, dark theme with the #8b93a1 accent, sticky blurred nav and footer, glow hero title, and card-based sections. - Hero with eyebrow + glowing "Plugins" title - Plugin registry as a styled table (swatch, version pill, monospace URL, right-aligned download counts, copy button) that collapses to cards on mobile - Helpful CLI commands as a card grid, plus a glowing CTA card - Copy button now uses navigator.clipboard with execCommand fallback and shows feedback - Split the markup into homeView.tsx (type-only imports) so the page can be rendered without the server data layer --- home.tsx | 94 +-------- homeView.tsx | 191 ++++++++++++++++++ style.css | 539 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 696 insertions(+), 128 deletions(-) create mode 100644 homeView.tsx diff --git a/home.tsx b/home.tsx index 11e10f3..cf5bb8a 100644 --- a/home.tsx +++ b/home.tsx @@ -1,95 +1,7 @@ -import { renderToString } from "preact-render-to-string"; -import { PluginData, PluginsData, readInfoFile } from "./readInfoFile.js"; +import { renderHomeHtml } from "./homeView.jsx"; +import { readInfoFile } from "./readInfoFile.js"; export async function renderHome(origin: string, ctx?: ExecutionContext) { - const content = await renderContent(origin, ctx); - return ` - - - - - - Plugins - dprint - - - - - ${content} - - -`; -} - -async function renderContent(origin: string, ctx?: ExecutionContext) { const pluginsData = await readInfoFile(origin, ctx); - const section = ( -
-

Plugins

- {renderPlugins(pluginsData)} -

- Helpful commands: -

-

-

- Documentation -

-
- ); - return renderToString(section); -} - -function renderPlugins(data: PluginsData) { - return ( -
-
-
Name
-
Latest URL
-
Downloads (30d)
-
-
- {data.latest.map((plugin) => renderPlugin(plugin))} -
- ); -} - -function renderPlugin(plugin: PluginData) { - return ( -
-
{plugin.name}
-
{plugin.url}
-
{plugin.downloadCount.allVersions?.toLocaleString("en-US")}
-
- -
-
- ); + return renderHomeHtml(pluginsData); } diff --git a/homeView.tsx b/homeView.tsx new file mode 100644 index 0000000..5b99b84 --- /dev/null +++ b/homeView.tsx @@ -0,0 +1,191 @@ +import { renderToString } from "preact-render-to-string"; +import type { PluginData, PluginsData } from "./readInfoFile.js"; + +// renders the full home page document for the given plugins data. kept free of +// any server/data imports so it can be rendered in isolation (e.g. previews). +export function renderHomeHtml(pluginsData: PluginsData) { + const content = renderToString(renderPage(pluginsData)); + return ` + + + + + + + Plugins - dprint - Code Formatter + + + + + + + + ${content} + + +`; +} + +function renderPage(pluginsData: PluginsData) { + return ( + <> + + +
+
+
+
+
// plugin registry
+

Plugins

+

+ The latest version of every dprint plugin, with a copy-paste URL for your{" "} + dprint.json. Updated automatically from each plugin's GitHub releases. +

+
+
+ + {renderPlugins(pluginsData)} + {renderCommands()} + {renderCta()} +
+ + + + ); +} + +function renderPlugins(data: PluginsData) { + return ( +
+
// latest versions
+

Grab a plugin URL.

+

Drop any of these into the plugins array of your dprint.json, or let the CLI manage them for you.

+ +
+
+
Plugin
+
Latest URL
+
Downloads (30d)
+
+
+ {data.latest.map((plugin) => renderPlugin(plugin))} +
+
+ ); +} + +function renderPlugin(plugin: PluginData) { + return ( +
+
+ + {plugin.name} + {plugin.version ? {plugin.version} : null} +
+
+ {plugin.url} +
+
+ Downloads (30d) + {plugin.downloadCount.allVersions?.toLocaleString("en-US")} +
+
+ +
+
+ ); +} + +function renderCommands() { + const commands: { cmd: string; desc: string }[] = [ + { cmd: "dprint config update", desc: "Automatically updates the plugins in a config file." }, + { cmd: "dprint add", desc: "Adds one of these plugins via a multi-select prompt." }, + { cmd: "dprint add ", desc: "Adds a plugin by name." }, + { cmd: "dprint add /", desc: "Adds a plugin by GitHub repo." }, + ]; + return ( +
+
// commands
+

Let the CLI do it.

+

Skip the copy-paste — dprint can add and update plugins for you.

+
+ {commands.map((c) => ( +
+ {c.cmd} +

{c.desc}

+
+ ))} +
+
+ ); +} + +function renderCta() { + return ( +
+
+
+
+
dprint fmt
+

Read the docs to configure each plugin, or learn how to publish your own to this registry.

+ +
+
+
+ ); +} diff --git a/style.css b/style.css index 97fe339..ff916df 100644 --- a/style.css +++ b/style.css @@ -1,68 +1,533 @@ -html { - background-color: #1e1e1e; - color: #fff; +/* ===================== base / theme ===================== */ + +:root { + --accent: #8b93a1; + --glow: 0.22; + --max: 1200px; } -body, -html { +* { + box-sizing: border-box; +} + +html, +body { margin: 0; padding: 0; } +html { + min-height: 100%; +} + body { + font-family: "IBM Plex Mono", ui-monospace, Menlo, Monaco, Consolas, "Courier New", monospace; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background: #1e1e1e; + color: #c2c8d0; + min-height: 100vh; display: flex; - flex: 1; flex-direction: column; - font-family: - "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", - "Helvetica Neue", sans-serif; } -a, -a:visited, -a:hover { +::selection { + background: #3a3f49; color: #fff; } -h1 { - padding: 0; +a { + text-decoration: none; + color: inherit; +} + +.site-main { + flex: 1; +} + +.eyebrow { + font-size: 12px; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--accent); + margin-bottom: 14px; +} + +/* shared buttons */ +.btn-primary { + display: inline-block; + background: #e8e8ea; + color: #1a1a1a; + font-weight: 600; + font-size: 15px; + padding: 14px 26px; + border-radius: 10px; + transition: background 0.15s; +} +.btn-primary:hover { + background: #fff; +} +.btn-secondary { + display: inline-block; + background: #282c34; + color: #dfe3ea; + font-weight: 500; + font-size: 15px; + padding: 14px 26px; + border-radius: 10px; + border: 1px solid #383d47; + transition: background 0.15s, border-color 0.15s; +} +.btn-secondary:hover { + border-color: #4d5564; + background: #2d323b; +} + +/* ===================== nav ===================== */ + +.site-nav { + position: sticky; + top: 0; + z-index: 20; + background: rgba(30, 30, 30, 0.82); + backdrop-filter: blur(10px); + border-bottom: 1px solid #2a2e35; +} +.nav-inner { + max-width: var(--max); + margin: 0 auto; + padding: 18px 28px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; +} +.brand { + font-weight: 600; + font-size: 20px; + letter-spacing: -0.01em; + color: #fff; + filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.18)); +} +.nav-links { + display: flex; + align-items: center; + gap: 30px; + font-size: 14px; +} +.nav-links a { + color: #9aa1ad; + transition: color 0.15s, border-color 0.15s; +} +.nav-links a:hover { + color: #fff; +} +.nav-links a.active { + color: #fff; + font-weight: 600; +} +.nav-links .gh-button { + display: flex; + align-items: center; + gap: 8px; + color: #cdd2da; + border: 1px solid #33373f; + border-radius: 8px; + padding: 8px 14px; +} +.nav-links .gh-button:hover { + border-color: #4d5564; + color: #fff; +} +.nav-links .gh-button span { + color: #6e7681; +} + +@media (max-width: 768px) { + .nav-inner { + flex-wrap: wrap; + gap: 14px; + } + .nav-links { + flex-wrap: wrap; + gap: 14px 18px; + font-size: 13px; + } +} + +/* ===================== footer ===================== */ + +.site-footer { + border-top: 1px solid #2a2e35; +} +.footer-inner { + max-width: var(--max); + margin: 0 auto; + padding: 36px 28px; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 18px; +} +.footer-brand { + font-weight: 600; + font-size: 16px; + color: #cdd2da; +} +.footer-links { + display: flex; + flex-wrap: wrap; + gap: 26px; + font-size: 13.5px; +} +.footer-links a { + color: #8b93a1; + transition: color 0.15s; +} +.footer-links a:hover { + color: #fff; +} +.footer-tag { + font-size: 12.5px; + color: #5b626d; +} + +/* ===================== hero ===================== */ + +.home-section { + max-width: 1080px; + margin: 0 auto; +} + +.hero { + position: relative; + overflow: hidden; + border-bottom: 1px solid #2a2e35; +} +.hero-glow { + position: absolute; + top: -120px; + left: 50%; + transform: translateX(-50%); + width: 760px; + height: 460px; + background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0) 70%); + pointer-events: none; +} +.hero-inner { + position: relative; + max-width: 1080px; + margin: 0 auto; + padding: 80px 28px 72px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} +.hero-kicker { + font-size: 12.5px; + letter-spacing: 0.18em; + text-transform: uppercase; + color: #6e7681; + margin-bottom: 28px; +} +.hero-title { margin: 0; - margin-bottom: 10px; + font-weight: 600; + font-size: clamp(56px, 11vw, 120px); + line-height: 0.92; + letter-spacing: -0.02em; + color: #fff; + filter: drop-shadow(0 0 28px rgba(255, 255, 255, var(--glow))) drop-shadow(0 0 72px rgba(255, 255, 255, calc(var(--glow) * 0.5))); +} +.hero-sub { + margin: 30px 0 0; + max-width: 640px; + font-size: clamp(16px, 2.2vw, 19px); + line-height: 1.6; + color: #c2c8d0; + text-wrap: pretty; +} +.hero-sub code { + font-size: 0.92em; + background: #23262d; + color: #dfe3ea; + padding: 2px 6px; + border-radius: 5px; +} + +/* ===================== registry table ===================== */ + +.registry { + padding: 80px 28px 32px; +} +.registry h2, +.commands h2 { + margin: 0 0 10px; + font-size: clamp(28px, 4vw, 40px); + font-weight: 600; + letter-spacing: -0.02em; + color: #f0f1f3; +} +.registry > p, +.commands > p { + margin: 0 0 36px; + font-size: 15px; + line-height: 1.65; + color: #a1a9b5; + max-width: 640px; + text-wrap: pretty; +} +.registry > p code, +.commands > p code { + font-size: 0.92em; + background: #23262d; + color: #dfe3ea; + padding: 2px 6px; + border-radius: 5px; } -#content { - background-color: #4d5564; - align-self: center; - padding: 20px; - margin-top: 20px; - border: 1px solid #000; +.plugin-table { + background: #181a1e; + border: 1px solid #33373f; + border-radius: 14px; + overflow: hidden; +} +.plugin-table-head, +.plugin-row { + display: grid; + grid-template-columns: minmax(160px, 0.9fr) minmax(0, 2.4fr) minmax(120px, 0.7fr) auto; + align-items: center; + gap: 18px; + padding: 14px 22px; +} +.plugin-table-head { + background: #1f2228; + border-bottom: 1px solid #2a2e35; + font-size: 11.5px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #6e7681; +} +.plugin-table-head .num-col { + text-align: right; +} +.plugin-row { + border-top: 1px solid #23262d; + transition: background 0.15s; +} +.plugin-row:first-of-type { + border-top: none; +} +.plugin-row:hover { + background: #1d2025; } -#plugins { - display: table; - border-spacing: 10px; +.col-name { + display: flex; + align-items: center; + gap: 11px; + min-width: 0; +} +.col-name .swatch { + width: 8px; + height: 8px; + border-radius: 2px; + flex-shrink: 0; + background: var(--accent); +} +.col-name .name-text { + font-size: 14.5px; + font-weight: 600; + color: #e8e8ea; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.col-name .version-tag { + flex-shrink: 0; + font-size: 11px; + color: var(--accent); + background: #23262d; + border: 1px solid #2f333b; + border-radius: 999px; + padding: 2px 8px; } -#plugins-header { - display: table-row; +.col-url { + min-width: 0; +} +.col-url code { + display: block; + font-family: inherit; + font-size: 13px; + color: #c2c8d0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -#plugins-header div { - display: table-cell; - font-weight: bold; +.col-downloads { + font-size: 13.5px; + color: #a1a9b5; + text-align: right; + white-space: nowrap; +} +.col-downloads .dl-label { + display: none; + color: #6e7681; } -.plugin { - display: table-row; +.col-action { + text-align: right; +} +.copy-btn { + background: #23262d; + border: 1px solid #383d47; + border-radius: 8px; + cursor: pointer; + font-family: inherit; + font-size: 12px; + letter-spacing: 0.04em; + text-transform: uppercase; + color: #9aa1ad; + padding: 7px 14px; + transition: color 0.15s, border-color 0.15s, background 0.15s; +} +.copy-btn:hover { + border-color: #4d5564; + color: #fff; +} +.copy-btn.copied { + color: #a7d39b; + border-color: #3f5340; } -.plugin div { - display: table-cell; +@media (max-width: 820px) { + .plugin-table-head { + display: none; + } + .plugin-row { + grid-template-columns: 1fr auto; + grid-template-areas: + "name action" + "url url" + "downloads downloads"; + gap: 10px 14px; + padding: 18px 20px; + } + .col-name { + grid-area: name; + } + .col-action { + grid-area: action; + } + .col-url { + grid-area: url; + } + .col-url code { + white-space: normal; + word-break: break-all; + background: #0f1114; + border: 1px solid #2a2e35; + border-radius: 8px; + padding: 10px 12px; + font-size: 12.5px; + } + .col-downloads { + grid-area: downloads; + text-align: left; + } + .col-downloads .dl-label { + display: inline; + } } -code { - background-color: #333; - border-radius: 3px; +/* ===================== commands ===================== */ + +.commands { + padding: 72px 28px 32px; +} +.command-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); + gap: 14px; +} +.command-card { + background: #23262d; + border: 1px solid #2f333b; + border-radius: 14px; + padding: 24px; + transition: border-color 0.15s; +} +.command-card:hover { + border-color: #3f4550; +} +.command-card code { display: inline-block; - padding: 2px 5px; + font-family: inherit; + font-size: 13.5px; + color: #fff; + background: #0f1114; + border: 1px solid #2a2e35; + border-radius: 7px; + padding: 7px 11px; + margin-bottom: 14px; +} +.command-card p { + margin: 0; + font-size: 14px; + line-height: 1.6; + color: #a1a9b5; + text-wrap: pretty; +} + +/* ===================== cta ===================== */ + +.cta { + padding: 56px 28px 90px; +} +.cta-card { + position: relative; + overflow: hidden; + background: #181a1e; + border: 1px solid #33373f; + border-radius: 18px; + padding: 64px 32px; + text-align: center; +} +.cta-glow { + position: absolute; + top: -80px; + left: 50%; + transform: translateX(-50%); + width: 520px; + height: 360px; + background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0) 70%); + pointer-events: none; +} +.cta-inner { + position: relative; +} +.cta-title { + font-weight: 600; + font-size: clamp(34px, 6vw, 56px); + color: #fff; + letter-spacing: -0.02em; + filter: drop-shadow(0 0 24px rgba(255, 255, 255, 0.16)); +} +.cta-inner p { + margin: 18px auto 32px; + max-width: 460px; + font-size: 15.5px; + line-height: 1.6; + color: #aab1bd; + text-wrap: pretty; +} +.cta-actions { + display: flex; + flex-wrap: wrap; + gap: 14px; + justify-content: center; } From f8fbf58aad06455e2942c107484f6027834090da Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jun 2026 15:30:09 -0400 Subject: [PATCH 2/8] Simplify to table-only page with docs link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plugin registry now lives on the dprint.dev docs site, so strip plugins.dprint.dev down to just the registry table plus a link to the docs — no hero, nav, command cards, or CTA. --- homeView.tsx | 117 +++--------------- style.css | 336 +++------------------------------------------------ 2 files changed, 29 insertions(+), 424 deletions(-) diff --git a/homeView.tsx b/homeView.tsx index 5b99b84..5ec9973 100644 --- a/homeView.tsx +++ b/homeView.tsx @@ -12,7 +12,7 @@ export function renderHomeHtml(pluginsData: PluginsData) { - Plugins - dprint - Code Formatter + Plugins - dprint @@ -56,71 +56,26 @@ export function renderHomeHtml(pluginsData: PluginsData) { function renderPage(pluginsData: PluginsData) { return ( - <> - - -
-
-
-
-
// plugin registry
-

Plugins

-

- The latest version of every dprint plugin, with a copy-paste URL for your{" "} - dprint.json. Updated automatically from each plugin's GitHub releases. -

-
-
- - {renderPlugins(pluginsData)} - {renderCommands()} - {renderCta()} -
- - - +
+ + {renderPlugins(pluginsData)} +
); } function renderPlugins(data: PluginsData) { return ( -
-
// latest versions
-

Grab a plugin URL.

-

Drop any of these into the plugins array of your dprint.json, or let the CLI manage them for you.

- -
-
-
Plugin
-
Latest URL
-
Downloads (30d)
-
-
- {data.latest.map((plugin) => renderPlugin(plugin))} +
+
+
Plugin
+
Latest URL
+
Downloads (30d)
+
-
+ {data.latest.map((plugin) => renderPlugin(plugin))} + ); } @@ -147,45 +102,3 @@ function renderPlugin(plugin: PluginData) { ); } - -function renderCommands() { - const commands: { cmd: string; desc: string }[] = [ - { cmd: "dprint config update", desc: "Automatically updates the plugins in a config file." }, - { cmd: "dprint add", desc: "Adds one of these plugins via a multi-select prompt." }, - { cmd: "dprint add ", desc: "Adds a plugin by name." }, - { cmd: "dprint add /", desc: "Adds a plugin by GitHub repo." }, - ]; - return ( -
-
// commands
-

Let the CLI do it.

-

Skip the copy-paste — dprint can add and update plugins for you.

-
- {commands.map((c) => ( -
- {c.cmd} -

{c.desc}

-
- ))} -
-
- ); -} - -function renderCta() { - return ( -
-
-
-
-
dprint fmt
-

Read the docs to configure each plugin, or learn how to publish your own to this registry.

- -
-
-
- ); -} diff --git a/style.css b/style.css index ff916df..54304e4 100644 --- a/style.css +++ b/style.css @@ -2,8 +2,7 @@ :root { --accent: #8b93a1; - --glow: 0.22; - --max: 1200px; + --max: 1100px; } * { @@ -16,10 +15,6 @@ body { padding: 0; } -html { - min-height: 100%; -} - body { font-family: "IBM Plex Mono", ui-monospace, Menlo, Monaco, Consolas, "Courier New", monospace; -webkit-font-smoothing: antialiased; @@ -27,8 +22,6 @@ body { background: #1e1e1e; color: #c2c8d0; min-height: 100vh; - display: flex; - flex-direction: column; } ::selection { @@ -41,253 +34,40 @@ a { color: inherit; } -.site-main { - flex: 1; -} - -.eyebrow { - font-size: 12px; - letter-spacing: 0.16em; - text-transform: uppercase; - color: var(--accent); - margin-bottom: 14px; -} +/* ===================== page ===================== */ -/* shared buttons */ -.btn-primary { - display: inline-block; - background: #e8e8ea; - color: #1a1a1a; - font-weight: 600; - font-size: 15px; - padding: 14px 26px; - border-radius: 10px; - transition: background 0.15s; -} -.btn-primary:hover { - background: #fff; -} -.btn-secondary { - display: inline-block; - background: #282c34; - color: #dfe3ea; - font-weight: 500; - font-size: 15px; - padding: 14px 26px; - border-radius: 10px; - border: 1px solid #383d47; - transition: background 0.15s, border-color 0.15s; -} -.btn-secondary:hover { - border-color: #4d5564; - background: #2d323b; -} - -/* ===================== nav ===================== */ - -.site-nav { - position: sticky; - top: 0; - z-index: 20; - background: rgba(30, 30, 30, 0.82); - backdrop-filter: blur(10px); - border-bottom: 1px solid #2a2e35; -} -.nav-inner { +.page { max-width: var(--max); margin: 0 auto; - padding: 18px 28px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 18px; -} -.brand { - font-weight: 600; - font-size: 20px; - letter-spacing: -0.01em; - color: #fff; - filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.18)); + padding: 26px 28px 64px; } -.nav-links { + +.topbar { display: flex; - align-items: center; - gap: 30px; - font-size: 14px; -} -.nav-links a { - color: #9aa1ad; - transition: color 0.15s, border-color 0.15s; + justify-content: flex-end; + margin-bottom: 22px; } -.nav-links a:hover { - color: #fff; -} -.nav-links a.active { - color: #fff; - font-weight: 600; -} -.nav-links .gh-button { - display: flex; +.docs-link { + display: inline-flex; align-items: center; gap: 8px; + font-size: 14px; color: #cdd2da; border: 1px solid #33373f; border-radius: 8px; padding: 8px 14px; + transition: color 0.15s, border-color 0.15s; } -.nav-links .gh-button:hover { +.docs-link:hover { border-color: #4d5564; color: #fff; } -.nav-links .gh-button span { +.docs-link span { color: #6e7681; } -@media (max-width: 768px) { - .nav-inner { - flex-wrap: wrap; - gap: 14px; - } - .nav-links { - flex-wrap: wrap; - gap: 14px 18px; - font-size: 13px; - } -} - -/* ===================== footer ===================== */ - -.site-footer { - border-top: 1px solid #2a2e35; -} -.footer-inner { - max-width: var(--max); - margin: 0 auto; - padding: 36px 28px; - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 18px; -} -.footer-brand { - font-weight: 600; - font-size: 16px; - color: #cdd2da; -} -.footer-links { - display: flex; - flex-wrap: wrap; - gap: 26px; - font-size: 13.5px; -} -.footer-links a { - color: #8b93a1; - transition: color 0.15s; -} -.footer-links a:hover { - color: #fff; -} -.footer-tag { - font-size: 12.5px; - color: #5b626d; -} - -/* ===================== hero ===================== */ - -.home-section { - max-width: 1080px; - margin: 0 auto; -} - -.hero { - position: relative; - overflow: hidden; - border-bottom: 1px solid #2a2e35; -} -.hero-glow { - position: absolute; - top: -120px; - left: 50%; - transform: translateX(-50%); - width: 760px; - height: 460px; - background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0) 70%); - pointer-events: none; -} -.hero-inner { - position: relative; - max-width: 1080px; - margin: 0 auto; - padding: 80px 28px 72px; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} -.hero-kicker { - font-size: 12.5px; - letter-spacing: 0.18em; - text-transform: uppercase; - color: #6e7681; - margin-bottom: 28px; -} -.hero-title { - margin: 0; - font-weight: 600; - font-size: clamp(56px, 11vw, 120px); - line-height: 0.92; - letter-spacing: -0.02em; - color: #fff; - filter: drop-shadow(0 0 28px rgba(255, 255, 255, var(--glow))) drop-shadow(0 0 72px rgba(255, 255, 255, calc(var(--glow) * 0.5))); -} -.hero-sub { - margin: 30px 0 0; - max-width: 640px; - font-size: clamp(16px, 2.2vw, 19px); - line-height: 1.6; - color: #c2c8d0; - text-wrap: pretty; -} -.hero-sub code { - font-size: 0.92em; - background: #23262d; - color: #dfe3ea; - padding: 2px 6px; - border-radius: 5px; -} - /* ===================== registry table ===================== */ -.registry { - padding: 80px 28px 32px; -} -.registry h2, -.commands h2 { - margin: 0 0 10px; - font-size: clamp(28px, 4vw, 40px); - font-weight: 600; - letter-spacing: -0.02em; - color: #f0f1f3; -} -.registry > p, -.commands > p { - margin: 0 0 36px; - font-size: 15px; - line-height: 1.65; - color: #a1a9b5; - max-width: 640px; - text-wrap: pretty; -} -.registry > p code, -.commands > p code { - font-size: 0.92em; - background: #23262d; - color: #dfe3ea; - padding: 2px 6px; - border-radius: 5px; -} - .plugin-table { background: #181a1e; border: 1px solid #33373f; @@ -443,91 +223,3 @@ a { display: inline; } } - -/* ===================== commands ===================== */ - -.commands { - padding: 72px 28px 32px; -} -.command-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); - gap: 14px; -} -.command-card { - background: #23262d; - border: 1px solid #2f333b; - border-radius: 14px; - padding: 24px; - transition: border-color 0.15s; -} -.command-card:hover { - border-color: #3f4550; -} -.command-card code { - display: inline-block; - font-family: inherit; - font-size: 13.5px; - color: #fff; - background: #0f1114; - border: 1px solid #2a2e35; - border-radius: 7px; - padding: 7px 11px; - margin-bottom: 14px; -} -.command-card p { - margin: 0; - font-size: 14px; - line-height: 1.6; - color: #a1a9b5; - text-wrap: pretty; -} - -/* ===================== cta ===================== */ - -.cta { - padding: 56px 28px 90px; -} -.cta-card { - position: relative; - overflow: hidden; - background: #181a1e; - border: 1px solid #33373f; - border-radius: 18px; - padding: 64px 32px; - text-align: center; -} -.cta-glow { - position: absolute; - top: -80px; - left: 50%; - transform: translateX(-50%); - width: 520px; - height: 360px; - background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0) 70%); - pointer-events: none; -} -.cta-inner { - position: relative; -} -.cta-title { - font-weight: 600; - font-size: clamp(34px, 6vw, 56px); - color: #fff; - letter-spacing: -0.02em; - filter: drop-shadow(0 0 24px rgba(255, 255, 255, 0.16)); -} -.cta-inner p { - margin: 18px auto 32px; - max-width: 460px; - font-size: 15.5px; - line-height: 1.6; - color: #aab1bd; - text-wrap: pretty; -} -.cta-actions { - display: flex; - flex-wrap: wrap; - gap: 14px; - justify-content: center; -} From cbd2b160bc022163e9e15e8b68de03de7fe616e8 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jun 2026 15:32:12 -0400 Subject: [PATCH 3/8] Widen table and restore helpful commands Size the plugin name column to its content so long names like dprint-plugin-typescript aren't truncated, widen the page, and add the helpful CLI commands list back below the table. --- homeView.tsx | 22 ++++++++++++++++++++++ style.css | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/homeView.tsx b/homeView.tsx index 5ec9973..1f691ee 100644 --- a/homeView.tsx +++ b/homeView.tsx @@ -61,10 +61,32 @@ function renderPage(pluginsData: PluginsData) { dprint plugin docs {renderPlugins(pluginsData)} + {renderCommands()} ); } +function renderCommands() { + const commands: { cmd: string; desc: string }[] = [ + { cmd: "dprint config update", desc: "Automatically updates the plugins in a config file." }, + { cmd: "dprint add", desc: "Adds one of these plugins via a multi-select prompt." }, + { cmd: "dprint add ", desc: "Adds a plugin by name." }, + { cmd: "dprint add /", desc: "Adds a plugin by GitHub repo." }, + ]; + return ( +
+

Helpful commands

+
    + {commands.map((c) => ( +
  • + {c.cmd} — {c.desc} +
  • + ))} +
+
+ ); +} + function renderPlugins(data: PluginsData) { return (
diff --git a/style.css b/style.css index 54304e4..40b90d7 100644 --- a/style.css +++ b/style.css @@ -2,7 +2,7 @@ :root { --accent: #8b93a1; - --max: 1100px; + --max: 1320px; } * { @@ -77,9 +77,9 @@ a { .plugin-table-head, .plugin-row { display: grid; - grid-template-columns: minmax(160px, 0.9fr) minmax(0, 2.4fr) minmax(120px, 0.7fr) auto; + grid-template-columns: minmax(260px, max-content) minmax(0, 2fr) minmax(120px, max-content) auto; align-items: center; - gap: 18px; + gap: 18px 24px; padding: 14px 22px; } .plugin-table-head { @@ -223,3 +223,37 @@ a { display: inline; } } + +/* ===================== helpful commands ===================== */ + +.commands { + margin-top: 44px; +} +.commands h2 { + margin: 0 0 16px; + font-size: 16px; + font-weight: 600; + color: #f0f1f3; +} +.commands ul { + margin: 0; + padding: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: 10px; +} +.commands li { + font-size: 14px; + line-height: 1.6; + color: #a1a9b5; +} +.commands code { + font-family: inherit; + font-size: 13px; + color: #fff; + background: #181a1e; + border: 1px solid #2a2e35; + border-radius: 6px; + padding: 3px 8px; +} From 8aed70b96a3e344e1dd83822340054fc97476a57 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jun 2026 15:34:13 -0400 Subject: [PATCH 4/8] bump From 130ac9646cb266e8d7f04da993551256ab774513 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jun 2026 15:44:41 -0400 Subject: [PATCH 5/8] Add search to filter the plugin list A slim filter input in the top bar narrows the table client-side as you type (matching plugin name and URL), with a "no matches" empty state. --- homeView.tsx | 31 ++++++++++++++++++++++++++++++- style.css | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/homeView.tsx b/homeView.tsx index 1f691ee..373ac7a 100644 --- a/homeView.tsx +++ b/homeView.tsx @@ -44,6 +44,25 @@ export function renderHomeHtml(pluginsData: PluginsData) { }, 1600); }); } + + // live filter + const search = document.getElementById("plugin-search"); + if (search != null) { + const rows = document.getElementsByClassName("plugin-row"); + const table = document.getElementsByClassName("plugin-table")[0]; + const noMatches = document.getElementById("no-matches"); + search.addEventListener("input", () => { + const query = search.value.trim().toLowerCase(); + let visible = 0; + for (const row of rows) { + const match = query === "" || (row.dataset.search || "").indexOf(query) !== -1; + row.hidden = !match; + if (match) visible++; + } + if (table != null) table.hidden = visible === 0; + if (noMatches != null) noMatches.hidden = visible !== 0; + }); + } }); @@ -58,9 +77,19 @@ function renderPage(pluginsData: PluginsData) { return (
{renderPlugins(pluginsData)} + {renderCommands()}
); @@ -103,7 +132,7 @@ function renderPlugins(data: PluginsData) { function renderPlugin(plugin: PluginData) { return ( -
+
{plugin.name} diff --git a/style.css b/style.css index 40b90d7..1dcc371 100644 --- a/style.css +++ b/style.css @@ -44,9 +44,32 @@ a { .topbar { display: flex; - justify-content: flex-end; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 14px; margin-bottom: 22px; } +.plugin-search { + flex: 1; + min-width: 200px; + max-width: 360px; + font-family: inherit; + font-size: 14px; + color: #e8e8ea; + background: #181a1e; + border: 1px solid #33373f; + border-radius: 8px; + padding: 9px 13px; + outline: none; + transition: border-color 0.15s; +} +.plugin-search::placeholder { + color: #6e7681; +} +.plugin-search:focus { + border-color: #4d5564; +} .docs-link { display: inline-flex; align-items: center; @@ -103,6 +126,20 @@ a { .plugin-row:hover { background: #1d2025; } +.plugin-row[hidden] { + display: none; +} + +.no-matches { + background: #181a1e; + border: 1px solid #33373f; + border-radius: 14px; + padding: 30px 22px; + margin-top: 12px; + color: #6e7681; + font-size: 14px; + text-align: center; +} .col-name { display: flex; From 05e87260d1bb4b8e634c51291de60c852f937df6 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jun 2026 15:52:48 -0400 Subject: [PATCH 6/8] Index extensions and descriptions in plugin search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build the search string from each plugin's description, config key, file extensions/names, and — for exec-style plugins — the extensions and commands under configItems. So "scss", "python", or "zig" now find the right plugin, not just its name or URL. --- homeView.tsx | 24 +++++++++++++++++++++++- readInfoFile.ts | 13 +++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/homeView.tsx b/homeView.tsx index 373ac7a..c31af5c 100644 --- a/homeView.tsx +++ b/homeView.tsx @@ -95,6 +95,28 @@ function renderPage(pluginsData: PluginsData) { ); } +// builds the lowercased string the search filters against: name, url, version, +// description, config key, and every file extension / file name / exec command +// the plugin handles. +function pluginSearchText(plugin: PluginData) { + const parts: (string | undefined)[] = [ + plugin.name, + plugin.url, + plugin.version, + plugin.description, + plugin.configKey, + ...(plugin.fileExtensions ?? []), + ...(plugin.fileNames ?? []), + ]; + for (const item of plugin.configItems ?? []) { + parts.push(...(item.match?.fileExtensions ?? [])); + for (const command of item.config?.commands ?? []) { + parts.push(command.command, ...(command.exts ?? [])); + } + } + return parts.filter(Boolean).join(" ").toLowerCase(); +} + function renderCommands() { const commands: { cmd: string; desc: string }[] = [ { cmd: "dprint config update", desc: "Automatically updates the plugins in a config file." }, @@ -132,7 +154,7 @@ function renderPlugins(data: PluginsData) { function renderPlugin(plugin: PluginData) { return ( -
+
{plugin.name} diff --git a/readInfoFile.ts b/readInfoFile.ts index d623ae2..b0d5c17 100644 --- a/readInfoFile.ts +++ b/readInfoFile.ts @@ -16,6 +16,19 @@ export interface PluginData { currentVersion: number; allVersions: number; }; + // descriptive fields carried over from info.json, used to index the search + description?: string; + configKey?: string; + fileExtensions?: string[]; + fileNames?: string[]; + // present on plugins like exec where the real extensions/commands live in + // per-match config rather than top-level fileExtensions + configItems?: PluginConfigItem[]; +} + +interface PluginConfigItem { + match?: { fileExtensions?: string[] }; + config?: { commands?: { command?: string; exts?: string[] }[] }; } interface CacheEntry { From 625dc78d8cd2c00348c3664079c2250cf6e312f4 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jun 2026 16:03:25 -0400 Subject: [PATCH 7/8] Add keywords to plugins and index them in search Add a curated "keywords" array to each plugin in info.json (e.g. exec gets "rust", pretty_yaml gets "kubernetes", biome gets "rome") and fold them into the search index so plugins surface by related terms, not just their name, description, or file extensions. --- homeView.tsx | 1 + info.json | 161 ++++++++++++++++++++++++++++++++++++++++++++++++ readInfoFile.ts | 1 + 3 files changed, 163 insertions(+) diff --git a/homeView.tsx b/homeView.tsx index c31af5c..7f2ca52 100644 --- a/homeView.tsx +++ b/homeView.tsx @@ -105,6 +105,7 @@ function pluginSearchText(plugin: PluginData) { plugin.version, plugin.description, plugin.configKey, + ...(plugin.keywords ?? []), ...(plugin.fileExtensions ?? []), ...(plugin.fileNames ?? []), ]; diff --git a/info.json b/info.json index b30e2be..2a03542 100644 --- a/info.json +++ b/info.json @@ -7,6 +7,13 @@ "description": "TypeScript/JavaScript code formatter.", "selected": true, "configKey": "typescript", + "keywords": [ + "javascript", + "typescript", + "node", + "deno", + "ecmascript" + ], "fileExtensions": [ "ts", "tsx", @@ -26,6 +33,10 @@ "description": "JSON/JSONC code formatter.", "selected": true, "configKey": "json", + "keywords": [ + "json", + "jsonc" + ], "fileExtensions": [ "json", "jsonc" @@ -39,6 +50,11 @@ "description": "Markdown code formatter.", "selected": true, "configKey": "markdown", + "keywords": [ + "markdown", + "commonmark", + "gfm" + ], "fileExtensions": [ "md", "mkd", @@ -54,6 +70,10 @@ "description": "TOML code formatter.", "selected": true, "configKey": "toml", + "keywords": [ + "toml", + "cargo" + ], "fileExtensions": [ "toml" ], @@ -64,6 +84,11 @@ "description": "Dockerfile code formatter.", "selected": false, "configKey": "dockerfile", + "keywords": [ + "docker", + "dockerfile", + "container" + ], "fileExtensions": [ "dockerfile" ], @@ -77,6 +102,13 @@ "description": "Biome (JS/TS/JSON) wrapper plugin.", "selected": false, "configKey": "biome", + "keywords": [ + "biome", + "rome", + "javascript", + "typescript", + "json" + ], "fileExtensions": [ "ts", "tsx", @@ -98,6 +130,12 @@ "description": "Oxc (JS/TS) wrapper plugin.", "selected": false, "configKey": "oxc", + "keywords": [ + "oxc", + "oxlint", + "javascript", + "typescript" + ], "fileExtensions": [ "ts", "tsx", @@ -117,6 +155,11 @@ "description": "Mago (PHP) wrapper plugin.", "selected": false, "configKey": "mago", + "keywords": [ + "php", + "mago", + "laravel" + ], "fileExtensions": [ "php" ], @@ -127,6 +170,12 @@ "description": "Ruff (Python) wrapper plugin.", "selected": false, "configKey": "ruff", + "keywords": [ + "python", + "ruff", + "black", + "flake8" + ], "fileExtensions": [ "py", "pyi" @@ -138,6 +187,12 @@ "description": "Jupyter notebook code block formatter.", "selected": false, "configKey": "jupyter", + "keywords": [ + "jupyter", + "notebook", + "ipython", + "python" + ], "fileExtensions": [ "ipynb" ], @@ -148,6 +203,13 @@ "description": "CSS, SCSS, Sass and Less formatter.", "selected": true, "configKey": "malva", + "keywords": [ + "css", + "scss", + "sass", + "less", + "stylesheet" + ], "fileExtensions": [ "css", "scss", @@ -163,6 +225,22 @@ "description": "HTML, Vue, Svelte, Astro, Angular, Jinja, Twig, Nunjucks, and Vento formatter.", "selected": true, "configKey": "markup", + "keywords": [ + "html", + "vue", + "svelte", + "astro", + "angular", + "jinja", + "twig", + "nunjucks", + "vento", + "handlebars", + "mustache", + "xml", + "markup", + "template" + ], "fileExtensions": [ "html", "vue", @@ -192,6 +270,12 @@ "description": "YAML formatter.", "selected": true, "configKey": "yaml", + "keywords": [ + "yaml", + "kubernetes", + "k8s", + "config" + ], "fileExtensions": [ "yaml", "yml" @@ -203,6 +287,11 @@ "description": "GraphQL formatter.", "selected": false, "configKey": "graphql", + "keywords": [ + "graphql", + "apollo", + "schema" + ], "fileExtensions": [ "graphql", "gql" @@ -214,6 +303,12 @@ "description": "CSS, SCSS, and Less formatter that never reinterprets your styles.", "selected": false, "configKey": "css", + "keywords": [ + "css", + "scss", + "less", + "stylesheet" + ], "fileExtensions": [ "css", "scss", @@ -228,6 +323,15 @@ "description": "HTML, XML, SVG, Vue, Svelte, and Astro formatter that preserves rendering-critical whitespace.", "selected": false, "configKey": "markup", + "keywords": [ + "html", + "xml", + "svg", + "vue", + "svelte", + "astro", + "markup" + ], "fileExtensions": [ "html", "htm", @@ -244,6 +348,14 @@ "description": "Dialect-agnostic SQL formatter.", "selected": false, "configKey": "sql", + "keywords": [ + "sql", + "mysql", + "postgres", + "postgresql", + "sqlite", + "database" + ], "fileExtensions": [ "sql" ], @@ -254,6 +366,12 @@ "description": "Quarto, Pandoc, R Markdown, and Markdown formatter.", "selected": false, "configKey": "panache", + "keywords": [ + "quarto", + "pandoc", + "rmarkdown", + "markdown" + ], "fileExtensions": [ "md", "qmd", @@ -272,6 +390,14 @@ "description": "Swift code formatter (SwiftFormat wrapper).", "selected": false, "configKey": "swiftformat", + "keywords": [ + "swift", + "swiftformat", + "ios", + "macos", + "apple", + "xcode" + ], "fileExtensions": [ "swift" ], @@ -282,6 +408,18 @@ "description": "Prettier wrapper plugin (consider faster dedicated plugins for JS/TS, CSS, etc.).", "selected": false, "configKey": "prettier", + "keywords": [ + "prettier", + "javascript", + "typescript", + "css", + "html", + "vue", + "yaml", + "graphql", + "markdown", + "mdx" + ], "fileExtensions": [ "js", "jsx", @@ -314,6 +452,15 @@ "description": "C# and Visual Basic code formatter (Roslyn).", "selected": false, "configKey": "roslyn", + "keywords": [ + "c#", + "csharp", + "visual basic", + "vb", + "dotnet", + ".net", + "roslyn" + ], "fileExtensions": [ "cs", "vb" @@ -325,6 +472,20 @@ "description": "Formats code using formatting CLIs installed on the host machine (rustfmt, clang-format, shfmt, etc.).", "selected": false, "configKey": "exec", + "keywords": [ + "rust", + "rustfmt", + "c", + "c++", + "cpp", + "clang", + "clang-format", + "shell", + "bash", + "shfmt", + "zig", + "cli" + ], "fileExtensions": [], "configExcludes": [], "defaultConfig": { diff --git a/readInfoFile.ts b/readInfoFile.ts index b0d5c17..3081b29 100644 --- a/readInfoFile.ts +++ b/readInfoFile.ts @@ -19,6 +19,7 @@ export interface PluginData { // descriptive fields carried over from info.json, used to index the search description?: string; configKey?: string; + keywords?: string[]; fileExtensions?: string[]; fileNames?: string[]; // present on plugins like exec where the real extensions/commands live in From ed7e80931e774c381435005f6911dd62c0e3bbc7 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jun 2026 16:07:12 -0400 Subject: [PATCH 8/8] Add gofumpt to the plugin list gofumpt (jakebailey/gofumpt) has docs and is a known repo but was never added to info.json's latest array, so it didn't show in the registry. --- info.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/info.json b/info.json index 2a03542..644a3f5 100644 --- a/info.json +++ b/info.json @@ -198,6 +198,22 @@ ], "configExcludes": [] }, + { + "name": "jakebailey/gofumpt", + "description": "Gofumpt (Go) wrapper plugin.", + "selected": false, + "configKey": "gofumpt", + "keywords": [ + "go", + "golang", + "gofumpt", + "gofmt" + ], + "fileExtensions": [ + "go" + ], + "configExcludes": [] + }, { "name": "g-plane/malva", "description": "CSS, SCSS, Sass and Less formatter.",