Browse Source

Display search results with a dedicated template

master
David Larlet 2 years ago
parent
commit
9988a3b959
2 changed files with 102 additions and 38 deletions
  1. 51
    19
      david/recherche/index.html
  2. 51
    19
      david/templates/recherche.html

+ 51
- 19
david/recherche/index.html View File

<button type="reset">Annuler</button> <button type="reset">Annuler</button>
</form> </form>


<div id="search-status" role="status"></div>
<p id="search-status" role="status"></p>
<div id="search-results"></div> <div id="search-results"></div>


<hr> <hr>


<p> <p>
Seuls les écrits de ces dernières années sont indexés.
Vous pouvez aussi consulter les archives chronologiques de Vous pouvez aussi consulter les archives chronologiques de
<a href="/david/2022/">2022</a>, <a href="/david/2022/">2022</a>,
<a href="/david/2021/">2021</a>, <a href="/david/2021/">2021</a>,
}) })
</script> </script>


<template id="search-result">
<h2>
<a href="${url}">${title}</a> (${date})
</h2>
<p>${content}</p>
</template>
<script id="search-index" type="application/json"> <script id="search-index" type="application/json">
[ [
{ {
let input = document.querySelector('#input-search') let input = document.querySelector('#input-search')
let resultList = document.querySelector('#search-results') let resultList = document.querySelector('#search-results')
let searchStatus = document.querySelector('#search-status') let searchStatus = document.querySelector('#search-status')
let searchResultTemplate = document.querySelector('#search-result')


// Make sure required content exists // Make sure required content exists
if (!form || !input || !resultList || !searchStatus || !searchIndex || !stopWords) return
if (!form || !input || !resultList || !searchStatus || !searchIndex || !stopWords || !searchResultTemplate) return


// Create a submit handler // Create a submit handler
form.addEventListener('submit', function (event) { form.addEventListener('submit', function (event) {
/** /**
* Show the search results in the UI * Show the search results in the UI
* @param {Array} results The results to display * @param {Array} results The results to display
* @param {List} regMap Regular expressions for the highlights
*/ */
function showResults (results, regMap) { function showResults (results, regMap) {
let status = 'Aucune publication n’a été trouvée 😢'
let searchResults = ''
if (results.length) { if (results.length) {
const plural = results.length > 1 ? 's' : '' const plural = results.length > 1 ? 's' : ''
searchStatus.innerHTML = `<p>${results.length} publication${plural} trouvée${plural} 🙌</p>`
resultList.innerHTML = results.map(function (result) {
return `
<h2>
<a href="${result.article.url}">${result.article.title}</a>
(${result.article.date})
</h2>
<p>${highlightContent(result.article.content, regMap)}</p>
`
status = `${results.length} publication${plural} trouvée${plural} 🙌`
searchResults = results.map(function (result) {
return interpolate(searchResultTemplate.innerHTML, {
url: result.article.url,
title: highlightText(result.article.title, regMap),
date: result.article.date,
content: highlightText(result.article.content, regMap),
})
}).join('') }).join('')
} else {
searchStatus.innerHTML = '<p>Aucune publication n’a été trouvée 😢<br>Seuls les écrits de ces dernières années sont indexés.</p>'
resultList.innerHTML = ''
} }
searchStatus.innerHTML = status
resultList.innerHTML = searchResults
}

/**
* Get a template from a string
* https://stackoverflow.com/a/41015840
* https://gomakethings.com/html-templates-with-vanilla-javascript/
* @param {String} str The string to interpolate
* @param {Object} params The parameters
* @return {String} The interpolated string
*/
function interpolate (str, params) {
let names = Object.keys(params);
let vals = Object.values(params);
return new Function(...names, `return \`${str}\``)(...vals);
} }


function highlightContent(content, regMap) {
/**
* Highlight the text in the UI
* @param {String} text The content to highlight
* @param {List} regMap Regular expressions for the highlights
*/
function highlightText(text, regMap) {
// TODO: deal with close matches when multiple words are looked for, // TODO: deal with close matches when multiple words are looked for,
// it does not look trivial because you have to memorize positions // it does not look trivial because you have to memorize positions
// then create extracts. // then create extracts.
// For instance: `microsoft github` // For instance: `microsoft github`
const extractBoundariesSize = 100 const extractBoundariesSize = 100
const contentLength = content.length
const textLength = text.length
let extracts = [] let extracts = []
for (let reg of regMap) { for (let reg of regMap) {
const index = content.search(reg)
const index = text.search(reg)
if (index === -1) { continue } if (index === -1) { continue }
let extract = content.substring(
let extract = text.substring(
index - extractBoundariesSize, index - extractBoundariesSize,
index + reg.source.length + extractBoundariesSize index + reg.source.length + extractBoundariesSize
) )
// (is that confusing or closer to what is expected?) // (is that confusing or closer to what is expected?)
extract = extract.replace(reg,`<mark>${reg.source}</mark>`) extract = extract.replace(reg,`<mark>${reg.source}</mark>`)
const prefixEllipsis = index - extractBoundariesSize >= 0 ? '…' : '' const prefixEllipsis = index - extractBoundariesSize >= 0 ? '…' : ''
const suffixEllipsis = index - extractBoundariesSize <= contentLength ? '…' : ''
const suffixEllipsis = index + extractBoundariesSize <= textLength ? '…' : ''
extracts.push(`${prefixEllipsis}${extract}${suffixEllipsis}`) extracts.push(`${prefixEllipsis}${extract}${suffixEllipsis}`)
} }
if (!extracts.length && textLength < 200) {
// If there is no match but it's a short title, return it.
return text
}
return extracts.join('') return extracts.join('')
} }



+ 51
- 19
david/templates/recherche.html View File

<button type="reset">Annuler</button> <button type="reset">Annuler</button>
</form> </form>


<div id="search-status" role="status"></div>
<p id="search-status" role="status"></p>
<div id="search-results"></div> <div id="search-results"></div>


<hr> <hr>


<p> <p>
Seuls les écrits de ces dernières années sont indexés.
Vous pouvez aussi consulter les archives chronologiques de Vous pouvez aussi consulter les archives chronologiques de
<a href="/david/2022/">2022</a>, <a href="/david/2022/">2022</a>,
<a href="/david/2021/">2021</a>, <a href="/david/2021/">2021</a>,
{% endblock content %} {% endblock content %}


{% block extra_body %} {% block extra_body %}
<template id="search-result">
<h2>
<a href="${url}">${title}</a> (${date})
</h2>
<p>${content}</p>
</template>
<script id="search-index" type="application/json"> <script id="search-index" type="application/json">
{{ search_index }} {{ search_index }}
</script> </script>
let input = document.querySelector('#input-search') let input = document.querySelector('#input-search')
let resultList = document.querySelector('#search-results') let resultList = document.querySelector('#search-results')
let searchStatus = document.querySelector('#search-status') let searchStatus = document.querySelector('#search-status')
let searchResultTemplate = document.querySelector('#search-result')


// Make sure required content exists // Make sure required content exists
if (!form || !input || !resultList || !searchStatus || !searchIndex || !stopWords) return
if (!form || !input || !resultList || !searchStatus || !searchIndex || !stopWords || !searchResultTemplate) return


// Create a submit handler // Create a submit handler
form.addEventListener('submit', function (event) { form.addEventListener('submit', function (event) {
/** /**
* Show the search results in the UI * Show the search results in the UI
* @param {Array} results The results to display * @param {Array} results The results to display
* @param {List} regMap Regular expressions for the highlights
*/ */
function showResults (results, regMap) { function showResults (results, regMap) {
let status = 'Aucune publication n’a été trouvée 😢'
let searchResults = ''
if (results.length) { if (results.length) {
const plural = results.length > 1 ? 's' : '' const plural = results.length > 1 ? 's' : ''
searchStatus.innerHTML = `<p>${results.length} publication${plural} trouvée${plural} 🙌</p>`
resultList.innerHTML = results.map(function (result) {
return `
<h2>
<a href="${result.article.url}">${result.article.title}</a>
(${result.article.date})
</h2>
<p>${highlightContent(result.article.content, regMap)}</p>
`
status = `${results.length} publication${plural} trouvée${plural} 🙌`
searchResults = results.map(function (result) {
return interpolate(searchResultTemplate.innerHTML, {
url: result.article.url,
title: highlightText(result.article.title, regMap),
date: result.article.date,
content: highlightText(result.article.content, regMap),
})
}).join('') }).join('')
} else {
searchStatus.innerHTML = '<p>Aucune publication n’a été trouvée 😢<br>Seuls les écrits de ces dernières années sont indexés.</p>'
resultList.innerHTML = ''
} }
searchStatus.innerHTML = status
resultList.innerHTML = searchResults
}

/**
* Get a template from a string
* https://stackoverflow.com/a/41015840
* https://gomakethings.com/html-templates-with-vanilla-javascript/
* @param {String} str The string to interpolate
* @param {Object} params The parameters
* @return {String} The interpolated string
*/
function interpolate (str, params) {
let names = Object.keys(params);
let vals = Object.values(params);
return new Function(...names, `return \`${str}\``)(...vals);
} }


function highlightContent(content, regMap) {
/**
* Highlight the text in the UI
* @param {String} text The content to highlight
* @param {List} regMap Regular expressions for the highlights
*/
function highlightText(text, regMap) {
// TODO: deal with close matches when multiple words are looked for, // TODO: deal with close matches when multiple words are looked for,
// it does not look trivial because you have to memorize positions // it does not look trivial because you have to memorize positions
// then create extracts. // then create extracts.
// For instance: `microsoft github` // For instance: `microsoft github`
const extractBoundariesSize = 100 const extractBoundariesSize = 100
const contentLength = content.length
const textLength = text.length
let extracts = [] let extracts = []
for (let reg of regMap) { for (let reg of regMap) {
const index = content.search(reg)
const index = text.search(reg)
if (index === -1) { continue } if (index === -1) { continue }
let extract = content.substring(
let extract = text.substring(
index - extractBoundariesSize, index - extractBoundariesSize,
index + reg.source.length + extractBoundariesSize index + reg.source.length + extractBoundariesSize
) )
// (is that confusing or closer to what is expected?) // (is that confusing or closer to what is expected?)
extract = extract.replace(reg,`<mark>${reg.source}</mark>`) extract = extract.replace(reg,`<mark>${reg.source}</mark>`)
const prefixEllipsis = index - extractBoundariesSize >= 0 ? '…' : '' const prefixEllipsis = index - extractBoundariesSize >= 0 ? '…' : ''
const suffixEllipsis = index - extractBoundariesSize <= contentLength ? '…' : ''
const suffixEllipsis = index + extractBoundariesSize <= textLength ? '…' : ''
extracts.push(`${prefixEllipsis}${extract}${suffixEllipsis}`) extracts.push(`${prefixEllipsis}${extract}${suffixEllipsis}`)
} }
if (!extracts.length && textLength < 200) {
// If there is no match but it's a short title, return it.
return text
}
return extracts.join('') return extracts.join('')
} }



Loading…
Cancel
Save