mirror of
https://github.com/discourse/discourse.git
synced 2025-06-07 19:34:54 +08:00
UX: Add simple-list setting type (#9970)
This commit is contained in:
57
app/assets/javascripts/admin/components/simple-list.js
Normal file
57
app/assets/javascripts/admin/components/simple-list.js
Normal file
@ -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)
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
});
|
@ -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"));
|
||||||
|
}
|
||||||
|
});
|
@ -25,7 +25,8 @@ const CUSTOM_TYPES = [
|
|||||||
"upload",
|
"upload",
|
||||||
"group_list",
|
"group_list",
|
||||||
"tag_list",
|
"tag_list",
|
||||||
"color"
|
"color",
|
||||||
|
"simple_list"
|
||||||
];
|
];
|
||||||
|
|
||||||
const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"];
|
const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"];
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
{{#if collection}}
|
||||||
|
<div class="values">
|
||||||
|
{{#each collection as |value index|}}
|
||||||
|
<div data-index={{index}} class="value">
|
||||||
|
{{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)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="simple-list-input">
|
||||||
|
{{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"
|
||||||
|
}}
|
||||||
|
</div>
|
@ -0,0 +1,3 @@
|
|||||||
|
{{simple-list values=value inputDelimiter=inputDelimiter onChange=(action "onChange")}}
|
||||||
|
{{setting-validation-message message=validationMessage}}
|
||||||
|
<div class="desc">{{html-safe setting.description}}</div>
|
@ -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 text-inputs need some padding
|
||||||
.mobile-view .admin-contents {
|
.mobile-view .admin-contents {
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
|
@ -4588,6 +4588,8 @@ en:
|
|||||||
modal_description: "Would you like to apply this change historically? This will change preferences for %{count} existing users."
|
modal_description: "Would you like to apply this change historically? This will change preferences for %{count} existing users."
|
||||||
modal_yes: "Yes"
|
modal_yes: "Yes"
|
||||||
modal_no: "No, only apply change going forward"
|
modal_no: "No, only apply change going forward"
|
||||||
|
simple_list:
|
||||||
|
add_item: "Add item..."
|
||||||
|
|
||||||
badges:
|
badges:
|
||||||
title: Badges
|
title: Badges
|
||||||
|
@ -1445,7 +1445,7 @@ security:
|
|||||||
content_security_policy_collect_reports:
|
content_security_policy_collect_reports:
|
||||||
default: false
|
default: false
|
||||||
content_security_policy_script_src:
|
content_security_policy_script_src:
|
||||||
type: list
|
type: simple_list
|
||||||
default: ""
|
default: ""
|
||||||
invalidate_inactive_admin_email_after_days:
|
invalidate_inactive_admin_email_after_days:
|
||||||
default: 365
|
default: 365
|
||||||
|
@ -34,7 +34,8 @@ class SiteSettings::TypeSupervisor
|
|||||||
group: 19,
|
group: 19,
|
||||||
group_list: 20,
|
group_list: 20,
|
||||||
tag_list: 21,
|
tag_list: 21,
|
||||||
color: 22
|
color: 22,
|
||||||
|
simple_list: 23
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -88,6 +88,9 @@ describe SiteSettings::TypeSupervisor do
|
|||||||
it "'color' should be at the right position" do
|
it "'color' should be at the right position" do
|
||||||
expect(SiteSettings::TypeSupervisor.types[:color]).to eq(22)
|
expect(SiteSettings::TypeSupervisor.types[:color]).to eq(22)
|
||||||
end
|
end
|
||||||
|
it "'simple_list' should be at the right position" do
|
||||||
|
expect(SiteSettings::TypeSupervisor.types[:simple_list]).to eq(23)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
84
test/javascripts/components/simple-list-test.js
Normal file
84
test/javascripts/components/simple-list-test.js
Normal file
@ -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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
Reference in New Issue
Block a user