Source code of my website
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

✨ : add basic search

+250
+68
assets/css/custom.css
··· 103 103 .books .info .links li a img { 104 104 margin: 0; 105 105 } 106 + 107 + /* search box style */ 108 + :root { 109 + --rad: .5rem; 110 + --dur: .3s; 111 + --color-dark: #2f2f2f; 112 + --color-light: #fff; 113 + --color-brand: #57bd84; 114 + --font-fam: 'Lato', sans-serif; 115 + --height: 2.5rem; 116 + --btn-width: 3rem; 117 + --bez: cubic-bezier(0, 0, 0.43, 1.49); 118 + } 119 + 120 + form { 121 + position: relative; 122 + width: 100%; 123 + background: var(--color-brand); 124 + border-radius: var(--rad); 125 + display: flex; 126 + } 127 + input, button { 128 + height: var(--height); 129 + border: 0; 130 + color: var(--color-dark); 131 + font-size: 1rem; 132 + } 133 + input[type="search"] { 134 + outline: 0; 135 + width: 100%; 136 + background: var(--color-light); 137 + padding: 0 1rem; 138 + border-radius: var(--rad); 139 + appearance: none; 140 + transition: all var(--dur) var(--bez); 141 + transition-property: width, border-radius; 142 + z-index: 1; 143 + position: relative; 144 + } 145 + button { 146 + display: none; 147 + position: absolute; 148 + top: 0; 149 + right: 0; 150 + width: var(--btn-width); 151 + font-weight: bold; 152 + background: var(--color-brand); 153 + border-radius: 0 var(--rad) var(--rad) 0; 154 + } 155 + input:not(:placeholder-shown) { 156 + border-radius: var(--rad) 0 0 var(--rad); 157 + width: calc(100% - var(--btn-width)); 158 + + button { 159 + display: block; 160 + } 161 + } 162 + label { 163 + position: absolute; 164 + clip: rect(1px, 1px, 1px, 1px); 165 + padding: 0; 166 + border: 0; 167 + height: 1px; 168 + width: 1px; 169 + overflow: hidden; 170 + } 171 + mark { 172 + background: #5ea4e280; 173 + }
+39
content/search.md
··· 1 + --- 2 + title: "Search Results" 3 + sitemap: 4 + priority : 0.1 5 + layout: "search" 6 + --- 7 + 8 + 9 + This file exists solely to respond to /search URL with the related `search` layout template. 10 + 11 + No content shown here is rendered, all content is based in the template layouts/page/search.html 12 + 13 + Setting a very low sitemap priority will tell search engines this is not important content. 14 + 15 + This implementation uses Fusejs, jquery and mark.js 16 + 17 + 18 + ## Initial setup 19 + 20 + Search depends on additional output content type of JSON in config.toml 21 + \``` 22 + [outputs] 23 + home = ["HTML", "JSON"] 24 + \``` 25 + 26 + ## Searching additional fileds 27 + 28 + To search additional fields defined in front matter, you must add it in 2 places. 29 + 30 + ### Edit layouts/_default/index.JSON 31 + This exposes the values in /index.json 32 + i.e. add `category` 33 + \``` 34 + ... 35 + "contents":{{ .Content | plainify | jsonify }} 36 + {{ if .Params.tags }}, 37 + "tags":{{ .Params.tags | jsonify }}{{end}}, 38 + "categories" : {{ .Params.categories | jsonify }}, 39 + ...
+3
hugo.toml
··· 28 28 [markup.goldmark.parser.attribute] 29 29 block = true 30 30 31 + [outputs] 32 + home = ['html', 'rss', 'json'] 33 + 31 34 [params] 32 35 brand = "Julien Wittouck" 33 36 brand_image = 'pp_ekite_itvw.png'
+5
layouts/_default/index.json
··· 1 + {{- $.Scratch.Add "index" slice -}} 2 + {{- range .Site.RegularPages -}} 3 + {{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "categories" .Params.categories "contents" .Plain "permalink" .Permalink) -}} 4 + {{- end -}} 5 + {{- $.Scratch.Get "index" | jsonify -}}
+17
layouts/_default/search.html
··· 1 + {{ define "main" }} 2 + <section class="resume-section p-3 p-lg-5 d-flex flex-column"> 3 + <div class="my-auto" > 4 + <div id="search-results"> 5 + <h3>Résultats de recherche</h3> 6 + </div> 7 + </div> 8 + </section> 9 + <!-- this template is sucked in by search.js and appended to the search-results div above. So editing here will adjust style --> 10 + <script id="search-result-template" type="text/x-js-template"> 11 + <div id="summary-${key}"> 12 + <h4><a href="${link}">${title}</a></h4> 13 + <p>${snippet}</p> 14 + ${ isset tags }<p>Tags: ${tags}</p>${ end } 15 + </div> 16 + </script> 17 + {{ end }}
+5
layouts/partials/head/scripts.html
··· 30 30 31 31 <script type="text/javascript" src="https://app.affilizz.com/affilizz.js" async></script> 32 32 33 + <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> 34 + <script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js"></script> 35 + <script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/jquery.mark.min.js"></script> 36 + <script src="{{ "js/search.js" | absURL }}"></script> 37 + 33 38 {{ if hugo.IsProduction -}} 34 39 {{ template "_internal/google_analytics.html" . -}} 35 40 {{ end -}}
+5
layouts/partials/sidebar/title.html
··· 26 26 {{ end }} 27 27 </ul> 28 28 29 + <form action="{{ "search" | absURL }}"> 30 + <input type="search" id="search" name="s" placeholder="Rechercher"/> 31 + <button type="submit">🔎</button> 32 + </form> 33 + 29 34 </div>
+108
static/js/search.js
··· 1 + summaryInclude=60; 2 + var fuseOptions = { 3 + shouldSort: true, 4 + includeMatches: true, 5 + threshold: 0.0, 6 + tokenize:true, 7 + location: 0, 8 + distance: 100, 9 + maxPatternLength: 32, 10 + minMatchCharLength: 1, 11 + keys: [ 12 + {name:"title",weight:0.8}, 13 + {name:"contents",weight:0.5}, 14 + {name:"tags",weight:0.3}, 15 + {name:"categories",weight:0.3} 16 + ] 17 + }; 18 + 19 + 20 + var searchQuery = param("s"); 21 + if(searchQuery){ 22 + $("#search-query").val(searchQuery); 23 + executeSearch(searchQuery); 24 + }else { 25 + $('#search-results').append("<p>Please enter a word or phrase above</p>"); 26 + } 27 + 28 + 29 + 30 + function executeSearch(searchQuery){ 31 + $.getJSON( "/index.json", function( data ) { 32 + var pages = data; 33 + var fuse = new Fuse(pages, fuseOptions); 34 + var result = fuse.search(searchQuery); 35 + console.log({"matches":result}); 36 + if(result.length > 0){ 37 + populateResults(result); 38 + }else{ 39 + $('#search-results').append("<p>No matches found</p>"); 40 + } 41 + }); 42 + } 43 + 44 + function populateResults(result){ 45 + $.each(result,function(key,value){ 46 + var contents= value.item.contents; 47 + var snippet = ""; 48 + var snippetHighlights=[]; 49 + var tags =[]; 50 + if( fuseOptions.tokenize ){ 51 + snippetHighlights.push(searchQuery); 52 + }else{ 53 + $.each(value.matches,function(matchKey,mvalue){ 54 + if(mvalue.key == "tags" || mvalue.key == "categories" ){ 55 + snippetHighlights.push(mvalue.value); 56 + }else if(mvalue.key == "contents"){ 57 + start = mvalue.indices[0][0]-summaryInclude>0?mvalue.indices[0][0]-summaryInclude:0; 58 + end = mvalue.indices[0][1]+summaryInclude<contents.length?mvalue.indices[0][1]+summaryInclude:contents.length; 59 + snippet += contents.substring(start,end); 60 + snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0],mvalue.indices[0][1]-mvalue.indices[0][0]+1)); 61 + } 62 + }); 63 + } 64 + 65 + if(snippet.length<1){ 66 + snippet += contents.substring(0,summaryInclude*2); 67 + } 68 + //pull template from hugo templarte definition 69 + var templateDefinition = $('#search-result-template').html(); 70 + //replace values 71 + var output = render(templateDefinition,{key:key,title:value.item.title,link:value.item.permalink,tags:value.item.tags,categories:value.item.categories,snippet:snippet}); 72 + $('#search-results').append(output); 73 + 74 + $.each(snippetHighlights,function(snipkey,snipvalue){ 75 + $("#summary-"+key).mark(snipvalue); 76 + }); 77 + 78 + }); 79 + } 80 + 81 + function param(name) { 82 + return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' '); 83 + } 84 + 85 + function render(templateString, data) { 86 + var conditionalMatches,conditionalPattern,copy; 87 + conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g; 88 + //since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop 89 + copy = templateString; 90 + while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) { 91 + if(data[conditionalMatches[1]]){ 92 + //valid key, remove conditionals, leave contents. 93 + copy = copy.replace(conditionalMatches[0],conditionalMatches[2]); 94 + }else{ 95 + //not valid, remove entire section 96 + copy = copy.replace(conditionalMatches[0],''); 97 + } 98 + } 99 + templateString = copy; 100 + //now any conditionals removed we can do simple substitution 101 + var key, find, re; 102 + for (key in data) { 103 + find = '\\$\\{\\s*' + key + '\\s*\\}'; 104 + re = new RegExp(find, 'g'); 105 + templateString = templateString.replace(re, data[key]); 106 + } 107 + return templateString; 108 + }