mirror of
https://github.com/tcsenpai/obsidiangarden_netlify.git
synced 2025-06-01 02:50:04 +00:00
feat: Search using lunr
Context: having search is almots essential feature for most of the websites. This commit adds very basic functionality based on lunrjs library. It allows to build an use search index. Changes: - Build search index using 11ty template - Adds postbuild step to build lunr index - Adds netlify function to serve as API for search - Adds search page and links to notes pages - Wraps hashtags with proper styling and adds search by hashtag link to them - Adds missing obsidian class for content
This commit is contained in:
parent
052e21193b
commit
fd92473178
@ -184,6 +184,9 @@ module.exports = function(eleventyConfig) {
|
||||
|
||||
eleventyConfig.addPassthroughCopy("src/site/img");
|
||||
eleventyConfig.addPlugin(faviconPlugin, { destination: 'dist' });
|
||||
eleventyConfig.addFilter('jsonify', function (variable) {
|
||||
return JSON.stringify(variable);
|
||||
});
|
||||
|
||||
return {
|
||||
dir: {
|
||||
|
1
.eleventyignore
Normal file
1
.eleventyignore
Normal file
@ -0,0 +1 @@
|
||||
netlify/functions
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
netlify/functions/search/data.json
|
||||
netlify/functions/search/index.json
|
||||
|
@ -2,7 +2,13 @@
|
||||
publish = "dist"
|
||||
command = "npm install && npm run build"
|
||||
|
||||
[[redirects]]
|
||||
from = "/api/*"
|
||||
to = "/.netlify/functions/:splat"
|
||||
status = 200
|
||||
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/404"
|
||||
status = 404
|
||||
status = 404
|
||||
|
||||
|
45
netlify/functions/search/search.js
Normal file
45
netlify/functions/search/search.js
Normal file
@ -0,0 +1,45 @@
|
||||
const lunrjs = require('lunr');
|
||||
|
||||
const handler = async (event) => {
|
||||
try {
|
||||
|
||||
|
||||
const search = event.queryStringParameters.term;
|
||||
if(!search) throw('Missing term query parameter');
|
||||
|
||||
const data = require('./data.json');
|
||||
const indexJson = require('./index.json');
|
||||
const index = lunrjs.Index.load(indexJson);
|
||||
console.log('index made');
|
||||
|
||||
let results = index.search(search);
|
||||
|
||||
results.forEach(r => {
|
||||
r.title = data[r.ref].title;
|
||||
r.content = truncate(data[r.ref].content, 400);
|
||||
r.date = data[r.ref].date;
|
||||
r.url = data[r.ref].url;
|
||||
|
||||
delete r.ref;
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(results),
|
||||
// // more keys you can return:
|
||||
// headers: { "headerName": "headerValue", ... },
|
||||
// isBase64Encoded: true,
|
||||
}
|
||||
} catch (error) {
|
||||
return { statusCode: 500, body: error.toString() }
|
||||
}
|
||||
}
|
||||
|
||||
function truncate(str, size) {
|
||||
//first, remove HTML
|
||||
str = str.replace(/<.*?>/g, '');
|
||||
if(str.length < size) return str;
|
||||
return str.substring(0, size-3) + '...';
|
||||
}
|
||||
|
||||
module.exports = { handler }
|
11
package-lock.json
generated
11
package-lock.json
generated
@ -17,6 +17,7 @@
|
||||
"eleventy-favicon": "^1.1.2",
|
||||
"fs-file-tree": "^1.1.1",
|
||||
"gray-matter": "^4.0.3",
|
||||
"lunr": "^2.3.9",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-footnote": "^3.0.3",
|
||||
"markdown-it-mathjax3": "^4.3.1",
|
||||
@ -3332,6 +3333,11 @@
|
||||
"yallist": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-2.3.1.tgz",
|
||||
@ -8855,6 +8861,11 @@
|
||||
"yallist": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
|
||||
},
|
||||
"luxon": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-2.3.1.tgz",
|
||||
|
@ -9,7 +9,8 @@
|
||||
"watch:eleventy": "cross-env ELEVENTY_ENV=dev eleventy --serve",
|
||||
"build:eleventy": "cross-env ELEVENTY_ENV=prod NODE_OPTIONS=--max-old-space-size=4096 eleventy",
|
||||
"build:sass": "sass src/site/styles:dist/styles",
|
||||
"build": "npm-run-all build:*"
|
||||
"build": "npm-run-all build:*",
|
||||
"postbuild": "node src/site/lunr-index.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@ -29,6 +30,7 @@
|
||||
"eleventy-favicon": "^1.1.2",
|
||||
"fs-file-tree": "^1.1.1",
|
||||
"gray-matter": "^4.0.3",
|
||||
"lunr": "^2.3.9",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-footnote": "^3.0.3",
|
||||
"markdown-it-mathjax3": "^4.3.1",
|
||||
|
8
src/site/_includes/components/wrapTagsScript.njk
Normal file
8
src/site/_includes/components/wrapTagsScript.njk
Normal file
@ -0,0 +1,8 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', init, false);
|
||||
async function init() {
|
||||
const content = document.body.querySelector('.content');
|
||||
content.innerHTML = content.innerHTML.replaceAll(/\s\#(\w+)/gm,
|
||||
`<span class="cm-formatting cm-formatting-hashtag cm-hashtag cm-hashtag-begin cm-meta">#</span><span class="cm-hashtag cm-hashtag-end cm-meta"><a href="/search?q=%23$1~1">$1</a></span>`);
|
||||
}
|
||||
</script>
|
@ -7,6 +7,7 @@ permalink: "notes/{{ page.fileSlug | slugify }}/"
|
||||
<head>
|
||||
<title>{{ page.fileSlug }}</title>
|
||||
{%include "components/pageheader.njk"%}
|
||||
{%include "components/wrapTagsScript.njk"%}
|
||||
</head>
|
||||
<body class="theme-{{meta.baseTheme}} markdown-preview-view markdown-rendered markdown-preview-section">
|
||||
{%include "components/notegrowthhistory.njk"%}
|
||||
@ -27,5 +28,8 @@ permalink: "notes/{{ page.fileSlug | slugify }}/"
|
||||
{%include "components/sidebar.njk"%}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if dgShowBacklinks === true or dgShowLocalGraph === true%}
|
||||
{%include "components/sidebar.njk"%}
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -26,4 +26,4 @@
|
||||
{%endif%}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
20
src/site/lunr-index.js
Normal file
20
src/site/lunr-index.js
Normal file
@ -0,0 +1,20 @@
|
||||
const lunrjs = require('lunr');
|
||||
const path = require('path');
|
||||
|
||||
function createIndex(posts) {
|
||||
return lunrjs(function() {
|
||||
this.ref('id');
|
||||
this.field('title');
|
||||
this.field('content');
|
||||
this.field('date');
|
||||
|
||||
posts.forEach((p,idx) => {
|
||||
p.id = idx;
|
||||
this.add(p);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const data = require('../../netlify/functions/search/data.json');
|
||||
const index = createIndex(data);
|
||||
require('fs').writeFileSync(path.join(__dirname, '../../netlify/functions/search/index.json'), JSON.stringify(index));
|
12
src/site/lunr.njk
Normal file
12
src/site/lunr.njk
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
permalink: netlify/functions/search/data.json
|
||||
permalinkBypassOutputDir: true
|
||||
---
|
||||
[{% for post in collections.note %}
|
||||
{
|
||||
"title": {{post.fileSlug | jsonify | safe }},
|
||||
"date":"{{ post.date }}",
|
||||
"url":"{{ post.url }}",
|
||||
"content": {{ post.templateContent | striptags(true) | jsonify | safe }}
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}]
|
71
src/site/search.njk
Normal file
71
src/site/search.njk
Normal file
@ -0,0 +1,71 @@
|
||||
---
|
||||
title: "Search"
|
||||
permalink: "search/"
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ collections.gardenEntry[0].fileSlug }}</title>
|
||||
{%include "components/pageheader.njk"%}
|
||||
</head>
|
||||
<body class="theme-{{meta.baseTheme}} markdown-preview-view">
|
||||
<div class="content cm-s-obsidian">
|
||||
{% if dgHomeLink !== false%}
|
||||
<a href="/">🏡 Back Home</a>
|
||||
{% endif %}
|
||||
<h1>Search</h1>
|
||||
<p>
|
||||
<label for="term">Type search query</label>
|
||||
<br/>
|
||||
<input type="search" id="term" placeholder="Start typing...">
|
||||
</p>
|
||||
|
||||
<div id="results"></div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', init, false);
|
||||
function debounce(func, timeout = 500){
|
||||
let timer;
|
||||
return (...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => { func.apply(this, args); }, timeout);
|
||||
};
|
||||
}
|
||||
async function init() {
|
||||
field = document.querySelector('#term');
|
||||
field.addEventListener('keydown', debounce(() => search()));
|
||||
resultsDiv = document.querySelector('#results');
|
||||
|
||||
const params = new URL(location.href).searchParams;
|
||||
if (params.get('q')) {
|
||||
field.setAttribute('value', params.get('q'));
|
||||
search();
|
||||
}
|
||||
}
|
||||
async function search() {
|
||||
let search = field.value.trim();
|
||||
if(!search) return;
|
||||
console.log(`search for ${search}`);
|
||||
let searchRequest = await fetch(`/api/search?term=${encodeURIComponent(search)}`);
|
||||
let results = await searchRequest.json();
|
||||
let resultsHTML = '<p><strong>Search Results</strong></p>';
|
||||
if(!results.length) {
|
||||
resultsHTML += '<p>Sorry, there were no results.</p>';
|
||||
resultsDiv.innerHTML = resultsHTML;
|
||||
return;
|
||||
}
|
||||
resultsHTML += '<ul>';
|
||||
// we need to add title, url from ref
|
||||
results.forEach(r => {
|
||||
resultsHTML += `<li><a href="${r.url}">${ r.title }</a> <span>${r.content}</span></li>`;
|
||||
});
|
||||
resultsHTML += '</ul>';
|
||||
resultsDiv.innerHTML = resultsHTML;
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
x
Reference in New Issue
Block a user