-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Changes from all commits
49f7c34
7a6df87
e8ce435
a9ab7c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -439,6 +439,7 @@ function initializeSearchEngine() { | |
anchorH3: elt.anchorH3, | ||
}); | ||
pageIndex.push({ | ||
file: res[i].file, | ||
h1: elt.h1, | ||
h2: elt.h2, | ||
h3: elt.h3, | ||
|
@@ -478,6 +479,8 @@ function initializeSearchEngine() { | |
return searchState.promise; | ||
} | ||
|
||
const __DEFAULT_SYMBOL__ = Symbol("__DEFAULT__"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice symbol usage, I like the idea. |
||
|
||
/** | ||
* Update search result in the search result HTMLElement according to the given | ||
* value. | ||
|
@@ -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) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comments of that function says that a |
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.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] = {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So from here we consider that |
||
} | ||
|
||
// 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; | ||
} | ||
|
||
/** | ||
|
@@ -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; | ||
} |
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?