mirror of
https://github.com/svemagie/blog-eleventy-indiekit.git
synced 2026-05-15 06:58:50 +02:00
feat: add Pagefind client-side search
Add Pagefind indexing after each Eleventy build with a search page at /search/. Indexes main content only (sidebars excluded), supports dark mode theming and URL query parameters (?q=). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ node_modules/
|
|||||||
|
|
||||||
# Build output
|
# Build output
|
||||||
_site/
|
_site/
|
||||||
|
_pagefind/
|
||||||
css/style.css
|
css/style.css
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
layout: layouts/base.njk
|
layout: layouts/base.njk
|
||||||
title: Page Not Found
|
title: Page Not Found
|
||||||
permalink: /404.html
|
permalink: /404.html
|
||||||
|
pagefindIgnore: true
|
||||||
---
|
---
|
||||||
<div class="text-center py-12">
|
<div class="text-center py-12">
|
||||||
<h1 class="text-4xl sm:text-6xl font-bold text-surface-300 dark:text-surface-700 mb-4">404</h1>
|
<h1 class="text-4xl sm:text-6xl font-bold text-surface-300 dark:text-surface-700 mb-4">404</h1>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
<link rel="{{ social.rel }}" href="{{ social.url }}">
|
<link rel="{{ social.rel }}" href="{{ social.url }}">
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body{% if pagefindIgnore %} data-pagefind-ignore="all"{% endif %}>
|
||||||
<script>
|
<script>
|
||||||
// Apply theme immediately to prevent flash
|
// Apply theme immediately to prevent flash
|
||||||
(function() {
|
(function() {
|
||||||
@@ -155,6 +155,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<a href="/interactions/">Interactions</a>
|
<a href="/interactions/">Interactions</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
<a href="/search/" aria-label="Search" title="Search" class="p-2 rounded-lg text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
<button id="theme-toggle" type="button" class="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">
|
<button id="theme-toggle" type="button" class="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">
|
||||||
<svg class="sun-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg class="sun-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<circle cx="12" cy="12" r="5"/>
|
<circle cx="12" cy="12" r="5"/>
|
||||||
@@ -214,6 +219,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/interactions/">Interactions</a>
|
<a href="/interactions/">Interactions</a>
|
||||||
|
<a href="/search/">Search</a>
|
||||||
{# Mobile theme toggle #}
|
{# Mobile theme toggle #}
|
||||||
<button type="button" class="mobile-theme-toggle" aria-label="Toggle dark mode">
|
<button type="button" class="mobile-theme-toggle" aria-label="Toggle dark mode">
|
||||||
<span class="theme-label">Theme</span>
|
<span class="theme-label">Theme</span>
|
||||||
@@ -230,13 +236,13 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="container py-8">
|
<main class="container py-8" data-pagefind-body>
|
||||||
{% if withSidebar %}
|
{% if withSidebar %}
|
||||||
<div class="layout-with-sidebar">
|
<div class="layout-with-sidebar">
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
{{ content | safe }}
|
{{ content | safe }}
|
||||||
</div>
|
</div>
|
||||||
<aside class="sidebar">
|
<aside class="sidebar" data-pagefind-ignore>
|
||||||
{% include "components/sidebar.njk" %}
|
{% include "components/sidebar.njk" %}
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
@@ -245,7 +251,7 @@
|
|||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
{{ content | safe }}
|
{{ content | safe }}
|
||||||
</div>
|
</div>
|
||||||
<aside class="sidebar blog-sidebar">
|
<aside class="sidebar blog-sidebar" data-pagefind-ignore>
|
||||||
{% include "components/blog-sidebar.njk" %}
|
{% include "components/blog-sidebar.njk" %}
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -450,6 +450,45 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pagefind UI theme overrides */
|
||||||
|
@layer components {
|
||||||
|
.pagefind-ui-container {
|
||||||
|
--pagefind-ui-scale: 1;
|
||||||
|
--pagefind-ui-primary: #2563eb;
|
||||||
|
--pagefind-ui-text: #18181b;
|
||||||
|
--pagefind-ui-background: #ffffff;
|
||||||
|
--pagefind-ui-border: #e4e4e7;
|
||||||
|
--pagefind-ui-tag: #f4f4f5;
|
||||||
|
--pagefind-ui-border-width: 1px;
|
||||||
|
--pagefind-ui-border-radius: 8px;
|
||||||
|
--pagefind-ui-font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .pagefind-ui-container {
|
||||||
|
--pagefind-ui-primary: #60a5fa;
|
||||||
|
--pagefind-ui-text: #f4f4f5;
|
||||||
|
--pagefind-ui-background: #09090b;
|
||||||
|
--pagefind-ui-border: #3f3f46;
|
||||||
|
--pagefind-ui-tag: #27272a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagefind-ui-container .pagefind-ui__search-input {
|
||||||
|
@apply bg-white dark:bg-surface-900 text-surface-900 dark:text-surface-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagefind-ui-container .pagefind-ui__result-link {
|
||||||
|
@apply text-primary-600 dark:text-primary-400 hover:underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagefind-ui-container .pagefind-ui__result-excerpt {
|
||||||
|
@apply text-surface-600 dark:text-surface-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagefind-ui-container .pagefind-ui__message {
|
||||||
|
@apply text-surface-600 dark:text-surface-400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile-specific improvements */
|
/* Mobile-specific improvements */
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
/* Ensure proper touch scrolling on overflow containers */
|
/* Ensure proper touch scrolling on overflow containers */
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import sitemap from "@quasibit/eleventy-plugin-sitemap";
|
|||||||
import markdownIt from "markdown-it";
|
import markdownIt from "markdown-it";
|
||||||
import { minify } from "html-minifier-terser";
|
import { minify } from "html-minifier-terser";
|
||||||
import { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
|
import { execFileSync } from "child_process";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { resolve, dirname } from "path";
|
import { resolve, dirname } from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
@@ -26,11 +27,17 @@ export default function (eleventyConfig) {
|
|||||||
eleventyConfig.ignores.add("node_modules");
|
eleventyConfig.ignores.add("node_modules");
|
||||||
eleventyConfig.ignores.add("node_modules/**");
|
eleventyConfig.ignores.add("node_modules/**");
|
||||||
|
|
||||||
|
// Ignore Pagefind output directory
|
||||||
|
eleventyConfig.ignores.add("_pagefind");
|
||||||
|
eleventyConfig.ignores.add("_pagefind/**");
|
||||||
|
|
||||||
// Configure watch targets to exclude output directory
|
// Configure watch targets to exclude output directory
|
||||||
eleventyConfig.watchIgnores.add("_site");
|
eleventyConfig.watchIgnores.add("_site");
|
||||||
eleventyConfig.watchIgnores.add("_site/**");
|
eleventyConfig.watchIgnores.add("_site/**");
|
||||||
eleventyConfig.watchIgnores.add("/app/data/site");
|
eleventyConfig.watchIgnores.add("/app/data/site");
|
||||||
eleventyConfig.watchIgnores.add("/app/data/site/**");
|
eleventyConfig.watchIgnores.add("/app/data/site/**");
|
||||||
|
eleventyConfig.watchIgnores.add("_pagefind");
|
||||||
|
eleventyConfig.watchIgnores.add("_pagefind/**");
|
||||||
|
|
||||||
// Configure markdown-it with linkify enabled (auto-convert URLs to links)
|
// Configure markdown-it with linkify enabled (auto-convert URLs to links)
|
||||||
const md = markdownIt({
|
const md = markdownIt({
|
||||||
@@ -395,6 +402,20 @@ export default function (eleventyConfig) {
|
|||||||
.slice(0, 5);
|
.slice(0, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pagefind indexing after each build
|
||||||
|
eleventyConfig.on("eleventy.after", ({ dir, runMode }) => {
|
||||||
|
try {
|
||||||
|
console.log(`[pagefind] Indexing ${dir.output} (${runMode})...`);
|
||||||
|
execFileSync("npx", ["pagefind", "--site", dir.output, "--glob", "**/*.html"], {
|
||||||
|
stdio: "inherit",
|
||||||
|
timeout: 60000,
|
||||||
|
});
|
||||||
|
console.log("[pagefind] Indexing complete");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[pagefind] Indexing failed:", err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dir: {
|
dir: {
|
||||||
input: ".",
|
input: ".",
|
||||||
|
|||||||
+2
-1
@@ -19,7 +19,8 @@
|
|||||||
"@quasibit/eleventy-plugin-sitemap": "^2.2.0",
|
"@quasibit/eleventy-plugin-sitemap": "^2.2.0",
|
||||||
"@atproto/api": "^0.12.0",
|
"@atproto/api": "^0.12.0",
|
||||||
"eleventy-plugin-embed-everything": "^1.21.0",
|
"eleventy-plugin-embed-everything": "^1.21.0",
|
||||||
"rss-parser": "^3.13.0"
|
"rss-parser": "^3.13.0",
|
||||||
|
"pagefind": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tailwindcss": "^3.4.0",
|
"tailwindcss": "^3.4.0",
|
||||||
|
|||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
layout: layouts/base.njk
|
||||||
|
title: Search
|
||||||
|
permalink: /search/
|
||||||
|
eleventyExcludeFromCollections: true
|
||||||
|
pagefindIgnore: true
|
||||||
|
---
|
||||||
|
<div class="page-header mb-6 sm:mb-8">
|
||||||
|
<h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100 mb-2">Search</h1>
|
||||||
|
<p class="text-surface-600 dark:text-surface-400">Search across all posts, articles, notes, and pages.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/_pagefind/pagefind-ui.css">
|
||||||
|
|
||||||
|
<div id="search"></div>
|
||||||
|
|
||||||
|
<script src="/_pagefind/pagefind-ui.js"></script>
|
||||||
|
<script>
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const ui = new PagefindUI({
|
||||||
|
element: "#search",
|
||||||
|
showSubResults: true,
|
||||||
|
showImages: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Support ?q= query parameter
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const query = params.get("q");
|
||||||
|
if (query) {
|
||||||
|
ui.triggerSearch(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-focus the search input
|
||||||
|
const input = document.querySelector("#search input[type='text']");
|
||||||
|
if (input && !query) {
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -3,6 +3,7 @@ layout: layouts/base.njk
|
|||||||
title: Webmention Debug
|
title: Webmention Debug
|
||||||
permalink: /debug/webmentions/
|
permalink: /debug/webmentions/
|
||||||
eleventyExcludeFromCollections: true
|
eleventyExcludeFromCollections: true
|
||||||
|
pagefindIgnore: true
|
||||||
---
|
---
|
||||||
<div class="page-header mb-6 sm:mb-8">
|
<div class="page-header mb-6 sm:mb-8">
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100 mb-2">Webmention Debug</h1>
|
<h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100 mb-2">Webmention Debug</h1>
|
||||||
|
|||||||
Reference in New Issue
Block a user