wp-packages/internal/http/templates/index.html
Ben Word 154cb2f376
Migrate templates from html/template to Jet v6
Replace Go's html/template with CloudyKit/jet/v6 for server-rendered
templates. Jet provides first-class parameterized blocks, template
inheritance via extends/yield, and C-like expressions — eliminating
the dict helper workarounds that html/template required for reusable
components.

Add components.html with four reusable Jet blocks:
- shell(command, variant, class) — copyable command boxes (replaces
  ~16 duplicated instances across 6 templates)
- notice(variant, class) with yield content — alert boxes with
  variant-based styling and content slots
- input(icon, name, ...) — form inputs with icon and keyboard hint
- pagination(pager, class) — pagination controls driven by
  pre-computed buildPagination() data

Key changes:
- templates.go: embed.FS loader for Jet, function registration via
  AddGlobal, buildPagination() helper, render() using VarMap
- handlers.go: simplified render calls (no more template combinations)
- router.go: *jet.Set replaces *templateSet
- All 13 templates converted to Jet syntax (extends/block/yield)
- Tailwind scanning unaffected (templates remain .html files)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:05:25 -05:00

103 lines
No EOL
7.2 KiB
HTML

{{extends "layout.html"}}
{{import "components.html"}}
{{block title()}}WP Packages — WordPress Packages for Composer | WPackagist Alternative{{end}}
{{block meta_seo()}}
<meta name="description" content="Composer repository for WordPress plugins and themes. A 17x faster, open-source WPackagist alternative with 5-minute update cycles.">
{{if AppURL}}<link rel="canonical" href="{{AppURL}}/">{{end}}
{{if Page && Page > 1}}<meta name="robots" content="noindex, follow">{{end}}
{{end}}
{{block json_ld()}}{{jsonLD(JSONLD)|raw}}{{end}}
{{block og_meta()}}
<meta property="og:title" content="WP Packages — WordPress Packages for Composer | WPackagist Alternative">
<meta property="og:description" content="Composer repository for WordPress plugins and themes.">
{{if OGImage}}<meta property="og:image" content="{{OGImage}}">{{end}}
{{if AppURL}}<meta property="og:url" content="{{AppURL}}">{{end}}
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="WP Packages — WordPress Packages for Composer | WPackagist Alternative">
<meta name="twitter:description" content="Composer repository for WordPress plugins and themes.">
{{if OGImage}}<meta name="twitter:image" content="{{OGImage}}">{{end}}
{{end}}
{{block body()}}
<section class="relative overflow-hidden border-b border-gray-200/50">
<div class="absolute inset-0 bg-gradient-to-b from-brand-lightest/40 to-transparent"></div>
<div class="relative mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 pt-16 pb-12 sm:pt-24 sm:pb-16 text-center">
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight text-gray-900 mb-6 text-balance">WordPress Packages<br><span class="text-brand-primary">for Composer</span></h1>
<h2 class="text-xl sm:text-2xl text-gray-500 max-w-xl mx-auto mb-4 text-balance">Composer repository for WordPress plugins and themes</h2>
<h3 class="text-base text-gray-500 max-w-xl mx-auto mb-6 text-balance">A 17x faster, fully open-source alternative to WPackagist that updates every 5 minutes</h3>
<p class="mb-10"><a href="/wp-packages-vs-wpackagist" class="text-sm font-medium text-brand-primary hover:underline">See how WP Packages compares to WPackagist &rarr;</a></p>
<a href="/docs" class="inline-flex items-center rounded-lg bg-brand-primary px-5 py-2.5 text-sm font-medium text-white hover:bg-brand-primary/85 transition-colors">Get Started</a>
</div>
<div class="relative border-t border-gray-200/50">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 sm:grid-cols-3 divide-y sm:divide-y-0 sm:divide-x divide-gray-200/50">
<div class="py-6 text-center">
<div class="text-2xl sm:text-3xl font-bold text-brand-primary tabular-nums">{{formatNumber(Stats.PluginInstalls)}}</div>
<div class="text-sm text-gray-500 mt-0.5">Plugin Installs</div>
</div>
<div class="py-6 text-center">
<div class="text-2xl sm:text-3xl font-bold text-brand-primary tabular-nums">{{formatNumber(Stats.ThemeInstalls)}}</div>
<div class="text-sm text-gray-500 mt-0.5">Theme Installs</div>
</div>
<div class="py-6 text-center">
<div class="text-2xl sm:text-3xl font-bold text-brand-primary tabular-nums">{{formatNumber(Stats.RootsDownloads)}}</div>
<div class="text-sm text-gray-500 mt-0.5"><a href="/wordpress-core" class="hover:text-gray-900 transition-colors"><code class="text-xs font-mono bg-gray-100 px-1.5 py-0.5 rounded-md border border-gray-200/40">roots/wordpress</code> Installs</a></div>
</div>
</div>
</div>
</div>
</section>
<section class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 py-10">
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-2">
<form id="filter-form" method="GET" action="/" hx-get="/packages-partial" hx-target="#package-results" hx-trigger="submit, change from:select, input from:[name=search] delay:300ms changed" hx-push-url="false" hx-swap="innerHTML" class="flex flex-col sm:flex-row items-start sm:items-center gap-2 flex-1 scroll-mt-16">
<div class="relative w-full sm:max-w-md">
{{yield input(icon="search", type="search", name="search", value=Filters.Search, placeholder="Search packages...", showShortcut=true)}}
</div>
<div class="flex items-center gap-2">
<label for="filter-type" class="sr-only">Package type</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z"/></svg>
<select id="filter-type" name="type" class="h-10 pl-9 pr-8 text-sm border border-gray-200 rounded-lg bg-white select-chevron focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary">
<option value="">All types</option>
<option value="plugin"{{if Filters.Type == "plugin"}} selected{{end}}>Plugins</option>
<option value="theme"{{if Filters.Type == "theme"}} selected{{end}}>Themes</option>
</select>
</div>
<label for="filter-sort" class="sr-only">Sort by</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 7.5 7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5"/></svg>
<select id="filter-sort" name="sort" class="h-10 pl-9 pr-8 text-sm border border-gray-200 rounded-lg bg-white select-chevron focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary">
<option value="composer_installs"{{if Filters.Sort == "composer_installs"}} selected{{end}}>Composer installs</option>
<option value="active_installs"{{if Filters.Sort == "active_installs"}} selected{{end}}>Active installs</option>
<option value="name"{{if Filters.Sort == "name"}} selected{{end}}>Name</option>
<option value="updated"{{if Filters.Sort == "updated"}} selected{{end}}>Recently updated</option>
</select>
</div>
<button type="submit" class="sr-only">Search</button>
</div>
</form>
</div>
<div id="package-results">
{{include "package_results.html"}}
</div>
<div class="flex justify-center mt-8">
{{yield notice() content}}
Some WordPress plugins have a latest version on WordPress.org that isn't tagged in SVN. These can be installed via <code class="text-xs font-mono bg-amber-100 px-1 py-0.5 rounded whitespace-nowrap">dev-trunk</code>. <a href="/untagged" class="font-medium underline underline-offset-2 hover:no-underline">Learn more on our Untagged Plugins page</a>.
{{end}}
</div>
</section>
{{if BlogPosts}}
<section class="border-t border-gray-200/50 py-14">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 text-center">
<h2 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-6">Latest updates</h2>
<ul class="space-y-2 max-w-xl mx-auto">
{{range _, post := BlogPosts}}
<li><a href="{{post.Link}}" class="text-sm font-medium text-brand-primary hover:underline">{{post.Title}}</a></li>
{{end}}
</ul>
</div>
</section>
{{end}}
{{end}}