Skip to content

Commit 44cb90d

Browse files
Add client-side full-text search and edit controls
Search: - Lunr.js-powered search with build-time JSON index - Search form in header with keyboard accessibility - Results page with highlighted term matches in excerpts - Responsive design (icon-only on mobile) Edit controls: - "Edit" button next to page title (GitHub Octicons compose icon) - Footer links: "Edit this page" and "View version history" - Links go to GitHub editor and history for source file
1 parent 608260b commit 44cb90d

5 files changed

Lines changed: 457 additions & 1 deletion

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ The wiki was initially populated with 294 pages from wiki.csswg.org covering:
1616
- **[Specification Issues and Planning](/spec/)** — Wiki pages for tracking spec-related thoughts
1717
- **[Testing](/test/)** — CSS testing documentation
1818

19+
## Search
20+
21+
The site includes client-side full-text search powered by [Lunr.js](https://lunrjs.com/):
22+
23+
- **Build-time indexing**`search.json` generates a JSON index of all pages at build time
24+
- **Client-side search** — No server required; search runs entirely in the browser
25+
- **Highlighted excerpts** — Search results show matching terms highlighted in context
26+
- **Keyboard accessible** — Header search form submits to dedicated results page
27+
28+
Search is available via the search box in the site header, or directly at `/search/`.
29+
1930
## Editing
2031

2132
Each page has an “edit this page” link that takes you to the GitHub editor. Changes are deployed automatically when merged to `main`.

_layouts/default.html

Lines changed: 262 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,234 @@
488488
.gfm-alert-caution .gfm-alert-title { color: #f85149; }
489489
}
490490

491+
/* Search */
492+
.search-form {
493+
display: flex;
494+
align-items: center;
495+
}
496+
.search-form input[type="search"] {
497+
font-family: inherit;
498+
font-size: 0.875rem;
499+
padding: var(--space-sm) var(--space-md);
500+
border: 1px solid var(--color-border);
501+
border-radius: var(--radius);
502+
background: var(--color-bg);
503+
color: var(--color-text);
504+
width: 160px;
505+
min-height: var(--min-tap-size);
506+
transition: border-color var(--transition), box-shadow var(--transition);
507+
}
508+
.search-form input[type="search"]:focus {
509+
outline: var(--focus-outline);
510+
outline-offset: var(--focus-offset);
511+
border-color: var(--color-focus);
512+
}
513+
.search-form input[type="search"]::placeholder {
514+
color: var(--color-text-muted);
515+
}
516+
@media (max-width: 640px) {
517+
.search-form {
518+
width: 100%;
519+
margin-top: var(--space-sm);
520+
order: 10;
521+
}
522+
.search-form input[type="search"] {
523+
width: 100%;
524+
}
525+
}
526+
527+
/* Search Results Page */
528+
.search-page-form {
529+
display: flex;
530+
gap: var(--space-sm);
531+
margin-bottom: var(--space-lg);
532+
}
533+
.search-page-form input[type="search"] {
534+
flex: 1;
535+
font-family: inherit;
536+
font-size: 1rem;
537+
padding: var(--space-sm) var(--space-md);
538+
border: 1px solid var(--color-border);
539+
border-radius: var(--radius);
540+
background: var(--color-bg);
541+
color: var(--color-text);
542+
min-height: var(--min-tap-size);
543+
}
544+
.search-page-form input[type="search"]:focus {
545+
outline: var(--focus-outline);
546+
outline-offset: var(--focus-offset);
547+
border-color: var(--color-focus);
548+
}
549+
.search-page-form button {
550+
padding: var(--space-sm) var(--space-lg);
551+
background: var(--color-link);
552+
color: #fff;
553+
border: none;
554+
border-radius: var(--radius);
555+
cursor: pointer;
556+
font-family: inherit;
557+
font-size: 1rem;
558+
font-weight: 500;
559+
min-height: var(--min-tap-size);
560+
min-width: var(--min-tap-size);
561+
transition: background var(--transition);
562+
}
563+
.search-page-form button:hover {
564+
background: var(--color-link-hover);
565+
}
566+
.search-page-form button:focus-visible {
567+
outline: var(--focus-outline);
568+
outline-offset: var(--focus-offset);
569+
}
570+
.search-page-form button {
571+
display: inline-flex;
572+
align-items: center;
573+
justify-content: center;
574+
}
575+
.search-btn-icon { display: none; }
576+
@media (max-width: 640px) {
577+
.search-btn-text { display: none; }
578+
.search-btn-icon { display: block; }
579+
.search-page-form button {
580+
padding: var(--space-sm);
581+
aspect-ratio: 1;
582+
}
583+
}
584+
.search-results-list {
585+
list-style: none;
586+
padding: 0;
587+
margin: 0;
588+
}
589+
.search-result {
590+
padding: var(--space-md) 0;
591+
border-bottom: 1px solid var(--color-border-muted);
592+
}
593+
.search-result:last-child {
594+
border-bottom: none;
595+
}
596+
.search-result-title {
597+
font-size: 1.125rem;
598+
font-weight: 500;
599+
text-decoration: none;
600+
display: block;
601+
margin-bottom: var(--space-xs);
602+
}
603+
.search-result-title:hover {
604+
text-decoration: underline;
605+
}
606+
.search-result-path {
607+
font-size: 0.8125rem;
608+
color: var(--color-text-muted);
609+
display: block;
610+
margin-bottom: var(--space-xs);
611+
}
612+
.search-result-excerpt {
613+
margin: 0;
614+
font-size: 0.9375rem;
615+
color: var(--color-text);
616+
line-height: 1.5;
617+
}
618+
.search-result-excerpt mark {
619+
background: #fef08a;
620+
color: #1f2328;
621+
padding: 0.1em 0.2em;
622+
border-radius: var(--radius-sm);
623+
}
624+
@media (prefers-color-scheme: dark) {
625+
.search-result-excerpt mark {
626+
background: #fbbf24;
627+
color: #1f2328;
628+
}
629+
}
630+
631+
/* Edit Controls */
632+
.edit-icon-link {
633+
display: inline-flex;
634+
align-items: center;
635+
justify-content: center;
636+
min-width: var(--min-tap-size);
637+
min-height: var(--min-tap-size);
638+
padding: var(--space-sm);
639+
color: var(--color-text-muted);
640+
text-decoration: none;
641+
border-radius: var(--radius);
642+
transition: color var(--transition), background var(--transition);
643+
}
644+
.edit-icon-link:hover,
645+
.edit-icon-link:focus {
646+
color: var(--color-link);
647+
background: var(--color-bg-subtle);
648+
}
649+
.edit-icon-link svg {
650+
width: 20px;
651+
height: 20px;
652+
}
653+
654+
/* Page title with edit (JS-injected) */
655+
.page-title-wrapper {
656+
display: flex;
657+
align-items: flex-start;
658+
gap: var(--space-sm);
659+
}
660+
.page-title-wrapper h1 {
661+
flex: 1;
662+
}
663+
.page-title-edit {
664+
flex-shrink: 0;
665+
margin-top: 0.35em;
666+
min-width: auto;
667+
min-height: auto;
668+
padding: var(--space-xs) var(--space-sm);
669+
display: inline-flex;
670+
align-items: center;
671+
gap: 0.3em;
672+
font-size: 0.875rem;
673+
font-weight: 500;
674+
border: 1px solid var(--color-border);
675+
}
676+
.page-title-edit svg {
677+
width: 14px;
678+
height: 14px;
679+
}
680+
681+
/* Footer edit link */
682+
.edit-footer {
683+
margin-top: var(--space-xl);
684+
padding-top: var(--space-lg);
685+
border-top: 1px solid var(--color-border-muted);
686+
display: flex;
687+
flex-wrap: wrap;
688+
align-items: center;
689+
gap: var(--space-sm) var(--space-lg);
690+
}
691+
.edit-footer-link {
692+
display: inline-flex;
693+
align-items: center;
694+
gap: var(--space-sm);
695+
color: var(--color-text-muted);
696+
text-decoration: none;
697+
font-size: 0.9375rem;
698+
padding: var(--space-sm) 0;
699+
}
700+
.edit-footer-link:hover,
701+
.edit-footer-link:focus {
702+
color: var(--color-link);
703+
}
704+
.edit-footer-link svg {
705+
width: 16px;
706+
height: 16px;
707+
}
708+
.edit-footer-link:last-of-type svg {
709+
width: 20px;
710+
height: 20px;
711+
position: relative;
712+
top: 1px;
713+
}
714+
.edit-footer-sep {
715+
color: var(--color-border);
716+
line-height: 1;
717+
}
718+
491719
/* Selection */
492720
::selection {
493721
background: var(--color-link);
@@ -510,7 +738,7 @@
510738
/* Print Styles */
511739
@media print {
512740
body { background: #fff; color: #000; }
513-
.edit-banner, .menu-toggle, .skip-link { display: none; }
741+
.edit-banner, .menu-toggle, .skip-link, .search-form, .page-title-edit, .edit-footer { display: none; }
514742
.site-header { position: static; border-bottom: 1px solid #ccc; }
515743
a { color: #000; text-decoration: underline; }
516744
a[href^="http"]::after {
@@ -550,6 +778,9 @@
550778
<a href="{{ site.baseurl }}/test/">Testing</a>
551779
<a href="{{ site.baseurl }}/wiki/">About</a>
552780
</nav>
781+
<form action="{{ site.baseurl }}/search/" method="get" class="search-form" role="search" aria-label="Site search">
782+
<input type="search" name="q" placeholder="Search…" aria-label="Search wiki">
783+
</form>
553784
</div>
554785
</header>
555786

@@ -574,6 +805,17 @@
574805
</nav>
575806
{% endif %}
576807
{{ content }}
808+
<footer class="edit-footer">
809+
<a href="https://github.com/{{ site.repository }}/edit/main/{{ page.path }}" class="edit-footer-link">
810+
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="m14.515.456.965.965a1.555 1.555 0 0 1 0 2.2L9.745 9.355a1.549 1.549 0 0 1-.672.396l-2.89.826a.67.67 0 0 1-.828-.474.66.66 0 0 1 .004-.35l.825-2.89c.073-.254.209-.486.396-.673L12.315.456c.144-.145.316-.259.505-.337a1.538 1.538 0 0 1 1.19 0c.189.078.361.192.505.337Zm-3.322 3.008-3.67 3.669a.214.214 0 0 0-.057.096L6.97 8.965l1.736-.496a.218.218 0 0 0 .096-.056l3.67-3.67Zm2.065-2.066L12.135 2.52l1.28 1.28 1.122-1.122a.216.216 0 0 0 .065-.157.216.216 0 0 0-.065-.157l-.965-.966a.216.216 0 0 0-.157-.065.226.226 0 0 0-.157.065Z"/><path d="M0 14.25V2.75A1.75 1.75 0 0 1 1.75 1H7a.75.75 0 0 1 0 1.5H1.75a.25.25 0 0 0-.25.25v11.5a.25.25 0 0 0 .25.25h11.5a.25.25 0 0 0 .25-.25V8.5a.75.75 0 0 1 1.5 0v5.75c0 .464-.184.909-.513 1.237A1.746 1.746 0 0 1 13.25 16H1.75A1.75 1.75 0 0 1 0 14.25Z"/></svg>
811+
Edit this page
812+
</a>
813+
<span class="edit-footer-sep" aria-hidden="true">|</span>
814+
<a href="https://github.com/{{ site.repository }}/commits/main/{{ page.path }}" class="edit-footer-link">
815+
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M7.75 14A1.75 1.75 0 0 1 6 12.25v-8.5C6 2.784 6.784 2 7.75 2h6.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0 1 14.25 14Zm-.25-1.75c0 .138.112.25.25.25h6.5a.25.25 0 0 0 .25-.25v-8.5a.25.25 0 0 0-.25-.25h-6.5a.25.25 0 0 0-.25.25ZM4.9 3.508a.75.75 0 0 1-.274 1.025.249.249 0 0 0-.126.217v6.5c0 .09.048.173.126.217a.75.75 0 0 1-.752 1.298A1.75 1.75 0 0 1 3 11.25v-6.5c0-.649.353-1.214.874-1.516a.75.75 0 0 1 1.025.274ZM1.625 5.533h.001a.249.249 0 0 0-.126.217v4.5c0 .09.048.173.126.217a.75.75 0 0 1-.752 1.298A1.748 1.748 0 0 1 0 10.25v-4.5a1.748 1.748 0 0 1 .873-1.516.75.75 0 1 1 .752 1.299Z"/></svg>
816+
View version history
817+
</a>
818+
</footer>
577819
</div>
578820
</main>
579821

@@ -627,6 +869,25 @@
627869
}
628870
});
629871

872+
// Inject edit icon next to first h1
873+
const firstH1 = document.querySelector('main h1');
874+
if (firstH1 && !firstH1.closest('.page-title-wrapper')) {
875+
const editUrl = document.querySelector('.edit-footer-link')?.href;
876+
if (editUrl) {
877+
const wrapper = document.createElement('div');
878+
wrapper.className = 'page-title-wrapper';
879+
firstH1.parentNode.insertBefore(wrapper, firstH1);
880+
wrapper.appendChild(firstH1);
881+
882+
const editLink = document.createElement('a');
883+
editLink.href = editUrl;
884+
editLink.className = 'edit-icon-link page-title-edit';
885+
editLink.setAttribute('aria-label', 'Edit this page on GitHub');
886+
editLink.setAttribute('title', 'Edit this page');
887+
editLink.innerHTML = '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="m14.515.456.965.965a1.555 1.555 0 0 1 0 2.2L9.745 9.355a1.549 1.549 0 0 1-.672.396l-2.89.826a.67.67 0 0 1-.828-.474.66.66 0 0 1 .004-.35l.825-2.89c.073-.254.209-.486.396-.673L12.315.456c.144-.145.316-.259.505-.337a1.538 1.538 0 0 1 1.19 0c.189.078.361.192.505.337Zm-3.322 3.008-3.67 3.669a.214.214 0 0 0-.057.096L6.97 8.965l1.736-.496a.218.218 0 0 0 .096-.056l3.67-3.67Zm2.065-2.066L12.135 2.52l1.28 1.28 1.122-1.122a.216.216 0 0 0 .065-.157.216.216 0 0 0-.065-.157l-.965-.966a.216.216 0 0 0-.157-.065.226.226 0 0 0-.157.065Z"/><path d="M0 14.25V2.75A1.75 1.75 0 0 1 1.75 1H7a.75.75 0 0 1 0 1.5H1.75a.25.25 0 0 0-.25.25v11.5a.25.25 0 0 0 .25.25h11.5a.25.25 0 0 0 .25-.25V8.5a.75.75 0 0 1 1.5 0v5.75c0 .464-.184.909-.513 1.237A1.746 1.746 0 0 1 13.25 16H1.75A1.75 1.75 0 0 1 0 14.25Z"/></svg>Edit';
888+
wrapper.appendChild(editLink);
889+
}
890+
}
630891
})();
631892
</script>
632893
<script src="{{ site.baseurl }}/assets/abbr.js"></script>

0 commit comments

Comments
 (0)