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 @@
@summary
+
+ +
+
@body
-
+