From 0996c3b7b36c37c3b478985da6e968f54dc4835c Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Mon, 30 Mar 2020 20:16:10 +0200 Subject: [PATCH] FEATURE: allows multiple custom emoji groups (#9308) Note: DBHelper would fail with a sql syntax error on columns like "group". Co-authored-by: Jarek Radosz --- .../admin/controllers/admin-emojis.js | 83 ++++++++++++++----- .../javascripts/admin/templates/emojis.hbs | 68 +++++++++------ .../discourse/components/emoji-picker.js | 29 +++++-- .../discourse/components/emoji-uploader.js | 48 +++++++++-- .../discourse/initializers/enable-emoji.js | 2 +- .../templates/components/emoji-uploader.hbs | 59 ++++++++++--- .../discourse/templates/emoji-picker.hbr.erb | 36 ++++---- app/assets/javascripts/pretty-text/emoji.js | 14 ++-- .../stylesheets/common/admin/admin_base.scss | 1 + .../common/admin/admin_emojis.scss | 50 +++++++++++ .../stylesheets/common/admin/customize.scss | 2 +- app/assets/stylesheets/common/base/emoji.scss | 2 + app/assets/stylesheets/mobile/emoji.scss | 4 + app/controllers/admin/emojis_controller.rb | 5 +- app/models/custom_emoji.rb | 1 + app/models/emoji.rb | 18 ++-- app/serializers/emoji_serializer.rb | 2 +- config/locales/client.en.yml | 3 +- ...200116092259_add_group_to_custom_emojis.rb | 7 ++ lib/db_helper.rb | 22 ++--- lib/plugin/instance.rb | 25 ++++-- spec/components/plugin/instance_spec.rb | 26 ++++++ spec/requests/admin/emojis_controller_spec.rb | 40 ++++++--- test/javascripts/lib/pretty-text-test.js | 19 +++++ 24 files changed, 428 insertions(+), 138 deletions(-) create mode 100644 app/assets/stylesheets/common/admin/admin_emojis.scss create mode 100644 db/migrate/20200116092259_add_group_to_custom_emojis.rb diff --git a/app/assets/javascripts/admin/controllers/admin-emojis.js b/app/assets/javascripts/admin/controllers/admin-emojis.js index 6a1d295759c..1a496707032 100644 --- a/app/assets/javascripts/admin/controllers/admin-emojis.js +++ b/app/assets/javascripts/admin/controllers/admin-emojis.js @@ -1,37 +1,74 @@ import { sort } from "@ember/object/computed"; -import EmberObject from "@ember/object"; +import EmberObject, { action, computed } from "@ember/object"; import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; + +const ALL_FILTER = "all"; + export default Controller.extend({ - sortedEmojis: sort("model", "emojiSorting"), + filter: null, + sorting: null, init() { this._super(...arguments); - this.emojiSorting = ["name"]; + this.setProperties({ + filter: ALL_FILTER, + sorting: ["group", "name"] + }); }, - actions: { - emojiUploaded(emoji) { - emoji.url += "?t=" + new Date().getTime(); - this.model.pushObject(EmberObject.create(emoji)); - }, + sortedEmojis: sort("filteredEmojis.[]", "sorting"), - destroy(emoji) { - return bootbox.confirm( - I18n.t("admin.emoji.delete_confirm", { name: emoji.get("name") }), - I18n.t("no_value"), - I18n.t("yes_value"), - destroy => { - if (destroy) { - return ajax("/admin/customize/emojis/" + emoji.get("name"), { - type: "DELETE" - }).then(() => { - this.model.removeObject(emoji); - }); - } - } - ); + emojiGroups: computed("model", { + get() { + return this.model.mapBy("group").uniq(); } + }), + + sortingGroups: computed("emojiGroups.[]", { + get() { + return [ALL_FILTER].concat(this.emojiGroups); + } + }), + + filteredEmojis: computed("model.[]", "filter", { + get() { + if (!this.filter || this.filter === ALL_FILTER) { + return this.model; + } else { + return this.model.filterBy("group", this.filter); + } + } + }), + + @action + filterGroups(value) { + this.set("filter", value); + }, + + @action + emojiUploaded(emoji, group) { + emoji.url += "?t=" + new Date().getTime(); + emoji.group = group; + this.model.pushObject(EmberObject.create(emoji)); + }, + + @action + destroyEmoji(emoji) { + return bootbox.confirm( + I18n.t("admin.emoji.delete_confirm", { name: emoji.get("name") }), + I18n.t("no_value"), + I18n.t("yes_value"), + destroy => { + if (destroy) { + return ajax("/admin/customize/emojis/" + emoji.get("name"), { + type: "DELETE" + }).then(() => { + this.model.removeObject(emoji); + }); + } + } + ); } }); diff --git a/app/assets/javascripts/admin/templates/emojis.hbs b/app/assets/javascripts/admin/templates/emojis.hbs index 5f9de8104ca..f2547b96780 100644 --- a/app/assets/javascripts/admin/templates/emojis.hbs +++ b/app/assets/javascripts/admin/templates/emojis.hbs @@ -1,35 +1,49 @@ -
-

{{i18n 'admin.emoji.title'}}

+
+

{{i18n 'admin.emoji.title'}}

-

{{i18n 'admin.emoji.help'}}

+

{{i18n "admin.emoji.help"}}

-

{{emoji-uploader done=(action "emojiUploaded")}}

+ {{emoji-uploader + emojiGroups=emojiGroups + done=(action "emojiUploaded") + }} + +
{{#if sortedEmojis}} -
- - +
+ + + + + + + + + + {{#each sortedEmojis as |e|}} - - - + + + + - - - {{#each sortedEmojis as |e|}} - - - - - - {{/each}} - -
{{i18n "admin.emoji.image"}}{{i18n "admin.emoji.name"}} + {{combo-box + value=filter + content=sortingGroups + nameProperty=null + valueProperty=null + onChange=(action "filterGroups") + }} +
{{i18n "admin.emoji.image"}}{{i18n "admin.emoji.name"}}:{{e.name}}:{{e.group}} + {{d-button + action=(action "destroyEmoji" e) + class="btn-danger" + icon="far-trash-alt" + }} +
:{{e.name}}: - {{d-button - action=(action "destroy" e) - class="btn-danger pull-right" - icon="far-trash-alt"}} -
-
+ {{/each}} + + {{/if}}
diff --git a/app/assets/javascripts/discourse/components/emoji-picker.js b/app/assets/javascripts/discourse/components/emoji-picker.js index 2696ca1358e..ef56c4182a8 100644 --- a/app/assets/javascripts/discourse/components/emoji-picker.js +++ b/app/assets/javascripts/discourse/components/emoji-picker.js @@ -14,9 +14,26 @@ import ENV, { INPUT_DELAY } from "discourse-common/config/environment"; const { run } = Ember; const PER_ROW = 11; -const customEmojis = _.keys(extendedEmojiList()).map(code => { - return { code, src: emojiUrlFor(code) }; -}); +function customEmojis() { + const list = extendedEmojiList(); + const emojis = Object.keys(list) + .map(code => { + const { group } = list[code]; + return { + code, + src: emojiUrlFor(code), + group, + key: `emoji_picker.${group || "default"}` + }; + }) + .reduce((acc, curr) => { + if (!acc[curr.group]) acc[curr.group] = []; + acc[curr.group].push(curr); + return acc; + }, {}); + + return Object.values(emojis); +} export default Component.extend({ automaticPositioning: true, @@ -35,7 +52,9 @@ export default Component.extend({ }, show() { - const template = findRawTemplate("emoji-picker")({ customEmojis }); + const template = findRawTemplate("emoji-picker")({ + customEmojis: customEmojis() + }); this.$picker.html(template); this.$filter = this.$picker.find(".filter"); @@ -579,7 +598,7 @@ export default Component.extend({ this.$picker.width() - this.$picker.find(".categories-column").width() - this.$picker.find(".diversity-picker").width() - - 32; + 60; this.$picker.find(".info").css("max-width", infoMaxWidth); }, diff --git a/app/assets/javascripts/discourse/components/emoji-uploader.js b/app/assets/javascripts/discourse/components/emoji-uploader.js index 77e2d5e9ffa..9693811c157 100644 --- a/app/assets/javascripts/discourse/components/emoji-uploader.js +++ b/app/assets/javascripts/discourse/components/emoji-uploader.js @@ -1,23 +1,53 @@ import { notEmpty, not } from "@ember/object/computed"; +import { action } from "@ember/object"; import Component from "@ember/component"; import discourseComputed from "discourse-common/utils/decorators"; import UploadMixin from "discourse/mixins/upload"; +const DEFAULT_GROUP = "default"; + export default Component.extend(UploadMixin, { type: "emoji", uploadUrl: "/admin/customize/emojis", hasName: notEmpty("name"), + hasGroup: notEmpty("group"), addDisabled: not("hasName"), + group: "default", + emojiGroups: null, + newEmojiGroups: null, + tagName: null, - uploadOptions() { - return { - sequentialUploads: true - }; + didReceiveAttrs() { + this._super(...arguments); + + this.set("newEmojiGroups", this.emojiGroups); }, - @discourseComputed("hasName", "name") - data(hasName, name) { - return hasName ? { name } : {}; + uploadOptions() { + return { sequentialUploads: true }; + }, + + @action + createEmojiGroup(group) { + this.setProperties({ + newEmojiGroups: this.emojiGroups.concat([group]).uniq(), + group + }); + }, + + @discourseComputed("hasName", "name", "hasGroup", "group") + data(hasName, name, hasGroup, group) { + const payload = {}; + + if (hasName) { + payload.name = name; + } + + if (hasGroup && group !== DEFAULT_GROUP) { + payload.group = group; + } + + return payload; }, validateUploadedFilesOptions() { @@ -25,7 +55,7 @@ export default Component.extend(UploadMixin, { }, uploadDone(upload) { - this.set("name", null); - this.done(upload); + this.done(upload, this.group); + this.setProperties({ name: null, group: DEFAULT_GROUP }); } }); diff --git a/app/assets/javascripts/discourse/initializers/enable-emoji.js b/app/assets/javascripts/discourse/initializers/enable-emoji.js index 71bf416025b..a6e3e6d03ad 100644 --- a/app/assets/javascripts/discourse/initializers/enable-emoji.js +++ b/app/assets/javascripts/discourse/initializers/enable-emoji.js @@ -24,7 +24,7 @@ export default { }); (PreloadStore.get("customEmoji") || []).forEach(emoji => - registerEmoji(emoji.name, emoji.url) + registerEmoji(emoji.name, emoji.url, emoji.group) ); } }; diff --git a/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs b/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs index 153fe696a68..186ee15afe2 100644 --- a/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs +++ b/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs @@ -1,11 +1,48 @@ -{{text-field name="name" placeholderKey="admin.emoji.name" value=name}} - - +{{#conditional-loading-section isLoading=uploading}} +
+
+ + {{i18n "admin.emoji.name"}} + +
+ {{input + name="name" + placeholderKey="admin.emoji.name" + value=(readonly name) + input=(action (mut name) value="target.value") + }} +
+
+
+ + {{i18n "admin.emoji.group"}} + +
+ {{combo-box + name="group" + value=group + content=newEmojiGroups + onChange=(action "createEmojiGroup") + valueProperty=null + nameProperty=null + options=(hash + allowAny=true + ) + }} +
+
+
+
+ +
+
+
+{{/conditional-loading-section}} diff --git a/app/assets/javascripts/discourse/templates/emoji-picker.hbr.erb b/app/assets/javascripts/discourse/templates/emoji-picker.hbr.erb index dd04f149d36..5a333521653 100644 --- a/app/assets/javascripts/discourse/templates/emoji-picker.hbr.erb +++ b/app/assets/javascripts/discourse/templates/emoji-picker.hbr.erb @@ -9,10 +9,12 @@
<% end %> - <% if !Emoji.custom.blank? %> -
- -
+ <% Emoji.custom.group_by { |emoji| emoji.group }.each do |group, emojis| %> + <% if emojis.present? %> +
+ +
+ <% end %> <% end %> @@ -49,18 +51,22 @@ <% end %> - {{#if customEmojis.length}} -
-
- {{i18n 'emoji_picker.custom'}} + {{#each customEmojis as |emojis|}} + {{#if emojis.length}} +
+
+ + {{i18n emojis.firstObject.key}} + +
+
+ {{#each emojis as |emoji|}} + + {{/each}} +
-
- {{#each customEmojis as |emoji|}} - - {{/each}} -
-
- {{/if}} + {{/if}} + {{/each}}