Motor de búsqueda del lado del cliente para el sitio de Hugo

En este tutorial, aprenderemos cómo configurar el motor de búsqueda del lado del cliente para el sitio web de Hugo sin necesidad de backend ni pasos de compilación adicionales.

Índice
  1. Visión de conjunto
  2. Sigue los pasos:-
    1. Poner al día config.toml
    2. Crear layouts/_default/index.json
    3. Crear search.js
    4. Crear content/search.md
    5. Crear layouts/_default/search.html
    6. Personalizacion para Temática Hugo Mainroad
  3. Conclusión

Visión de conjunto

Cuando genera un sitio web utilizando Hugo Static Site Builder. Escribes tus páginas generalmente en rebajas (archivos .md) por lo que todo su contenido está en los archivos de rebajas.

Seguiremos estos pasos para crear nuestro motor de búsqueda:-

  1. Poner al día config.toml archivo para pedirle a Hugo que genere JSON archivo de todo el contenido del sitio web
  2. Crear diseños/_predeterminado/index.json proporcionar una plantilla para que Hugo genere un índice.json expediente
  3. Crear estático/js/search.js use Fuse.js para buscar contenido en este JSON expediente
  4. Crear contenido/búsqueda.md servir una página para /buscar URL
  5. Crear diseños/_predeterminado/búsqueda.html proporcionar una plantilla para mostrar los resultados de su página de búsqueda

Sigue los pasos:-

Poner al día config.toml

Hugo brinda soporte listo para usar para generar contenido en múltiples formatos, incluidos JSON. Solo dile a Hugo que queremos generar índice.json archivo para todo el contenido del sitio web.

Agregue el siguiente fragmento en config.toml archivo para pedirle a Hugo que genere JSON salir con HTML y RSS salidas por defecto:

config.toml
...
[outputs]
  home = ["HTML", "RSS", "JSON"]

Crear layouts/_default/index.json

Una vez que le pedimos a Hugo que genere índice.json archivo, Hugo busca el modelo para generar el archivo. Agrega lo siguiente índice.json modelo en la carpeta especificada:

diseños/_predeterminado/index.json
{{- $.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 -}}

Crear search.js

Ahora crea un buscar.js archivo que utiliza jquery, fuse.js y mark.js analizar generado índice.json archivo y devuelve el contenido correspondiente, con resaltado.

Tenga en cuenta que la primera parte del código en buscar.js proporcionar configuración: -

  • resumenIncluir: Número de páginas a mostrar en el resultado de búsqueda
  • FusibleOpciones: teclas es una configuración importante donde puede aumentar el peso de los valores de título, contenido, etiqueta o categoría para darles una alta prioridad en el contenido coincidente en los resultados de búsqueda.
estático/js/search.js
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*ends*}/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;
}

Crear content/search.md

Crear buscar.md crea una pagina solo para responder /buscar URL. Ninguno de los contenidos de esta página está renderizado. Como definimos el diseño del tema principal como "búsqueda", el contenido se muestra en esta página desde la plantilla diseños/_predeterminado/búsqueda.html

contenido/búsqueda.md
---
title: "Search Results"
sitemap:
  priority : 0.1
layout: "search"
---

Nothing on this page will be visible. This file exists solely to respond to /search URL.

Setting a very low sitemap priority will tell search engines this is not important content.

Crear layouts/_default/search.html

Esta es la página representada cuando se ve /buscar en tu navegador. Este ejemplo utiliza la función de plantilla para cargar contenido y todos los archivos JS necesarios en el bloque "principal".

