Skip to content

Commit 27939d5

Browse files
Add client-side full-text search and edit controls
Search (Lunr.js): - search.json: Jekyll template generating JSON search index at build time - search.html: Search results page with accessible form - assets/search.js: Client-side search logic with highlighted excerpts - On mobile, search button shows magnifying glass icon - High-contrast highlight colors in both light and dark modes Edit controls: - "Edit" button (compose icon + text) next to page title - Footer: "Edit this page" and "View version history" links - Uses GitHub Octicons icons
1 parent 49da694 commit 27939d5

4 files changed

Lines changed: 446 additions & 1 deletion

File tree

_layouts/default.html

Lines changed: 262 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,234 @@
471471
.gfm-alert-caution .gfm-alert-title { color: #f85149; }
472472
}
473473

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

@@ -555,6 +786,17 @@
555786
</nav>
556787
{% endif %}
557788
{{ content }}
789+
<footer class="edit-footer">
790+
<a href="https://github.com/{{ site.repository }}/edit/main/{{ page.path }}" class="edit-footer-link">
791+
<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>
792+
Edit this page
793+
</a>
794+
<span class="edit-footer-sep" aria-hidden="true">|</span>
795+
<a href="https://github.com/{{ site.repository }}/commits/main/{{ page.path }}" class="edit-footer-link">
796+
<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>
797+
View version history
798+
</a>
799+
</footer>
558800
</div>
559801
</main>
560802

@@ -608,6 +850,25 @@
608850
}
609851
});
610852

853+
// Inject edit icon next to first h1
854+
const firstH1 = document.querySelector('main h1');
855+
if (firstH1 && !firstH1.closest('.page-title-wrapper')) {
856+
const editUrl = document.querySelector('.edit-footer-link')?.href;
857+
if (editUrl) {
858+
const wrapper = document.createElement('div');
859+
wrapper.className = 'page-title-wrapper';
860+
firstH1.parentNode.insertBefore(wrapper, firstH1);
861+
wrapper.appendChild(firstH1);
862+
863+
const editLink = document.createElement('a');
864+
editLink.href = editUrl;
865+
editLink.className = 'edit-icon-link page-title-edit';
866+
editLink.setAttribute('aria-label', 'Edit this page on GitHub');
867+
editLink.setAttribute('title', 'Edit this page');
868+
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';
869+
wrapper.appendChild(editLink);
870+
}
871+
}
611872
})();
612873
</script>
613874
<script src="{{ site.baseurl }}/assets/abbr.js"></script>

0 commit comments

Comments
 (0)