FEATURE: Add user custom fields to user directory (#13238)

FEATURE: Add user custom fields to user directory (#13238)

diff --git a/app/assets/javascripts/discourse/app/components/directory-item.js b/app/assets/javascripts/discourse/app/components/directory-item.js
index 709ad7a..0b557d6 100644
--- a/app/assets/javascripts/discourse/app/components/directory-item.js
+++ b/app/assets/javascripts/discourse/app/components/directory-item.js
@@ -5,4 +5,5 @@ export default Component.extend({
   tagName: "tr",
   classNameBindings: ["me"],
   me: propertyEqual("item.user.id", "currentUser.id"),
+  columns: null,
 });
diff --git a/app/assets/javascripts/discourse/app/components/directory-table.js b/app/assets/javascripts/discourse/app/components/directory-table.js
new file mode 100644
index 0000000..f1b8193
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/directory-table.js
@@ -0,0 +1,17 @@
+import Component from "@ember/component";
+import { action } from "@ember/object";
+
+export default Component.extend({
+  classNames: ["directory-table-container"],
+
+  @action
+  setActiveHeader(header) {
+    // After render, scroll table left to ensure the order by column is visible
+    const scrollPixels =
+      header.offsetLeft + header.offsetWidth + 10 - this.element.offsetWidth;
+
+    if (scrollPixels > 0) {
+      this.element.scrollLeft = scrollPixels;
+    }
+  },
+});
diff --git a/app/assets/javascripts/discourse/app/components/table-header-toggle.js b/app/assets/javascripts/discourse/app/components/table-header-toggle.js
index 0ab184a..afe7ef3 100644
--- a/app/assets/javascripts/discourse/app/components/table-header-toggle.js
+++ b/app/assets/javascripts/discourse/app/components/table-header-toggle.js
@@ -1,6 +1,4 @@
 import Component from "@ember/component";
-import I18n from "I18n";
-import discourseComputed from "discourse-common/utils/decorators";
 import { iconHTML } from "discourse-common/lib/icon-library";
 
 export default Component.extend({
@@ -10,15 +8,8 @@ export default Component.extend({
   labelKey: null,
   chevronIcon: null,
   columnIcon: null,
-
-  @discourseComputed("field", "labelKey")
-  title(field, labelKey) {
-    if (!labelKey) {
-      labelKey = `directory.${this.field}`;
-    }
-
-    return I18n.t(labelKey + "_long", { defaultValue: I18n.t(labelKey) });
-  },
+  translated: false,
+  onActiveRender: null,
 
   toggleProperties() {
     if (this.order === this.field) {
@@ -40,13 +31,12 @@ export default Component.extend({
   },
   didReceiveAttrs() {
     this._super(...arguments);
+    this.set("id", `table-header-toggle-${this.field.replace(/\s/g, "")}`);
     this.toggleChevron();
   },
-  init() {
-    this._super(...arguments);
-    if (this.icon) {
-      let columnIcon = iconHTML(this.icon);
-      this.set("columnIcon", `${columnIcon}`.htmlSafe());
+  didRender() {
+    if (this.onActiveRender && this.chevronIcon) {
+      this.onActiveRender(this.element);
     }
   },
 });
diff --git a/app/assets/javascripts/discourse/app/controllers/edit-user-directory-columns.js b/app/assets/javascripts/discourse/app/controllers/edit-user-directory-columns.js
new file mode 100644
index 0000000..a7fd826
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/controllers/edit-user-directory-columns.js
@@ -0,0 +1,101 @@
+import Controller from "@ember/controller";
+import ModalFunctionality from "discourse/mixins/modal-functionality";
+import { ajax } from "discourse/lib/ajax";
+import EmberObject, { action } from "@ember/object";
+import { extractError } from "discourse/lib/ajax-error";
+import { reload } from "discourse/helpers/page-reloader";
+
+const UP = "up";
+const DOWN = "down";
+
+export default Controller.extend(ModalFunctionality, {
+  loading: true,
+  columns: null,
+  labelKey: null,
+
+  onShow() {
+    ajax("directory-columns.json")
+      .then((response) => {
+        this.setProperties({
+          loading: false,
+          columns: response.directory_columns
+            .sort((a, b) => (a.position > b.position ? 1 : -1))
+            .map((c) => EmberObject.create(c)),
+        });
+      })
+      .catch(extractError);
+  },
+
+  @action
+  save() {
+    this.set("loading", true);
+    const data = {
+      directory_columns: this.columns.map((c) =>
+        c.getProperties("id", "enabled", "position")
+      ),
+    };
+
+    ajax("directory-columns.json", { type: "PUT", data })
+      .then(() => {
+        reload();
+      })
+      .catch((e) => {
+        this.set("loading", false);
+        this.flash(extractError(e), "error");
+      });
+  },
+
+  @action
+  resetToDefault() {
+    let resetColumns = this.columns;
+    resetColumns
+      .sort((a, b) =>
+        (a.automatic_position || a.user_field.position + 1000) >
+        (b.automatic_position || b.user_field.position + 1000)
+          ? 1
+          : -1
+      )
+      .forEach((column, index) => {
+        column.setProperties({
+          position: column.automatic_position || index + 1,
+          enabled: column.automatic,
+        });
+      });
+    this.set("columns", resetColumns);
+    this.notifyPropertyChange("columns");
+  },
+
+  @action
+  moveUp(column) {
+    this._moveColumn(UP, column);
+  },
+
+  @action
+  moveDown(column) {
+    this._moveColumn(DOWN, column);
+  },
+
+  _moveColumn(direction, column) {
+    if (
+      (direction === UP && column.position === 1) ||
+      (direction === DOWN && column.position === this.columns.length)
+    ) {
+      return;
+    }
+
+    const positionOnClick = column.position;
+    const newPosition =
+      direction === UP ? positionOnClick - 1 : positionOnClick + 1;
+
+    const previousColumn = this.columns.find((c) => c.position === newPosition);
+
+    column.set("position", newPosition);
+    previousColumn.set("position", positionOnClick);
+
+    this.set(
+      "columns",
+      this.columns.sort((a, b) => (a.position > b.position ? 1 : -1))
+    );
+    this.notifyPropertyChange("columns");
+  },
+});
diff --git a/app/assets/javascripts/discourse/app/controllers/users.js b/app/assets/javascripts/discourse/app/controllers/users.js
index fe78c4c..98f4872 100644
--- a/app/assets/javascripts/discourse/app/controllers/users.js
+++ b/app/assets/javascripts/discourse/app/controllers/users.js
@@ -1,6 +1,7 @@
 import Controller, { inject as controller } from "@ember/controller";
 import { action } from "@ember/object";
 import discourseDebounce from "discourse-common/lib/debounce";
+import showModal from "discourse/lib/show-modal";
 import { equal } from "@ember/object/computed";
 import { longDate } from "discourse/lib/formatter";
 import { observes } from "discourse-common/utils/decorators";
@@ -9,13 +10,14 @@ export default Controller.extend({
   application: controller(),
   queryParams: ["period", "order", "asc", "name", "group", "exclude_usernames"],
   period: "weekly",
-  order: "likes_received",
+  order: "",
   asc: null,
   name: "",
   group: null,
   nameInput: null,
   exclude_usernames: null,
   isLoading: false,
+  columns: null,
 
   showTimeRead: equal("period", "all"),
 
@@ -23,9 +25,15 @@ export default Controller.extend({
     this.set("isLoading", true);
 
     this.set("nameInput", params.name);
+    this.set("order", params.order);
+
+    const custom_field_columns = this.columns.filter((c) => !c.automatic);
+    const user_field_ids = custom_field_columns
+      .map((c) => c.user_field_id)
+      .join("|");
 
     this.store
-      .find("directoryItem", params)
+      .find("directoryItem", Object.assign(params, { user_field_ids }))
       .then((model) => {
         const lastUpdatedAt = model.get("resultSetMeta.last_updated_at");
         this.setProperties({
@@ -40,6 +48,11 @@ export default Controller.extend({
   },
 
   @action
+  showEditColumnsModal() {
+    showModal("edit-user-directory-columns");
+  },
+
+  @action
   onFilterChanged(filter) {
     discourseDebounce(this, this._setName, filter, 500);
   },

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

GitHub sha: 0cba4d73

This commit appears in #13238 which was approved by eviltrout. It was merged by markvanlan.