Master de II. ULL. 1er cuatrimestre. 2020/2021
El ejercicio consiste en que añada la capacidad de bĂșsqueda al sitio asociado a alguna de las prĂĄcticas, por ejemplo a p8-t3-jekyll-netlify.
Estos son algunos requisitos:
We will be creating a JSON file in which we will store title
, url
, content
, excerpt
, etc., at building time
1
2
$ bundle exec jekyll build
$ head -n 30 _site/assets/src/search.json
1
2
3
4
5
6
7
8
9
[
{
"title": "Clase del Lunes 30/09/2019",
"excerpt": "Clase del Lunes 30/09/2019\n\n", â Resumen
"content": "Clase del Lunes 30/09/2019\n\n\n ...", â Contenido del fichero
"url": "/clases/2019/09/30/leccion.html"
},
...
]
Para lograrlo usaremos este template Liquid: search.json (protected)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- - -
layout: null
sitemap: false
- - -
{ % capture json % }
[
{ % assign collections = site.collections | where_exp:'collection','collection.output != false' %}
{ % for collection in collections %}
{ % assign docs = collection.docs | where_exp:'doc','doc.sitemap != false' %}
{ % for doc in docs %}
{
"title": { { doc.title | jsonify }},
"excerpt": { { doc.excerpt | markdownify | strip_html | jsonify }},
"content": { { doc.content | markdownify | strip_html | jsonify }},
"url": { { site.baseurl | append: doc.url | jsonify }}
},
{ % endfor %}
{ % endfor %}
{ % assign pages = site.html_pages | where_exp:'doc','doc.sitemap != false' | where_exp:'doc','doc.title != null' %}
{ % for page in pages % }
{
"title": { { page.title | jsonify }},
"excerpt": { { page.excerpt | markdownify | strip_html | jsonify }},
"content": { { page.content | markdownify | strip_html | jsonify }},
"url": { { site.baseurl | append: page.url | jsonify }}
}{ % unless forloop.last %},{ % endunless %}
{ % endfor %}
]
{ % endcapture %}
{ { json | lstrip }}
layout: null
: To disable layout in Jekyll.sitemap: false
:
robots.txt
, a URL exclusion protocol. We can use the front-matter to set the sitemap
property to false
{ % capture json % } ... { % endcapture %}
Captures the string inside of the opening and closing tags and assigns it to a variable. Variables that you create using capture are stored as strings.{ { json | lstrip }}
:
{ { } }
and are denoted by a pipe character |
.{ % assign collections = site.collections ...
site.collections
: Collections are also available under site.collections
. Posts are considered a collections by Jekyll.where_exp:'collection','collection.output != false'
site.collections
is an array. With where_exp
we select all the objects in the array with the elements for which the attribute collection
has its output
attribute to true
.output
attribute of a collection controls whether the collectionâs documents will be output as individual files.site.html_pages
: A subset of site.pages
listing those which end in .html
."content": { { page.content | markdownify | strip_html | jsonify }},
page.content
el contenido de la pĂĄgina todavia sin renderizar (se supone que es fundamentalmente markdown, pero puede contener yml en el front-matter, html, scripts, liquid, etc.)markdownify
: Convert a Markdown-formatted string into HTML.jsonify
: If the data is an array or hash you can use the jsonify filter to convert it to JSON.Por ejemplo, supongamos que tenemos estas definiciones en el front-matter de nuestra pĂĄgina:
1
2
3
4
5
6
7
chuchu: "Cadena **negritas** e *italicas*"
html: "<h1>hello</h1> <b>world</b>"
colors:
- red
- blue
- green
---
y que en el contenido de nuestra pĂĄgina tenemos algo asĂ:
1
2
3
4
5
Compara < script>{ { page.chuchu }} </script> con su markdownify: < script>{ { page.chuchu | markdownify }}</script>
Compara < script> { { page.colors}} </script> con su jsonify: < script>{ { page.colors | jsonify }} </script>
Compara < script>{ {page.html}}</script> con su `strip_html` < script> { { page.html | strip_html }} </script>
Esta es la salida que produce jekyll 4.0.0:
1
2
3
4
5
6
<p>Compara < script>Cadena **negritas** e *italicas* </script> con su markdownify: < script><p>Cadena <strong>negritas</strong> e <em>italicas</em></p>
</script></p>
<p>Compara < script> redbluegreen </script> con su jsonify: < script>["red","blue","green"] </script></p>
<p>Compara < script><h1>hello</h1> <b>world</b></script> con su <code class="highlighter-rouge">strip_html</code> < script> hello world </script></p>
La idea general es que necesitamos suprimir los tags, tanto yml, markdown, HTML, etc. para que no confundan al método de busca. Por eso convertimos el markdown a HTML y después suprimimos los tags HTML. También convertimos el yml a JSON.
Fichero search.md
:
La idea es que vamos a escribir una clase JekyllSearch
que implementa la bĂșsqueda.
Debe disponer de un constructor al que se le pasan cuatro argumentos:
jekyll build
)id
del objeto del DOM en la pĂĄgina en la que estĂĄ el tag input
de la bĂșsquedaid
del objeto del DOM en el que se deben volcar los resultadosurl
del lugar en el que estĂĄ el deployment (pudiera ser que el site en el que queremos buscar fuera una subcarpeta de todo el site)1
2
3
4
5
6
7
const search = new JekyllSearch(
'/assets/src/search.json',
'#search',
'#list',
''
);
search.init();
Los objetos JekyllSearch
deben disponer de un método init
que realiza la bĂșsqueda especificada en el elemento del DOM #search
y añade los resultados en en el elemento del DOM #list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- - -
layout: error
permalink: /search/
title: Search
- - -
{ % capture initSearch % }
<h1>Search</h1>
<form id="search-form" action="">
<label class="label" for="search">Search term (accepts a regex):</label>
<br/>
<input class="input" id="search" type="text" name="search" autofocus placeholder="e.g. Promise" autocomplete="off">
<ul class="list list--results" id="list">
</ul>
</form>
< script type="text/javascript" src="/assets/src/fetch.js"></script>
< script type="text/javascript" src="/assets/src/search.js"></script>
< script type="text/javascript">
const search = new JekyllSearch(
'{ {site.url} }/assets/src/search.json',
'#search',
'#list',
'{ {site.url} }'
);
search.init();
</script>
<noscript>Please enable JavaScript to use the search form.</noscript>
{ % endcapture % }
{ { initSearch | lstrip } }
autocomplete="off"
1
autocomplete="nope"
Dado que este valor no es vĂĄlido para el atributo autocompletar, el navegador no tiene forma de reconocerlo y deja de intentar autocompletarlo.
{ { } }
and are denoted by a pipe character |
.
site.url
vs site.baseurl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class JekyllSearch {
constructor(dataSource, searchField, resultsList, siteURL) {
this.dataSource = dataSource // ruta al fichero json
this.searchField = document.querySelector(searchField) // DOM el. del input
this.resultsList = document.querySelector(resultsList) // DOM el. para el output
this.siteURL = siteURL // folder
this.data = '';
}
fetchedData() {
return fetch(this.dataSource)
.then(blob => blob.json())
}
async findResults() {
this.data = await this.fetchedData()
const regex = new RegExp(this.searchField.value, 'gi')
return this.data.filter(item => {
return item.title.match(regex) || item.content.match(regex)
})
}
async displayResults() {
const results = await this.findResults()
const html = results.map(item => {
return `
<li class="result">
<article class="result__article article">
<h4>
<a href="${this.siteURL + item.url}">${item.title}</a>
</h4>
<p>${item.excerpt}</p>
</article>
</li>`
}).join('')
if ((results.length == 0) || (this.searchField.value == '')) {
this.resultsList.innerHTML = `<p>Sorry, nothing was found</p>`
} else {
this.resultsList.innerHTML = html
}
}
// https://stackoverflow.com/questions/43431550/async-await-class-constructor
init() {
const url = new URL(document.location)
if (url.searchParams.get("search")) {
this.searchField.value = url.searchParams.get("search") // Update the attribute
this.displayResults()
}
this.searchField.addEventListener('keyup', () => {
this.displayResults()
// So that when going back in the browser we keep the search
url.searchParams.set("search", this.searchField.value)
window.history.pushState('', '', url.href)
})
// to not send the form each time <enter> is pressed
this.searchField.addEventListener('keypress', event => {
if (event.keyCode == 13) {
event.preventDefault()
}
})
}
}
If the URL of your page is https://example.com/?name=Jonathan%20Smith&age=18
you could parse out the name
and age
parameters using:
1
2
3
let params = (new URL(document.location)).searchParams;
let name = params.get('name'); // is the string "Jonathan Smith".
let age = parseInt(params.get('age')); // is the number 18
All methods getElementsBy*
return a live collection.
Such collections always reflect the current state of the document and auto-update when it changes.
In contrast, querySelectorAll
returns a static collection.
Itâs like a fixed array of elements.
1
2
3
4
5
6
7
8
constructor(dataSource, searchField, resultsList, siteURL) {
this.dataSource = dataSource // ruta al fichero json
this.searchField = document.querySelector(searchField) // DOM el. del input
this.resultsList = document.querySelector(resultsList) // DOM el. para el output
this.siteURL = siteURL // folder
this.data = '';
}
The window
object provides access to the browserâs session history through the history
object.
The history.pushState(state, title, url)
method adds a state to the browserâs session history stack.
1
2
3
4
5
6
7
... // inside init
this.searchField.addEventListener('keyup', () => {
this.displayResults()
// So that when going back in the browser we keep the search
url.searchParams.set("search", this.searchField.value)
window.history.pushState('', '', url.href)
})
The resources downloaded through fetch()
, similar to other resources that the browser downloads, are subject to the HTTP cache. Â
1
2
3
fetchedData() {
return fetch(this.dataSource).then(blob => blob.json())
}
This is usually fine, since it means that if your browser has a cached copy of the response to the HTTP request, it can use the cached copy instead of wasting time and bandwidth re-downloading from a remote server.
The search.json
is not going to change until the next push
npm install whatwg-fetch --save
bower install fetch --save
Esta imagen muestra los ficheros implicados en este ejercicio dentro de la estructura del sitio de estos apuntes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$ tree -I _site
.
âââ 404.md
âââ assets
â âââ css
â â âââ style.scss
â âââ images
â â âââ event-emitter-methods.png
â â âââ ,,,
â âââ src
â âââ fetch.js â Polyfill for fetch
â âââ search.js â LibrerĂa con la Clase JekyllSearch que implementa el CĂłdigo de bĂșsqueda
â âââ search.json â Plantilla Liquid para generar el fichero JSON
âââ search.md â PĂĄgina de la bĂșsqueda. Formulario y script de arranque
âââ clases.md
âââ _config.yml â Fichero de configuraciĂłn de Jekyll
âââ degree.md
âââ favicon.ico
âââ Gemfile
âââ Gemfile.lock
âââ _includes â The include tag allows to include the content of files stored here
â âââ navigation-bar.html
â âââ ...
âââ _layouts â templates that wrap around your content
â âââ default.html
â âââ error.html
â âââ post.html
âââ _posts â File names must follow YEAR-MONTH-DAY-title.MARKUP and must begin with front matter
â âââ ...
â âââ 2019-12-02-leccion.md
âââ _practicas â Folder for the collection "practicas" (list of published "practicas")
â âââ ...
â âââ p9-t3-transfoming-data.md
âââ practicas.md â { % for practica in site.practicas % } ... { % endfor % }
âââ Rakefile â For tasks
âââ README.md
âââ references.md
âââ resources.md
âââ tema0-presentacion â Pages folders
â âââ README.md
â âââ ...
âââ tema ...
âââ tfa
â âââ README.md
âââ timetables.md
58 directories, 219 files