Improve how much is highlighted when search settings
Proposal
Highlight only the search term and not the whole line.
Current | Proposal |
---|---|
Implementation guide
The current implementation of search settings captures the entire text node that contains the text that matches the search criteria. To refine the highlighted results, we have to split the text node that matches the search term in three subnodes:
- The text within the node before the actual match.
- The text within the node that matches the search term.
- The text within the node after the actual match.
Here’s a small prototype that demonstrates how to do it. This is not production ready code and we should optimize and make this code more maintainable.
diff --git a/app/assets/javascripts/search_settings/components/search_settings.vue b/app/assets/javascripts/search_settings/components/search_settings.vue
index 116967a62c8..ae4233d8f8e 100644
--- a/app/assets/javascripts/search_settings/components/search_settings.vue
+++ b/app/assets/javascripts/search_settings/components/search_settings.vue
@@ -9,6 +9,9 @@ const findSettingsSection = (sectionSelector, node) => {
return node.parentElement.closest(sectionSelector);
};
+const findSections = (sectionSelector, matches) =>
+ uniq(matches.map((match) => findSettingsSection(sectionSelector, match.parentElement)));
+
const restoreExpansionState = ({ expandSection, collapseSection }) => {
origExpansions.forEach((isExpanded, section) => {
if (isExpanded) {
@@ -50,17 +53,27 @@ const hideSectionsExcept = (sectionSelector, visibleSections) => {
});
};
-const highlightElements = (elements = []) => {
- elements.forEach((element) => element.classList.add(HIGHLIGHT_CLASS));
+const highlightElements = (searchTerm, matches = []) => {
+ matches.forEach((match) => {
+ const { parentElement } = match;
+ const searchTermPosition = match.wholeText.indexOf(searchTerm);
+ const matchedText = match.splitText(searchTermPosition);
+ const unmatchedTextAfter = matchedText.splitText(searchTerm.length);
+ const highlightedElement = document.createElement('span');
+
+ highlightedElement.classList.add(HIGHLIGHT_CLASS);
+ highlightedElement.append(matchedText);
+
+ parentElement.append(highlightedElement, unmatchedTextAfter);
+ });
};
-const displayResults = ({ sectionSelector, expandSection }, matches) => {
- const elements = matches.map((match) => match.parentElement);
- const sections = uniq(elements.map((element) => findSettingsSection(sectionSelector, element)));
+const displayResults = ({ searchTerm, sectionSelector, expandSection }, matches) => {
+ const sections = findSections(sectionSelector, matches);
hideSectionsExcept(sectionSelector, sections);
sections.forEach(expandSection);
- highlightElements(elements);
+ highlightElements(searchTerm, matches);
};
const clearResults = (params) => {
@@ -116,21 +129,23 @@ export default {
},
methods: {
search(value) {
+ const searchTerm = value.toLowerCase();
const displayOptions = {
sectionSelector: this.sectionSelector,
expandSection: this.expandSection,
collapseSection: this.collapseSection,
isExpanded: this.isExpandedFn,
+ searchTerm,
};
- this.searchTerm = value;
+ this.searchTerm = searchTerm;
clearResults(displayOptions);
if (value.length) {
saveExpansionState(document.querySelectorAll(this.sectionSelector), displayOptions);
- displayResults(displayOptions, search(this.searchRoot, value));
+ displayResults(displayOptions, search(this.searchRoot, searchTerm));
} else {
restoreExpansionState(displayOptions);
}
Edited by Enrique Alcántara