Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions lib/search.js
Original file line number Diff line number Diff line change
@@ -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 = `
<a href="${result.path}">
<h3>${result.title}</h3>
<p>${result.content.substring(0, 200)}...</p>
</a>
`;
resultsContainer.appendChild(resultElement);
});
});
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@
"vitest": "3.2.4",
"vitest-environment-clarinet": "2.3.0"
},
"homepage": "https://github.com/clarity-lang/book#readme",
"description": "",
"dependencies": {
"marked-highlight": "^2.2.2"
"fs": "^0.0.1-security",
"markdown-it": "^14.1.0",
"path": "^0.12.7"
}
}
143 changes: 66 additions & 77 deletions scripts/build.js
Original file line number Diff line number Diff line change
@@ -1,89 +1,78 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
const fs = require('fs/promises');
const path = require('path');
const fetch = require('node-fetch');
const { build_page, link_page } = require('../lib/builder');
const generateSearchIndex = require('./genSearchIndex');

import { build_page, link_page } from "../lib/builder.js";
import { fileURLToPath } from "node:url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
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');

const [, , input, output] = process.argv;
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));
}

const input_path = path.resolve(__dirname, input || "../src");
const output_path = path.resolve(__dirname, output || "../build");
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));

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));
}
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');
}

async function build(input_path, output_path, template) {
console.log("Starting build");
const summary = await fs.readFile(
path.resolve(__dirname, "../src/SUMMARY.md"),
"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'));

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('Generating search index...');
await generateSearchIndex();

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"
);
});
}
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)
);

fs.copyFile(
path.resolve(__dirname, "../public/clarinet-worker.js"),
path.resolve(__dirname, "../build/clarinet-worker.js")
);
fs.readFile(path.resolve(__dirname,'../templates/base.html'),'utf8')
.then(template => build(input_path,output_path,template));
36 changes: 36 additions & 0 deletions scripts/genSearchIndex.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion src/ch06-03-unwrap-flavours.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
11 changes: 8 additions & 3 deletions templates/base.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<html>
<head>
<script src="/client.min.js" async></script>
<link rel="stylesheet" href="/style.css"/>
<script src="./client.min.js" async></script>
<link rel="stylesheet" href="./style.css"/>
<script src="./search.js" defer></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@title - Clarity Book</title>
</head>
Expand All @@ -12,8 +13,12 @@
<label id="mobile_menu_overlay" for="mobile_menu_toggle"></label>
<section id="summary">@summary</section>
<section id="article">
<div id="search-bar-container">
<input type="text" id="search-bar" placeholder="Search..." />
<div id="search-results"></div>
</div>
<article>@body</article>
</section>
</section>
</div>
</body>
</html>