FEATURE: Allow List for PMs (#10270)

FEATURE: Allow List for PMs (#10270)

  • FEATURE: Allow List for PMs

This feature adds a new user setting that is disabled by default that allows them to specify a list of users that are allowed to send them private messages. This way they don’t have to maintain a large list of users they don’t want to here from and instead just list the people they know they do want. Staff will still always be able to send messages to the user.

  • Update PR based on feedback
diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js
index 2dc3fa4..9baf458 100644
--- a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js
+++ b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js
@@ -38,6 +38,7 @@ export default Controller.extend({
       "enable_defer",
       "automatically_unpin_topics",
       "allow_private_messages",
+      "enable_allowed_pm_users",
       "homepage_id",
       "hide_profile_and_presence",
       "text_size",
diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/notifications.js b/app/assets/javascripts/discourse/app/controllers/preferences/notifications.js
index f738f82..4568619 100644
--- a/app/assets/javascripts/discourse/app/controllers/preferences/notifications.js
+++ b/app/assets/javascripts/discourse/app/controllers/preferences/notifications.js
@@ -2,6 +2,7 @@ import I18n from "I18n";
 import Controller from "@ember/controller";
 import { NotificationLevels } from "discourse/lib/notification-levels";
 import { popupAjaxError } from "discourse/lib/ajax-error";
+import discourseComputed from "discourse-common/utils/decorators";
 
 export default Controller.extend({
   init() {
@@ -14,7 +15,8 @@ export default Controller.extend({
       "auto_track_topics_after_msecs",
       "notification_level_when_replying",
       "like_notification_frequency",
-      "allow_private_messages"
+      "allow_private_messages",
+      "enable_allowed_pm_users"
     ];
 
     this.likeNotificationFrequencies = [
@@ -91,6 +93,11 @@ export default Controller.extend({
     this.isIOS = caps.isIOS;
   },
 
+  @discourseComputed("model.user_option.allow_private_messages")
+  disableAllowPmUsersSetting(allowPrivateMessages) {
+    return !allowPrivateMessages;
+  },
+
   actions: {
     save() {
       this.set("saved", false);
diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/users.js b/app/assets/javascripts/discourse/app/controllers/preferences/users.js
index b0ac026..d16f196 100644
--- a/app/assets/javascripts/discourse/app/controllers/preferences/users.js
+++ b/app/assets/javascripts/discourse/app/controllers/preferences/users.js
@@ -1,13 +1,18 @@
 import { makeArray } from "discourse-common/lib/helpers";
-import { alias, gte, or } from "@ember/object/computed";
+import { alias, gte, or, and } from "@ember/object/computed";
 import { action, computed } from "@ember/object";
 import Controller from "@ember/controller";
 import { popupAjaxError } from "discourse/lib/ajax-error";
 
 export default Controller.extend({
   ignoredUsernames: alias("model.ignored_usernames"),
+  allowedPmUsernames: alias("model.allowed_pm_usernames"),
   userIsMemberOrAbove: gte("model.trust_level", 2),
   ignoredEnabled: or("userIsMemberOrAbove", "model.staff"),
+  allowPmUsersEnabled: and(
+    "model.user_option.enable_allowed_pm_users",
+    "model.user_option.allow_private_messages"
+  ),
 
   mutedUsernames: computed("model.muted_usernames", {
     get() {
@@ -21,10 +26,26 @@ export default Controller.extend({
     }
   }),
 
+  allowedPmUsernames: computed("model.allowed_pm_usernames", {
+    get() {
+      let usernames = this.model.allowed_pm_usernames;
+
+      if (typeof usernames === "string") {
+        usernames = usernames.split(",").filter(Boolean);
+      }
+
+      return makeArray(usernames).uniq();
+    }
+  }),
+
   init() {
     this._super(...arguments);
 
-    this.saveAttrNames = ["muted_usernames", "ignored_usernames"];
+    this.saveAttrNames = [
+      "muted_usernames",
+      "ignored_usernames",
+      "allowed_pm_usernames"
+    ];
   },
 
   @action
@@ -33,6 +54,11 @@ export default Controller.extend({
   },
 
   @action
+  onChangeAllowedPmUsernames(usernames) {
+    this.model.set("allowed_pm_usernames", usernames.uniq().join(","));
+  },
+
+  @action
   save() {
     this.set("saved", false);
 
diff --git a/app/assets/javascripts/discourse/app/models/user.js b/app/assets/javascripts/discourse/app/models/user.js
index 72c2695..c4bf6b5 100644
--- a/app/assets/javascripts/discourse/app/models/user.js
+++ b/app/assets/javascripts/discourse/app/models/user.js
@@ -276,6 +276,7 @@ const User = RestModel.extend({
       "user_fields",
       "muted_usernames",
       "ignored_usernames",
+      "allowed_pm_usernames",
       "profile_background_upload_url",
       "card_background_upload_url",
       "muted_tags",
@@ -311,6 +312,7 @@ const User = RestModel.extend({
       "include_tl0_in_digests",
       "theme_ids",
       "allow_private_messages",
+      "enable_allowed_pm_users",
       "homepage_id",
       "hide_profile_and_presence",
       "text_size",
diff --git a/app/assets/javascripts/discourse/app/templates/preferences/notifications.hbs b/app/assets/javascripts/discourse/app/templates/preferences/notifications.hbs
index 9e079fd..450ebde 100644
--- a/app/assets/javascripts/discourse/app/templates/preferences/notifications.hbs
+++ b/app/assets/javascripts/discourse/app/templates/preferences/notifications.hbs
@@ -61,7 +61,15 @@
           labelKey="user.allow_private_messages"
           checked=model.user_option.allow_private_messages}}
     </div>
+
+    <div class="controls">
+      {{preference-checkbox
+          labelKey="user.allow_private_messages_from_specific_users"
+          checked=model.user_option.enable_allowed_pm_users
+          disabled=disableAllowPmUsersSetting}}
+    </div>
   </div>
+
 {{/if}}
 
 {{plugin-outlet name="user-preferences-notifications" args=(hash model=model save=(action "save"))}}
diff --git a/app/assets/javascripts/discourse/app/templates/preferences/users.hbs b/app/assets/javascripts/discourse/app/templates/preferences/users.hbs
index 7db61c6..91eb15d 100644
--- a/app/assets/javascripts/discourse/app/templates/preferences/users.hbs
+++ b/app/assets/javascripts/discourse/app/templates/preferences/users.hbs
@@ -25,6 +25,25 @@
   <div class="instructions">{{i18n "user.muted_users_instructions"}}</div>
 </div>
 
+{{#if allowPmUsersEnabled}}
+  <div class="control-group user-allow-pm">
+    <div class="controls tracking-controls">
+      <label>
+        {{d-icon "far-envelope" class="icon"}}
+        <span>{{i18n "user.allowed_pm_users"}}</span>
+      </label>
+      {{user-chooser
+        value=allowedPmUsernames
+        onChange=(action "onChangeAllowedPmUsernames")
+        options=(hash
+          excludeCurrentUser=true
+        )
+      }}
+    </div>
+    <div class="instructions">{{i18n "user.allowed_pm_users_instructions"}}</div>
+  </div>
+{{/if}}
+
 {{plugin-outlet name="user-custom-controls" args=(hash model=model)}}
 
 {{#save-controls model=model action=(action "save") saved=saved}}{{/save-controls}}
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 5127dc8..fccb772 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1581,6 +1581,7 @@ class UsersController < ApplicationController
       :date_of_birth,
       :muted_usernames,
       :ignored_usernames,
+      :allowed_pm_usernames,
       :theme_ids,
       :locale,
       :bio_raw,
diff --git a/app/models/allowed_pm_user.rb b/app/models/allowed_pm_user.rb
new file mode 100644
index 0000000..dea8835
--- /dev/null
+++ b/app/models/allowed_pm_user.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AllowedPmUser < ActiveRecord::Base
+  belongs_to :user
+  belongs_to :allowed_pm_user, class_name: "User"
+end
+
+# == Schema Information
+#
+# Table name: allowed_pm_users
+#
+#  id                 :bigint           not null, primary key
+#  user_id            :integer          not null
+#  allowed_pm_user_id :integer          not null
+#  created_at         :datetime         not null
+#  updated_at         :datetime         not null
+#
+# Indexes
+#

[... diff too long, it was truncated ...]

GitHub sha: 690f17bc

This commit appears in #10270 which was approved by eviltrout. It was merged by blake.