Skip to content

Commit acd5005

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 a33e3ed commit acd5005

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
@@ -14,6 +14,17 @@ This repo was initially populated with 294 pages converted from wiki.csswg.org,
1414
- **[Specification Issues and Planning](/spec/)** — Wiki pages for tracking spec-related thoughts
1515
- **[Testing](/test/)** — CSS testing documentation
1616

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

1930
Each page has _“edit this page”_ links that take you to the GitHub web editing UI. Changes are deployed automatically when merged or committed to `main`.

_layouts/default.html

Lines changed: 262 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,234 @@
493493
.gfm-alert-caution .gfm-alert-title { color: #f85149; }
494494
}
495495

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

@@ -582,6 +813,17 @@
582813
</nav>
583814
{% endif %}
584815
{{ content }}
816+
<footer class="edit-footer">
817+
<a href="https://github.com/{{ site.repository }}/edit/main/{{ page.path }}" class="edit-footer-link">
818+
<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>
819+
Edit this page
820+
</a>
821+
<span class="edit-footer-sep" aria-hidden="true">|</span>
822+
<a href="https://github.com/{{ site.repository }}/commits/main/{{ page.path }}" class="edit-footer-link">
823+
<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>
824+
View version history
825+
</a>
826+
</footer>
585827
</div>
586828
</main>
587829

@@ -635,6 +877,25 @@
635877
}
636878
});
637879

880+
// Inject edit icon next to first h1
881+
const firstH1 = document.querySelector('main h1');
882+
if (firstH1 && !firstH1.closest('.page-title-wrapper')) {
883+
const editUrl = document.querySelector('.edit-footer-link')?.href;
884+
if (editUrl) {
885+
const wrapper = document.createElement('div');
886+
wrapper.className = 'page-title-wrapper';
887+
firstH1.parentNode.insertBefore(wrapper, firstH1);
888+
wrapper.appendChild(firstH1);
889+
890+
const editLink = document.createElement('a');
891+
editLink.href = editUrl;
892+
editLink.className = 'edit-icon-link page-title-edit';
893+
editLink.setAttribute('aria-label', 'Edit this page on GitHub');
894+
editLink.setAttribute('title', 'Edit this page');
895+
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';
896+
wrapper.appendChild(editLink);
897+
}
898+
}
638899
})();
639900
</script>
640901
<script src="{{ site.baseurl }}/assets/abbr.js"></script>

0 commit comments

Comments
 (0)