Skip to content

Commit

Permalink
Merge pull request #1 from apvarun/add-search
Browse files Browse the repository at this point in the history
Add search UI
  • Loading branch information
apvarun authored Jul 17, 2021
2 parents 414c5aa + 21bc39d commit a103e6b
Show file tree
Hide file tree
Showing 18 changed files with 296 additions and 14 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Changelog

All the changes made to Showcase theme for Hugo.

## v1.1.0 - 2021-07-17

### Added

- Add support for text search

## v1.0.0 - 2021-07-16

- Initial Release
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ Blist is a clean and fast blog theme for your Hugo site.

- Responsive content
- Blog pagination
- Text Search
- Social links
- Dark mode
- Fast performance

## Preview

Expand Down Expand Up @@ -43,7 +45,6 @@ Blist theme ships with an fully configured example site. For a quick preview:

Copy the `package.json` file from `themes/showcase` folder to your hugo website root folder, and run `npm install`.


```sh
cd themes/blist/exampleSite/
hugo serve --themesDir ../..
Expand All @@ -68,7 +69,7 @@ The following explains how to add content to your Hugo site. You can find sample
├── blog # Blog Section
│ ├── post1 # Post 1
│ ├── post2 # Post 2
│ └── _index
│ └── _index
└── ...

## Configure your site
Expand All @@ -79,14 +80,41 @@ From `exampleSite/`, copy `config.toml` to the root folder of your Hugo site and

Menu in Blist theme is pre-set to have all section names. You can include custom links in header using the `menu.main` option config.toml.

## Darkmode

`[params.darkModeToggle]` enables the dark mode toggle in header. The preference is then saved so that the mode is automatically chosen for return visits.

## Search

`[params.enableSearch]` option is used to enable search option in the theme.

- Adds the search icon in header
- Generates the search index
- Uses fuse.js to enable searching through content

In order to search, you can either click on the search icon from header or press `Ctrl/Cmd + /` key combination.

**Note:**

Make sure to enable JSON in outputs array.

```
[outputs]
home = ["HTML", "RSS", "JSON"]
```

### Latex

Enable Mathematical options: set `math: true` in your markdown frontmatter

## Google Analytics
### Google Analytics

Set `googleAnalytics` in `config.toml` to activate Hugo's [internal Google Analytics template](https://gohugo.io/templates/internal/#google-analytics).

## Performance

[![Pagespeed Insights Performance](https://github.com/apvarun/blist-hugo-theme/raw/main/images/pagespeed-performance.png)](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fblist.vercel.app&tab=mobile)

## Issues

If you have a question, please [open an issue](https://github.com/apvarun/blist-hugo-theme/issues) for help and to help those who come after you. The more information you can provide, the better!
Expand Down
1 change: 1 addition & 0 deletions assets/css/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
themeDir + "content/**/*.html",
"layouts/**/*.html",
"content/**/*.html",
"assets/js/search.js",
"exampleSite/layouts/**/*.html",
"exampleSite/content/**/*.html",
],
Expand Down
9 changes: 9 additions & 0 deletions assets/js/fuse.min.js

Large diffs are not rendered by default.

170 changes: 170 additions & 0 deletions assets/js/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Credits to search implementation: https://gist.github.com/cmod/5410eae147e4318164258742dd053993

var fuse; // holds our search engine
var searchVisible = false;
var firstRun = true; // allow us to delay loading json data unless search activated
var list = document.querySelector('.search-list'); // targets the <ul>
var first = list.firstChild; // first child of search list
var last = list.lastChild; // last child of search list
var maininput = document.querySelector('.search-ui input'); // input box for search
var searchResultsHeading = document.querySelector('.search-results'); // input box for search
var noResults = document.querySelector('.no-results'); // input box for search
var resultsAvailable = false; // Did we get any search results?

// ==========================================
// The main keyboard event listener running the show
//
document.querySelector('.open-search').addEventListener('click', openSearch);
document.querySelector('.close-search').addEventListener('click', closeSearch);

function closeSearch() {
document.querySelector('.search-ui').classList.add("hidden");
document.activeElement.blur(); // remove focus from search box
searchVisible = false; // search not visible
searchResultsHeading.classList.add('hidden');
}

function openSearch() {
// Load json search index if first time invoking search
// Means we don't load json unless searches are going to happen; keep user payload small unless needed
if (firstRun) {
loadSearch(); // loads our json data and builds fuse.js search index
firstRun = false; // let's never do this again
}

// Toggle visibility of search box
if (!searchVisible) {
document.querySelector('.search-ui').classList.remove("hidden");
document.querySelector('.search-ui input').focus(); // put focus in input box so you can just start typing
searchVisible = true; // search visible
}
}

document.addEventListener('keydown', function (event) {

if (event.metaKey && event.which === 191) {
openSearch()
}

// Allow ESC (27) to close search box
if (event.keyCode == 27) {
if (searchVisible) {
document.querySelector('.search-ui').classList.add("hidden");
document.activeElement.blur();
searchVisible = false;
searchResultsHeading.classList.add('hidden');
}
}

// DOWN (40) arrow
if (event.keyCode == 40) {
if (searchVisible && resultsAvailable) {
console.log("down");
event.preventDefault(); // stop window from scrolling
if (document.activeElement == maininput) { first.focus(); } // if the currently focused element is the main input --> focus the first <li>
else if (document.activeElement == last) { last.focus(); } // if we're at the bottom, stay there
else { document.activeElement.parentElement.nextSibling.firstElementChild.focus(); } // otherwise select the next search result
}
}

// UP (38) arrow
if (event.keyCode == 38) {
if (searchVisible && resultsAvailable) {
event.preventDefault(); // stop window from scrolling
if (document.activeElement == maininput) { maininput.focus(); } // If we're in the input box, do nothing
else if (document.activeElement == first) { maininput.focus(); } // If we're at the first item, go to input box
else { document.activeElement.parentElement.previousSibling.firstElementChild.focus(); } // Otherwise, select the search result above the current active one
}
}
})


// ==========================================
// execute search as each character is typed
//
document.querySelector('.search-ui input').onkeyup = function (e) {
executeSearch(this.value);
}


// ==========================================
// fetch some json without jquery
//
function fetchJSONFile(path, callback) {
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
var data = JSON.parse(httpRequest.responseText);
if (callback) callback(data);
}
}
};
httpRequest.open('GET', path);
httpRequest.send();
}


// ==========================================
// load our search index, only executed once
// on first call of search box (CMD-/)
//
function loadSearch() {
fetchJSONFile('/index.json', function (data) {

var options = { // fuse.js options; check fuse.js website for details
shouldSort: true,
location: 0,
distance: 100,
threshold: 0.4,
minMatchCharLength: 2,
keys: [
'title',
'permalink',
'contents'
]
};
fuse = new Fuse(data, options); // build the index from the json file
});
}


// ==========================================
// using the index we loaded on CMD-/, run
// a search query (for "term") every time a letter is typed
// in the search box
//
function executeSearch(term) {
let results = fuse.search(term); // the actual query being run using fuse.js
let searchitems = ''; // our results bucket

if (results.length === 0) { // no results based on what was typed into the input box
resultsAvailable = false;
searchitems = '';
if (term !== "") {
noResults.classList.remove('hidden')
} else {
noResults.classList.add('hidden')
}
} else { // build our html
noResults.classList.add('hidden')
if (term !== "") {
searchResultsHeading.classList.remove('hidden');
}

for (let item in results.slice(0, 5)) { // only show first 5 results
const title = '<div class="text-2xl mb-2 font-bold">' + results[item].item.title + '</div>';
const date = results[item].item.date ? '<div><em class="">' + new Date(results[item].item.date).toDateString() + '</em></div>' : '';
const contents = '<div>' + results[item].item.contents + '</div>';

searchitems = searchitems + '<li><a class="block mb-2 px-4 py-2 rounded pb-2 border-b border-gray-200 dark:border-gray-600 focus:bg-gray-100 dark:focus:bg-gray-700 focus:outline-none" href="' + results[item].item.permalink + '" tabindex="0">' + title + date + contents + '</a></li>';
}
resultsAvailable = true;
}

list.innerHTML = searchitems;
if (results.length > 0) {
first = list.firstChild.firstElementChild; // first result container — used for checking against keyboard up/down location
last = list.lastChild.firstElementChild; // last result container — used for checking against keyboard up/down location
}
}
8 changes: 7 additions & 1 deletion exampleSite/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ baseurl = "https://blist.vercel.app"
title = "Blist Theme"

[params]
name = "Blist Theme"
description = "Modern blog theme for your Hugo site."
name = "Blist Theme"

# Enable the darkmode toggle in header
darkModeToggle = true

# Enable search in header
enableSearch = true

# Custom copyright - optional
copyright = "Copyright © 2021 - Katheryn Fox · All rights reserved"
Expand All @@ -35,6 +38,9 @@ title = "Blist Theme"
[build]
writeStats = true

[outputs]
home = ["HTML", "RSS", "JSON"]

# syntax highlight settings
[markup]
[markup.highlight]
Expand Down
Binary file added images/pagespeed-performance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion layouts/_default/baseof.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode }}">
{{- partial "head.html" . -}}
<body class="dark:bg-gray-800 dark:text-white">
<body class="dark:bg-gray-800 dark:text-white relative">
{{- partial "header.html" . -}}
<main>
{{- block "main" . }}{{- end }}
Expand Down
5 changes: 5 additions & 0 deletions layouts/_default/index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{- $.Scratch.Add "index" slice -}}
{{- range .Site.RegularPages -}}
{{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "date" .Params.Lastmod "categories" .Params.categories "contents" .Summary "permalink" .Permalink) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}
2 changes: 1 addition & 1 deletion layouts/partials/blog-card.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<a class="p-2" href="{{ .Permalink }}">
{{ if .Params.thumbnail }}
<div class="relative">
<img src="{{ .Params.thumbnail }}" class="rounded-lg shadow-sm w-full h-52 object-cover" />
<img src="{{ .Params.thumbnail }}" alt="{{ .Params.title }}" class="rounded-lg shadow-sm w-full h-52 object-cover" />
<div class="absolute top-4 right-4 rounded shadow bg-white text-gray-900 dark:bg-gray-900 dark:text-white text-sm px-2 py-0.5">
{{ .Params.date.Format "Jan 2, 06" }}
</div>
Expand Down
4 changes: 4 additions & 0 deletions layouts/partials/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
</span>
</footer>

{{ if .Site.Params.enableSearch }}
{{- partial "search-ui.html" . -}}
{{ end }}

{{ template "_internal/google_analytics_async.html" . }}

{{ if .Site.Params.darkModeToggle }}
Expand Down
4 changes: 1 addition & 3 deletions layouts/partials/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
<title>
{{ block "title" . }} {{- .Title }} - {{ .Site.Title -}} {{ end }}
</title>
{{- if .Description }}
<meta name="description" content="{{ .Description }}" />
{{ end -}}
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end -}}" />
{{- if .Keywords }}
<meta name="keywords" content="{{ delimit .Keywords "," }}" />
{{ end -}}
Expand Down
15 changes: 14 additions & 1 deletion layouts/partials/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
{{ end }}

{{ if .Site.Params.enableSearch }}
<li class="grid place-items-center">
<span class="open-search inline-block cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="10" cy="10" r="7" />
<line x1="21" y1="21" x2="15" y2="15" />
</svg>
</span>
</li>
{{ end }}

{{ if .Site.Params.darkModeToggle }}
<li class="grid place-items-center">
<span class="toggle-dark-mode inline-block cursor-pointer">
Expand All @@ -32,4 +45,4 @@
</li>
{{ end }}
</ul>
</header>
</header>
2 changes: 1 addition & 1 deletion layouts/partials/intro.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<h1 class="text-4xl font-bold mb-4">{{ .Site.Params.homepage.title }}</h1>
<p class="font-light text-lg">{{ .Site.Params.homepage.description }}</p>
</div>
<img class="rounded-lg shadow-sm" src="{{ .Site.Params.homepage.photo }}" />
<img class="rounded-lg shadow-sm" src="{{ .Site.Params.homepage.photo }}" alt="{{ .Site.Title }}" />
</div>
</section>
Loading

0 comments on commit a103e6b

Please sign in to comment.