summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
authorhmhealey <harrisonmhealey@gmail.com>2015-11-12 16:53:32 -0500
committerhmhealey <harrisonmhealey@gmail.com>2015-11-12 16:53:32 -0500
commit477b4d3edd5f93928c6fdb807ba84697f177a3b9 (patch)
treed24babeae34047592231ce1232524abc4cbb64e4 /web
parent13a251a5ee940383e5a026284275b1f31fb476df (diff)
downloadchat-477b4d3edd5f93928c6fdb807ba84697f177a3b9.tar.gz
chat-477b4d3edd5f93928c6fdb807ba84697f177a3b9.tar.bz2
chat-477b4d3edd5f93928c6fdb807ba84697f177a3b9.zip
Improved highlighting of search flags to handle quotes and flags
Diffstat (limited to 'web')
-rw-r--r--web/react/utils/text_formatting.jsx104
1 files changed, 86 insertions, 18 deletions
diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx
index 705d85cf6..7f1d7175d 100644
--- a/web/react/utils/text_formatting.jsx
+++ b/web/react/utils/text_formatting.jsx
@@ -259,30 +259,73 @@ function autolinkHashtags(text, tokens) {
return output.replace(/(^|\W)(#[a-zA-Z][a-zA-Z0-9.\-_]*)\b/g, replaceHashtagWithToken);
}
-function highlightSearchTerm(text, tokens, searchTerm) {
- let output = text;
+const puncStart = /^[.,()&$!\[\]{}':;\\]+/;
+const puncEnd = /[.,()&$#!\[\]{}':;\\]+$/;
- var newTokens = new Map();
- for (const [alias, token] of tokens) {
- if (token.originalText.indexOf(searchTerm.replace(/\*$/, '')) > -1) {
- const index = tokens.size + newTokens.size;
- const newAlias = `MM_SEARCHTERM${index}`;
+function parseSearchTerms(searchTerm) {
+ let terms = [];
- newTokens.set(newAlias, {
- value: `<span class='search-highlight'>${alias}</span>`,
- originalText: token.originalText
- });
+ let termString = searchTerm;
- output = output.replace(alias, newAlias);
+ while (termString) {
+ let captured;
+
+ // check for a quoted string
+ captured = (/^"(.*?)"/).exec(termString);
+ if (captured) {
+ termString = termString.substring(captured[0].length);
+ terms.push(captured[1]);
+ continue;
+ }
+
+ // check for a search flag (and don't add it to terms)
+ captured = (/^(?:in|from|channel): ?\S+/).exec(termString);
+ if (captured) {
+ termString = termString.substring(captured[0].length);
+ continue;
+ }
+
+ // capture any plain text up until the next quote or search flag
+ captured = (/^.+?(?=\bin|\bfrom|\bchannel|"|$)/).exec(termString);
+ if (captured) {
+ termString = termString.substring(captured[0].length);
+
+ // break the text up into words based on how the server splits them in SqlPostStore.SearchPosts and then discard empty terms
+ terms.push(...captured[0].split(/[ <>+\-\(\)\~\@]/).filter((term) => !!term));
+ continue;
}
+
+ // we should never reach this point since at least one of the regexes should match something in the remaining text
+ throw new Error('Infinite loop in search term parsing: ' + termString);
}
- // the new tokens are stashed in a separate map since we can't add objects to a map during iteration
- for (const newToken of newTokens) {
- tokens.set(newToken[0], newToken[1]);
+ // remove punctuation from each term
+ terms = terms.map((term) => term.replace(puncStart, '').replace(puncEnd, ''));
+
+ return terms;
+}
+
+function convertSearchTermToRegex(term) {
+ let pattern;
+ if (term.endsWith('*')) {
+ pattern = '\\b' + escapeRegex(term.substring(0, term.length - 1));
+ } else {
+ pattern = '\\b' + escapeRegex(term) + '\\b';
}
- function replaceSearchTermWithToken(fullMatch, prefix, word) {
+ return new RegExp(pattern, 'gi');
+}
+
+function highlightSearchTerm(text, tokens, searchTerm) {
+ const terms = parseSearchTerms(searchTerm);
+
+ if (terms.length === 0) {
+ return text;
+ }
+
+ let output = text;
+
+ function replaceSearchTermWithToken(word) {
const index = tokens.size;
const alias = `MM_SEARCHTERM${index}`;
@@ -291,10 +334,35 @@ function highlightSearchTerm(text, tokens, searchTerm) {
originalText: word
});
- return prefix + alias;
+ return alias;
}
- return output.replace(new RegExp(`()(${escapeRegex(searchTerm)})`, 'gi'), replaceSearchTermWithToken);
+ for (const term of terms) {
+ // highlight existing tokens matching search terms
+ var newTokens = new Map();
+ for (const [alias, token] of tokens) {
+ if (token.originalText === term.replace(/\*$/, '')) {
+ const index = tokens.size + newTokens.size;
+ const newAlias = `MM_SEARCHTERM${index}`;
+
+ newTokens.set(newAlias, {
+ value: `<span class='search-highlight'>${alias}</span>`,
+ originalText: token.originalText
+ });
+
+ output = output.replace(alias, newAlias);
+ }
+ }
+
+ // the new tokens are stashed in a separate map since we can't add objects to a map during iteration
+ for (const newToken of newTokens) {
+ tokens.set(newToken[0], newToken[1]);
+ }
+
+ output = output.replace(convertSearchTermToRegex(term), replaceSearchTermWithToken);
+ }
+
+ return output;
}
function replaceTokens(text, tokens) {