mirror of
https://github.com/codeninjasllc/discourse.git
synced 2025-04-30 07:53:57 -04:00
FEATURE: Custom orders for user fields
This commit is contained in:
parent
8e603503e6
commit
aa6f792ce1
16 changed files with 126 additions and 83 deletions
app
assets
javascripts
admin
components
controllers
models
routes
templates
discourse
stylesheets/common/admin
controllers/admin
serializers
db/migrate
test/javascripts
|
@ -6,6 +6,9 @@ export default Ember.Component.extend(bufferedProperty('userField'), {
|
||||||
editing: Ember.computed.empty('userField.id'),
|
editing: Ember.computed.empty('userField.id'),
|
||||||
classNameBindings: [':user-field'],
|
classNameBindings: [':user-field'],
|
||||||
|
|
||||||
|
cantMoveUp: Discourse.computed.propertyEqual('userField', 'firstField'),
|
||||||
|
cantMoveDown: Discourse.computed.propertyEqual('userField', 'lastField'),
|
||||||
|
|
||||||
userFieldsDescription: function() {
|
userFieldsDescription: function() {
|
||||||
return I18n.t('admin.user_fields.description');
|
return I18n.t('admin.user_fields.description');
|
||||||
}.property(),
|
}.property(),
|
||||||
|
@ -55,13 +58,20 @@ export default Ember.Component.extend(bufferedProperty('userField'), {
|
||||||
'show_on_profile',
|
'show_on_profile',
|
||||||
'options');
|
'options');
|
||||||
|
|
||||||
this.get('userField').save(attrs).then(function(res) {
|
this.get('userField').save(attrs).then(function() {
|
||||||
self.set('userField.id', res.user_field.id);
|
|
||||||
self.set('editing', false);
|
self.set('editing', false);
|
||||||
self.commitBuffer();
|
self.commitBuffer();
|
||||||
}).catch(popupAjaxError);
|
}).catch(popupAjaxError);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
moveUp() {
|
||||||
|
this.sendAction('moveUpAction', this.get('userField'));
|
||||||
|
},
|
||||||
|
|
||||||
|
moveDown() {
|
||||||
|
this.sendAction('moveDownAction', this.get('userField'));
|
||||||
|
},
|
||||||
|
|
||||||
edit() {
|
edit() {
|
||||||
this.set('editing', true);
|
this.set('editing', true);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,31 +1,60 @@
|
||||||
import UserField from 'admin/models/user-field';
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
export default Ember.ArrayController.extend({
|
const MAX_FIELDS = 20;
|
||||||
|
|
||||||
|
export default Ember.Controller.extend({
|
||||||
fieldTypes: null,
|
fieldTypes: null,
|
||||||
createDisabled: Em.computed.gte('model.length', 20),
|
createDisabled: Em.computed.gte('model.length', MAX_FIELDS),
|
||||||
|
|
||||||
_performDestroy(f, model) {
|
arrangedContent: function() {
|
||||||
return f.destroy().then(function() {
|
return Ember.ArrayProxy.extend(Ember.SortableMixin).create({
|
||||||
model.removeObject(f);
|
sortProperties: ['position'],
|
||||||
|
content: this.get('model')
|
||||||
});
|
});
|
||||||
},
|
}.property('model'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
createField() {
|
createField() {
|
||||||
this.pushObject(UserField.create({ field_type: 'text' }));
|
const f = this.store.createRecord('user-field', { field_type: 'text', position: MAX_FIELDS });
|
||||||
|
this.get('model').pushObject(f);
|
||||||
|
},
|
||||||
|
|
||||||
|
moveUp(f) {
|
||||||
|
const idx = this.get('arrangedContent').indexOf(f);
|
||||||
|
if (idx) {
|
||||||
|
const prev = this.get('arrangedContent').objectAt(idx-1);
|
||||||
|
const prevPos = prev.get('position');
|
||||||
|
|
||||||
|
prev.update({ position: f.get('position') });
|
||||||
|
f.update({ position: prevPos });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
moveDown(f) {
|
||||||
|
const idx = this.get('arrangedContent').indexOf(f);
|
||||||
|
if (idx > -1) {
|
||||||
|
const next = this.get('arrangedContent').objectAt(idx+1);
|
||||||
|
const nextPos = next.get('position');
|
||||||
|
|
||||||
|
next.update({ position: f.get('position') });
|
||||||
|
f.update({ position: nextPos });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy(f) {
|
destroy(f) {
|
||||||
const model = this.get('model'),
|
const model = this.get('model');
|
||||||
self = this;
|
|
||||||
|
|
||||||
// Only confirm if we already been saved
|
// Only confirm if we already been saved
|
||||||
if (f.get('id')) {
|
if (f.get('id')) {
|
||||||
bootbox.confirm(I18n.t("admin.user_fields.delete_confirm"), function(result) {
|
bootbox.confirm(I18n.t("admin.user_fields.delete_confirm"), function(result) {
|
||||||
if (result) { self._performDestroy(f, model); }
|
if (result) {
|
||||||
|
f.destroyRecord().then(function() {
|
||||||
|
model.removeObject(f);
|
||||||
|
}).catch(popupAjaxError);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self._performDestroy(f, model);
|
model.removeObject(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,12 @@
|
||||||
const UserField = Ember.Object.extend({
|
import RestModel from 'discourse/models/rest';
|
||||||
|
|
||||||
destroy() {
|
const UserField = RestModel.extend();
|
||||||
const self = this;
|
|
||||||
return new Ember.RSVP.Promise(function(resolve) {
|
|
||||||
const id = self.get('id');
|
|
||||||
if (id) {
|
|
||||||
return Discourse.ajax("/admin/customize/user_fields/" + id, { type: 'DELETE' }).then(function() {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
save(attrs) {
|
|
||||||
const id = this.get('id');
|
|
||||||
if (!id) {
|
|
||||||
return Discourse.ajax("/admin/customize/user_fields", {
|
|
||||||
type: "POST",
|
|
||||||
data: { user_field: attrs }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Discourse.ajax("/admin/customize/user_fields/" + id, {
|
|
||||||
type: "PUT",
|
|
||||||
data: { user_field: attrs }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const UserFieldType = Ember.Object.extend({
|
const UserFieldType = Ember.Object.extend({
|
||||||
name: Discourse.computed.i18n('id', 'admin.user_fields.field_types.%@')
|
name: Discourse.computed.i18n('id', 'admin.user_fields.field_types.%@')
|
||||||
});
|
});
|
||||||
|
|
||||||
UserField.reopenClass({
|
UserField.reopenClass({
|
||||||
findAll() {
|
|
||||||
return Discourse.ajax("/admin/customize/user_fields").then(function(result) {
|
|
||||||
return result.user_fields.map(function(uf) {
|
|
||||||
return UserField.create(uf);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldTypes() {
|
fieldTypes() {
|
||||||
if (!this._fieldTypes) {
|
if (!this._fieldTypes) {
|
||||||
this._fieldTypes = [
|
this._fieldTypes = [
|
||||||
|
|
|
@ -2,13 +2,10 @@ import UserField from 'admin/models/user-field';
|
||||||
|
|
||||||
export default Discourse.Route.extend({
|
export default Discourse.Route.extend({
|
||||||
model: function() {
|
model: function() {
|
||||||
return UserField.findAll();
|
return this.store.findAll('user-field');
|
||||||
},
|
},
|
||||||
|
|
||||||
setupController: function(controller, model) {
|
setupController: function(controller, model) {
|
||||||
controller.setProperties({
|
controller.setProperties({ model, fieldTypes: UserField.fieldTypes() });
|
||||||
model: model,
|
|
||||||
fieldTypes: UserField.fieldTypes()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,13 +35,17 @@
|
||||||
{{/admin-form-row}}
|
{{/admin-form-row}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class='form-display'><strong>{{userField.name}}</strong></div>
|
<div class='form-display'>
|
||||||
<div class='form-display'>{{{userField.description}}}</div>
|
<strong>{{userField.name}}</strong>
|
||||||
|
<br/>
|
||||||
|
{{{userField.description}}}
|
||||||
|
</div>
|
||||||
<div class='form-display'>{{fieldName}}</div>
|
<div class='form-display'>{{fieldName}}</div>
|
||||||
<div class='form-display'></div>
|
|
||||||
<div class='form-element controls'>
|
<div class='form-element controls'>
|
||||||
{{d-button action="edit" class="btn-default" icon="pencil" label="admin.user_fields.edit"}}
|
{{d-button action="edit" class="btn-default" icon="pencil" label="admin.user_fields.edit"}}
|
||||||
{{d-button action="destroy" class="btn-danger" icon="trash-o" label="admin.user_fields.delete"}}
|
{{d-button action="destroy" class="btn-danger" icon="trash-o" label="admin.user_fields.delete"}}
|
||||||
|
{{d-button action="moveUp" icon="arrow-up" disabled=cantMoveUp}}
|
||||||
|
{{d-button action="moveDown" icon="arrow-down" disabled=cantMoveDown}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">{{flags}}</div>
|
<div class="row">{{flags}}</div>
|
||||||
|
|
|
@ -4,8 +4,14 @@
|
||||||
<p class="desc">{{i18n 'admin.user_fields.help'}}</p>
|
<p class="desc">{{i18n 'admin.user_fields.help'}}</p>
|
||||||
|
|
||||||
{{#if model}}
|
{{#if model}}
|
||||||
{{#each model as |uf|}}
|
{{#each arrangedContent as |uf|}}
|
||||||
{{admin-user-field-item userField=uf fieldTypes=fieldTypes destroyAction="destroy"}}
|
{{admin-user-field-item userField=uf
|
||||||
|
fieldTypes=fieldTypes
|
||||||
|
firstField=arrangedContent.firstObject
|
||||||
|
lastField=arrangedContent.lastObject
|
||||||
|
destroyAction="destroy"
|
||||||
|
moveUpAction="moveUp"
|
||||||
|
moveDownAction="moveDown"}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,14 @@ function rethrow(error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Ember.Object.extend({
|
export default Ember.Object.extend({
|
||||||
pathFor(store, type, findArgs) {
|
|
||||||
let path = "/" + Ember.String.underscore(store.pluralize(type));
|
|
||||||
|
|
||||||
if (ADMIN_MODELS.indexOf(type) !== -1) { path = "/admin" + path; }
|
basePath(store, type) {
|
||||||
|
if (ADMIN_MODELS.indexOf(type) !== -1) { return "/admin/"; }
|
||||||
|
return "/";
|
||||||
|
},
|
||||||
|
|
||||||
|
pathFor(store, type, findArgs) {
|
||||||
|
let path = this.basePath(store, type, findArgs) + Ember.String.underscore(store.pluralize(type));
|
||||||
|
|
||||||
if (findArgs) {
|
if (findArgs) {
|
||||||
if (typeof findArgs === "object") {
|
if (typeof findArgs === "object") {
|
||||||
|
@ -51,9 +55,10 @@ export default Ember.Object.extend({
|
||||||
|
|
||||||
update(store, type, id, attrs) {
|
update(store, type, id, attrs) {
|
||||||
const data = {};
|
const data = {};
|
||||||
data[Ember.String.underscore(type)] = attrs;
|
const typeField = Ember.String.underscore(type);
|
||||||
|
data[typeField] = attrs;
|
||||||
return ajax(this.pathFor(store, type, id), { method: 'PUT', data }).then(function(json) {
|
return ajax(this.pathFor(store, type, id), { method: 'PUT', data }).then(function(json) {
|
||||||
return new Result(json[type], json);
|
return new Result(json[typeField], json);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import RestAdapter from 'discourse/adapters/rest';
|
||||||
|
|
||||||
|
export default RestAdapter.extend({
|
||||||
|
basePath() {
|
||||||
|
return "/admin/customize/";
|
||||||
|
}
|
||||||
|
});
|
|
@ -394,11 +394,8 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||||
|
|
||||||
let userFields = this.site.get('user_fields');
|
let userFields = this.site.get('user_fields');
|
||||||
if (userFields) {
|
if (userFields) {
|
||||||
userFields = userFields.map(function(f) {
|
userFields = _.sortBy(userFields, 'position').map(function(f) {
|
||||||
return Ember.Object.create({
|
return Ember.Object.create({ value: null, field: f });
|
||||||
value: null,
|
|
||||||
field: f
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.set('userFields', userFields);
|
this.set('userFields', userFields);
|
||||||
|
|
|
@ -18,7 +18,6 @@ const RestModel = Ember.Object.extend(Presence, {
|
||||||
const self = this;
|
const self = this;
|
||||||
self.set('isSaving', true);
|
self.set('isSaving', true);
|
||||||
return store.update(type, this.get('id'), props).then(function(res) {
|
return store.update(type, this.get('id'), props).then(function(res) {
|
||||||
|
|
||||||
const payload = self.__munge(res.payload || res.responseJson);
|
const payload = self.__munge(res.payload || res.responseJson);
|
||||||
|
|
||||||
if (payload.success === "OK") {
|
if (payload.success === "OK") {
|
||||||
|
|
|
@ -1460,7 +1460,6 @@ tr.not-activated {
|
||||||
.controls {
|
.controls {
|
||||||
float: right;
|
float: right;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 20%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.clearfix {
|
.clearfix {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
class Admin::UserFieldsController < Admin::AdminController
|
class Admin::UserFieldsController < Admin::AdminController
|
||||||
|
|
||||||
def self.columns
|
def self.columns
|
||||||
[:name, :field_type, :editable, :description, :required, :show_on_profile]
|
[:name, :field_type, :editable, :description, :required, :show_on_profile, :position]
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
field = UserField.new(params.require(:user_field).permit(*Admin::UserFieldsController.columns))
|
field = UserField.new(params.require(:user_field).permit(*Admin::UserFieldsController.columns))
|
||||||
|
|
||||||
|
field.position = (UserField.maximum(:position) || 0) + 1
|
||||||
field.required = params[:required] == "true"
|
field.required = params[:required] == "true"
|
||||||
fetch_options(field)
|
fetch_options(field)
|
||||||
|
|
||||||
|
@ -15,33 +17,35 @@ class Admin::UserFieldsController < Admin::AdminController
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
user_fields = UserField.all.includes(:user_field_options)
|
user_fields = UserField.all.includes(:user_field_options).order(:position)
|
||||||
render_serialized(user_fields, UserFieldSerializer, root: 'user_fields')
|
render_serialized(user_fields, UserFieldSerializer, root: 'user_fields')
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
field_params = params.require(:user_field)
|
field_params = params[:user_field]
|
||||||
|
|
||||||
field = UserField.where(id: params.require(:id)).first
|
field = UserField.where(id: params.require(:id)).first
|
||||||
|
|
||||||
Admin::UserFieldsController.columns.each do |col|
|
Admin::UserFieldsController.columns.each do |col|
|
||||||
field.send("#{col}=", field_params[col] || false)
|
unless field_params[col].nil?
|
||||||
|
field.send("#{col}=", field_params[col])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
UserFieldOption.where(user_field_id: field.id).delete_all
|
UserFieldOption.where(user_field_id: field.id).delete_all
|
||||||
fetch_options(field)
|
fetch_options(field)
|
||||||
|
|
||||||
json_result(field, serializer: UserFieldSerializer) do
|
if field.save
|
||||||
field.save
|
render_serialized(field, UserFieldSerializer, root: 'user_field')
|
||||||
|
else
|
||||||
|
render_json_error(field)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
field = UserField.where(id: params.require(:id)).first
|
field = UserField.where(id: params.require(:id)).first
|
||||||
field.destroy if field.present?
|
field.destroy if field.present?
|
||||||
render nothing: true
|
render json: success_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def fetch_options(field)
|
def fetch_options(field)
|
||||||
|
|
|
@ -6,6 +6,7 @@ class UserFieldSerializer < ApplicationSerializer
|
||||||
:editable,
|
:editable,
|
||||||
:required,
|
:required,
|
||||||
:show_on_profile,
|
:show_on_profile,
|
||||||
|
:position,
|
||||||
:options
|
:options
|
||||||
|
|
||||||
def options
|
def options
|
||||||
|
|
6
db/migrate/20150730154830_add_position_to_user_fields.rb
Normal file
6
db/migrate/20150730154830_add_position_to_user_fields.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class AddPositionToUserFields < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :user_fields, :position, :integer, default: 0
|
||||||
|
execute "UPDATE user_fields SET position = (SELECT COUNT(*) from user_fields as uf2 where uf2.id < user_fields.id)"
|
||||||
|
end
|
||||||
|
end
|
|
@ -249,6 +249,12 @@ export default function() {
|
||||||
return response({ widget });
|
return response({ widget });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.put('/cool_things/:cool_thing_id', function(request) {
|
||||||
|
const cool_thing = parsePostData(request.requestBody).cool_thing;
|
||||||
|
return response({ cool_thing });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
this.get('/widgets', function(request) {
|
this.get('/widgets', function(request) {
|
||||||
let result = _widgets;
|
let result = _widgets;
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,14 @@ test('update', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('update with a multi world name', function(assert) {
|
||||||
|
const store = createStore();
|
||||||
|
return store.update('cool-thing', 123, {name: 'hello'}).then(function(result) {
|
||||||
|
assert.ok(result);
|
||||||
|
assert.equal(result.payload.name, 'hello');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('findAll', function() {
|
test('findAll', function() {
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
return store.findAll('widget').then(function(result) {
|
return store.findAll('widget').then(function(result) {
|
||||||
|
@ -84,7 +92,7 @@ test('destroyRecord', function(assert) {
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
return store.find('widget', 123).then(function(w) {
|
return store.find('widget', 123).then(function(w) {
|
||||||
store.destroyRecord('widget', w).then(function(result) {
|
store.destroyRecord('widget', w).then(function(result) {
|
||||||
ok(result);
|
assert.ok(result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue