mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 22:43:33 +08:00
FIX: Make replace watched words work with wildcard (#13084)
Watched words are always regular expressions, despite watched_words_ _regular_expressions being enabled or not. Internally, wildcard characters are replaced with a regular expression that matches any non whitespace character.
This commit is contained in:
@ -6,50 +6,30 @@ function isLinkClose(str) {
|
|||||||
return /^<\/a\s*>/i.test(str);
|
return /^<\/a\s*>/i.test(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findAllMatches(text, matchers, useRegExp) {
|
function findAllMatches(text, matchers) {
|
||||||
const matches = [];
|
const matches = [];
|
||||||
|
|
||||||
if (useRegExp) {
|
const maxMatches = 100;
|
||||||
const maxMatches = 100;
|
let count = 0;
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
matchers.forEach((matcher) => {
|
matchers.forEach((matcher) => {
|
||||||
let match;
|
let match;
|
||||||
while (
|
while (
|
||||||
(match = matcher.pattern.exec(text)) !== null &&
|
(match = matcher.pattern.exec(text)) !== null &&
|
||||||
count++ < maxMatches
|
count++ < maxMatches
|
||||||
) {
|
) {
|
||||||
matches.push({
|
matches.push({
|
||||||
index: match.index,
|
index: match.index,
|
||||||
text: match[0],
|
text: match[0],
|
||||||
replacement: matcher.replacement,
|
replacement: matcher.replacement,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
const lowerText = text.toLowerCase();
|
|
||||||
matchers.forEach((matcher) => {
|
|
||||||
const lowerPattern = matcher.pattern.toLowerCase();
|
|
||||||
let index = -1;
|
|
||||||
while ((index = lowerText.indexOf(lowerPattern, index + 1)) !== -1) {
|
|
||||||
matches.push({
|
|
||||||
index,
|
|
||||||
text: text.substr(index, lowerPattern.length),
|
|
||||||
replacement: matcher.replacement,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches.sort((a, b) => a.index - b.index);
|
return matches.sort((a, b) => a.index - b.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setup(helper) {
|
export function setup(helper) {
|
||||||
helper.registerOptions((opts, siteSettings) => {
|
|
||||||
opts.watchedWordsRegularExpressions =
|
|
||||||
siteSettings.watched_words_regular_expressions;
|
|
||||||
});
|
|
||||||
|
|
||||||
helper.registerPlugin((md) => {
|
helper.registerPlugin((md) => {
|
||||||
const replacements = md.options.discourse.watchedWordsReplacements;
|
const replacements = md.options.discourse.watchedWordsReplacements;
|
||||||
if (!replacements) {
|
if (!replacements) {
|
||||||
@ -57,9 +37,7 @@ export function setup(helper) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const matchers = Object.keys(replacements).map((word) => ({
|
const matchers = Object.keys(replacements).map((word) => ({
|
||||||
pattern: md.options.discourse.watchedWordsRegularExpressions
|
pattern: new RegExp(word, "gi"),
|
||||||
? new RegExp(word, "gi")
|
|
||||||
: word,
|
|
||||||
replacement: replacements[word],
|
replacement: replacements[word],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -110,12 +88,7 @@ export function setup(helper) {
|
|||||||
if (currentToken.type === "text") {
|
if (currentToken.type === "text") {
|
||||||
const text = currentToken.content;
|
const text = currentToken.content;
|
||||||
const matches = (cache[text] =
|
const matches = (cache[text] =
|
||||||
cache[text] ||
|
cache[text] || findAllMatches(text, matchers));
|
||||||
findAllMatches(
|
|
||||||
text,
|
|
||||||
matchers,
|
|
||||||
md.options.discourse.watchedWordsRegularExpressions
|
|
||||||
));
|
|
||||||
|
|
||||||
// Now split string to nodes
|
// Now split string to nodes
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
|
@ -187,7 +187,7 @@ class SiteSerializer < ApplicationSerializer
|
|||||||
end
|
end
|
||||||
|
|
||||||
def watched_words_replace
|
def watched_words_replace
|
||||||
WordWatcher.get_cached_words(:replace)
|
WordWatcher.word_matcher_regexps(:replace)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -51,6 +51,12 @@ class WordWatcher
|
|||||||
nil # Admin will be alerted via admin_dashboard_data.rb
|
nil # Admin will be alerted via admin_dashboard_data.rb
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.word_matcher_regexps(action)
|
||||||
|
if words = get_cached_words(action)
|
||||||
|
words.map { |w, r| [word_to_regexp(w), r] }.to_h
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.word_to_regexp(word)
|
def self.word_to_regexp(word)
|
||||||
if SiteSetting.watched_words_regular_expressions?
|
if SiteSetting.watched_words_regular_expressions?
|
||||||
# Strip ruby regexp format if present, we're going to make the whole thing
|
# Strip ruby regexp format if present, we're going to make the whole thing
|
||||||
|
@ -173,7 +173,7 @@ module PrettyText
|
|||||||
__optInput.emojiUnicodeReplacer = __emojiUnicodeReplacer;
|
__optInput.emojiUnicodeReplacer = __emojiUnicodeReplacer;
|
||||||
__optInput.lookupUploadUrls = __lookupUploadUrls;
|
__optInput.lookupUploadUrls = __lookupUploadUrls;
|
||||||
__optInput.censoredRegexp = #{WordWatcher.word_matcher_regexp(:censor)&.source.to_json};
|
__optInput.censoredRegexp = #{WordWatcher.word_matcher_regexp(:censor)&.source.to_json};
|
||||||
__optInput.watchedWordsReplacements = #{WordWatcher.get_cached_words(:replace).to_json};
|
__optInput.watchedWordsReplacements = #{WordWatcher.word_matcher_regexps(:replace).to_json};
|
||||||
JS
|
JS
|
||||||
|
|
||||||
if opts[:topicId]
|
if opts[:topicId]
|
||||||
|
@ -1401,11 +1401,19 @@ HTML
|
|||||||
after(:all) { Discourse.redis.flushdb }
|
after(:all) { Discourse.redis.flushdb }
|
||||||
|
|
||||||
it "replaces words with other words" do
|
it "replaces words with other words" do
|
||||||
Fabricate(:watched_word, action: WatchedWord.actions[:replace], word: "dolor sit", replacement: "something else")
|
Fabricate(:watched_word, action: WatchedWord.actions[:replace], word: "dolor sit*", replacement: "something else")
|
||||||
|
|
||||||
expect(PrettyText.cook("Lorem ipsum dolor sit amet")).to match_html(<<~HTML)
|
expect(PrettyText.cook("Lorem ipsum dolor sit amet")).to match_html(<<~HTML)
|
||||||
<p>Lorem ipsum something else amet</p>
|
<p>Lorem ipsum something else amet</p>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
|
expect(PrettyText.cook("Lorem ipsum dolor sits amet")).to match_html(<<~HTML)
|
||||||
|
<p>Lorem ipsum something else amet</p>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
expect(PrettyText.cook("Lorem ipsum dolor sittt amet")).to match_html(<<~HTML)
|
||||||
|
<p>Lorem ipsum something else amet</p>
|
||||||
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
it "replaces words with links" do
|
it "replaces words with links" do
|
||||||
|
Reference in New Issue
Block a user