diff options
author | Jeremy Bise <jeremy.bise@thosegeeks.com> | 2019-04-17 17:25:46 +0300 |
---|---|---|
committer | Jeremy Bise <jeremy.bise@thosegeeks.com> | 2019-04-17 17:25:46 +0300 |
commit | 2095d341178a30956e66799428d229d7e6f363ea (patch) | |
tree | 23ac5709aff0b0830bdd7bb490e3e45d3e76781f | |
parent | 83b7e483856793bcac02281d1bc554ed53a2a573 (diff) |
search generally functional
-rw-r--r-- | README.md | 24 | ||||
-rw-r--r-- | assets/scss/main.scss | 4 | ||||
-rw-r--r-- | layouts/_default/search.html | 36 | ||||
-rw-r--r-- | layouts/index.json | 5 | ||||
-rw-r--r-- | layouts/partials/footer/footer-widgets.html | 12 | ||||
-rw-r--r-- | static/js/search.js | 126 | ||||
-rw-r--r-- | static/syntax.css | 70 | ||||
-rw-r--r-- | theme.toml | 3 |
8 files changed, 202 insertions, 78 deletions
@@ -87,6 +87,25 @@ pygmentsUseClasses=true pygmentsCodefences=true ``` +## Search + +Add the JSON output format to your `config.toml` to create the index: + +```toml +[outputs] + home = ["HTML", "RSS", "JSON"] +``` + +Add `search.md` at the root of your `content` folder with the following frontmatter: + +```yaml +--- +title: "Search" +type: static +layout: search +--- +``` + ## Google Analytics Add your Google Analytics Tracking Code ID to your `config.toml`: @@ -95,6 +114,7 @@ Add your Google Analytics Tracking Code ID to your `config.toml`: The asynchronous tracking script will be included on pages on the live server, but not the dev server. -## Roadmap +## Getting Help + -- [ ] Include search via Lunr or Fuse
\ No newline at end of file +## Contributing diff --git a/assets/scss/main.scss b/assets/scss/main.scss index 02fa5a0..3391d19 100644 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -81,4 +81,8 @@ $color__border-abbr: #666; .main-navigation .main-menu>li.menu-item-has-children .submenu-expand svg { width: 24px; height: 24px; +} + +.searchPage-field { + padding: .36rem .66rem; }
\ No newline at end of file diff --git a/layouts/_default/search.html b/layouts/_default/search.html new file mode 100644 index 0000000..d2a1738 --- /dev/null +++ b/layouts/_default/search.html @@ -0,0 +1,36 @@ +{{ define "main" }} + <header class="page-header"> + <h1 class="page-title"> + Search results for: + <form action="{{ "search" | absURL }}"> + <input placeholder="Search..." id="search-query" name="s" class="searchPage-field" /> + </form> + </h1> + </header> + + <div id="search-results"></div> + + <script id="search-result-template" type="text/x-js-template"> + <div id="summary-${key}"> + <article class="post entry"> + <header class="entry-header"> + <h2 class="entry-title"><a href="${link}">${title}</a></h2> + </header> + <div class="entry-content"> + <p>${snippet}</p> + </div> + <div class="entry-footer"> + ${ isset tags }<span class="cat-links">Tags: ${tags}</span>${ end } + ${ isset categories }<p>Categories: ${categories}</p>${ end } + </div> + </article> + </div> + </script> +{{ end }} + +{{ define "footer_scripts" }} + <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> +{{ end }} diff --git a/layouts/index.json b/layouts/index.json new file mode 100644 index 0000000..c93f805 --- /dev/null +++ b/layouts/index.json @@ -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) -}} +{{- end -}} +{{- $.Scratch.Get "index" | jsonify -}} diff --git a/layouts/partials/footer/footer-widgets.html b/layouts/partials/footer/footer-widgets.html index 167f776..414a794 100644 --- a/layouts/partials/footer/footer-widgets.html +++ b/layouts/partials/footer/footer-widgets.html @@ -1,8 +1,14 @@ <aside class="widget-area" role="complementary" aria-label="Footer"> <div class="widget-column"> - <!-- <section id="search" class="widget widget_search"> - <h2 class="widget-title">Search</h2> - </section> --> + <section id="search" class="widget widget_search"> + <form role="search" class="search-form" action="/search/"> + <label> + <span class="screen-reader-text">Search for:</span> + <input type="search" class="search-field" placeholder="Search ..." value name="s"> + </label> + <input type="submit" class="search-submit" value="Search"> + </form> + </section> <section id="recent-posts" class="widget widget_recent_entries"> <h2 class="widget-title">Recent Posts</h2> diff --git a/static/js/search.js b/static/js/search.js new file mode 100644 index 0000000..d6204d5 --- /dev/null +++ b/static/js/search.js @@ -0,0 +1,126 @@ +summaryInclude = 60; +var 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 } + ] +}; + +var 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", function(data) { + var pages = data; + var fuse = new Fuse(pages, fuseOptions); + var result = fuse.search(searchQuery); + console.log({ matches: result }); + if (result.length > 0) { + populateResults(result); + } else { + $("#search-results").append("<p>No matches found</p>"); + } + }); +} + +function populateResults(result) { + $.each(result, function(key, value) { + var contents = value.item.contents; + var snippet = ""; + var snippetHighlights = []; + var tags = []; + if (fuseOptions.tokenize) { + snippetHighlights.push(searchQuery); + } else { + $.each(value.matches, function(matchKey, mvalue) { + if (mvalue.key == "tags" || mvalue.key == "categories") { + snippetHighlights.push(mvalue.value); + } else if (mvalue.key == "contents") { + start = + mvalue.indices[0][0] - summaryInclude > 0 + ? mvalue.indices[0][0] - summaryInclude + : 0; + 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 + var templateDefinition = $("#search-result-template").html(); + //replace values + var output = render(templateDefinition, { + key: key, + title: value.item.title, + link: value.item.permalink, + tags: value.item.tags, + categories: value.item.categories, + snippet: snippet + }); + $("#search-results").append(output); + + $.each(snippetHighlights, function(snipkey, snipvalue) { + $("#summary-" + key).mark(snipvalue); + }); + }); +} + +function param(name) { + return decodeURIComponent( + (location.search.split(name + "=")[1] || "").split("&")[0] + ).replace(/\+/g, " "); +} + +function render(templateString, data) { + var conditionalMatches, conditionalPattern, copy; + 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 + 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], ""); + } + } + templateString = 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; +} diff --git a/static/syntax.css b/static/syntax.css deleted file mode 100644 index e3b54c8..0000000 --- a/static/syntax.css +++ /dev/null @@ -1,70 +0,0 @@ -/* Background */ .chroma { color: #272822; background-color: #fafafa } -/* Error */ .chroma .err { color: #960050; background-color: #1e0010 } -/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } -/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; } -/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc } -/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } -/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } -/* Keyword */ .chroma .k { color: #00a8c8 } -/* KeywordConstant */ .chroma .kc { color: #00a8c8 } -/* KeywordDeclaration */ .chroma .kd { color: #00a8c8 } -/* KeywordNamespace */ .chroma .kn { color: #f92672 } -/* KeywordPseudo */ .chroma .kp { color: #00a8c8 } -/* KeywordReserved */ .chroma .kr { color: #00a8c8 } -/* KeywordType */ .chroma .kt { color: #00a8c8 } -/* Name */ .chroma .n { color: #111111 } -/* NameAttribute */ .chroma .na { color: #75af00 } -/* NameBuiltin */ .chroma .nb { color: #111111 } -/* NameBuiltinPseudo */ .chroma .bp { color: #111111 } -/* NameClass */ .chroma .nc { color: #75af00 } -/* NameConstant */ .chroma .no { color: #00a8c8 } -/* NameDecorator */ .chroma .nd { color: #75af00 } -/* NameEntity */ .chroma .ni { color: #111111 } -/* NameException */ .chroma .ne { color: #75af00 } -/* NameFunction */ .chroma .nf { color: #75af00 } -/* NameFunctionMagic */ .chroma .fm { color: #111111 } -/* NameLabel */ .chroma .nl { color: #111111 } -/* NameNamespace */ .chroma .nn { color: #111111 } -/* NameOther */ .chroma .nx { color: #75af00 } -/* NameProperty */ .chroma .py { color: #111111 } -/* NameTag */ .chroma .nt { color: #f92672 } -/* NameVariable */ .chroma .nv { color: #111111 } -/* NameVariableClass */ .chroma .vc { color: #111111 } -/* NameVariableGlobal */ .chroma .vg { color: #111111 } -/* NameVariableInstance */ .chroma .vi { color: #111111 } -/* NameVariableMagic */ .chroma .vm { color: #111111 } -/* Literal */ .chroma .l { color: #ae81ff } -/* LiteralDate */ .chroma .ld { color: #d88200 } -/* LiteralString */ .chroma .s { color: #d88200 } -/* LiteralStringAffix */ .chroma .sa { color: #d88200 } -/* LiteralStringBacktick */ .chroma .sb { color: #d88200 } -/* LiteralStringChar */ .chroma .sc { color: #d88200 } -/* LiteralStringDelimiter */ .chroma .dl { color: #d88200 } -/* LiteralStringDoc */ .chroma .sd { color: #d88200 } -/* LiteralStringDouble */ .chroma .s2 { color: #d88200 } -/* LiteralStringEscape */ .chroma .se { color: #8045ff } -/* LiteralStringHeredoc */ .chroma .sh { color: #d88200 } -/* LiteralStringInterpol */ .chroma .si { color: #d88200 } -/* LiteralStringOther */ .chroma .sx { color: #d88200 } -/* LiteralStringRegex */ .chroma .sr { color: #d88200 } -/* LiteralStringSingle */ .chroma .s1 { color: #d88200 } -/* LiteralStringSymbol */ .chroma .ss { color: #d88200 } -/* LiteralNumber */ .chroma .m { color: #ae81ff } -/* LiteralNumberBin */ .chroma .mb { color: #ae81ff } -/* LiteralNumberFloat */ .chroma .mf { color: #ae81ff } -/* LiteralNumberHex */ .chroma .mh { color: #ae81ff } -/* LiteralNumberInteger */ .chroma .mi { color: #ae81ff } -/* LiteralNumberIntegerLong */ .chroma .il { color: #ae81ff } -/* LiteralNumberOct */ .chroma .mo { color: #ae81ff } -/* Operator */ .chroma .o { color: #f92672 } -/* OperatorWord */ .chroma .ow { color: #f92672 } -/* Punctuation */ .chroma .p { color: #111111 } -/* Comment */ .chroma .c { color: #75715e } -/* CommentHashbang */ .chroma .ch { color: #75715e } -/* CommentMultiline */ .chroma .cm { color: #75715e } -/* CommentSingle */ .chroma .c1 { color: #75715e } -/* CommentSpecial */ .chroma .cs { color: #75715e } -/* CommentPreproc */ .chroma .cp { color: #75715e } -/* CommentPreprocFile */ .chroma .cpf { color: #75715e } -/* GenericEmph */ .chroma .ge { font-style: italic } -/* GenericStrong */ .chroma .gs { font-weight: bold } @@ -7,9 +7,6 @@ tags = ["blog", "clean", "color configuration"] features = ["blog", "clean", "color configuration"] min_version = "0.54.0" -pygmentsUseClasses=true -pygmentsCodefences=true - [author] name = "Jeremy Bise" homepage = "https://jeremybise.com/" |