diff --git a/.eleventy.js b/.eleventy.js
index f2c962a..91664a9 100644
--- a/.eleventy.js
+++ b/.eleventy.js
@@ -1,5 +1,6 @@
require('dotenv').config();
+const { execSync } = require('child_process');
const { JSDOM } = require('jsdom');
const { parse, stringify } = require('himalaya');
const dateFilter = require('./src/filters/date.js');
@@ -198,6 +199,10 @@ module.exports = config => {
`;
});
+ config.on('eleventy.after', () => {
+ execSync(`npx pagefind --site dist --glob \"**/*.html\"`, { encoding: 'utf-8' })
+ });
+
if (isProduction) {
config.addTransform('htmlmin', htmlMinTransform);
}
diff --git a/package-lock.json b/package-lock.json
index 47c52bc..7604b7c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
"markdown-it-anchor": "^8.6.7",
"moment": "^2.30.1",
"npm-run-all": "^4.1.5",
+ "pagefind": "^1.0.4",
"sass": "^1.71.1",
"slugify": "^1.6.6",
"sprucecss": "^2.3.1",
@@ -1166,6 +1167,71 @@
"node": ">= 8"
}
},
+ "node_modules/@pagefind/darwin-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.0.4.tgz",
+ "integrity": "sha512-2OcthvceX2xhm5XbgOmW+lT45oLuHqCmvFeFtxh1gsuP5cO8vcD8ZH8Laj4pXQFCcK6eAdSShx+Ztx/LsQWZFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@pagefind/darwin-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xkdvp0D9Ld/ZKsjo/y1bgfhTEU72ITimd2PMMQtts7jf6JPIOJbsiErCvm37m/qMFuPGEq/8d+fZ4pydOj08HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@pagefind/linux-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-jGBrcCzIrMnNxLKVtogaQyajVfTAXM59KlBEwg6vTn8NW4fQ6nuFbbhlG4dTIsaamjEM5e8ZBEAKZfTB/qd9xw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@pagefind/linux-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.0.4.tgz",
+ "integrity": "sha512-LIn/QcvcEtLEBqKe5vpSbSC2O3fvqbRCWOTIklslqSORisCsvzsWbP6j+LYxE9q0oWIfkdMoWV1vrE/oCKRxHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@pagefind/windows-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.0.4.tgz",
+ "integrity": "sha512-QlBCVeZfj9fc9sbUgdOz76ZDbeK4xZihOBAFqGuRJeChfM8pnVeH9iqSnXgO3+m9oITugTf7PicyRUFAG76xeQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -5366,6 +5432,22 @@
"node": ">=8"
}
},
+ "node_modules/pagefind": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.0.4.tgz",
+ "integrity": "sha512-oRIizYe+zSI2Jw4zcMU0ebDZm27751hRFiSOBLwc1OIYMrsZKk+3m8p9EVaOmc6zZdtqwwdilNUNxXvBeHcP9w==",
+ "dev": true,
+ "bin": {
+ "pagefind": "lib/runner/bin.cjs"
+ },
+ "optionalDependencies": {
+ "@pagefind/darwin-arm64": "1.0.4",
+ "@pagefind/darwin-x64": "1.0.4",
+ "@pagefind/linux-arm64": "1.0.4",
+ "@pagefind/linux-x64": "1.0.4",
+ "@pagefind/windows-x64": "1.0.4"
+ }
+ },
"node_modules/param-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
diff --git a/package.json b/package.json
index 611113b..902f351 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"markdown-it-anchor": "^8.6.7",
"moment": "^2.30.1",
"npm-run-all": "^4.1.5",
+ "pagefind": "^1.0.4",
"sass": "^1.71.1",
"slugify": "^1.6.6",
"sprucecss": "^2.3.1",
diff --git a/src/_includes/icon/search.svg b/src/_includes/icon/search.svg
new file mode 100644
index 0000000..9f0f6eb
--- /dev/null
+++ b/src/_includes/icon/search.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/_includes/layout/base.html b/src/_includes/layout/base.html
index 84b3e69..dbd272d 100644
--- a/src/_includes/layout/base.html
+++ b/src/_includes/layout/base.html
@@ -13,19 +13,24 @@
- Skip to content
- {% include "partial/header.html" %}
+
+
Skip to content
+ {% include "partial/header.html" %}
-
- {% block content %}{% endblock %}
-
+
+ {% block content %}{% endblock %}
+
- {% include "partial/github-cta.html" %}
- {% include "partial/footer.html" %}
+ {% include "partial/github-cta.html" %}
+ {% include "partial/footer.html" %}
+
+
+ {% include "partial/search-modal.html" %}
+
diff --git a/src/_includes/layout/component.html b/src/_includes/layout/component.html
index 5000b22..58d1b29 100644
--- a/src/_includes/layout/component.html
+++ b/src/_includes/layout/component.html
@@ -28,7 +28,7 @@ {{ title }}
{{ component.content | toc | safe }}
-
+
{{ component.content | markdown | safe }}
diff --git a/src/_includes/layout/docs.html b/src/_includes/layout/docs.html
index 31a56c2..39a7428 100644
--- a/src/_includes/layout/docs.html
+++ b/src/_includes/layout/docs.html
@@ -8,7 +8,7 @@
{% include "partial/sidebar-docs.html" %}
-
+
{{ title }}
diff --git a/src/_includes/layout/template.html b/src/_includes/layout/template.html
index c39cdef..7bc0ca9 100644
--- a/src/_includes/layout/template.html
+++ b/src/_includes/layout/template.html
@@ -6,7 +6,7 @@
} %}
{% block content %}
-
+
{% include "partial/post-heading.html" %}
diff --git a/src/_includes/partial/header.html b/src/_includes/partial/header.html
index 045cf06..546dca9 100644
--- a/src/_includes/partial/header.html
+++ b/src/_includes/partial/header.html
@@ -41,17 +41,20 @@
{% if site.socials.length %}
{% endif %}
diff --git a/src/_includes/partial/open-search.html b/src/_includes/partial/open-search.html
new file mode 100644
index 0000000..99490fd
--- /dev/null
+++ b/src/_includes/partial/open-search.html
@@ -0,0 +1,7 @@
+
+ {% svgIcon './src/_includes/icon/search.svg', 'open-search__icon' %}
+ Search
+
+
diff --git a/src/_includes/partial/search-modal.html b/src/_includes/partial/search-modal.html
new file mode 100644
index 0000000..f70d22f
--- /dev/null
+++ b/src/_includes/partial/search-modal.html
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/js/cookie-consent.js b/src/js/cookie-consent.js
index 60e343d..664b4ea 100644
--- a/src/js/cookie-consent.js
+++ b/src/js/cookie-consent.js
@@ -1,6 +1,6 @@
import {
setCookie, getCookie, issetCookie, removeCookie,
-} from './cookie';
+} from './cookie.js';
(() => {
const prefix = 'spruce';
diff --git a/src/js/modal.js b/src/js/modal.js
new file mode 100644
index 0000000..eab7735
--- /dev/null
+++ b/src/js/modal.js
@@ -0,0 +1,45 @@
+(() => {
+ let activeElement = null;
+ const siteWrapper = document.querySelector('.site-wrapper');
+ const button = document.querySelector('[data-action="open-search"]');
+ const input = document.querySelector('.pagefind-ui__search-input');
+ const modal = document.querySelector('.modal-backdrop');
+
+ if (!button || !modal) return;
+
+ function openModal() {
+ activeElement = document.activeElement;
+ siteWrapper.setAttribute('inert', '');
+ modal.classList.add('modal-backdrop--open');
+ input.focus();
+ }
+
+ function closeModal() {
+ siteWrapper.removeAttribute('inert');
+ modal.classList.remove('modal-backdrop--open');
+ activeElement.focus();
+ }
+
+ function handleKeyDown(e) {
+ if (e.code === 'Escape') {
+ closeModal();
+ }
+
+ if (e.ctrlKey && e.code === 'KeyK') {
+ e.preventDefault();
+ openModal();
+ }
+ }
+
+ modal.addEventListener('click', (e) => {
+ if (e.target === e.currentTarget) {
+ closeModal();
+ }
+ });
+
+ button.addEventListener('click', () => {
+ openModal();
+ });
+
+ window.addEventListener('keydown', handleKeyDown);
+})();
diff --git a/src/scss/component/_index.scss b/src/scss/component/_index.scss
index c766bec..be536b1 100644
--- a/src/scss/component/_index.scss
+++ b/src/scss/component/_index.scss
@@ -10,8 +10,11 @@
@forward 'text-btn';
@forward 'form';
@forward 'pagination';
+@forward 'pagefind';
+@forward 'modal';
@forward 'template';
@forward 'notification';
+@forward 'open-search';
@forward 'tags';
@forward 'ide';
@forward 'code-tab';
diff --git a/src/scss/component/_modal.scss b/src/scss/component/_modal.scss
new file mode 100644
index 0000000..4ea0d1e
--- /dev/null
+++ b/src/scss/component/_modal.scss
@@ -0,0 +1,24 @@
+@use 'sprucecss/scss/spruce' as *;
+
+.modal-backdrop {
+ backdrop-filter: blur(0.35rem);
+ background-color: hsl(0deg 0% 100% / 35%);
+ display: none;
+ inset: 0;
+ padding: spacer('s');
+ position: fixed;
+ z-index: 100;
+
+ &--open {
+ display: block;
+ }
+}
+
+.modal {
+ background-color: color('background');
+ border-radius: config('border-radius-sm', $display);
+ box-shadow: get-css-variable(--box-shadow);
+ margin-block: 7rem;
+ margin-inline: auto;
+ max-inline-size: 40rem;
+}
diff --git a/src/scss/component/_open-search.scss b/src/scss/component/_open-search.scss
new file mode 100644
index 0000000..597008f
--- /dev/null
+++ b/src/scss/component/_open-search.scss
@@ -0,0 +1,15 @@
+@use 'sprucecss/scss/spruce' as *;
+
+.open-search {
+ @include a11y-card-link('.open-search__btn', true);
+ align-items: center;
+ display: flex;
+ gap: spacer('xs');
+
+ &__icon {
+ --size: 1rem;
+ block-size: var(--size);
+ color: color('icon', 'search');
+ inline-size: var(--size);
+ }
+}
diff --git a/src/scss/component/_pagefind.scss b/src/scss/component/_pagefind.scss
new file mode 100644
index 0000000..4cec8ab
--- /dev/null
+++ b/src/scss/component/_pagefind.scss
@@ -0,0 +1,58 @@
+@use 'sprucecss/scss/spruce' as *;
+
+@include generate-form-control('.pagefind-ui__search-input', false, false, false);
+
+.pagefind-ui {
+ position: relative;
+
+ &__search-input {
+ padding: config('padding', $form-control, false) !important;
+ }
+
+ &__form {
+ @include layout-stack('s');
+ }
+
+ &__search-clear {
+ background: none;
+ border: 0;
+ font-size: config('font-size-sm', $typography);
+ inset-block-start: 0.8em;
+ inset-inline-end: 0.5em;
+ margin-block-start: 0;
+ position: absolute;
+ text-transform: uppercase;
+ }
+
+ &__drawer {
+ @include scrollbar;
+ max-block-size: 20rem;
+ overflow-y: auto;
+ }
+
+ &__results {
+ @include clear-list;
+ @include layout-stack('s');
+ padding-inline-end: spacer('m');
+
+ &:empty {
+ display: none;
+ }
+ }
+
+ &__results-area {
+ @include layout-stack('xs');
+ }
+
+ &__result-inner {
+ @include layout-stack('xxs');
+ }
+
+ &__result-title {
+ font-weight: 700;
+ }
+
+ &__hidden {
+ display: none;
+ }
+}
diff --git a/src/scss/config/_config.scss b/src/scss/config/_config.scss
index b43475e..91b0086 100644
--- a/src/scss/config/_config.scss
+++ b/src/scss/config/_config.scss
@@ -68,6 +68,9 @@ $color-dark: hsl(260deg 70% 6%);
'punctuation': hsl(279deg 50% 53%),
'regex': hsl(1deg 66% 47%),
),
+ 'search': (
+ 'icon': hsl(259deg 53% 94%),
+ ),
),
$dark-colors: dark.$colors,
$layout: (
diff --git a/src/scss/section/_header.scss b/src/scss/section/_header.scss
index 835d888..e6501ec 100644
--- a/src/scss/section/_header.scss
+++ b/src/scss/section/_header.scss
@@ -48,6 +48,11 @@
}
&__actions {
+ display: flex;
+ gap: spacer('l');
+ }
+
+ &__socials {
display: flex;
gap: spacer('s');