Skip to content

Commit 1153ab6

Browse files
author
Chris Williams
authored
Merge pull request #88 from chrismwilliams/feature/search
Feature: search
2 parents b364ecf + 64c0ea2 commit 1153ab6

10 files changed

Lines changed: 317 additions & 77 deletions

File tree

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
enable-pre-post-scripts=true

README.md

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ Astro Theme Cactus is a simple opinionated starter built with the Astro framewor
1616
- Dark / Light mode, using Tailwind and CSS variables
1717
- [Astro Assets Integration](https://docs.astro.build/en/guides/assets/) for optimised images
1818
- MD & [MDX](https://docs.astro.build/en/guides/markdown-content/#mdx-only-features) posts
19-
- [Satori](https://github.com/vercel/satori) for creating open graph png images.
19+
- [Satori](https://github.com/vercel/satori) for creating open graph png images
2020
- Pagination
2121
- [Automatic RSS feed](https://docs.astro.build/en/guides/rss)
2222
- Shiki code syntax styling
2323
- Auto-generated [sitemap](https://docs.astro.build/en/guides/integrations-guide/sitemap/)
24+
- [Pagefind](https://pagefind.app/) static search library integration
2425

2526
## Demo 💻
2627

@@ -36,13 +37,14 @@ Check out the [Demo](https://astro-theme-cactus.netlify.app/), hosted on Netlify
3637

3738
Replace pnpm with your choice of npm / yarn
3839

39-
| Command | Action |
40-
| :------------- | :------------------------------------------------------------- |
41-
| `pnpm install` | Installs dependencies |
42-
| `pnpm dev` | Starts local dev server at `localhost:3000` |
43-
| `pnpm build` | Build your production site to `./dist/` |
44-
| `pnpm preview` | Preview your build locally, before deploying |
45-
| `pnpm sync` | Generate types based on your config in `src/content/config.ts` |
40+
| Command | Action |
41+
| :--------------- | :------------------------------------------------------------- |
42+
| `pnpm install` | Installs dependencies |
43+
| `pnpm dev` | Starts local dev server at `localhost:3000` |
44+
| `pnpm build` | Build your production site to `./dist/` |
45+
| `pnpm postbuild` | Pagefind script to build the static search of your blog posts |
46+
| `pnpm preview` | Preview your build locally, before deploying |
47+
| `pnpm sync` | Generate types based on your config in `src/content/config.ts` |
4648

4749
## Configure
4850

@@ -79,6 +81,18 @@ Adding a post is a simple as adding your .md(x) file(s) to the `src/content/post
7981
| coverImage | This is an optional object that will add a cover image to the top of a post. Include both a `src`: "_path-to-image_" and `alt`: "_image alt_". You can view an example in `src/content/post/cover-image.md` |
8082
| ogImage | This is an optional property. An OG Image will be generated automatically for every post where this property **isn't** provided. If you would like to create your own for a specific post, include this property and a link to your image, the theme will then skip automatically generating one. |
8183

84+
## Pagefind search
85+
86+
This integration brings a static search feature for searching blog posts. In its current form, pagefind only works once the site has been built. This theme adds a postbuild script that should be run after Astro has built the site. You can preview locally by running both build && postbuild.
87+
88+
Search results only includes blog posts. If you would like to include other/all your pages, remove/re-locate the attribute `data-pagefind-body` to the article tag found in `src/layouts/BlogPost.astro`.
89+
90+
It also allows you to filter posts by tags added in the frontmatter of blog posts. If you would rather remove this, remove the data attribute `data-pagefind-filter="tag"` from the link in `src/components/blog/Hero.astro`.
91+
92+
Note the current build will display a warning in the console, you can follow this issue [here](https://github.com/CloudCannon/pagefind/issues/290)
93+
94+
If you would rather not include this integration, simply remove the component `src/components/Search.astro`, and uninstall both `@pagefind/default-ui` & `pagefind` from package.json. You will also need to remove the postbuild script from here as well.
95+
8296
## Analytics
8397

8498
You may want to track the number of visitors you receive to your blog/website in order to understand trends and popular posts/pages you've created. There are a number of providers out there one could use, including web hosts such as [vercel](https://vercel.com/analytics), [netlify](https://www.netlify.com/products/analytics/), and [cloudflare](https://www.cloudflare.com/web-analytics/).

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
"sync": "astro sync",
99
"check": "astro check",
1010
"build": "astro build",
11+
"postbuild": "pagefind --source dist",
1112
"preview": "astro preview",
1213
"format": "prettier -w --plugin-search-dir=. --cache ."
1314
},
1415
"devDependencies": {
1516
"@astrojs/mdx": "^0.19.7",
1617
"@astrojs/sitemap": "^1.3.3",
1718
"@astrojs/tailwind": "4.0.0",
19+
"@pagefind/default-ui": "^0.12.0",
1820
"@resvg/resvg-js": "^2.4.1",
1921
"@tailwindcss/aspect-ratio": "^0.4.2",
2022
"@tailwindcss/typography": "^0.5.8",
@@ -31,6 +33,7 @@
3133
"eslint-plugin-import": "^2.26.0",
3234
"eslint-plugin-jsx-a11y": "^6.7.1",
3335
"eslint-plugin-prettier": "^4.2.1",
36+
"pagefind": "^0.12.0",
3437
"postcss": "^8.4.24",
3538
"postcss-html": "^1.5.0",
3639
"prettier": "^2.8.8",

pnpm-lock.yaml

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Search.astro

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
---
2+
// Heavy inspiration taken from Astro Starlight -> https://github.com/withastro/starlight/blob/main/packages/starlight/components/Search.astro
3+
4+
import "@pagefind/default-ui/css/ui.css";
5+
---
6+
7+
<site-search id="search" class="ms-auto">
8+
<button
9+
data-open-modal
10+
disabled
11+
class="flex h-9 w-9 items-center justify-center rounded-md ring-zinc-400 transition-all hover:ring-2"
12+
>
13+
<svg
14+
aria-label="search"
15+
class="h-6 w-6"
16+
xmlns="http://www.w3.org/2000/svg"
17+
width="16"
18+
height="16"
19+
viewBox="0 0 24 24"
20+
fill="none"
21+
stroke="currentColor"
22+
stroke-linecap="round"
23+
stroke-linejoin="round"
24+
stroke-width="1.5"
25+
>
26+
<path stroke="none" d="M0 0h24v24H0z"></path>
27+
<path d="M3 10a7 7 0 1 0 14 0 7 7 0 1 0-14 0M21 21l-6-6"></path>
28+
</svg>
29+
</button>
30+
<dialog
31+
aria-label="search"
32+
class="h-full max-h-full w-full max-w-full border border-zinc-400 bg-[--theme-bg] shadow backdrop:bg-[--theme-menu-bg] backdrop:backdrop-blur sm:mx-auto sm:mb-auto sm:mt-16 sm:h-max sm:max-h-[calc(100%-8rem)] sm:min-h-[15rem] sm:w-5/6 sm:max-w-[48rem] sm:rounded-md"
33+
>
34+
<div class="dialog-frame flex flex-col gap-4 p-6 pt-12 sm:pt-6">
35+
<button
36+
data-close-modal
37+
class="ms-auto cursor-pointer rounded-md bg-zinc-200 p-2 font-semibold dark:bg-zinc-700"
38+
>Close</button
39+
>
40+
{
41+
import.meta.env.DEV ? (
42+
<div class="mx-auto text-center">
43+
<p>
44+
Search is only available in production builds. <br />
45+
Try building and previewing the site to test it out locally.
46+
</p>
47+
</div>
48+
) : (
49+
<div class="search-container">
50+
<div id="cactus__search" />
51+
</div>
52+
)
53+
}
54+
</div>
55+
</dialog>
56+
</site-search>
57+
58+
<script>
59+
class SiteSearch extends HTMLElement {
60+
constructor() {
61+
super();
62+
const openBtn = this.querySelector<HTMLButtonElement>("button[data-open-modal]")!;
63+
const closeBtn = this.querySelector<HTMLButtonElement>("button[data-close-modal]")!;
64+
const dialog = this.querySelector("dialog")!;
65+
const dialogFrame = this.querySelector(".dialog-frame")!;
66+
67+
const onWindowClick = (event: MouseEvent) => {
68+
// make sure the click is outside the of the dialog
69+
if (
70+
document.body.contains(event.target as Node) &&
71+
!dialogFrame.contains(event.target as Node)
72+
)
73+
closeModal();
74+
};
75+
76+
const openModal = (event?: MouseEvent) => {
77+
dialog.showModal();
78+
this.querySelector("input")?.focus();
79+
event?.stopPropagation();
80+
window.addEventListener("click", onWindowClick);
81+
};
82+
83+
const closeModal = () => {
84+
dialog.close();
85+
window.removeEventListener("click", onWindowClick);
86+
};
87+
88+
openBtn.addEventListener("click", openModal);
89+
openBtn.disabled = false;
90+
closeBtn.addEventListener("click", closeModal);
91+
92+
// Listen for `/` keyboard shortcut
93+
window.addEventListener("keydown", (e) => {
94+
if (e.key === "/" && !dialog.open) {
95+
openModal();
96+
e.preventDefault();
97+
}
98+
});
99+
100+
window.addEventListener("DOMContentLoaded", () => {
101+
if (import.meta.env.DEV) return;
102+
const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
103+
onIdle(async () => {
104+
const { PagefindUI } = await import("@pagefind/default-ui");
105+
new PagefindUI({
106+
element: "#cactus__search",
107+
baseUrl: import.meta.env.BASE_URL,
108+
bundlePath: import.meta.env.BASE_URL.replace(/\/$/, "") + "/_pagefind/",
109+
showImages: false,
110+
});
111+
});
112+
});
113+
}
114+
}
115+
116+
customElements.define("site-search", SiteSearch);
117+
</script>
118+
119+
<style is:global>
120+
:root {
121+
--pagefind-ui-font: inherit;
122+
}
123+
124+
#cactus__search .pagefind-ui__search-clear {
125+
width: calc(60px * var(--pagefind-ui-scale));
126+
padding: 0;
127+
background-color: transparent;
128+
overflow: hidden;
129+
}
130+
#cactus__search .pagefind-ui__search-clear:focus {
131+
outline: 1px solid var(--theme-accent-2);
132+
}
133+
#cactus__search .pagefind-ui__search-clear::before {
134+
content: "";
135+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' %3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 18L18 6M6 6l12 12'%3E%3C/path%3E%3C/svg%3E")
136+
center / 60% no-repeat;
137+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' %3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 18L18 6M6 6l12 12'%3E%3C/path%3E%3C/svg%3E")
138+
center / 60% no-repeat;
139+
background-color: var(--theme-accent);
140+
display: block;
141+
width: 100%;
142+
height: 100%;
143+
}
144+
145+
#cactus__search .pagefind-ui__result {
146+
border: 0;
147+
}
148+
149+
#cactus__search .pagefind-ui__result-link {
150+
background-size: 100% 6px;
151+
background-position: bottom;
152+
background-repeat: repeat-x;
153+
background-image: linear-gradient(
154+
transparent,
155+
transparent 5px,
156+
var(--theme-text) 5px,
157+
var(--theme-text)
158+
);
159+
}
160+
161+
#cactus__search .pagefind-ui__result-link:hover {
162+
text-decoration: none;
163+
background-image: linear-gradient(
164+
transparent,
165+
transparent 4px,
166+
var(--theme-link) 4px,
167+
var(--theme-link)
168+
);
169+
}
170+
171+
#cactus__search mark {
172+
color: var(--theme-quote);
173+
background-color: transparent;
174+
font-weight: 600;
175+
}
176+
</style>
177+
178+
<style>
179+
#cactus__search {
180+
--pagefind-ui-primary: var(--theme-accent);
181+
--pagefind-ui-text: var(--theme-text);
182+
--pagefind-ui-background: var(--theme-bg);
183+
--pagefind-ui-border: #a1a1aa;
184+
--pagefind-ui-border-width: 1px;
185+
}
186+
</style>

src/components/ThemeToggle.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
customElements.define("theme-toggle", ThemeToggle);
3131
</script>
3232

33-
<theme-toggle class="ms-auto">
33+
<theme-toggle class="ms-2 sm:ms-4">
3434
<button
3535
type="button"
3636
id="toggle-theme"

src/components/blog/Hero.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const postDate = getFormattedDate(data.publishDate, { month: "long" });
3737
aria-hidden="true"
3838
focusable="false"
3939
xmlns="http://www.w3.org/2000/svg"
40-
class="mr-1 inline-block h-6 w-6"
40+
class="me-1 inline-block h-6 w-6"
4141
viewBox="0 0 24 24"
4242
stroke-width="1.5"
4343
stroke="currentColor"
@@ -56,6 +56,7 @@ const postDate = getFormattedDate(data.publishDate, { month: "long" });
5656
class="cactus-link inline-block before:content-['#']"
5757
aria-label={`View more blogs with the tag ${tag}`}
5858
href={`/tags/${tag}/`}
59+
data-pagefind-filter="tag"
5960
>
6061
{tag}
6162
</a>

0 commit comments

Comments
 (0)