Improved highlighting of search flags to handle quotes and flags
Этот коммит содержится в:
@@ -259,30 +259,73 @@ function autolinkHashtags(text, tokens) {
|
|||||||
return output.replace(/(^|\W)(#[a-zA-Z][a-zA-Z0-9.\-_]*)\b/g, replaceHashtagWithToken);
|
return output.replace(/(^|\W)(#[a-zA-Z][a-zA-Z0-9.\-_]*)\b/g, replaceHashtagWithToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const puncStart = /^[.,()&$!\[\]{}':;\\]+/;
|
||||||
|
const puncEnd = /[.,()&$#!\[\]{}':;\\]+$/;
|
||||||
|
|
||||||
|
function parseSearchTerms(searchTerm) {
|
||||||
|
let terms = [];
|
||||||
|
|
||||||
|
let termString = searchTerm;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegExp(pattern, 'gi');
|
||||||
|
}
|
||||||
|
|
||||||
function highlightSearchTerm(text, tokens, searchTerm) {
|
function highlightSearchTerm(text, tokens, searchTerm) {
|
||||||
|
const terms = parseSearchTerms(searchTerm);
|
||||||
|
|
||||||
|
if (terms.length === 0) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
let output = text;
|
let output = text;
|
||||||
|
|
||||||
var newTokens = new Map();
|
function replaceSearchTermWithToken(word) {
|
||||||
for (const [alias, token] of tokens) {
|
|
||||||
if (token.originalText.indexOf(searchTerm.replace(/\*$/, '')) > -1) {
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceSearchTermWithToken(fullMatch, prefix, word) {
|
|
||||||
const index = tokens.size;
|
const index = tokens.size;
|
||||||
const alias = `MM_SEARCHTERM${index}`;
|
const alias = `MM_SEARCHTERM${index}`;
|
||||||
|
|
||||||
@@ -291,10 +334,35 @@ function highlightSearchTerm(text, tokens, searchTerm) {
|
|||||||
originalText: word
|
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) {
|
function replaceTokens(text, tokens) {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user