From e39088d60001e525eec27a47db5da9dcf4e2c6dd Mon Sep 17 00:00:00 2001
From: fabohax <40230@pm.me>
Date: Fri, 25 Apr 2025 02:21:32 -0300
Subject: [PATCH] Adding Searching Feature
---
lib/search.js | 33 ++++++++++
package.json | 9 ++-
scripts/build.js | 110 ++++++++++++++++++---------------
scripts/genSearchIndex.js | 36 +++++++++++
src/ch06-03-unwrap-flavours.md | 2 +-
src/style.css | 25 ++++++++
templates/base.html | 11 +++-
7 files changed, 170 insertions(+), 56 deletions(-)
create mode 100644 lib/search.js
create mode 100644 scripts/genSearchIndex.js
diff --git a/lib/search.js b/lib/search.js
new file mode 100644
index 0000000..60bc571
--- /dev/null
+++ b/lib/search.js
@@ -0,0 +1,33 @@
+async function loadSearchIndex() {
+ const response = await fetch('./search-index.json');
+ return response.json();
+}
+
+function search(query, index) {
+ return index.filter(entry =>
+ entry.title.toLowerCase().includes(query.toLowerCase()) ||
+ entry.content.toLowerCase().includes(query.toLowerCase())
+ );
+}
+
+document.getElementById('search-bar').addEventListener('input', async (event) => {
+ const query = event.target.value;
+ const resultsContainer = document.getElementById('search-results');
+ resultsContainer.innerHTML = '';
+
+ if (query.length < 3) return; // Minimum query length
+
+ const index = await loadSearchIndex();
+ const results = search(query, index);
+
+ results.forEach(result => {
+ const resultElement = document.createElement('div');
+ resultElement.innerHTML = `
+
+ ${result.title}
+ ${result.content.substring(0, 200)}...
+
+ `;
+ resultsContainer.appendChild(resultElement);
+ });
+});
\ No newline at end of file
diff --git a/package.json b/package.json
index 992c4c6..01f36b1 100644
--- a/package.json
+++ b/package.json
@@ -38,5 +38,10 @@
"url": "https://github.com/clarity-lang/book/issues"
},
"homepage": "https://github.com/clarity-lang/book#readme",
- "description": ""
-}
\ No newline at end of file
+ "description": "",
+ "dependencies": {
+ "fs": "^0.0.1-security",
+ "markdown-it": "^14.1.0",
+ "path": "^0.12.7"
+ }
+}
diff --git a/scripts/build.js b/scripts/build.js
index f0937b2..1708b14 100755
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -2,66 +2,76 @@
const fs = require('fs/promises');
const path = require('path');
const fetch = require('node-fetch');
-const {build_page,link_page} = require('../lib/builder');
+const { build_page, link_page } = require('../lib/builder');
+const generateSearchIndex = require('./genSearchIndex');
const repl_binary_url = 'https://clarity-repl.s3.amazonaws.com/clarity_repl.wasm';
const [,,input,output] = process.argv;
const input_path = path.resolve(__dirname,input || '../src');
const output_path = path.resolve(__dirname,output || '../build');
+const lib_path = path.resolve(__dirname, '../lib');
async function readdir_sorted(dir,subdir)
- {
- let entries = [];
- for await (const d of await fs.opendir(dir))
- {
- const entry = path.join(dir, d.name);
- if (d.isDirectory())
- entries = entries.concat(await readdir_sorted(entry,true));
- else if (d.isFile())
- entries.push({entry,name:d.name});
- }
- return subdir ? entries : entries.sort((a,b) => a.entry.localeCompare(b.entry));
- }
+ {
+ let entries = [];
+ for await (const d of await fs.opendir(dir))
+ {
+ const entry = path.join(dir, d.name);
+ if (d.isDirectory())
+ entries = entries.concat(await readdir_sorted(entry,true));
+ else if (d.isFile())
+ entries.push({entry,name:d.name});
+ }
+ return subdir ? entries : entries.sort((a,b) => a.entry.localeCompare(b.entry));
+ }
async function build(input_path,output_path,template)
- {
- console.log('Starting build');
- const summary = await fs.readFile(path.resolve(__dirname,'../src/SUMMARY.md'),'utf8');
- for (const {entry,name} of await readdir_sorted(input_path))
- {
- console.log(entry);
- await fs.mkdir(path.join(output_path,entry.substr(input_path.length,entry.length-input_path.length-name.length)),{recursive:true});
- const output_file_path = path.join(output_path,entry.substr(input_path.length));
+ {
+ console.log('Starting build');
+ const summary = await fs.readFile(path.resolve(__dirname,'../src/SUMMARY.md'),'utf8');
+ for (const {entry,name} of await readdir_sorted(input_path))
+ {
+ console.log(entry);
+ await fs.mkdir(path.join(output_path,entry.substr(input_path.length,entry.length-input_path.length-name.length)),{recursive:true});
+ const output_file_path = path.join(output_path,entry.substr(input_path.length));
- entry.substr(-3) === '.md'
- ? fs.writeFile(output_file_path.slice(0,-3)+'.html',await build_page(entry,{template,summary}),'utf8')
- : fs.copyFile(entry,output_file_path);
- if (name === 'title-page.md')
- fs.writeFile(path.join(output_path,'index.html'),await build_page(entry,{template,summary}),'utf8');
- }
- console.log('Linking chapters');
- const chapter_files = (await readdir_sorted(output_path)).filter(d => d.name.match(/^ch[0-9]+-[0-9]+-[\S]+\.html$/));
- chapter_files.forEach(async ({entry}, index) =>
- {
- const [prev, next] = [chapter_files[index-1], chapter_files[index+1]];
- if (prev || next)
- fs.writeFile(entry,await link_page(entry,prev && prev.name,next && next.name),'utf8');
- });
- const repl_file = path.join(output_path,'repl/clarity_repl_bg.wasm');
- let repl_exists = false;
- try
- {
- repl_exists = (await fs.stat(repl_file)).code !== 'ENOENT';
- }
- catch (error){}
- if (!repl_exists)
- {
- console.log(`Downloading REPL WASM from ${repl_binary_url}`);
- await fs.mkdir(path.join(output_path,'repl'));
- fs.writeFile(repl_file,Buffer.from(await (await fetch(repl_binary_url)).arrayBuffer()));
- }
- }
+ entry.substr(-3) === '.md'
+ ? fs.writeFile(output_file_path.slice(0,-3)+'.html',await build_page(entry,{template,summary}),'utf8')
+ : fs.copyFile(entry,output_file_path);
+ if (name === 'title-page.md')
+ fs.writeFile(path.join(output_path,'index.html'),await build_page(entry,{template,summary}),'utf8');
+ }
+
+ await fs.mkdir(output_path, { recursive: true });
+ // Copy search.js to the build directory
+ await fs.copyFile(path.join(lib_path, 'search.js'), path.join(output_path, 'search.js'));
+
+ console.log('Generating search index...');
+ await generateSearchIndex();
+
+ console.log('Linking chapters');
+ const chapter_files = (await readdir_sorted(output_path)).filter(d => d.name.match(/^ch[0-9]+-[0-9]+-[\S]+\.html$/));
+ chapter_files.forEach(async ({entry}, index) =>
+ {
+ const [prev, next] = [chapter_files[index-1], chapter_files[index+1]];
+ if (prev || next)
+ fs.writeFile(entry,await link_page(entry,prev && prev.name,next && next.name),'utf8');
+ });
+ const repl_file = path.join(output_path,'repl/clarity_repl_bg.wasm');
+ let repl_exists = false;
+ try
+ {
+ repl_exists = (await fs.stat(repl_file)).code !== 'ENOENT';
+ }
+ catch (error){}
+ if (!repl_exists)
+ {
+ console.log(`Downloading REPL WASM from ${repl_binary_url}`);
+ await fs.mkdir(path.join(output_path,'repl'));
+ fs.writeFile(repl_file,Buffer.from(await (await fetch(repl_binary_url)).arrayBuffer()));
+ }
+ }
fs.readFile(path.resolve(__dirname,'../templates/base.html'),'utf8')
- .then(template => build(input_path,output_path,template));
+ .then(template => build(input_path,output_path,template));
diff --git a/scripts/genSearchIndex.js b/scripts/genSearchIndex.js
new file mode 100644
index 0000000..ee1992c
--- /dev/null
+++ b/scripts/genSearchIndex.js
@@ -0,0 +1,36 @@
+const fs = require('fs');
+const path = require('path');
+const markdownIt = require('markdown-it')();
+
+const srcDir = path.join(__dirname, '../src');
+const outputFile = path.join(__dirname, '../build/search-index.json');
+
+async function generateSearchIndex() {
+ const index = [];
+
+ function processMarkdownFile(filePath) {
+ const content = fs.readFileSync(filePath, 'utf-8');
+ const html = markdownIt.render(content);
+ const titleMatch = content.match(/^#\s+(.*)/m);
+ const title = titleMatch ? titleMatch[1] : 'Untitled';
+ const relativePath = path.relative(srcDir, filePath);
+
+ index.push({
+ title,
+ content: html.replace(/<[^>]*>/g, ''), // Strip HTML tags
+ path: relativePath.replace(/\.md$/, '.html'),
+ });
+ }
+
+ fs.readdirSync(srcDir).forEach(file => {
+ if (file.endsWith('.md')) {
+ processMarkdownFile(path.join(srcDir, file));
+ }
+ });
+
+ await fs.promises.mkdir(path.dirname(outputFile), { recursive: true });
+ fs.writeFileSync(outputFile, JSON.stringify(index, null, 2));
+ console.log('Search index generated:', outputFile);
+}
+
+module.exports = generateSearchIndex;
\ No newline at end of file
diff --git a/src/ch06-03-unwrap-flavours.md b/src/ch06-03-unwrap-flavours.md
index c3e7028..26bf624 100644
--- a/src/ch06-03-unwrap-flavours.md
+++ b/src/ch06-03-unwrap-flavours.md
@@ -5,7 +5,7 @@ in a slightly different manner.
`unwrap!` takes an `optional` or `response` as the first input and a throw value
as the second input. It follows the same unwrapping behaviour of `try!` , but
-instead of propagating the `none` or the `err` it will return the the throw
+instead of propagating the `none` or the `err` it will return the throw
value instead.
```Clarity
diff --git a/src/style.css b/src/style.css
index 5166bd3..0bf5afe 100644
--- a/src/style.css
+++ b/src/style.css
@@ -668,4 +668,29 @@ pre,
body.playground .code .result{
flex-basis: 50%;
}
+}
+
+#search-bar-container{
+ display: block;
+ justify-content: center;
+ align-items: center;
+ margin: 0 0 2em;
+}
+
+#search-bar{
+ display: block;
+}
+
+#search-results{
+ display:block;
+ margin: 2em 0;
+}
+
+input[type="text"]{
+ display:block;
+ border: 1px solid #777;
+ border-radius: 1em;
+ padding: 1em;
+ width: 100%;
+ box-sizing: border-box;
}
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index 26526a1..bb4cf56 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -1,7 +1,8 @@
-
-
+
+
+
@title - Clarity Book
@@ -12,8 +13,12 @@
+