diseños/_predeterminado/búsqueda.html
{{ 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>
<section class="resume-section p-3 p-lg-5 d-flex flex-column">
  <div class="my-auto" >
    <form action="{{ "search" | absURL }}">
      <input id="search-query" name="s"/>
    </form>
    <div id="search-results">
     <h3>Matching pages</h3>
    </div>
  </div>
</section>
<!-- 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">
    <div id="summary-${key}">
      <h4><a href="${link}">${title}</a></h4>
      <p>${snippet}</p>
      ${ isset tags }<p>Tags: ${tags}</p>${ end }
      ${ isset categories }<p>Categories: ${categories}</p>${ end }
    </div>
</script>
{{ end }}

Puede que tengas que esforzarte un poco diseños/_predeterminado/búsqueda.html archivo para que coincida con el resultado de la búsqueda de acuerdo con su tema. Puede crear cualquier plantilla, siempre que incluya las bibliotecas de terceros (jquery, fuse, mark.js) antes de search.js, funcionará.

Personalizacion para Temática Hugo Mainroad

Creé este sitio web de blog utilizando el generador de sitios estáticos Hugo y el tema Hugo Mainroad. Hice algunos cambios arriba buscar.html modelo coincidente Carretera principal tema.

diseños/_predeterminado/búsqueda.html
{{ 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 class="main list" role="main">
  {{- with .Title }}
	<header class="main__header">
    <h1 class="main__title">{{ . }}  <span class="list__lead post__lead" id="search-string"></span></h1>
   
	</header>
  {{- end }}
  <div id="search-results">
  </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 class="list__item post" id="summary-${key}">
      <header class="list__header">
        <h3 class="list__title post__title ">
          <a href="${link}" rel="bookmark">
            ${title} 
          </a>         
        </h3>
        <div class="list__meta meta">
          <div class="meta__item-categories meta__item">
            <svg class="meta__icon icon icon-category" width="16" height="16" viewBox="0 0 16 16"><path d="m7 2l1 2h8v11h-16v-13z"/></svg>
            <span class="meta__text">
              ${ isset categories } ${categories}${ end }
            </span>
          </div>
          <div class="meta__item-categories meta__item">
            <svg class="meta__icon icon icon-tag" width="16" height="16" viewBox="0 0 32 32"><path d="M32 19c0 1-1 2-1 2L21 31s-1 1-2 1-2-1-2-1L2 16c-1-1-1.4-2-1.4-2S0 12.5 0 11V3C0 1.5.8.8.8.8S1.5 0 3 0h8c1.5 0 3 .6 3 .6S15 1 16 2l15 15s1 1 1 2zM7 10a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></svg>
            <span class="meta__text">
              ${ isset tags } ${tags}${ end }
            </span>
          </div>
        </div>
      </header>
      <div class="content list__excerpt post__content clearfix">
        ${snippet}
      </div>
    </article>
</script>
{{ end }}

También realicé algunos cambios en el widget del cuadro de búsqueda para el tema de Hugo Mainroad de la siguiente manera:

diseños/parciales/widgets/search.html
<div class="widget-search widget">
  <form class="widget-search__form" role="search" method="get" action="{{ "search" | absURL }}">
    <label>
      <input class="widget-search__field" type="search" placeholder="{{ T "search_placeholder" }}" value="" name="s" aria-label="{{ T "search_placeholder" }}">
    </label>
    <input class="widget-search__submit" type="submit" value="Search">
  </form>
</div>

Conclusión

En este tutorial aprendimos con qué facilidad podemos crear un motor de búsqueda del lado del cliente para nuestro sitio web de Hugo. Puede seguir los primeros cuatro pasos a ciegas y requerir esfuerzo para que el quinto paso coincida buscar.html modelo de acuerdo a su tema.

Una vez que haya terminado con todos los cambios, verifique el resultado de la búsqueda en su servidor de desarrollo de la siguiente manera:

http://localhost:1313/search/?s=searchquery

Si desea ver cómo se ven los resultados de búsqueda para el sitio web de mi blog Conceptos de codificaciónGolpear https://codingnconcepts.com/search/?s=java

Referencia: gist.github.com/eddiewebb

Si quieres conocer otros artículos parecidos a Motor de búsqueda del lado del cliente para el sitio de Hugo puedes visitar la categoría Tutoriales.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Esta página web utiliza cookies para analizar de forma anónima y estadística el uso que haces de la web, mejorar los contenidos y tu experiencia de navegación. Para más información accede a la Política de Cookies . Ver mas