add search functionality in /posts page
This commit is contained in:
parent
6c2cca0127
commit
4e7f74bcf0
6 changed files with 250 additions and 5 deletions
|
@ -77,3 +77,12 @@ params:
|
|||
- I am a Developer
|
||||
- I work with Go
|
||||
- I love to work with some fun projects
|
||||
|
||||
outputs:
|
||||
home:
|
||||
- HTML
|
||||
- JSON
|
||||
- RSS
|
||||
page:
|
||||
- HTML
|
||||
- RSS
|
5
layouts/_default/index.json
Normal file
5
layouts/_default/index.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{- $.Scratch.Add "index" slice -}}
|
||||
{{- range .Site.RegularPages -}}
|
||||
{{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "content" .Plain "relPermalink" .RelPermalink "summary" .Summary) -}}
|
||||
{{- end -}}
|
||||
{{- $.Scratch.Get "index" | jsonify -}}
|
|
@ -29,18 +29,27 @@
|
|||
<section class="content-section" id="content-section">
|
||||
<div class="content container-fluid" id="content">
|
||||
<div class="container-fluid post-card-holder" id="post-card-holder">
|
||||
{{ $paginator := .Paginate .RegularPagesRecursive 12 }}
|
||||
{{ range $paginator.Pages }}
|
||||
{{ partial "cards/post.html" . }}
|
||||
{{ end }}
|
||||
{{ $paginator := .Paginate .RegularPagesRecursive 12 }}
|
||||
{{ range $paginator.Pages }}
|
||||
{{ partial "cards/post.html" . }}
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="paginator">
|
||||
{{ template "_internal/pagination.html" . }}
|
||||
{{ template "_internal/pagination.html" . }}
|
||||
</div>
|
||||
<div class="container-fluid post-card-holder" id="search-results-holder">
|
||||
</div>
|
||||
<div class="search-results-template" id="search-results-template">
|
||||
{{ partial "cards/search-result-post.html" . }}
|
||||
</div>
|
||||
<div class="search-results" id="search-results"></div>
|
||||
</div>
|
||||
|
||||
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
{{ define "scripts" }}
|
||||
<script src="/assets/js/list.js"></script>
|
||||
<script src="/assets/js/search.js"></script>
|
||||
{{ end }}
|
||||
|
|
21
layouts/partials/cards/search-result-post.html
Normal file
21
layouts/partials/cards/search-result-post.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<div class="post-card" id="${RelPermalink}">
|
||||
<a href="${RelPermalink}" class="post-card-link">
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<img class="card-img-top" src='{{ partial "helpers/get-hero.html" . }}'/>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">${Title}</h5>
|
||||
<p class="card-text post-summary">${Summary}</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="float-left">{{ .Date.Format "January 2, 2006" }}</span>
|
||||
<a
|
||||
href="${RelPermalink}"
|
||||
class="float-right btn btn-outline-info btn-sm"
|
||||
>Read</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
|
@ -86,6 +86,22 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
#search-results-template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.search-results {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
color: #248aaa;
|
||||
background-color: white;
|
||||
border-radius: 0.25em;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
|
185
static/assets/js/search.js
Normal file
185
static/assets/js/search.js
Normal file
|
@ -0,0 +1,185 @@
|
|||
"use strict";
|
||||
var searchFn = function () {
|
||||
var lastTerm = "You are likely to be eaten by a grue.";
|
||||
var stopwords = ["i", "me", "my", "we", "our", "you", "it",
|
||||
"its", "this", "that", "am", "is", "are", "was", "be",
|
||||
"has", "had", "do", "a", "an", "the", "but", "if", "or", "as",
|
||||
"of", "at", "by", "for", "with", "to", "then", "no", "not",
|
||||
"so", "too", "can", "and", "but"];
|
||||
var normalizer = document.createElement("textarea");
|
||||
var normalize = function (input) {
|
||||
normalizer.innerHTML = input;
|
||||
var inputDecoded = normalizer.value;
|
||||
return " " + inputDecoded.trim().toLowerCase().replace(/[^0-9a-z ]/gi, " ").replace(/\s+/g, " ") + " ";
|
||||
}
|
||||
|
||||
var limit = 30;
|
||||
var minChars = 2;
|
||||
var searching = false;
|
||||
var render = function (results) {
|
||||
results.sort(function (a, b) { return b.weight - a.weight; });
|
||||
for (var i = 0; i < results.length && i < limit; i += 1) {
|
||||
var result = results[i].item;
|
||||
var templateDefinition = $('#search-results-template').html();
|
||||
var output = renderTemplate(templateDefinition,{"Title":result.showTitle,"Summary":result.summary,"RelPermalink":result.relPermalink});
|
||||
$('#search-results-holder').append(output);
|
||||
}
|
||||
showSearchResultsAndHideCards();
|
||||
};
|
||||
|
||||
var showSearchResultsAndHideCards = function() {
|
||||
$("#search-results-holder").show();
|
||||
$("#post-card-holder ").hide();
|
||||
$("#search-results-holder .post-card").show();
|
||||
$('.paginator').hide();
|
||||
$('#search-results').show();
|
||||
}
|
||||
var showCardssAndHideSearchResults = function() {
|
||||
$("#search-results-holder").hide();
|
||||
$('#post-card-holder').show();
|
||||
$('#search-results-holder').html("");
|
||||
$('.paginator').show();
|
||||
$('#search-results').hide();
|
||||
}
|
||||
|
||||
var renderTemplate = function(templateString, data) {
|
||||
var conditionalMatches,conditionalPattern,copy;
|
||||
//now any conditionals removed we can do simple substitution
|
||||
var key, find, re;
|
||||
for (key in data) {
|
||||
find = '\\$\\{\\s*' + key + '\\s*\\}';
|
||||
re = new RegExp(find, 'g');
|
||||
templateString = templateString.replace(re, data[key]);
|
||||
}
|
||||
return templateString;
|
||||
}
|
||||
var checkTerms = function (terms, weight, target) {
|
||||
var weightResult = 0;
|
||||
terms.forEach(function (term) {
|
||||
if (~target.indexOf(term.term)) {
|
||||
var idx = target.indexOf(term.term);
|
||||
while (~idx) {
|
||||
weightResult += term.weight * weight;
|
||||
idx = target.indexOf(term.term, idx + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
return weightResult;
|
||||
};
|
||||
|
||||
var search = function (terms) {
|
||||
var results = [];
|
||||
searchHost.index.forEach(function (item) {
|
||||
if (item.tags) {
|
||||
var weight_1 = 0;
|
||||
terms.forEach(function (term) {
|
||||
if (item.title.startsWith(term.term)) {
|
||||
weight_1 += term.weight * 32;
|
||||
}
|
||||
});
|
||||
weight_1 += checkTerms(terms, 1, item.content);
|
||||
//weight_1 += checkTerms(terms, 2, item.description);
|
||||
//weight_1 += checkTerms(terms, 2, item.subtitle);
|
||||
item.tags.forEach(function (tag) {
|
||||
weight_1 += checkTerms(terms, 4, tag);
|
||||
});
|
||||
weight_1 += checkTerms(terms, 16, item.title);
|
||||
if (weight_1) {
|
||||
results.push({
|
||||
weight: weight_1,
|
||||
item: item
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (results.length) {
|
||||
var resultsMessage = results.length + " items found.";
|
||||
if (results.length > limit) {
|
||||
resultsMessage += " Showing first " + limit + " results.";
|
||||
}
|
||||
$("#search-results").html("<p>" + resultsMessage + "</p>");
|
||||
render(results);
|
||||
}
|
||||
else {
|
||||
showCardssAndHideSearchResults();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var runSearch = function () {
|
||||
if (searching) {
|
||||
return;
|
||||
}
|
||||
var term = normalize($("#search-box").val()).trim();
|
||||
if (term === lastTerm) {
|
||||
return;
|
||||
}
|
||||
lastTerm = term;
|
||||
if (term.length < minChars) {
|
||||
showCardssAndHideSearchResults();
|
||||
return;
|
||||
}
|
||||
searching = true;
|
||||
var startSearch = new Date();
|
||||
$("#search-results").html('<p>Processing search...</p>');
|
||||
var terms = term.split(" ");
|
||||
var termsTree = [];
|
||||
for (var i = 0; i < terms.length; i += 1) {
|
||||
for (var j = i; j < terms.length; j += 1) {
|
||||
var weight = Math.pow(2, j - i);
|
||||
var str = "";
|
||||
for (var k = i; k <= j; k += 1) {
|
||||
str += (terms[k] + " ");
|
||||
}
|
||||
var newTerm = str.trim();
|
||||
if (newTerm.length >= minChars && stopwords.indexOf(newTerm) < 0) {
|
||||
termsTree.push({
|
||||
weight: weight,
|
||||
term: " " + str.trim() + " "
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
search(termsTree);
|
||||
searching = false;
|
||||
var endSearch = new Date();
|
||||
$("#search-results").append("<p><small>Search took " + (endSearch - startSearch) + "ms.</small></p>");
|
||||
};
|
||||
var initSearch = function () {
|
||||
$("#search-box").keyup(function () {
|
||||
runSearch();
|
||||
});
|
||||
runSearch();
|
||||
};
|
||||
$("#search-box").hide();
|
||||
var searchHost = {};
|
||||
$.getJSON("/index.json", function (results) {
|
||||
searchHost.index = [];
|
||||
var dup = {};
|
||||
results.forEach(function (result) {
|
||||
if (result.tags) {
|
||||
var res = {};
|
||||
res.showTitle = result.title;
|
||||
res.title = normalize(result.title);
|
||||
res.subtitle = normalize(result.subtitle);
|
||||
res.summary = result.summary;
|
||||
res.content = normalize(result.content);
|
||||
res.relPermalink = result.relPermalink;
|
||||
var newTags_1 = [];
|
||||
result.tags.forEach(function (tag) {
|
||||
return newTags_1.push(normalize(tag));
|
||||
});
|
||||
res.tags = newTags_1;
|
||||
searchHost.index.push(res);
|
||||
dup[result.permalink] = true;
|
||||
}
|
||||
});
|
||||
$("#loading").hide();
|
||||
|
||||
$("#search-box").show()
|
||||
.removeAttr("disabled")
|
||||
.focus();
|
||||
initSearch();
|
||||
});
|
||||
};
|
||||
window.addEventListener("DOMContentLoaded", searchFn);
|
Loading…
Add table
Add a link
Reference in a new issue