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 00000000000..e7096a71dad --- /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 d96bd40c22b..fc6c6c24d5f 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 00000000000..aeed56f4586 --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/components/emoji-value-list.hbs @@ -0,0 +1,53 @@ +{{#if collection}} + +{{/if}} + +
+ {{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" + }} +
+ +{{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 00000000000..50d1d18898b --- /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}} +
{{html-safe setting.description}}
+{{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 a65d7ff0add..e920bb76c90 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; + background-color: var(--primary-very-low); + } + } + + .value-input { + flex-direction: row; + } +} + +.value .add-emoji-button { + display: block; + background-color: var(--primary-low); + border: none; +} + +.value .add-value-btn, +.shift-up-value-btn, +.shift-down-value-btn { + @include value-btn; + margin-right: 0 !important; + margin-left: 0.25em; +} + .secret-value-list { .value { flex-flow: row wrap; diff --git a/app/assets/stylesheets/desktop.scss b/app/assets/stylesheets/desktop.scss index dfae3eca774..d302363926b 100644 --- a/app/assets/stylesheets/desktop.scss +++ b/app/assets/stylesheets/desktop.scss @@ -4,3 +4,6 @@ // Import all component-specific files @import "desktop/components/_index"; + +// Import all admin-specific files +@import "desktop/admin/_index"; diff --git a/app/assets/stylesheets/desktop/admin/_index.scss b/app/assets/stylesheets/desktop/admin/_index.scss new file mode 100644 index 00000000000..b45777ec742 --- /dev/null +++ b/app/assets/stylesheets/desktop/admin/_index.scss @@ -0,0 +1 @@ +@import "admin_base"; diff --git a/app/assets/stylesheets/desktop/admin/admin_base.scss b/app/assets/stylesheets/desktop/admin/admin_base.scss new file mode 100644 index 00000000000..c014649b9e0 --- /dev/null +++ b/app/assets/stylesheets/desktop/admin/admin_base.scss @@ -0,0 +1,15 @@ +.emoji-value-list { + .value { + .shift-up-value-btn, + .shift-down-value-btn { + display: none; + } + + &:hover { + .shift-up-value-btn, + .shift-down-value-btn { + display: block; + } + } + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 31de720fd34..9201bb510bb 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -5010,6 +5010,10 @@ en: reset: "reset" none: "none" site_settings: + emoji_list: + invalid_input: "Emoji list should only contain valid emoji names, eg: hugs" + add_emoji_button: + label: "Add Emoji" title: "Settings" no_results: "No results found." more_than_30_results: "There are more than 30 results. Please refine your search or select a category." diff --git a/lib/site_settings/type_supervisor.rb b/lib/site_settings/type_supervisor.rb index f5bb727d356..ee78697ebf2 100644 --- a/lib/site_settings/type_supervisor.rb +++ b/lib/site_settings/type_supervisor.rb @@ -35,7 +35,8 @@ class SiteSettings::TypeSupervisor group_list: 20, tag_list: 21, color: 22, - simple_list: 23 + simple_list: 23, + emoji_list: 24 ) end diff --git a/lib/svg_sprite/svg_sprite.rb b/lib/svg_sprite/svg_sprite.rb index 61aa7c3df30..9a2d18b9831 100644 --- a/lib/svg_sprite/svg_sprite.rb +++ b/lib/svg_sprite/svg_sprite.rb @@ -66,6 +66,7 @@ module SvgSprite "download", "ellipsis-h", "ellipsis-v", + "emoji-icon", "envelope", "envelope-square", "exchange-alt", diff --git a/spec/components/site_settings/type_supervisor_spec.rb b/spec/components/site_settings/type_supervisor_spec.rb index 86fd7267ed2..36c6f82a197 100644 --- a/spec/components/site_settings/type_supervisor_spec.rb +++ b/spec/components/site_settings/type_supervisor_spec.rb @@ -91,6 +91,9 @@ describe SiteSettings::TypeSupervisor do it "'simple_list' should be at the right position" do expect(SiteSettings::TypeSupervisor.types[:simple_list]).to eq(23) end + it "'emoji_list' should be at the right position" do + expect(SiteSettings::TypeSupervisor.types[:emoji_list]).to eq(24) + end end end diff --git a/vendor/assets/svg-icons/emoji.svg b/vendor/assets/svg-icons/emoji.svg new file mode 100644 index 00000000000..9b715176936 --- /dev/null +++ b/vendor/assets/svg-icons/emoji.svg @@ -0,0 +1,7 @@ + + + + + + +