Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
248 changes: 180 additions & 68 deletions build/scripts/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ function initializeSearchEngine() {
anchorH3: elt.anchorH3,
});
pageIndex.push({
file: res[i].file,
h1: elt.h1,
h2: elt.h2,
h3: elt.h3,
Expand Down Expand Up @@ -478,6 +479,8 @@ function initializeSearchEngine() {
return searchState.promise;
}

const __DEFAULT_SYMBOL__ = Symbol("__DEFAULT__");

/**
* Update search result in the search result HTMLElement according to the given
* value.
Expand Down Expand Up @@ -519,84 +522,137 @@ function updateSearchResults(value) {
'<div class="message">' + "No result for that search." + "</div>";
return;
}

// only consider the first 30 results
const searchResultSlice = searchResults.slice(0, 29);
const searchResultSorted = reorderSearchResultByGroup(searchResultSlice);
searchResultsElt.innerHTML = "";

for (let resIdx = 0; resIdx < searchResults.length && resIdx < 30; resIdx++) {
const res = searchResults[resIdx];
const links = searchIndexLinks[+res.refIndex];
const contentDiv = document.createElement("div");
contentDiv.className = "search-result-item";

const locationDiv = document.createElement("div");
locationDiv.className = "search-result-location";

let needSeparator = false;
if (res.item.h1 !== undefined && res.item.h1 !== "") {
let linkH1;
if (links.anchorH1 !== undefined) {
const href = rootUrl + "/" + links.file + "#" + links.anchorH1;
linkH1 = document.createElement("a");
linkH1.href = href;
} else {
linkH1 = document.createElement("span");
for (const file of Object.values(searchResultSorted)) {
for (const h1 of Object.values(file)) {
if (h1[__DEFAULT_SYMBOL__]) {
const elem = createResultElement(h1[__DEFAULT_SYMBOL__]);
searchResultsElt.appendChild(elem);
}
linkH1.className = "h1";
linkH1.textContent = res.item.h1;
locationDiv.appendChild(linkH1);
needSeparator = true;
}
for (const [keyH2, valueH2] of Object.entries(h1)) {
if (keyH2 === __DEFAULT_SYMBOL__) {
continue;
}
if (valueH2[__DEFAULT_SYMBOL__]) {
const elem = createResultElement(valueH2[__DEFAULT_SYMBOL__]);
searchResultsElt.appendChild(elem);
}

if (res.item.h2 !== undefined && res.item.h2 !== "") {
if (needSeparator) {
const separatorSpan = document.createElement("span");
separatorSpan.textContent = " > ";
locationDiv.appendChild(separatorSpan);
needSeparator = false;
}
let linkH2;
if (links.anchorH2 !== undefined) {
const href = rootUrl + "/" + links.file + "#" + links.anchorH2;
linkH2 = document.createElement("a");
linkH2.href = href;
} else {
linkH2 = document.createElement("span");
for (const [keyH3, valueH3] of Object.entries(valueH2)) {
if (keyH3 === __DEFAULT_SYMBOL__) {
continue;
}
valueH3.forEach((val) => {
const elem = createResultElement(val);
searchResultsElt.appendChild(elem);
});
}
}
linkH2.className = "h2";
linkH2.textContent = res.item.h2;
locationDiv.appendChild(linkH2);
needSeparator = true;
}
if (res.item.h3 !== undefined && res.item.h3 !== "") {
if (needSeparator) {
const separatorSpan = document.createElement("span");
separatorSpan.textContent = " > ";
locationDiv.appendChild(separatorSpan);
needSeparator = false;
}
}

/**
* Add missing top sections headers (h1) in search results.
* Search result can be h1, h2 or h3, but the search algorithm can
* find a match for a h3 section and not for the h1.
* To not display h3 orphan in the tree view, we manually add to
* the search result the h1 that is a parent to that h3.
* @param {array} searchResults
* @returns
*/
function addMissingTopSections(searchResults) {
const h1Sections = {};
const missingH1 = [];
for (result of searchResults) {
const keyName = `${result.item.file}-${result.item.h1}`;
if (!h1Sections[keyName]) {
h1Sections[keyName] = [];
}
h1Sections[keyName].push(result);
}

for (const value of Object.values(h1Sections)) {
const topSection = value.find((r) => {
return r.item.h2 === undefined;
});
if (topSection === undefined) {
const fakeH1 = structuredClone(value[0]);
fakeH1.item.h2 = undefined;
fakeH1.item.h3 = undefined;
fakeH1.item.body = "";
missingH1.push(fakeH1);
}
}
return searchResults.concat(missingH1);
}

/**
* Re-orders search results by grouping them according
* to their shared section headings (h1, h2, h3).
* @param {array} searchResults The array of search results to be re-ordered.
* @returns An array re-ordered based on section headings.
*
* @example
*/
function reorderSearchResultByGroup(searchResults) {
const results = addMissingTopSections(searchResults);

// group by h1
const groupedSearchResult = groupItems(results);

return groupedSearchResult;
}
/**
* Groups items in an array based on their h1, h2 and h3 titles.
* @param {array} array The array to group.
* @returns An object containing items grouped.
*/
function groupItems(results) {
const grouping = {};

results.forEach((res) => {
let item = res.item;

if (!grouping[item.file]) {
grouping[item.file] = {};
}
// Create the h1 key
if (!grouping[item.file][item.h1]) {
grouping[item.file][item.h1] = {};
}

// Create the h2 key, if exists
if (item.h2) {
if (!grouping[item.file][item.h1][item.h2]) {
grouping[item.file][item.h1][item.h2] = {};
}
let linkH3;
if (links.anchorH3 !== undefined) {
const href = rootUrl + "/" + links.file + "#" + links.anchorH3;
linkH3 = document.createElement("a");
linkH3.href = href;

// Create the h3 key, if exists
if (item.h3) {
if (!grouping[item.file][item.h1][item.h2][item.h3]) {
grouping[item.file][item.h1][item.h2][item.h3] = [];
}
grouping[item.file][item.h1][item.h2][item.h3].push(item);
} else {
linkH3 = document.createElement("span");
// If no h3, use '__DEFAULT__'
if (!grouping[item.file][item.h1][item.h2][__DEFAULT_SYMBOL__]) {
grouping[item.file][item.h1][item.h2][__DEFAULT_SYMBOL__] = item;
}
}
} else {
// If no h2, use '__DEFAULT__' under h1
if (!grouping[item.file][item.h1][__DEFAULT_SYMBOL__]) {
grouping[item.file][item.h1][__DEFAULT_SYMBOL__] = item;
}
linkH3.className = "h3";
linkH3.textContent = res.item.h3;
locationDiv.appendChild(linkH3);
}
const bodyDiv = document.createElement("div");
bodyDiv.className = "search-result-body";
let body = res.item.body ?? "";
if (body.length > 300) {
body = body.substring(0, 300) + "...";
}
bodyDiv.textContent = body;

contentDiv.appendChild(locationDiv);
contentDiv.appendChild(bodyDiv);
searchResultsElt.appendChild(contentDiv);
}
});
return grouping;
}

/**
Expand Down Expand Up @@ -1031,3 +1087,59 @@ function getSearchIconElements() {
function getSearchWrapperElement() {
return document.getElementById("search-wrapper");
}

function createResultElement(item) {
const links = searchIndexLinks[+item.id];
let elementLevel = item.h2 ? (item.h3 ? "h3" : "h2") : "h1";
const contentDiv = document.createElement("div");
contentDiv.className = "search-result-item";
const locationDiv = document.createElement("div");
locationDiv.className = "search-result-location";

let href;
let textContent;
if (elementLevel === "h3") {
contentDiv.classList.add("search-result-item-is-h3");
if (links.anchorH3 !== undefined) {
href = rootUrl + "/" + links.file + "#" + links.anchorH3;
}
textContent = item.h3;
} else if (elementLevel === "h2") {
contentDiv.classList.add("search-result-item-is-h2");
if (links.anchorH2 !== undefined) {
href = rootUrl + "/" + links.file + "#" + links.anchorH2;
}
textContent = item.h2;
} else if (elementLevel === "h1") {
contentDiv.classList.add("search-result-item-is-h1");
if (links.anchorH1 !== undefined) {
href = rootUrl + "/" + links.file + "#" + links.anchorH1;
}
textContent = item.h1;
}

let anchorElement;
if (href) {
anchorElement = document.createElement("a");
anchorElement.href = href;
} else {
anchorElement = document.createElement("span");
}

anchorElement.textContent = textContent;
anchorElement.className = elementLevel;
locationDiv.appendChild(anchorElement);

const bodyDiv = document.createElement("div");
bodyDiv.className = "search-result-body";
let body = item.body ?? "";
if (body.length > 300) {
body = body.substring(0, 300) + "...";
}
bodyDiv.textContent = body;

contentDiv.appendChild(locationDiv);
contentDiv.appendChild(bodyDiv);

return contentDiv;
}
19 changes: 19 additions & 0 deletions build/styles/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,25 @@ table tr td {
margin: 17px 0px;
}

.search-result-item-is-h2 {
padding-left: 40px;
}

.search-result-item-is-h3 {
padding-left: 80px;
}

.search-result-item-is-h1 .search-result-location::before {
content: "📄";
padding-right: 8px;
}

.search-result-item-is-h2 .search-result-location::before,
.search-result-item-is-h3 .search-result-location::before {
content: "#️";
padding-right: 8px;
}

#search-results {
margin-top: 20px;
padding-bottom: 20px;
Expand Down
2 changes: 1 addition & 1 deletion src/create_documentation_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export default async function createDocumentationPage({
tocMd,
nbTocElements,
} = await parseMD(data, inputDir, outputDir, baseOutDir, linkTranslator);
const searchData = getSearchDataForContent(resHtml);
const searchData = getSearchDataForContent(resHtml, outputUrlFromRoot);
searchIndex.push({
file: outputUrlFromRoot,
index: searchData,
Expand Down
5 changes: 5 additions & 0 deletions src/get_search_data_for_content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AnyNode } from "cheerio";
import { load } from "cheerio";

export interface FileSearchIndex {
fileURL: string;
h1: string | undefined;
h2?: string | undefined;
h3?: string | undefined;
Expand All @@ -18,6 +19,7 @@ export interface FileSearchIndex {
*/
export default function getSearchDataForContent(
contentHtml: string,
fileURL: string,
): FileSearchIndex[] {
const indexForFile: FileSearchIndex[] = [];
const $ = load(contentHtml);
Expand Down Expand Up @@ -87,6 +89,7 @@ export default function getSearchDataForContent(
if (currentLevel === "h3") {
const body = currentBody.length > 0 ? currentBody.join(" ") : "";
indexForFile.push({
fileURL,
h1: currentH1,
h2: currentH2,
h3: currentH3,
Expand All @@ -98,6 +101,7 @@ export default function getSearchDataForContent(
} else if (currentLevel === "h2") {
const body = currentBody.length > 0 ? currentBody.join(" ") : "";
indexForFile.push({
fileURL,
h1: currentH1,
h2: currentH2,
body,
Expand All @@ -107,6 +111,7 @@ export default function getSearchDataForContent(
} else if (currentLevel === "h1") {
const body = currentBody.length > 0 ? currentBody.join(" ") : "";
indexForFile.push({
fileURL,
h1: currentH1,
body,
anchorH1: currentH1Anchor,
Expand Down