Browse Source

add search page

master
Cian Butler 3 years ago
parent
commit
e2a02dcd70
Signed by untrusted user: butlerx GPG Key ID: B37CA765BAA89170
8 changed files with 205 additions and 2 deletions
  1. +4
    -0
      config.toml
  2. +6
    -0
      content/search.md
  3. +5
    -0
      layouts/_default/index.json
  4. +29
    -0
      layouts/_default/search.html
  5. +2
    -1
      layouts/partials/header.html
  6. +1
    -1
      layouts/partials/hero.html
  7. +28
    -0
      static/css/style.css
  8. +130
    -0
      static/js/search.js

+ 4
- 0
config.toml View File

@@ -3,5 +3,9 @@ languageCode = "en-us"
title = "Redbrick Admin Blog"
author = "Redbrick Admin"
pygmentsCodeFences = true
[outputs]
home = ["HTML", "RSS", "JSON"]

[params]
hero = "root@redbrick"
message = "FIX IT!!! FIX IT !!! FIX ITTTTTT!!!!!!!!!"

+ 6
- 0
content/search.md View File

@@ -0,0 +1,6 @@
---
title: "Search"
sitemap:
priority : 0.1
layout: "search"
---

+ 5
- 0
layouts/_default/index.json View File

@@ -0,0 +1,5 @@
{{- $.Scratch.Add "index" slice -}}
{{- range .Site.RegularPages -}}
{{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "categories" .Params.categories "contents" .Plain "permalink" .Permalink "author" .Params.author "dates" .Params.date) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}

+ 29
- 0
layouts/_default/search.html View File

@@ -0,0 +1,29 @@
{{ define "main" }}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.0/fuse.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/jquery.mark.min.js"></script>
<script src="{{ "js/search.js" | absURL }}"></script>
<main role="main" class="article-list">
<span class="search">
<h1 class="list-title">{{ .Title }}</h1>
<form action="{{ "search" | absURL }}">
<input id="search-query" name="s" class="search-box"/>
</form>
</span>
<h1 class="list-title">Matching pages</h1>
<div id="search-results" class="article-list"></div>
</main>
<!-- this template is sucked in by search.js and appended to the search-results div above. So editing here will adjust style -->
<script id="search-result-template" type="text/x-js-template">
<article id="summary-${key}" class="list-item" itemscope itemtype="http://schema.org/Blog">
<h2 class="headline" itemprop="headline"><a href="${link}">${title}</a></h2>
<div class="meta">
${ isset tags }<br><span class="key">Tags:</span><span class="val"> ${tags}</span>${ end }
${ isset categories }<br><span class="key">Categories:</span><span class="val"> ${categories}</span>${ end }
${ isset date }<br><span class="key">published on:</span><span class="val"> <time itemprop="datePublished" datetime="${ date }">${dateFmt}</time></span>${ end }
${ isset author }<br><span class="key">author:</span> <span class="val">${author}</span>${ end }
</div>
<section class="summary">${snippet}</section>
</article>
</script>
{{ end }}

+ 2
- 1
layouts/partials/header.html View File

@@ -1,12 +1,13 @@
<header>
<div class="container clearfix">
<a class="path" href="{{ .Site.BaseURL }}">[{{ .Site.Title }}]</a>
<span class="caret"># _</span>
<span class="caret">#_</span>
<div class="right">
{{ range $i, $page := (where .Site.Pages "Section" "pages") }}
{{ if not (eq $i 0) }}|{{ end }}
<a class="path" href="{{ $page.RelPermalink }}">{{ $page.Title }}</a>
{{ end }}
<a class="path" href="{{ .Site.BaseURL }}/search">/Search</a>
</div>
</div>
</header>

+ 1
- 1
layouts/partials/hero.html View File

@@ -14,5 +14,5 @@
</div>

<div style="text-align: center;">
FIX IT!!! FIX IT !!! FIX ITTTTTT!!!!!!!!
{{- .Site.Params.message -}}
</div>

+ 28
- 0
static/css/style.css View File

@@ -80,6 +80,34 @@ div.clearfix {
}
}

