Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

display search result with a tree #1

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already present in searchIndexLinks no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but If I don't add it there, there is no way to distinguish if 2 items come from the same page

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me the id property is supposed to make the link between the two or I miss something?

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__");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice symbol usage, I like the idea.
We could add a comment


/**
* 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) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments of that function says that a top section is h1 but here apparently it's h2, I'm lost

return r.item.h2 === undefined;
});
peaBerberian marked this conversation as resolved.
Show resolved Hide resolved
if (topSection === undefined) {
const fakeH1 = structuredClone(value[0]);
peaBerberian marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've no idea what we're trying to do there with those fakeH1 = ... (fakeH1 is an object?), it may need comments

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] = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So from here we consider that h1 cannot be undefined?

}

// 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