diff --git a/app/assets/javascripts/admin/templates/site_settings.hbs b/app/assets/javascripts/admin/templates/site_settings.hbs
index e851a172e..f318c7131 100644
--- a/app/assets/javascripts/admin/templates/site_settings.hbs
+++ b/app/assets/javascripts/admin/templates/site_settings.hbs
@@ -28,7 +28,7 @@
diff --git a/app/assets/javascripts/admin/views/site_setting_view.js b/app/assets/javascripts/admin/views/site_setting_view.js
index f7e955442..0a3983bd2 100644
--- a/app/assets/javascripts/admin/views/site_setting_view.js
+++ b/app/assets/javascripts/admin/views/site_setting_view.js
@@ -14,10 +14,10 @@ Discourse.SiteSettingView = Discourse.View.extend(Discourse.ScrollTop, {
if (this.get('content.type') === 'bool') return 'admin/templates/site_settings/setting_bool';
// If we're editing an enum field, show a dropdown
- if (this.get('content.type') === 'enum' ) return 'admin/templates/site_settings/setting_enum';
+ if (this.get('content.type') === 'enum') return 'admin/templates/site_settings/setting_enum';
// If we're editing a list, show a list editor
- if (this.get('content.type') === 'list' ) return 'admin/templates/site_settings/setting_list';
+ if (this.get('content.type') === 'list') return 'admin/templates/site_settings/setting_list';
// Default to string editor
return 'admin/templates/site_settings/setting_string';
diff --git a/app/assets/javascripts/discourse/dialects/censored_dialect.js b/app/assets/javascripts/discourse/dialects/censored_dialect.js
new file mode 100644
index 000000000..947f70f79
--- /dev/null
+++ b/app/assets/javascripts/discourse/dialects/censored_dialect.js
@@ -0,0 +1,24 @@
+var censorRegexp;
+
+Discourse.Dialect.addPreProcessor(function(text) {
+ var censored = Discourse.SiteSettings.censored_words;
+ if (censored && censored.length) {
+ if (!censorRegexp) {
+ var split = censored.split("|");
+ if (split && split.length) {
+ censorRegexp = new RegExp(split.map(function (t) { return "(" + t.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + ")"; }).join("|"), "ig");
+ }
+ }
+
+ if (censorRegexp) {
+ var m = censorRegexp.exec(text);
+ while (m && m[0]) {
+ var replacement = new Array(m[0].length+1).join('■');
+ text = text.replace(m[0], replacement);
+ m = censorRegexp.exec(text);
+ }
+
+ }
+ }
+ return text;
+});
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 368d8d422..a1c6cefc7 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -631,6 +631,7 @@ en:
description: "A message that will be displayed at the top of all notification emails."
site_settings:
+ censored_words: "Words that will be automatically replaced with ■■■■"
delete_old_hidden_posts: "Auto-delete any hidden posts that stay hidden for more than 30 days."
default_locale: "The default language of this Discourse instance (ISO 639-1 Code)"
allow_user_locale: "Allow users to choose their own language interface preference"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index b796b2920..b63f85028 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -380,6 +380,11 @@ posting:
client: true
default: false
delete_old_hidden_posts: true
+ censored_words:
+ client: true
+ default: ''
+ refresh: true
+ type: list
email:
email_time_window_mins: 10
diff --git a/test/javascripts/lib/markdown-test.js.es6 b/test/javascripts/lib/markdown-test.js.es6
index 3e007c1f4..2420f51d1 100644
--- a/test/javascripts/lib/markdown-test.js.es6
+++ b/test/javascripts/lib/markdown-test.js.es6
@@ -471,7 +471,6 @@ test("urlAllowed", function() {
});
test("images", function() {
-
cooked("[](http://folksy.com/)",
"

",
"It allows images with links around them");
@@ -480,3 +479,10 @@ test("images", function() {
"

",
"It allows data images");
});
+
+test("censoring", function() {
+ Discourse.SiteSettings.censored_words = "shucks|whiz";
+ cooked("aw shucks, golly gee whiz.",
+ "
aw ■■■■■■, golly gee ■■■■.
",
+ "it censors words in the Site Settings");
+});