DEV: Added support for custom site setting 'emoji_list' (#12414)

DEV: Added support for custom site setting ‘emoji_list’ (#12414)

Example usage:

best_emojis:
    type: emoji_list
    default: laughing|open_mouth|cry|angry|hugs
    client: true
diff --git a/app/assets/javascripts/admin/addon/components/emoji-value-list.js b/app/assets/javascripts/admin/addon/components/emoji-value-list.js
new file mode 100644
index 0000000..e7096a7
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/components/emoji-value-list.js
@@ -0,0 +1,168 @@
+import Component from "@ember/component";
+import I18n from "I18n";
+import discourseComputed from "discourse-common/utils/decorators";
+import { emojiUrlFor } from "discourse/lib/text";
+import { action, set, setProperties } from "@ember/object";
+import { later, schedule } from "@ember/runloop";
+
+export default Component.extend({
+  classNameBindings: [":value-list", ":emoji-list"],
+  values: null,
+  validationMessage: null,
+  emojiPickerIsActive: false,
+  isEditorFocused: false,
+
+  @discourseComputed("values")
+  collection(values) {
+    values = values || "";
+
+    return values
+      .split("|")
+      .filter(Boolean)
+      .map((value) => {
+        return {
+          isEditable: true,
+          isEditing: false,
+          value,
+          emojiUrl: emojiUrlFor(value),
+        };
+      });
+  },
+
+  @action
+  closeEmojiPicker() {
+    this.collection.setEach("isEditing", false);
+    this.set("emojiPickerIsActive", false);
+    this.set("isEditorFocused", false);
+  },
+
+  @action
+  emojiSelected(code) {
+    if (!this._validateInput(code)) {
+      return;
+    }
+
+    const item = this.collection.findBy("isEditing");
+    if (item) {
+      setProperties(item, {
+        value: code,
+        emojiUrl: emojiUrlFor(code),
+        isEditing: false,
+      });
+
+      this._saveValues();
+    } else {
+      const newCollectionValue = {
+        value: code,
+        emojiUrl: emojiUrlFor(code),
+        isEditable: true,
+        isEditing: false,
+      };
+      this.collection.addObject(newCollectionValue);
+      this._saveValues();
+    }
+
+    this.set("emojiPickerIsActive", false);
+    this.set("isEditorFocused", false);
+  },
+
+  @discourseComputed("collection")
+  showUpDownButtons(collection) {
+    return collection.length - 1 ? true : false;
+  },
+
+  _splitValues(values) {
+    if (values && values.length) {
+      const emojiList = [];
+      const emojis = values.split("|").filter(Boolean);
+      emojis.forEach((emojiName) => {
+        const emoji = {
+          isEditable: true,
+          isEditing: false,
+        };
+        emoji.value = emojiName;
+        emoji.emojiUrl = emojiUrlFor(emojiName);
+
+        emojiList.push(emoji);
+      });
+
+      return emojiList;
+    } else {
+      return [];
+    }
+  },
+
+  @action
+  editValue(index) {
+    this.closeEmojiPicker();
+    schedule("afterRender", () => {
+      if (parseInt(index, 10) >= 0) {
+        const item = this.collection[index];
+        if (item.isEditable) {
+          set(item, "isEditing", true);
+        }
+      }
+
+      this.set("isEditorFocused", true);
+      later(() => {
+        if (this.element && !this.isDestroying && !this.isDestroyed) {
+          this.set("emojiPickerIsActive", true);
+        }
+      }, 100);
+    });
+  },
+
+  @action
+  removeValue(value) {
+    this._removeValue(value);
+  },
+
+  @action
+  shift(operation, index) {
+    let futureIndex = index + operation;
+
+    if (futureIndex > this.collection.length - 1) {
+      futureIndex = 0;
+    } else if (futureIndex < 0) {
+      futureIndex = this.collection.length - 1;
+    }
+
+    const shiftedEmoji = this.collection[index];
+    this.collection.removeAt(index);
+    this.collection.insertAt(futureIndex, shiftedEmoji);
+
+    this._saveValues();
+  },
+
+  _validateInput(input) {
+    this.set("validationMessage", null);
+
+    if (!emojiUrlFor(input)) {
+      this.set(
+        "validationMessage",
+        I18n.t("admin.site_settings.emoji_list.invalid_input")
+      );
+      return false;
+    }
+
+    return true;
+  },
+
+  _removeValue(value) {
+    this.collection.removeObject(value);
+    this._saveValues();
+  },
+
+  _replaceValue(index, newValue) {
+    const item = this.collection[index];
+    if (item.value === newValue) {
+      return;
+    }
+    set(item, "value", newValue);
+    this._saveValues();
+  },
+
+  _saveValues() {
+    this.set("values", this.collection.mapBy("value").join("|"));
+  },
+});
diff --git a/app/assets/javascripts/admin/addon/mixins/setting-component.js b/app/assets/javascripts/admin/addon/mixins/setting-component.js
index d96bd40..fc6c6c2 100644
--- a/app/assets/javascripts/admin/addon/mixins/setting-component.js
+++ b/app/assets/javascripts/admin/addon/mixins/setting-component.js
@@ -27,6 +27,7 @@ const CUSTOM_TYPES = [
   "tag_list",
   "color",
   "simple_list",
+  "emoji_list",
 ];
 
 const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"];
diff --git a/app/assets/javascripts/admin/addon/templates/components/emoji-value-list.hbs b/app/assets/javascripts/admin/addon/templates/components/emoji-value-list.hbs
new file mode 100644
index 0000000..aeed56f
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/templates/components/emoji-value-list.hbs
@@ -0,0 +1,53 @@
+{{#if collection}}
+  <ul class="values emoji-value-list">
+    {{#each collection as |data index|}}
+      <li class="value" data-index={{index}}>
+        {{#if data.isEditable}}
+          {{d-button
+            action=(action "removeValue")
+            actionParam=data
+            icon="times"
+            class="remove-value-btn btn-small"
+          }}
+        {{/if}}
+
+        <div class="value-input emoji-details {{if data.isEditable "can-edit"}} {{if data.isEditing "d-editor-textarea-wrapper"}}" {{on "click" (fn this.editValue index)}} role="button">
+          <img height="15px" width="15px" src={{data.emojiUrl}} class="emoji-list-emoji">
+          <span class="emoji-name">{{data.value}}</span>
+        </div>
+
+        {{#if showUpDownButtons}}
+          {{d-button
+            action=(action "shift" -1 index)
+            icon="arrow-up"
+            class="shift-up-value-btn btn-small"
+          }}
+          {{d-button
+            action=(action "shift" 1 index)
+            icon="arrow-down"
+            class="shift-down-value-btn btn-small"
+          }}
+        {{/if}}
+      </li>
+    {{/each}}
+  </ul>
+{{/if}}
+
+<div class="value">
+  {{d-button
+    action=(action "editValue")
+    actionParam=data
+    icon="emoji-icon"
+    class="add-emoji-button d-editor-textarea-wrapper"
+    label="admin.site_settings.emoji_list.add_emoji_button.label"
+  }}
+</div>
+
+{{emoji-picker
+  isActive=emojiPickerIsActive
+  isEditorFocused=isEditorFocused
+  emojiSelected=(action "emojiSelected")
+  onEmojiPickerClose=(action "closeEmojiPicker")
+}}
+
+{{setting-validation-message message=validationMessage}}
diff --git a/app/assets/javascripts/admin/addon/templates/components/site-settings/emoji-list.hbs b/app/assets/javascripts/admin/addon/templates/components/site-settings/emoji-list.hbs
new file mode 100644
index 0000000..50d1d18
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/templates/components/site-settings/emoji-list.hbs
@@ -0,0 +1,3 @@
+{{emoji-value-list setting=setting values=value}}
+<div class="desc">{{html-safe setting.description}}</div>
+{{setting-validation-message message=validationMessage}}
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index a65d7ff..e920bb7 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -884,6 +884,47 @@ table#user-badges {
   }
 }
 
+.emoji-value-list {
+  margin-left: 0;
+
+  .emoji-details {
+    display: flex;
+    align-items: center;
+    min-height: 30px;
+    padding: $input-padding;
+    line-height: 1;
+    color: var(--primary);
+    border: 1px solid var(--primary-low);
+
+    .emoji-name {
+      margin-left: 0.5em;
+    }
+
+    &:not(.can-edit) {
+      pointer-events: none;

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

GitHub sha: 2308a581

1 Like

This commit appears in #12414 which was approved by jjaffeux. It was merged by jjaffeux.