FEATURE: Support regular expressions for watched words

This commit is contained in:
Robin Ward 2017-09-27 15:48:57 -04:00
parent fa41913ba5
commit 41c3941c4c
12 changed files with 61 additions and 8 deletions

View File

@ -1,5 +1,5 @@
import WatchedWord from 'admin/models/watched-word';
import { on, observes } from 'ember-addons/ember-computed-decorators';
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
classNames: ['watched-word-form'],
@ -7,6 +7,12 @@ export default Ember.Component.extend({
actionKey: null,
showSuccessMessage: false,
@computed('regularExpressions')
placeholderKey(regularExpressions) {
return "admin.watched_words.form.placeholder" +
(regularExpressions ? "_regexp" : "");
},
@observes('word')
removeSuccessMessage() {
if (this.get('showSuccessMessage') && !Ember.isEmpty(this.get('word'))) {

View File

@ -5,6 +5,7 @@ export default Ember.Controller.extend({
filtered: false,
showWords: false,
disableShowWords: Ember.computed.alias('filtered'),
regularExpressions: null,
filterContentNow() {

View File

@ -16,7 +16,7 @@ const WatchedWord = Discourse.Model.extend({
WatchedWord.reopenClass({
findAll() {
return ajax("/admin/logs/watched_words").then(function (list) {
return ajax("/admin/logs/watched_words").then(list => {
const actions = {};
list.words.forEach(s => {
if (!actions[s.action]) { actions[s.action] = []; }
@ -27,8 +27,14 @@ WatchedWord.reopenClass({
if (!actions[a]) { actions[a] = []; }
});
return Object.keys(actions).map(function(n) {
return Ember.Object.create({nameKey: n, name: I18n.t('admin.watched_words.actions.' + n), words: actions[n], count: actions[n].length});
return Object.keys(actions).map(n => {
return Ember.Object.create({
nameKey: n,
name: I18n.t('admin.watched_words.actions.' + n),
words: actions[n],
count: actions[n].length,
regularExpressions: list.regular_expressions
});
});
});
}

View File

@ -9,6 +9,13 @@ export default Discourse.Route.extend({
return WatchedWord.findAll();
},
setupController(controller, model) {
controller.set('model', model);
if (model && model.length) {
controller.set('regularExpressions', model[0].get('regularExpressions'));
}
},
afterModel(watchedWordsList) {
this.controllerFor('adminWatchedWords').set('allWatchedWords', watchedWordsList);
}

View File

@ -1,5 +1,5 @@
<b>{{i18n 'admin.watched_words.form.label'}}</b>
{{text-field value=word disabled=formSubmitted class="watched-word-input" autocorrect="off" autocapitalize="off" placeholderKey="admin.watched_words.form.placeholder"}}
{{text-field value=word disabled=formSubmitted class="watched-word-input" autocorrect="off" autocapitalize="off" placeholderKey=placeholderKey}}
{{d-button action="submit" disabled=formSubmitted label="admin.watched_words.form.add"}}
{{#if showSuccessMessage}}

View File

@ -2,7 +2,10 @@
<p class="about">{{actionDescription}}</p>
{{watched-word-form actionKey=actionNameKey action="recordAdded"}}
{{watched-word-form
actionKey=actionNameKey
action="recordAdded"
regularExpressions=adminWatchedWords.regularExpressions}}
{{watched-word-uploader uploading=uploading actionKey=actionNameKey done="uploadComplete"}}

View File

@ -1,5 +1,5 @@
class WatchedWordListSerializer < ApplicationSerializer
attributes :actions, :words
attributes :actions, :words, :regular_expressions
def actions
WatchedWord.actions.keys
@ -10,4 +10,10 @@ class WatchedWordListSerializer < ApplicationSerializer
WatchedWordSerializer.new(word, root: false)
end
end
# No point making this site setting `client: true` when it's only used
# in the admin section
def regular_expressions
SiteSetting.watched_words_regular_expressions?
end
end

View File

@ -15,12 +15,17 @@ class WordWatcher
def self.word_matcher_regexp(action)
s = Discourse.cache.fetch(word_matcher_regexp_key(action), expires_in: 1.day) do
words = words_for_action(action)
words.empty? ? nil : '\b(' + words.map { |w| Regexp.escape(w).gsub("\\*", '\S*') }.join('|'.freeze) + ')\b'
words.empty? ? nil : '\b(' + words.map { |w| word_to_regexp(w) }.join('|'.freeze) + ')\b'
end
s.present? ? Regexp.new(s, Regexp::IGNORECASE) : nil
end
def self.word_to_regexp(word)
return word if SiteSetting.watched_words_regular_expressions?
Regexp.escape(word).gsub("\\*", '\S*')
end
def self.word_matcher_regexp_key(action)
"watched-words-regexp:#{action}"
end

View File

@ -3203,6 +3203,7 @@ en:
form:
label: 'New Word:'
placeholder: 'full word or * as wildcard'
placeholder_regexp: "regular expression"
add: 'Add'
success: 'Success'
upload: "Upload"

View File

@ -1521,6 +1521,7 @@ en:
code_formatting_style: "Code button in composer will default to this code formatting style"
max_allowed_message_recipients: "Maximum recipients allowed in a message."
watched_words_regular_expressions: "Watched words are regular expressions."
default_email_digest_frequency: "How often users receive summary emails by default."
default_include_tl0_in_digests: "Include posts from new users in summary emails by default. Users can change this in their preferences."

View File

@ -625,6 +625,8 @@ posting:
max_allowed_message_recipients:
default: 30
min: 1
watched_words_regular_expressions:
default: false
email:
email_time_window_mins:

View File

@ -47,6 +47,21 @@ describe WordWatcher do
m = WordWatcher.new("I acknowledge you.").word_matches_for_action?(:require_approval)
expect(m[1]).to eq("acknowledge")
end
it "supports regular expressions as a site setting" do
SiteSetting.watched_words_regular_expressions = true
Fabricate(
:watched_word,
word: "tro[uo]+t",
action: WatchedWord.actions[:require_approval]
)
m = WordWatcher.new("Evil Trout is cool").word_matches_for_action?(:require_approval)
expect(m[1]).to eq("Trout")
m = WordWatcher.new("Evil Troot is cool").word_matches_for_action?(:require_approval)
expect(m[1]).to eq("Troot")
m = WordWatcher.new("trooooooooot").word_matches_for_action?(:require_approval)
expect(m[1]).to eq("trooooooooot")
end
end
end