From 2d880b42a34de16fd99787d9a6898a36cf9bc14f Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Thu, 4 Jun 2020 10:44:54 -0400 Subject: [PATCH] UX: Add simple-list setting type (#9970) --- .../admin/components/simple-list.js | 57 +++++++++++++ .../components/site-settings/simple-list.js | 11 +++ .../admin/mixins/setting-component.js | 3 +- .../templates/components/simple-list.hbs | 40 +++++++++ .../components/site-settings/simple-list.hbs | 3 + .../stylesheets/common/admin/admin_base.scss | 14 ++++ config/locales/client.en.yml | 2 + config/site_settings.yml | 2 +- lib/site_settings/type_supervisor.rb | 3 +- .../site_settings/type_supervisor_spec.rb | 3 + .../components/simple-list-test.js | 84 +++++++++++++++++++ 11 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/admin/components/simple-list.js create mode 100644 app/assets/javascripts/admin/components/site-settings/simple-list.js create mode 100644 app/assets/javascripts/admin/templates/components/simple-list.hbs create mode 100644 app/assets/javascripts/admin/templates/components/site-settings/simple-list.hbs create mode 100644 test/javascripts/components/simple-list-test.js diff --git a/app/assets/javascripts/admin/components/simple-list.js b/app/assets/javascripts/admin/components/simple-list.js new file mode 100644 index 00000000000..3f43885d77d --- /dev/null +++ b/app/assets/javascripts/admin/components/simple-list.js @@ -0,0 +1,57 @@ +import { empty } from "@ember/object/computed"; +import Component from "@ember/component"; +import { action } from "@ember/object"; +import { on } from "discourse-common/utils/decorators"; + +export default Component.extend({ + classNameBindings: [":simple-list", ":value-list"], + inputEmpty: empty("newValue"), + inputDelimiter: null, + newValue: "", + collection: null, + values: null, + + @on("didReceiveAttrs") + _setupCollection() { + this.set("collection", this._splitValues(this.values, this.inputDelimiter)); + }, + + keyDown(event) { + if (event.which === 13) { + this.addValue(this.newValue); + return; + } + }, + + @action + changeValue(index, newValue) { + this.collection.replace(index, 1, [newValue]); + this.collection.arrayContentDidChange(index); + this._onChange(); + }, + + @action + addValue(newValue) { + if (this.inputEmpty) return; + + this.set("newValue", null); + this.collection.addObject(newValue); + this._onChange(); + }, + + @action + removeValue(value) { + this.collection.removeObject(value); + this._onChange(); + }, + + _onChange() { + this.attrs.onChange && this.attrs.onChange(this.collection); + }, + + _splitValues(values, delimiter) { + return values && values.length + ? values.split(delimiter || "\n").filter(Boolean) + : []; + } +}); diff --git a/app/assets/javascripts/admin/components/site-settings/simple-list.js b/app/assets/javascripts/admin/components/site-settings/simple-list.js new file mode 100644 index 00000000000..aab078bbe3c --- /dev/null +++ b/app/assets/javascripts/admin/components/site-settings/simple-list.js @@ -0,0 +1,11 @@ +import Component from "@ember/component"; +import { action } from "@ember/object"; + +export default Component.extend({ + inputDelimiter: "|", + + @action + onChange(value) { + this.set("value", value.join(this.inputDelimiter || "\n")); + } +}); diff --git a/app/assets/javascripts/admin/mixins/setting-component.js b/app/assets/javascripts/admin/mixins/setting-component.js index 83dcc31e65a..699cecd32eb 100644 --- a/app/assets/javascripts/admin/mixins/setting-component.js +++ b/app/assets/javascripts/admin/mixins/setting-component.js @@ -25,7 +25,8 @@ const CUSTOM_TYPES = [ "upload", "group_list", "tag_list", - "color" + "color", + "simple_list" ]; const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"]; diff --git a/app/assets/javascripts/admin/templates/components/simple-list.hbs b/app/assets/javascripts/admin/templates/components/simple-list.hbs new file mode 100644 index 00000000000..3e78f77b059 --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/simple-list.hbs @@ -0,0 +1,40 @@ +{{#if collection}} +
+ {{#each collection as |value index|}} +
+ {{d-button + action=(action "removeValue") + actionParam=value + icon="times" + class="remove-value-btn btn-small" + }} + + {{input + title=value + value=value + class="value-input" + focus-out=(action "changeValue" index) + }} +
+ {{/each}} +
+{{/if}} + +
+ {{input + type="text" + value=newValue + placeholderKey="admin.site_settings.simple_list.add_item" + class="add-value-input" + autocomplete="discourse" + autocorrect="off" + autocapitalize="off"}} + + {{d-button + action=(action "addValue") + actionParam=newValue + disabled=inputEmpty + icon="plus" + class="add-value-btn btn-small" + }} +
diff --git a/app/assets/javascripts/admin/templates/components/site-settings/simple-list.hbs b/app/assets/javascripts/admin/templates/components/site-settings/simple-list.hbs new file mode 100644 index 00000000000..3eca57db83a --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/site-settings/simple-list.hbs @@ -0,0 +1,3 @@ +{{simple-list values=value inputDelimiter=inputDelimiter onChange=(action "onChange")}} +{{setting-validation-message message=validationMessage}} +
{{html-safe setting.description}}
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index afdc9a7e3be..208e9127b36 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -931,6 +931,20 @@ table#user-badges { } } +.simple-list-input { + display: flex; + + .add-value-input { + margin: 0; + box-sizing: border-box; + flex: 1 0 0px; + } + + .add-value-btn { + margin-left: 0.25em; + } +} + // Mobile view text-inputs need some padding .mobile-view .admin-contents { input[type="text"] { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index b5db1f5059a..308944b3b28 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -4588,6 +4588,8 @@ en: modal_description: "Would you like to apply this change historically? This will change preferences for %{count} existing users." modal_yes: "Yes" modal_no: "No, only apply change going forward" + simple_list: + add_item: "Add item..." badges: title: Badges diff --git a/config/site_settings.yml b/config/site_settings.yml index 5901d6d3388..c658ec727c1 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1445,7 +1445,7 @@ security: content_security_policy_collect_reports: default: false content_security_policy_script_src: - type: list + type: simple_list default: "" invalidate_inactive_admin_email_after_days: default: 365 diff --git a/lib/site_settings/type_supervisor.rb b/lib/site_settings/type_supervisor.rb index 26cc20b84e5..ae78d5dd1ff 100644 --- a/lib/site_settings/type_supervisor.rb +++ b/lib/site_settings/type_supervisor.rb @@ -34,7 +34,8 @@ class SiteSettings::TypeSupervisor group: 19, group_list: 20, tag_list: 21, - color: 22 + color: 22, + simple_list: 23 ) end diff --git a/spec/components/site_settings/type_supervisor_spec.rb b/spec/components/site_settings/type_supervisor_spec.rb index caea601178d..cc697c01d5c 100644 --- a/spec/components/site_settings/type_supervisor_spec.rb +++ b/spec/components/site_settings/type_supervisor_spec.rb @@ -88,6 +88,9 @@ describe SiteSettings::TypeSupervisor do it "'color' should be at the right position" do expect(SiteSettings::TypeSupervisor.types[:color]).to eq(22) end + it "'simple_list' should be at the right position" do + expect(SiteSettings::TypeSupervisor.types[:simple_list]).to eq(23) + end end end diff --git a/test/javascripts/components/simple-list-test.js b/test/javascripts/components/simple-list-test.js new file mode 100644 index 00000000000..5c8d26dd51c --- /dev/null +++ b/test/javascripts/components/simple-list-test.js @@ -0,0 +1,84 @@ +import componentTest from "helpers/component-test"; +moduleForComponent("simple-list", { integration: true }); + +componentTest("adding a value", { + template: "{{simple-list values=values}}", + + beforeEach() { + this.set("values", "vinkas\nosama"); + }, + + async test(assert) { + assert.ok( + find(".add-value-btn[disabled]").length, + "while loading the + button is disabled" + ); + + await fillIn(".add-value-input", "penar"); + await click(".add-value-btn"); + + assert.ok( + find(".values .value").length === 3, + "it adds the value to the list of values" + ); + + assert.ok( + find(".values .value[data-index='2'] .value-input")[0].value === "penar", + "it sets the correct value for added item" + ); + + await fillIn(".add-value-input", "eviltrout"); + await keyEvent(".add-value-input", "keydown", 13); // enter + + assert.ok( + find(".values .value").length === 4, + "it adds the value when keying Enter" + ); + } +}); + +componentTest("removing a value", { + template: "{{simple-list values=values}}", + + beforeEach() { + this.set("values", "vinkas\nosama"); + }, + + async test(assert) { + await click(".values .value[data-index='0'] .remove-value-btn"); + + assert.ok( + find(".values .value").length === 1, + "it removes the value from the list of values" + ); + + assert.ok( + find(".values .value[data-index='0'] .value-input")[0].value === "osama", + "it removes the correct value" + ); + } +}); + +componentTest("delimiter support", { + template: "{{simple-list values=values inputDelimiter='|'}}", + + beforeEach() { + this.set("values", "vinkas|osama"); + }, + + async test(assert) { + await fillIn(".add-value-input", "eviltrout"); + await click(".add-value-btn"); + + assert.ok( + find(".values .value").length === 3, + "it adds the value to the list of values" + ); + + assert.ok( + find(".values .value[data-index='2'] .value-input")[0].value === + "eviltrout", + "it adds the correct value" + ); + } +});