ssimp
A mod of Super Search originally written by Kushagra Gour and implemented into the Minimal Mistakes Jekyll theme.
An Alternative Website Search Mechanism
Having the need for a local website-based search simplified for the average personal website, the super-search implementation project was created.
Super-Search
Author: Kushagra Gour (https://kushagragour.dev)
MIT Licensed
Mods in SS JS : BrightHaven (https://brighthaven.net)
Mod Date : 2019-02-24
Mod Name : ssimp (super-search implementation)
Mod Ver : 1.0.0
Mod Purpose : Provide Minimal Mistakes Theme with an RSS based search alternative
Mod Changelog :
Starting with Kushagra’s Super-Search script as a base for RSS scraping
functionality the following has been changed/added
- change queryselector names to bring in line with Minimal Mistakes
- add handleinput function call to init
- add currentinput check, and give “ “ value if first run
- remove less than three input length check
(behavior change)
instead of blank page, display results of “ “ (result is all items shown)
- change searchresults output html to bring in line with Minimal Mistakes
- add description to searchresults output
- remove date from searchresults output
- remove ‘/’ key event listener and assignment
- change ESC key assignment function to handle the closing of search toggle
- add site-page scroll-to-top function to ESC key assignment function
- rename js file to bring in line with this Jekyll search plugin
- add functions for searchbar toggle handling
- add functions to properly handle hash url fragment links when already on page
- remove previous match check from handleinput function
- add very basic approach of match on Up lo cases and highlight via html span text
- add check for at least 3 chars of input before highlighting takes place
Download ssimp-1.0.0.tar
Context
Originally implemented with Minimal Mistakes version 4.14.1
Files to Alter
First, make sure your _config.yml file has search enabled, ssimp as it’s provider and the feed gem specified. The sections should look something like this:
_config.yml
search : true
search_provider : "ssimp"
# Plugins
theme: minimal-mistakes-jekyll
gems:
- jekyll-target-blank
- jekyll-paginate
- jekyll-sitemap
- jekyll-gist
- jekyll-feed
- jekyll-responsive-image
- jemoji
# mimic GitHub Pages with --safe
whitelist:
- jekyll-paginate
- jekyll-sitemap
- jekyll-gist
- jekyll-feed
- jemoji
Next create a feed.xml file in the root of your project folder with the following contents:
feed.xml
---
layout: null
hidden_pages:
- '/feed.xml'
- '/404.html'
- '/sitemap.xml'
- '/robots.txt'
- '/assets/css/main.css'
- '/assets/js/lunr/lunr-en.js'
- '/assets/js/lunr/lunr-gr.js'
- '/assets/js/lunr/lunr-store.js'
---
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ site.title | xml_escape }}</title>
<description>{{ site.description | xml_escape }}</description>
<link>{{ site.url }}{{ site.baseurl }}/</link>
<atom:link href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}" rel="self" type="application/rss+xml"/>
<pubDate>{{ site.time | date_to_rfc822 }}</pubDate>
<lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>{% for post in site.posts limit:10 %}
<item>
<title>{{ post.title | xml_escape }}</title>
<description>{{ post.content | xml_escape }}</description>
<pubDate>{{ post.date | date_to_rfc822 }}</pubDate>
<link>{{ post.url | prepend: site.baseurl | prepend: site.url }}</link>
<guid isPermaLink="true">{{ post.url | prepend: site.baseurl | prepend: site.url }}</guid>
{% for tag in post.tags %}
<category>{{ tag | xml_escape }}</category>
{% endfor %}
{% for cat in post.categories %}
<category>{{ cat | xml_escape }}</category>
{% endfor %}
</item>{% endfor %}{% for pg in site.pages %}{% if page.hidden_pages contains pg.url %}{% continue %}{% endif %}
<item>
<title>{{ pg.title | markdownify | strip_html | strip_newlines | xml_escape }}</title>
<description>{{ pg.content | strip_html | strip_newlines | truncatewords: 55 | xml_escape }}</description>
<link>{{ pg.url | prepend: site.url }}</link>
</item>{% capture my_toc %}{% endcapture %}{% assign minHeader = include.h_min | default: 1 %}{% assign maxHeader = include.h_max | default: 6 %}{% assign nodes = pg.content | split: '<h' %}{% assign firstHeader = true %}{% for node in nodes %}{% if node == "" %}{% continue %}{% endif %}{% assign headerLevel = node | replace: '"', '' | slice: 0, 1 | times: 1 %}{% if headerLevel < minHeader or headerLevel > maxHeader %}{% continue %}{% endif %}{% if firstHeader %}{% assign firstHeader = false %}{% assign minHeader = headerLevel %}{% endif %}{% assign indentAmount = headerLevel | minus: minHeader | add: 1 %}{% assign titleHdrEnd = 'h' %}{% capture titleHdrEnd %}{{ titleHdrEnd }}{{ headerLevel }}{{ '>' }}{%endcapture %}{% assign _paragraph = node | split: titleHdrEnd %}{% assign idParagraph = _paragraph[1] %}{% assign _workspace = node | split: '</h' %}{% assign _idWorkspace = _workspace[0] | split: 'id="' %}{% assign _idWorkspace = _idWorkspace[1] | split: '"' %}{% assign html_id = _idWorkspace[0] %}{% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}{% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}{% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
<item>
<title>{{ header | markdownify | strip_html | strip_newlines | xml_escape }}</title>
<description>{{ idParagraph | strip_html | strip_newlines | truncatewords: 55 | xml_escape }}</description>
<link>{{ html_id | prepend: '#' | prepend: pg.url | prepend: site.url | xml_escape }}</link>
</item>{% endfor %}{% endfor %}
</channel>
</rss>
Create Local Copies
assets/js/ssimp.js
Place the main ssimp.js file in your local assets/js/ folder with the following contents:
/* super-search
Author: Kushagra Gour (https://kushagragour.dev)
MIT Licensed
Mods in SS JS : BrightHaven (https://brighthaven.net)
Mod Date : 2019-02-24
Mod Name : ssimp (super-search implementation)
Mod Ver : 1.0.0
Mod Purpose : Provide Minimal Mistakes Theme with an RSS based search alternative
Mod Changelog : Starting with Kushagra's Super-Search script as a base for RSS scraping
functionality the following has been changed/added
- change queryselector names to bring in line with Minimal Mistakes
- add handleinput function call to init
- add currentinput check, and give " " value if first run
- remove less than three input length check
(behavior change)
instead of blank page, display results of " " (result is all items shown)
- change searchresults output html to bring in line with Minimal Mistakes
- add description to searchresults output
- remove date from searchresults output
- remove '/' key event listener and assignment
- change ESC key assignment function to handle the closing of search toggle
- add site-page scroll-to-top function to ESC key assignment function
- rename js file to bring in line with this Jekyll search plugin
- add functions for searchbar toggle handling
- add functions to properly handle hash url fragment links when already on page
*/
function scrollToTheTop() {
// without animation
//$('html,body').scrollTop(0);
// with animation
$('html, body').animate({ scrollTop: 0 }, 'fast');
}
function searchBar__toggle() {
$(".search-content").toggleClass("is--visible");
$(".initial-content").toggleClass("is--hidden");
}
function scrollToHashElement(ele) {
$(window).scrollTop(ele.offset().top).scrollLeft(ele.offset().left);
}
function codeForHashLinkClicks() {
// When clicking a link check if it has a hash url fragment
// then let the browser know it can toggle the search bar away
// and scroll to the hash element if it happens to be that the
// destination page is the page the browser is already on
// -- MM note: mainly when using search__form instead of search page
$("a").click(function () {
var searchContent = document.getElementById('search-content');
var hash = this.hash;
if (hash != "" || hash!= null) {
if (searchContent.classList.contains('is--visible')) {
searchBar__toggle();
scrollToHashElement(hash);
}
}
});
}
// --MM note: added xtra functionality within original Jquery function (Search toggle with page scroll)
$(".searchbar__toggle").on("click", function() {
searchBar__toggle();
// set focus on input
setTimeout(function() {
$(".search-content input").focus();
}, 400);
scrollToTheTop();
var searchContent = document.getElementById('search-content');
if (searchContent.classList.contains('is--visible')) {
codeForHashLinkClicks();
} else {
$("a").unbind('click');
}
});
(function () {
var searchFile = '/feed.xml',
searchBar,
searchInputEl,
searchResultsEl,
currentInputValue = '',
lastSearchResultHash,
posts = [];
// Changes XML to JSON
// Modified version from here: http://davidwalsh.name/convert-xml-json
function xmlToJson(xml) {
// Create the return object
var obj = {};
if (xml.nodeType == 3) { // text
obj = xml.nodeValue;
}
// do children
// If all text nodes inside, get concatenated text from them.
var textNodes = [].slice.call(xml.childNodes).filter(function (node) { return node.nodeType === 3; });
if (xml.hasChildNodes() && xml.childNodes.length === textNodes.length) {
obj = [].slice.call(xml.childNodes).reduce(function (text, node) { return text + node.nodeValue; }, '');
}
else if (xml.hasChildNodes()) {
for(var i = 0; i < xml.childNodes.length; i++) {
var item = xml.childNodes.item(i);
var nodeName = item.nodeName;
if (typeof(obj[nodeName]) == "undefined") {
obj[nodeName] = xmlToJson(item);
} else {
if (typeof(obj[nodeName].push) == "undefined") {
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(xmlToJson(item));
}
}
}
return obj;
}
function getPostsFromXml(xml) {
var json = xmlToJson(xml);
// Atom 1.0 format
if (json.entry && json.entry instanceof Array) {
return json.entry;
}
// Atom 2.0 format
else {
return json.channel.item;
}
}
window.closeSearchBackToTop = function closeSearchBackToTop() {
if (searchBar.classList.contains('is--visible')) {
searchBar__toggle();
$(".search-content input").blur();
}
scrollToTheTop();
}
function handleInput() {
var currentResultHash;
currentInputValue = (searchInputEl.value + '').toLowerCase();
// first run check
if (!currentInputValue) {
currentInputValue = ' ';
}
searchResultsEl.style.offsetWidth;
var matchingPosts = posts.filter(function (post) {
// Search `description` and `content` both to support 1.0 and 2.0 formats.
if ((post.title + '').toLowerCase().indexOf(currentInputValue) !== -1 || ((post.description || post.content) + '').toLowerCase().indexOf(currentInputValue) !== -1) {
return true;
}
});
if (!matchingPosts.length) {
searchResultsEl.classList.add('is-hidden');
}
currentResultHash = matchingPosts.reduce(function(hash, post) { return post.title + hash; }, '');
if (matchingPosts.length && currentResultHash !== lastSearchResultHash) {
searchResultsEl.classList.remove('is-hidden');
searchResultsEl.innerHTML = matchingPosts.map(function (post) {
return '<li><h2><a href="' + post.link + '">' + post.title + '</a></h2><p>' + post.description + '</p></li>';
}).join('');
}
lastSearchResultHash = currentResultHash;
}
function init(options) {
searchFile = options.searchFile || searchFile;
searchInputEl = document.querySelector(options.inputSelector || '#search');
searchResultsEl = document.querySelector(options.resultsSelector || '#result__items');
searchBar = document.querySelector(options.searchSelector || '#search-content');
var xmlhttp=new XMLHttpRequest();
xmlhttp.open('GET', searchFile);
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState != 4) return;
if (xmlhttp.status != 200 && xmlhttp.status != 304) { return; }
var node = (new DOMParser).parseFromString(xmlhttp.responseText, 'text/xml');
node = node.children[0];
posts = getPostsFromXml(node);
}
xmlhttp.send();
$(document).ready(function(){
// First run to populate page
handleInput();
});
// ESC key actions
window.addEventListener('keyup', function onKeyPress(e) {
if (e.which === 27) {
closeSearchBackToTop();
}
});
searchInputEl.addEventListener('input', function onInputChange() {
handleInput();
codeForHashLinkClicks();
});
}
init.keyESC = closeSearchBackToTop;
window.ssimp = init;
})();
_sass/minimal-mistakes/_ssimp.scss
Next place a _ssimp.scss in your _sass/minimal-mistakes/ folder with the following contents:
/* ==========================================================================
SSIMP
========================================================================== */
.results {
padding: 0;
h2 {
margin-top: 0;
}
p {
margin-bottom: 0;
}
.result__items {
text-align: left;
list-style: none;
padding: 0;
overflow-x: hidden;
height: calc(100% - 250px);
transition: 0.2s ease;
}
.result__items.is-hidden {
opacity: 0;
transform: translateY(-1vh);
}
}
_includes/search/ssimp-search-scripts.html
As well as a ssimp-search-scripts.html in your _includes/search/ folder with the following contents:
{% assign lang = site.locale | slice: 0,2 | default: "en" %}
{% case lang %}
{% when "gr" %}
{% assign lang = "gr" %}
{% else %}
{% assign lang = "en" %}
{% endcase %}
<script src="{{ '/assets/js/ssimp.js'| relative_url }}"></script>
<script>ssimp({ searchFile: "{{ '/feed.xml' | relative_url }}" });</script>
Next you’ll need to patch some of the files from the Minimal Mistakes theme. Check your Jekyll website project folder for the following files that need to be updated. If you do not yet have the file locally simply make a copy of the original from the theme folder into the corresponding folder of your project. Any file in your project overrides the original of the theme. You can easily find the themes folder location by issuing:
cd `bundle show minimal-mistakes`
Patch The Files
Apply the patches to each file. This is done with the patch command. Something like:
patch -p0 original-file diff-file
_includes/search/search_form.html diff
--- /Git/minimal-mistakes-jekyll-4.14.1/_includes/search/search_form.html 2019-01-09 17:49:30.634101490 -0600
+++ /Git/project/ssimp/_includes/search/search_form.html 2019-03-13 17:48:58.233317141 -0600
@@ -14,6 +14,11 @@
{%- when "algolia" -%}
<div class="search-searchbar"></div>
<div class="search-hits"></div>
+ {%- when "ssimp" -%}
+ <input type="text" id="search" class="search-input" tabindex="-1" placeholder="{{ site.data.ui-text[site.locale].search_placeholder_text | default: 'Enter your search term...' }}">
+ <div class="results" id="results">
+ <ul class="result__items" id="result__items"></ul>
+ </div>
{%- endcase -%}
</div>
_includes/head/custom.html diff
--- /Git/minimal-mistakes-jekyll-4.14.1/_includes/head/custom.html 2019-01-14 12:52:25.942011000 -0500
+++ /Git/project/ssimp/_includes/head/custom.html 2019-03-13 16:31:45.668634532 -0600
@@ -1,5 +1,5 @@
<!-- start custom head snippets -->
-
+<link rel="alternate" type="application/rss+xml" title="YourSiteName RSS" href="/feed.xml">
<!-- insert favicons. use https://realfavicongenerator.net/ -->
<!-- end custom head snippets -->
_includes/masthead.html diff
--- /Git/minimal-mistakes-jekyll-4.14.1/_includes/masthead.html 2019-01-14 12:52:25.942011000 -0400
+++ /Git/project/ssimp/_includes/masthead.html 2019-03-13 16:53:53.603683522 -0500
@@ -17,6 +17,7 @@
</ul>
{% if site.search == true %}
<button class="search__toggle" type="button">
+ <span class="visually-hidden">{{ site.data.ui-text[site.locale].menu_label | default: "Toggle search" }}</span>
<svg class="icon" width="16" height="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.99 16">
<path d="M15.5,13.12L13.19,10.8a1.69,1.69,0,0,0-1.28-.55l-0.06-.06A6.5,6.5,0,0,0,5.77,0,6.5,6.5,0,0,0,2.46,11.59a6.47,6.47,0,0,0,7.74.26l0.05,0.05a1.65,1.65,0,0,0,.5,1.24l2.38,2.38A1.68,1.68,0,0,0,15.5,13.12ZM6.4,2A4.41,4.41,0,1,1,2,6.4,4.43,4.43,0,0,1,6.4,2Z" transform="translate(-.01)"></path>
</svg>
_includes/scripts.html diff
--- /Git/minimal-mistakes-jekyll-4.14.1/_includes/scripts.html 2019-01-14 12:52:25.942011000 -0400
+++ /Git/project/ssimp/_includes/scripts.html 2019-03-13 17:40:22.344635871 -0500
@@ -21,6 +21,8 @@
{% include_cached search/google-search-scripts.html %}
{%- when "algolia" -%}
{% include_cached search/algolia-search-scripts.html %}
+ {%- when "ssimp" -%}
+ {% include_cached search/ssimp-search-scripts.html %}
{%- endcase -%}
{% endif %}
_layouts/default.html diff
--- /Git/minimal-mistakes-jekyll-4.14.1/_layouts/default.html 2019-01-14 12:52:25.942011000 -0400
+++ /Git/project/ssimp/_layouts/default.html 2019-03-13 16:28:37.625296481 -0500
@@ -24,7 +24,7 @@
</div>
{% if site.search == true %}
- <div class="search-content">
+ <div id="search-content" class="search-content">
{% include_cached search/search_form.html %}
</div>
{% endif %}
_layouts/search.html diff
--- /Git/minimal-mistakes-jekyll-4.14.1/_layouts/search.html 2019-01-09 17:52:50.250670872 -0400
+++ /Git/project/ssimp/_layputs/search.html 2019-03-13 17:53:09.039081184 -0500
@@ -37,6 +37,11 @@
{%- when "algolia" -%}
<div class="search-searchbar"></div>
<div class="search-hits"></div>
+ {%- when "ssimp" -%}
+ <input type="text" id="search" class="search-input" tabindex="-1" placeholder="{{ site.data.ui-text[site.locale].search_placeholder_text | default: 'Enter your search term...' }}">
+ <div class="results" id="results">
+ <ul class="result__items" id="result__items"></ul>
+ </div>
{%- endcase -%}
</div>
</div>
_sass/minimal_mistakes.scss diff
--- /Git/minimal-mistakes-jekyll-4.14.1/_sass/minimal-mistakes.scss 2019-01-14 12:52:25.942011000 -0400
+++ /Git/project/ssimp/_sass/minimal-mistakes.scss 2019-03-13 18:23:08.548814235 -0500
@@ -28,6 +28,7 @@
@import "minimal-mistakes/navigation";
@import "minimal-mistakes/footer";
@import "minimal-mistakes/search";
+@import "minimal-mistakes/ssimp";
@import "minimal-mistakes/syntax";
/* Utility classes */