.search {
display: flex;
justify-content: space-between;
}

.search.form {
margin-top: 1em;
margin-bottom: 0.5em;
font-size: 3em;
}
.search-box {
-moz-border-radius: 5px;
-moz-transition: background 0.55s ease;
-ms-transition: background 0.55s ease;
-o-transition: background 0.55s ease;
-webkit-border-radius: 5px;
-webkit-transition: background 0.55s ease;
border-radius: 5px;
border: none;
color: #262626;
font-size: 10pt;
height: 50px;
margin-top: 3em;
padding-left: 45px;
transition: background 0.55s ease;
width: 300px;
}

article.single section,
.article-list article {
background-color: #f2f0ec;


+ 130
- 0
static/js/search.js View File

@@ -0,0 +1,130 @@
const summaryInclude = 60;
const fuseOptions = {
shouldSort: true,
includeMatches: true,
threshold: 0.0,
tokenize: true,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [
{ name: 'title', weight: 0.8 },
{ name: 'contents', weight: 0.5 },
{ name: 'tags', weight: 0.3 },
{ name: 'categories', weight: 0.3 },
],
};

const searchQuery = param('s');
if (searchQuery) {
$('#search-query').val(searchQuery);
executeSearch(searchQuery);
} else {
$('#search-results').append('<p>Please enter a word or phrase above</p>');
}

function executeSearch(searchQuery) {
$.getJSON('/index.json', data => {
const pages = data;
const fuse = new Fuse(pages, fuseOptions);
const result = fuse.search(searchQuery);
if (result.length > 0) {
populateResults(result);
} else {
$('#search-results').append('<p>No matches found</p>');
}
});
}

function populateResults(result) {
$.each(result, (key, value) => {
const contents = value.item.contents;
let snippet = '';
const snippetHighlights = [];
if (fuseOptions.tokenize) {
snippetHighlights.push(searchQuery);
} else {
$.each(value.matches, (matchKey, mvalue) => {
if (mvalue.key === 'tags' || mvalue.key === 'categories') {
snippetHighlights.push(mvalue.value);
} else if (mvalue.key === 'contents') {
const start =
mvalue.indices[0][0] - summaryInclude > 0 ? mvalue.indices[0][0] - summaryInclude : 0;
const end =
mvalue.indices[0][1] + summaryInclude < contents.length
? mvalue.indices[0][1] + summaryInclude
: contents.length;
snippet += contents.substring(start, end);
snippetHighlights.push(
mvalue.value.substring(
mvalue.indices[0][0],
mvalue.indices[0][1] - mvalue.indices[0][0] + 1,
),
);
}
});
}

if (snippet.length < 1) {
snippet += contents.substring(0, summaryInclude * 2);
}
// pull template from hugo templarte definition
const templateDefinition = $('#search-result-template').html();
// replace values
const output = render(templateDefinition, {
key,
title: value.item.title,
link: value.item.permalink,
tags: value.item.tags,
author: value.item.author,
date: value.item.dates,
categories: value.item.categories,
snippet,
});
$('#search-results').append(output);

$.each(snippetHighlights, (snipkey, snipvalue) => {
$(`#summary-${key}`).mark(snipvalue);
});
});
}

function param(name) {
return decodeURIComponent((location.search.split(`${name}=`)[1] || '').split('&')[0]).replace(
/\+/g,
' ',
);
}

function render(templateString, data) {
let conditionalMatches;
const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g;
// since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop
let copy = templateString;
while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
if (data[conditionalMatches[1]]) {
// valid key, remove conditionals, leave contents.
copy = copy.replace(conditionalMatches[0], conditionalMatches[2]);
} else {
// not valid, remove entire section
copy = copy.replace(conditionalMatches[0], '');
}
}
for (const key in data) {
copy = copy.replace(new RegExp(`\\$\\{\\s*${key}\\s*\\}`, 'g'), data[key]);
if (key === 'date') {
copy = copy.replace(
new RegExp('\\$\\{\\s*dateFmt\\s*\\}', 'g'),
new Date(data[key]).toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}),
data[key],
);
}
}
return copy;
}

Loading…
Cancel
Save