DEV: Convert admin user fields to FormKit (#29070)
This change replaces the admin form for adding and editing custom user fields with a new FormKit implementation.
This commit is contained in:
parent
ede06ffd43
commit
408de686bb
|
@ -1,134 +1,163 @@
|
||||||
<div class="user-field">
|
{{#if (or this.isEditing (not @userField.id))}}
|
||||||
{{#if (or this.isEditing (not this.userField.id))}}
|
<div class="admin-config-area user-field">
|
||||||
<AdminFormRow @label="admin.user_fields.type">
|
<div class="admin-config-area__primary-content">
|
||||||
<ComboBox
|
<div class="admin-config-area-card">
|
||||||
@content={{this.fieldTypes}}
|
<Form
|
||||||
@value={{this.buffered.field_type}}
|
@data={{this.formData}}
|
||||||
@onChange={{fn (mut this.buffered.field_type)}}
|
@onSubmit={{this.save}}
|
||||||
/>
|
{{did-insert this._focusName}}
|
||||||
</AdminFormRow>
|
as |form transientData|
|
||||||
|
>
|
||||||
|
<form.Field
|
||||||
|
@name="field_type"
|
||||||
|
@title={{i18n "admin.user_fields.type"}}
|
||||||
|
@format="large"
|
||||||
|
@validation="required"
|
||||||
|
as |field|
|
||||||
|
>
|
||||||
|
<field.Select as |select|>
|
||||||
|
{{#each @fieldTypes as |fieldType|}}
|
||||||
|
<select.Option
|
||||||
|
@value={{fieldType.id}}
|
||||||
|
>{{fieldType.name}}</select.Option>
|
||||||
|
{{/each}}
|
||||||
|
</field.Select>
|
||||||
|
</form.Field>
|
||||||
|
|
||||||
<AdminFormRow @label="admin.user_fields.name">
|
<form.Field
|
||||||
<Input
|
@name="name"
|
||||||
@value={{this.buffered.name}}
|
@title={{i18n "admin.user_fields.name"}}
|
||||||
class="user-field-name"
|
@format="large"
|
||||||
maxlength="255"
|
@validation="required"
|
||||||
/>
|
as |field|
|
||||||
</AdminFormRow>
|
>
|
||||||
|
<field.Input class="user-field-name" maxlength="255" />
|
||||||
|
</form.Field>
|
||||||
|
|
||||||
<AdminFormRow @label="admin.user_fields.description">
|
<form.Field
|
||||||
<Input
|
@name="description"
|
||||||
@value={{this.buffered.description}}
|
@title={{i18n "admin.user_fields.description"}}
|
||||||
class="user-field-desc"
|
@format="large"
|
||||||
maxlength="1000"
|
@validation="required"
|
||||||
/>
|
as |field|
|
||||||
</AdminFormRow>
|
>
|
||||||
|
<field.Input class="user-field-desc" maxlength="1000" />
|
||||||
|
</form.Field>
|
||||||
|
|
||||||
{{#if this.bufferedFieldType.hasOptions}}
|
{{#if
|
||||||
<AdminFormRow @label="admin.user_fields.options">
|
(or
|
||||||
<ValueList @values={{this.buffered.options}} @inputType="array" />
|
(eq transientData.field_type "dropdown")
|
||||||
</AdminFormRow>
|
(eq transientData.field_type "multiselect")
|
||||||
{{/if}}
|
)
|
||||||
|
}}
|
||||||
|
<form.Field
|
||||||
|
@name="options"
|
||||||
|
@title={{i18n "admin.user_fields.options"}}
|
||||||
|
@format="large"
|
||||||
|
@validation="required"
|
||||||
|
as |field|
|
||||||
|
>
|
||||||
|
<field.Custom>
|
||||||
|
<ValueList
|
||||||
|
@values={{transientData.options}}
|
||||||
|
@inputType="array"
|
||||||
|
@onChange={{field.set}}
|
||||||
|
/>
|
||||||
|
</field.Custom>
|
||||||
|
</form.Field>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<AdminFormRow @label="admin.user_fields.requirement.title">
|
<form.Field
|
||||||
<label class="optional">
|
@name="requirement"
|
||||||
<RadioButton
|
@title={{i18n "admin.user_fields.requirement.title"}}
|
||||||
@value="optional"
|
@validation="required"
|
||||||
@name="requirement"
|
@onSet={{this.setRequirement}}
|
||||||
@selection={{this.buffered.requirement}}
|
@format="full"
|
||||||
@onChange={{action "changeRequirementType"}}
|
as |field|
|
||||||
/>
|
>
|
||||||
<span>{{i18n "admin.user_fields.requirement.optional.title"}}</span>
|
<field.RadioGroup as |radioGroup|>
|
||||||
</label>
|
<radioGroup.Radio @value="optional">
|
||||||
|
{{i18n "admin.user_fields.requirement.optional.title"}}
|
||||||
|
</radioGroup.Radio>
|
||||||
|
<radioGroup.Radio @value="for_all_users" as |radio|>
|
||||||
|
{{i18n "admin.user_fields.requirement.for_all_users.title"}}
|
||||||
|
<radio.Description>{{i18n
|
||||||
|
"admin.user_fields.requirement.for_all_users.description"
|
||||||
|
}}</radio.Description>
|
||||||
|
</radioGroup.Radio>
|
||||||
|
<radioGroup.Radio @value="on_signup" as |radio|>
|
||||||
|
{{i18n "admin.user_fields.requirement.on_signup.title"}}
|
||||||
|
<radio.Description>{{i18n
|
||||||
|
"admin.user_fields.requirement.on_signup.description"
|
||||||
|
}}</radio.Description>
|
||||||
|
</radioGroup.Radio>
|
||||||
|
</field.RadioGroup>
|
||||||
|
</form.Field>
|
||||||
|
|
||||||
<label class="for_all_users">
|
<form.CheckboxGroup
|
||||||
<RadioButton
|
class="user-field-preferences"
|
||||||
@value="for_all_users"
|
@title={{i18n "admin.user_fields.preferences"}}
|
||||||
@name="requirement"
|
as |group|
|
||||||
@selection={{this.buffered.requirement}}
|
>
|
||||||
@onChange={{action "changeRequirementType"}}
|
<group.Field
|
||||||
/>
|
@name="editable"
|
||||||
<div class="label-text">
|
@showTitle={{false}}
|
||||||
<span>{{i18n
|
@title={{i18n "admin.user_fields.editable.title"}}
|
||||||
"admin.user_fields.requirement.for_all_users.title"
|
as |field|
|
||||||
}}</span>
|
>
|
||||||
<div class="description">{{i18n
|
<field.Checkbox disabled={{this.editableDisabled}} />
|
||||||
"admin.user_fields.requirement.for_all_users.description"
|
</group.Field>
|
||||||
}}</div>
|
<group.Field
|
||||||
</div>
|
@name="show_on_profile"
|
||||||
</label>
|
@showTitle={{false}}
|
||||||
|
@title={{i18n "admin.user_fields.show_on_profile.title"}}
|
||||||
|
as |field|
|
||||||
|
>
|
||||||
|
<field.Checkbox />
|
||||||
|
</group.Field>
|
||||||
|
<group.Field
|
||||||
|
@name="show_on_user_card"
|
||||||
|
@showTitle={{false}}
|
||||||
|
@title={{i18n "admin.user_fields.show_on_user_card.title"}}
|
||||||
|
as |field|
|
||||||
|
>
|
||||||
|
<field.Checkbox />
|
||||||
|
</group.Field>
|
||||||
|
<group.Field
|
||||||
|
@name="searchable"
|
||||||
|
@showTitle={{false}}
|
||||||
|
@title={{i18n "admin.user_fields.searchable.title"}}
|
||||||
|
as |field|
|
||||||
|
>
|
||||||
|
<field.Checkbox />
|
||||||
|
</group.Field>
|
||||||
|
</form.CheckboxGroup>
|
||||||
|
|
||||||
<label class="on_signup">
|
<form.Actions>
|
||||||
<RadioButton
|
<form.Submit
|
||||||
@value="on_signup"
|
class="save"
|
||||||
@name="requirement"
|
@icon="check"
|
||||||
@selection={{this.buffered.requirement}}
|
@label="admin.user_fields.save"
|
||||||
@onChange={{action "changeRequirementType"}}
|
/>
|
||||||
/>
|
<form.Button
|
||||||
<div class="label-text">
|
@action={{this.cancel}}
|
||||||
<span>{{i18n "admin.user_fields.requirement.on_signup.title"}}</span>
|
@label="admin.user_fields.cancel"
|
||||||
<div class="description">{{i18n
|
class="btn-default"
|
||||||
"admin.user_fields.requirement.on_signup.description"
|
/>
|
||||||
}}</div>
|
</form.Actions>
|
||||||
</div>
|
</Form>
|
||||||
</label>
|
</div>
|
||||||
</AdminFormRow>
|
</div>
|
||||||
|
</div>
|
||||||
<AdminFormRow @label="admin.user_fields.preferences">
|
{{else}}
|
||||||
<label>
|
<div class="user-field">
|
||||||
<Input
|
|
||||||
@type="checkbox"
|
|
||||||
@checked={{this.buffered.editable}}
|
|
||||||
disabled={{this.editableDisabled}}
|
|
||||||
/>
|
|
||||||
<span>{{i18n "admin.user_fields.editable.title"}}</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
<Input @type="checkbox" @checked={{this.buffered.show_on_profile}} />
|
|
||||||
<span>{{i18n "admin.user_fields.show_on_profile.title"}}</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
<Input @type="checkbox" @checked={{this.buffered.show_on_user_card}} />
|
|
||||||
<span>{{i18n "admin.user_fields.show_on_user_card.title"}}</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
<Input @type="checkbox" @checked={{this.buffered.searchable}} />
|
|
||||||
<span>{{i18n "admin.user_fields.searchable.title"}}</span>
|
|
||||||
</label>
|
|
||||||
</AdminFormRow>
|
|
||||||
|
|
||||||
<PluginOutlet
|
|
||||||
@name="after-admin-user-fields"
|
|
||||||
@outletArgs={{hash buffered=this.buffered}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AdminFormRow>
|
|
||||||
<DButton
|
|
||||||
@action={{this.save}}
|
|
||||||
@icon="check"
|
|
||||||
@label="admin.user_fields.save"
|
|
||||||
class="btn-primary save"
|
|
||||||
/>
|
|
||||||
<DButton
|
|
||||||
@action={{this.cancel}}
|
|
||||||
@icon="xmark"
|
|
||||||
@label="admin.user_fields.cancel"
|
|
||||||
class="btn-danger cancel"
|
|
||||||
/>
|
|
||||||
</AdminFormRow>
|
|
||||||
{{else}}
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-display">
|
<div class="form-display">
|
||||||
<b class="name">{{this.userField.name}}</b>
|
<b class="name">{{@userField.name}}</b>
|
||||||
<br />
|
<br />
|
||||||
<span class="description">{{html-safe
|
<span class="description">{{html-safe @userField.description}}</span>
|
||||||
this.userField.description
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-display field-type">{{this.fieldName}}</div>
|
<div class="form-display field-type">{{@userField.fieldTypeName}}</div>
|
||||||
<div class="form-element controls">
|
<div class="form-element controls">
|
||||||
<DButton
|
<DButton
|
||||||
@action={{this.edit}}
|
@action={{this.edit}}
|
||||||
|
@ -137,19 +166,19 @@
|
||||||
class="btn-default edit"
|
class="btn-default edit"
|
||||||
/>
|
/>
|
||||||
<DButton
|
<DButton
|
||||||
@action={{fn this.destroyAction this.userField}}
|
@action={{fn @destroyAction @userField}}
|
||||||
@icon="trash-can"
|
@icon="trash-can"
|
||||||
@label="admin.user_fields.delete"
|
@label="admin.user_fields.delete"
|
||||||
class="btn-danger cancel"
|
class="btn-danger cancel"
|
||||||
/>
|
/>
|
||||||
<DButton
|
<DButton
|
||||||
@action={{fn this.moveUpAction this.userField}}
|
@action={{fn @moveUpAction @userField}}
|
||||||
@icon="arrow-up"
|
@icon="arrow-up"
|
||||||
@disabled={{this.cantMoveUp}}
|
@disabled={{this.cantMoveUp}}
|
||||||
class="btn-default"
|
class="btn-default"
|
||||||
/>
|
/>
|
||||||
<DButton
|
<DButton
|
||||||
@action={{fn this.moveDownAction this.userField}}
|
@action={{fn @moveDownAction @userField}}
|
||||||
@icon="arrow-down"
|
@icon="arrow-down"
|
||||||
@disabled={{this.cantMoveDown}}
|
@disabled={{this.cantMoveDown}}
|
||||||
class="btn-default"
|
class="btn-default"
|
||||||
|
@ -157,5 +186,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row user-field-flags">{{this.flags}}</div>
|
<div class="row user-field-flags">{{this.flags}}</div>
|
||||||
{{/if}}
|
</div>
|
||||||
</div>
|
{{/if}}
|
|
@ -1,4 +1,5 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@glimmer/component";
|
||||||
|
import { cached, tracked } from "@glimmer/tracking";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { schedule } from "@ember/runloop";
|
import { schedule } from "@ember/runloop";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
|
@ -6,78 +7,102 @@ import { isEmpty } from "@ember/utils";
|
||||||
import { tagName } from "@ember-decorators/component";
|
import { tagName } from "@ember-decorators/component";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { i18n, propertyEqual } from "discourse/lib/computed";
|
|
||||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import UserField from "admin/models/user-field";
|
import UserField from "admin/models/user-field";
|
||||||
|
|
||||||
@tagName("")
|
@tagName("")
|
||||||
export default class AdminUserFieldItem extends Component.extend(
|
export default class AdminUserFieldItem extends Component {
|
||||||
bufferedProperty("userField")
|
|
||||||
) {
|
|
||||||
@service adminCustomUserFields;
|
@service adminCustomUserFields;
|
||||||
@service dialog;
|
@service dialog;
|
||||||
|
|
||||||
isEditing = false;
|
@tracked isEditing = false;
|
||||||
|
@tracked
|
||||||
|
editableDisabled = this.args.userField.requirement === "for_all_users";
|
||||||
|
|
||||||
@propertyEqual("userField", "firstField") cantMoveUp;
|
get fieldName() {
|
||||||
@propertyEqual("userField", "lastField") cantMoveDown;
|
return UserField.fieldTypeById(this.fieldType)?.name;
|
||||||
|
|
||||||
@i18n("admin.user_fields.description") userFieldsDescription;
|
|
||||||
|
|
||||||
@discourseComputed("buffered.field_type")
|
|
||||||
bufferedFieldType(fieldType) {
|
|
||||||
return UserField.fieldTypeById(fieldType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
didInsertElement() {
|
get cantMoveUp() {
|
||||||
super.didInsertElement(...arguments);
|
return this.args.userField.id === this.args.firstField?.id;
|
||||||
|
|
||||||
this._focusName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_focusName() {
|
get cantMoveDown() {
|
||||||
schedule("afterRender", () => {
|
return this.args.userField.id === this.args.lastField?.id;
|
||||||
document.querySelector(".user-field-name")?.focus();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@discourseComputed("userField.field_type")
|
get isNewRecord() {
|
||||||
fieldName(fieldType) {
|
return isEmpty(this.args.userField?.id);
|
||||||
return UserField.fieldTypeById(fieldType)?.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@discourseComputed(
|
get flags() {
|
||||||
"userField.{editable,show_on_profile,show_on_user_card,searchable}"
|
const flags = [
|
||||||
)
|
"editable",
|
||||||
flags(userField) {
|
"show_on_profile",
|
||||||
const ret = [];
|
"show_on_user_card",
|
||||||
if (userField.editable) {
|
"searchable",
|
||||||
ret.push(I18n.t("admin.user_fields.editable.enabled"));
|
];
|
||||||
}
|
|
||||||
if (userField.show_on_profile) {
|
|
||||||
ret.push(I18n.t("admin.user_fields.show_on_profile.enabled"));
|
|
||||||
}
|
|
||||||
if (userField.show_on_user_card) {
|
|
||||||
ret.push(I18n.t("admin.user_fields.show_on_user_card.enabled"));
|
|
||||||
}
|
|
||||||
if (userField.searchable) {
|
|
||||||
ret.push(I18n.t("admin.user_fields.searchable.enabled"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.join(", ");
|
return flags
|
||||||
|
.map((flag) => {
|
||||||
|
if (this.args.userField[flag]) {
|
||||||
|
return I18n.t(`admin.user_fields.${flag}.enabled`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@discourseComputed("buffered.requirement")
|
@cached
|
||||||
editableDisabled(requirement) {
|
get formData() {
|
||||||
return requirement === "for_all_users";
|
return this.args.userField.getProperties(
|
||||||
|
"field_type",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"requirement",
|
||||||
|
"editable",
|
||||||
|
"show_on_profile",
|
||||||
|
"show_on_user_card",
|
||||||
|
"searchable",
|
||||||
|
"options",
|
||||||
|
...this.adminCustomUserFields.additionalProperties
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
changeRequirementType(requirement) {
|
setRequirement(value, { set }) {
|
||||||
this.buffered.set("requirement", requirement);
|
set("requirement", value);
|
||||||
this.buffered.set("editable", requirement === "for_all_users");
|
|
||||||
|
if (value === "for_all_users") {
|
||||||
|
this.editableDisabled = true;
|
||||||
|
set("editable", true);
|
||||||
|
} else {
|
||||||
|
this.editableDisabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async save(data) {
|
||||||
|
let confirm = true;
|
||||||
|
|
||||||
|
if (data.requirement === "for_all_users") {
|
||||||
|
confirm = await this._confirmChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.args.userField
|
||||||
|
.save(data)
|
||||||
|
.then(() => {
|
||||||
|
if (this.isDestroying || this.isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isEditing = false;
|
||||||
|
})
|
||||||
|
.catch(popupAjaxError);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _confirmChanges() {
|
async _confirmChanges() {
|
||||||
|
@ -90,57 +115,23 @@ export default class AdminUserFieldItem extends Component.extend(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
async save() {
|
|
||||||
const attrs = this.buffered.getProperties(
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"field_type",
|
|
||||||
"editable",
|
|
||||||
"requirement",
|
|
||||||
"show_on_profile",
|
|
||||||
"show_on_user_card",
|
|
||||||
"searchable",
|
|
||||||
"options",
|
|
||||||
...this.adminCustomUserFields.additionalProperties
|
|
||||||
);
|
|
||||||
|
|
||||||
let confirm = true;
|
|
||||||
|
|
||||||
if (attrs.requirement === "for_all_users") {
|
|
||||||
confirm = await this._confirmChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!confirm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.userField
|
|
||||||
.save(attrs)
|
|
||||||
.then(() => {
|
|
||||||
if (this.isDestroying || this.isDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set("isEditing", false);
|
|
||||||
this.commitBuffer();
|
|
||||||
})
|
|
||||||
.catch(popupAjaxError);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
edit() {
|
edit() {
|
||||||
this.set("isEditing", true);
|
this.isEditing = true;
|
||||||
this._focusName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
cancel() {
|
cancel() {
|
||||||
if (isEmpty(this.userField?.id)) {
|
if (this.isNewRecord) {
|
||||||
this.destroyAction(this.userField);
|
this.args.destroyAction(this.args.userField);
|
||||||
} else {
|
} else {
|
||||||
this.rollbackBuffer();
|
this.isEditing = false;
|
||||||
this.set("isEditing", false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_focusName() {
|
||||||
|
schedule("afterRender", () =>
|
||||||
|
document.querySelector(".user-field-name")?.focus()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export default class ValueList extends Component {
|
||||||
newValue = "";
|
newValue = "";
|
||||||
collection = null;
|
collection = null;
|
||||||
values = null;
|
values = null;
|
||||||
|
onChange = null;
|
||||||
|
|
||||||
@reads("addKey") noneKey;
|
@reads("addKey") noneKey;
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ export default class ValueList extends Component {
|
||||||
super.didReceiveAttrs(...arguments);
|
super.didReceiveAttrs(...arguments);
|
||||||
|
|
||||||
if (this.inputType === "array") {
|
if (this.inputType === "array") {
|
||||||
this.set("collection", this.values || []);
|
this.set("collection", this.values ? [...this.values] : []);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +115,11 @@ export default class ValueList extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveValues() {
|
_saveValues() {
|
||||||
|
if (this.onChange) {
|
||||||
|
this.onChange([...this.collection]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.inputType === "array") {
|
if (this.inputType === "array") {
|
||||||
this.set("values", this.collection);
|
this.set("values", this.collection);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -28,6 +28,7 @@ export default class AdminUserFieldsController extends Controller {
|
||||||
createField() {
|
createField() {
|
||||||
const f = this.store.createRecord("user-field", {
|
const f = this.store.createRecord("user-field", {
|
||||||
field_type: "text",
|
field_type: "text",
|
||||||
|
requirement: "optional",
|
||||||
position: MAX_FIELDS,
|
position: MAX_FIELDS,
|
||||||
});
|
});
|
||||||
this.model.pushObject(f);
|
this.model.pushObject(f);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
import { i18n } from "discourse/lib/computed";
|
import { i18n } from "discourse/lib/computed";
|
||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
|
@ -19,6 +20,16 @@ export default class UserField extends RestModel {
|
||||||
static fieldTypeById(id) {
|
static fieldTypeById(id) {
|
||||||
return this.fieldTypes().findBy("id", id);
|
return this.fieldTypes().findBy("id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@tracked field_type;
|
||||||
|
@tracked editable;
|
||||||
|
@tracked show_on_profile;
|
||||||
|
@tracked show_on_user_card;
|
||||||
|
@tracked searchable;
|
||||||
|
|
||||||
|
get fieldTypeName() {
|
||||||
|
return UserField.fieldTypes().find((ft) => ft.id === this.field_type).name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserFieldType extends EmberObject {
|
class UserFieldType extends EmberObject {
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
<div class="user-fields">
|
<div class="admin-config-page__main-area">
|
||||||
<h2>{{i18n "admin.user_fields.title"}}</h2>
|
<div class="user-fields">
|
||||||
|
<h2>{{i18n "admin.user_fields.title"}}</h2>
|
||||||
|
|
||||||
<p class="desc">{{i18n "admin.user_fields.help"}}</p>
|
<p class="desc">{{i18n "admin.user_fields.help"}}</p>
|
||||||
|
|
||||||
{{#if this.model}}
|
{{#if this.model}}
|
||||||
{{#each this.sortedFields as |uf|}}
|
{{#each this.sortedFields as |uf|}}
|
||||||
<AdminUserFieldItem
|
<AdminUserFieldItem
|
||||||
@userField={{uf}}
|
@userField={{uf}}
|
||||||
@fieldTypes={{this.fieldTypes}}
|
@fieldTypes={{this.fieldTypes}}
|
||||||
@firstField={{this.firstField}}
|
@firstField={{this.firstField}}
|
||||||
@lastField={{this.lastField}}
|
@lastField={{this.lastField}}
|
||||||
@destroyAction={{this.destroyField}}
|
@destroyAction={{this.destroyField}}
|
||||||
@moveUpAction={{this.moveUp}}
|
@moveUpAction={{this.moveUp}}
|
||||||
@moveDownAction={{this.moveDown}}
|
@moveDownAction={{this.moveDown}}
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<DButton
|
<DButton
|
||||||
@disabled={{this.createDisabled}}
|
@disabled={{this.createDisabled}}
|
||||||
@action={{this.createField}}
|
@action={{this.createField}}
|
||||||
@label="admin.user_fields.create"
|
@label="admin.user_fields.create"
|
||||||
@icon="plus"
|
@icon="plus"
|
||||||
class="btn-primary"
|
class="btn-primary"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,95 +0,0 @@
|
||||||
import { click, render } from "@ember/test-helpers";
|
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
|
||||||
import { module, test } from "qunit";
|
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
|
||||||
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
|
|
||||||
import I18n from "discourse-i18n";
|
|
||||||
|
|
||||||
module("Integration | Component | admin-user-field-item", function (hooks) {
|
|
||||||
setupRenderingTest(hooks);
|
|
||||||
|
|
||||||
test("user field without an id", async function (assert) {
|
|
||||||
await render(hbs`<AdminUserFieldItem @userField={{this.userField}} />`);
|
|
||||||
|
|
||||||
assert.ok(exists(".save"), "displays editing mode");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("cancel action", async function (assert) {
|
|
||||||
this.set("userField", { id: 1, field_type: "text" });
|
|
||||||
this.set("isEditing", true);
|
|
||||||
this.set("destroyAction", () => {});
|
|
||||||
this.set("moveUpAction", () => {});
|
|
||||||
this.set("moveDownAction", () => {});
|
|
||||||
|
|
||||||
await render(hbs`
|
|
||||||
<AdminUserFieldItem
|
|
||||||
@isEditing={{this.isEditing}}
|
|
||||||
@destroyAction={{this.destroyAction}}
|
|
||||||
@moveUpAction={{this.moveUpAction}}
|
|
||||||
@moveDownAction={{this.moveDownAction}}
|
|
||||||
@userField={{this.userField}}
|
|
||||||
/>`);
|
|
||||||
|
|
||||||
await click(".cancel");
|
|
||||||
assert.ok(exists(".edit"));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("edit action", async function (assert) {
|
|
||||||
this.set("userField", { id: 1, field_type: "text" });
|
|
||||||
this.set("destroyAction", () => {});
|
|
||||||
this.set("moveUpAction", () => {});
|
|
||||||
this.set("moveDownAction", () => {});
|
|
||||||
|
|
||||||
await render(hbs`
|
|
||||||
<AdminUserFieldItem
|
|
||||||
@destroyAction={{this.destroyAction}}
|
|
||||||
@moveUpAction={{this.moveUpAction}}
|
|
||||||
@moveDownAction={{this.moveDownAction}}
|
|
||||||
@userField={{this.userField}}
|
|
||||||
/>`);
|
|
||||||
|
|
||||||
await click(".edit");
|
|
||||||
assert.ok(exists(".save"));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("field attributes are rendered correctly", async function (assert) {
|
|
||||||
this.set("userField", {
|
|
||||||
id: 1,
|
|
||||||
field_type: "text",
|
|
||||||
name: "foo",
|
|
||||||
description: "what is foo",
|
|
||||||
show_on_profile: true,
|
|
||||||
show_on_user_card: true,
|
|
||||||
searchable: true,
|
|
||||||
});
|
|
||||||
this.set("destroyAction", () => {});
|
|
||||||
this.set("moveUpAction", () => {});
|
|
||||||
this.set("moveDownAction", () => {});
|
|
||||||
|
|
||||||
await render(hbs`
|
|
||||||
<AdminUserFieldItem
|
|
||||||
@destroyAction={{this.destroyAction}}
|
|
||||||
@moveUpAction={{this.moveUpAction}}
|
|
||||||
@moveDownAction={{this.moveDownAction}}
|
|
||||||
@userField={{this.userField}}
|
|
||||||
/>`);
|
|
||||||
|
|
||||||
assert.strictEqual(query(".name").innerText, this.userField.name);
|
|
||||||
assert.strictEqual(
|
|
||||||
query(".description").innerText,
|
|
||||||
this.userField.description
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
query(".field-type").innerText,
|
|
||||||
I18n.t("admin.user_fields.field_types.text")
|
|
||||||
);
|
|
||||||
|
|
||||||
assert
|
|
||||||
.dom(".user-field-flags")
|
|
||||||
.hasText(
|
|
||||||
`${I18n.t("admin.user_fields.show_on_profile.enabled")}, ${I18n.t(
|
|
||||||
"admin.user_fields.show_on_user_card.enabled"
|
|
||||||
)}, ${I18n.t("admin.user_fields.searchable.enabled")}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -956,64 +956,16 @@ table.permalinks {
|
||||||
|
|
||||||
.user-fields {
|
.user-fields {
|
||||||
h2 {
|
h2 {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
.user-field {
|
.user-field {
|
||||||
padding: 10px;
|
padding-block: 0.5em;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 1em;
|
||||||
border-bottom: 1px solid var(--primary-low);
|
border-bottom: 1px solid var(--primary-low);
|
||||||
.form-display {
|
.form-display {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
.form-element,
|
|
||||||
.form-element-desc {
|
|
||||||
float: left;
|
|
||||||
min-height: 30px;
|
|
||||||
padding: 0.25em 0;
|
|
||||||
&.input-area {
|
|
||||||
width: 75%;
|
|
||||||
.value-list,
|
|
||||||
.select-kit,
|
|
||||||
input[type="text"] {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value-list {
|
|
||||||
.select-kit {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label-text {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin-top: 0.25em;
|
|
||||||
color: var(--primary-medium);
|
|
||||||
font-size: var(--font-down-1);
|
|
||||||
line-height: var(--line-height-large);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.label-area {
|
|
||||||
width: 25%;
|
|
||||||
label {
|
|
||||||
margin: 0.5em 1em 0 0;
|
|
||||||
text-align: right;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.controls {
|
|
||||||
float: right;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.clearfix {
|
.clearfix {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
.form-kit__checkbox-group {
|
.form-kit__checkbox-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
gap: 0em;
|
||||||
gap: 0.75em;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
.form-kit__control-custom {
|
||||||
|
.value-list {
|
||||||
|
.single-select.combobox {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@
|
||||||
@import "_control-select";
|
@import "_control-select";
|
||||||
@import "_control-custom";
|
@import "_control-custom";
|
||||||
@import "_control-textarea";
|
@import "_control-textarea";
|
||||||
|
@import "_control-custom-value-list";
|
||||||
@import "_errors";
|
@import "_errors";
|
||||||
@import "_errors-summary";
|
@import "_errors-summary";
|
||||||
@import "_field";
|
@import "_field";
|
||||||
|
|
|
@ -23,7 +23,7 @@ describe "Admin User Fields", type: :system, js: true do
|
||||||
|
|
||||||
user_fields_page.add_field(name: "Occupation", description: "")
|
user_fields_page.add_field(name: "Occupation", description: "")
|
||||||
|
|
||||||
expect(user_fields_page).to have_text(/Description can't be blank/)
|
expect(user_fields_page.form.field(:description)).to have_errors("Required")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "makes sure new required fields are editable after signup" do
|
it "makes sure new required fields are editable after signup" do
|
||||||
|
@ -40,7 +40,7 @@ describe "Admin User Fields", type: :system, js: true do
|
||||||
|
|
||||||
user_fields_page.choose_requirement("optional")
|
user_fields_page.choose_requirement("optional")
|
||||||
|
|
||||||
expect(form).to have_field(editable_label, checked: false, disabled: false)
|
expect(form).to have_field(editable_label, checked: true, disabled: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "requires confirmation when applying required fields retroactively" do
|
it "requires confirmation when applying required fields retroactively" do
|
||||||
|
|
|
@ -67,6 +67,16 @@ module PageObjects
|
||||||
expect(self.value).to eq(expected_value)
|
expect(self.value).to eq(expected_value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_errors?(*messages)
|
||||||
|
within component do
|
||||||
|
messages.all? { |m| find(".form-kit__errors", text: m) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_no_errors?
|
||||||
|
!has_css?(".form-kit__errors")
|
||||||
|
end
|
||||||
|
|
||||||
def control_type
|
def control_type
|
||||||
component["data-control-type"]
|
component["data-control-type"]
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,10 @@ module PageObjects
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def form
|
||||||
|
PageObjects::Components::FormKit.new(".user-field .form-kit")
|
||||||
|
end
|
||||||
|
|
||||||
def choose_requirement(requirement)
|
def choose_requirement(requirement)
|
||||||
form = page.find(".user-field")
|
form = page.find(".user-field")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue