@eccenca/gui-elements
Version:
GUI elements based on other libraries, usable in React application, written in Typescript.
79 lines (70 loc) • 3 kB
text/typescript
import { Content, Parent, Root, Text } from "hast";
import { Transformer } from "unified";
import { Node } from "unist";
import { highlighterUtils } from "../../components/Typography/Highlighter";
/**
* Creates a react-markdown reHype plugin that marks text based on a multi-word search query.
*/
const highlightSearchWordsPluginFactory = (searchQuery: string | undefined) => {
const searchStringParts = searchQuery ? highlighterUtils.extractSearchWords(searchQuery) : [];
const multiWordRegex = highlighterUtils.createMultiWordRegex(searchStringParts);
const createTextNode = (text: string): Text => ({ type: "text", value: text });
// Highlight a text node by returning an array of text and mark elements
const highlightTextNode = (textNode: Text): Content[] => {
if (searchStringParts.length === 0) {
return [textNode];
}
const result: Content[] = [];
const text = textNode.value;
let offset = 0;
// loop through matches and add unmatched and matched parts to result array
let matchArray = multiWordRegex.exec(text);
while (matchArray !== null) {
result.push(createTextNode(text.slice(offset, matchArray.index)));
result.push({
type: "element",
tagName: "mark",
properties: {},
children: [createTextNode(matchArray[0])],
});
offset = multiWordRegex.lastIndex;
matchArray = multiWordRegex.exec(text);
}
// Add remaining unmatched string
result.push(createTextNode(text.slice(offset)));
return result;
};
// Upper level highlight function
const highlightRootNode = (node: Root): Root => highlightParentNode(node);
// Highlight function to be called on Parent nodes
const highlightParentNode = <T extends Parent>(parentNode: T): T => {
const newChildren: Content[] = [];
parentNode.children.forEach((child) => {
const highlightedChild = highlightTextNodes(child);
if (Array.isArray(highlightedChild)) {
newChildren.push(...(highlightedChild as Content[]));
} else {
newChildren.push(highlightedChild as Content);
}
});
return { ...parentNode, children: newChildren } as T;
};
// Highlight function to be called on generic inner nodes
const highlightTextNodes = (node: Node): Node | Node[] => {
if (node.type === "text") {
return highlightTextNode(node as Text);
} else {
if ((node as Parent).children) {
return highlightParentNode(node as Parent);
} else {
return node;
}
}
};
return function highlightSearchWords(): Transformer<Root, Root> {
return (input: Root) => highlightRootNode(input);
};
};
export const markdownUtils = {
highlightSearchWordsPluginFactory,
};