Support saving posts via Store
This commit is contained in:
parent
d4a05825da
commit
76f7786d0d
|
@ -1,11 +1,20 @@
|
||||||
import RestAdapter from 'discourse/adapters/rest';
|
import RestAdapter from 'discourse/adapters/rest';
|
||||||
|
import { Result } from 'discourse/adapters/rest';
|
||||||
|
|
||||||
export default RestAdapter.extend({
|
export default RestAdapter.extend({
|
||||||
|
|
||||||
// GET /posts doesn't include a type
|
|
||||||
find(store, type, findArgs) {
|
find(store, type, findArgs) {
|
||||||
return this._super(store, type, findArgs).then(function(result) {
|
return this._super(store, type, findArgs).then(function(result) {
|
||||||
return {post: result};
|
return {post: result};
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, args) {
|
||||||
|
const typeField = Ember.String.underscore(type);
|
||||||
|
args.nested_post = true;
|
||||||
|
return Discourse.ajax(this.pathFor(store, type), { method: 'POST', data: args }).then(function (json) {
|
||||||
|
return new Result(json[typeField], json);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
const ADMIN_MODELS = ['plugin'];
|
const ADMIN_MODELS = ['plugin'];
|
||||||
|
|
||||||
|
export function Result(payload, responseJson) {
|
||||||
|
this.payload = payload;
|
||||||
|
this.responseJson = responseJson;
|
||||||
|
this.target = null;
|
||||||
|
}
|
||||||
|
|
||||||
export default Ember.Object.extend({
|
export default Ember.Object.extend({
|
||||||
pathFor(store, type, findArgs) {
|
pathFor(store, type, findArgs) {
|
||||||
let path = "/" + Ember.String.underscore(store.pluralize(type));
|
let path = "/" + Ember.String.underscore(store.pluralize(type));
|
||||||
|
@ -35,7 +41,18 @@ 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;
|
data[Ember.String.underscore(type)] = attrs;
|
||||||
return Discourse.ajax(this.pathFor(store, type, id), { method: 'PUT', data });
|
return Discourse.ajax(this.pathFor(store, type, id), { method: 'PUT', data }).then(function(json) {
|
||||||
|
return new Result(json[type], json);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, attrs) {
|
||||||
|
const data = {};
|
||||||
|
const typeField = Ember.String.underscore(type);
|
||||||
|
data[typeField] = attrs;
|
||||||
|
return Discourse.ajax(this.pathFor(store, type), { method: 'POST', data }).then(function (json) {
|
||||||
|
return new Result(json[typeField], json);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyRecord(store, type, record) {
|
destroyRecord(store, type, record) {
|
||||||
|
|
|
@ -219,6 +219,7 @@ export default DiscourseController.extend({
|
||||||
imageSizes: this.get('view').imageSizes(),
|
imageSizes: this.get('view').imageSizes(),
|
||||||
editReason: this.get("editReason")
|
editReason: this.get("editReason")
|
||||||
}).then(function(opts) {
|
}).then(function(opts) {
|
||||||
|
|
||||||
// If we replied as a new topic successfully, remove the draft.
|
// If we replied as a new topic successfully, remove the draft.
|
||||||
if (self.get('replyAsNewTopicDraft')) {
|
if (self.get('replyAsNewTopicDraft')) {
|
||||||
self.destroyDraft();
|
self.destroyDraft();
|
||||||
|
@ -246,7 +247,6 @@ export default DiscourseController.extend({
|
||||||
bootbox.alert(error);
|
bootbox.alert(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (this.get('controllers.application.currentRouteName').split('.')[0] === 'topic' &&
|
if (this.get('controllers.application.currentRouteName').split('.')[0] === 'topic' &&
|
||||||
composer.get('topic.id') === this.get('controllers.topic.model.id')) {
|
composer.get('topic.id') === this.get('controllers.topic.model.id')) {
|
||||||
staged = composer.get('stagedPost');
|
staged = composer.get('stagedPost');
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
export function throwAjaxError(undoCallback) {
|
||||||
|
return function(error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
Ember.Logger.error(error.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof error === "string") {
|
||||||
|
Ember.Logger.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we provided an `undo` callback
|
||||||
|
if (undoCallback) { undoCallback(error); }
|
||||||
|
|
||||||
|
let parsedError;
|
||||||
|
if (error.responseText) {
|
||||||
|
try {
|
||||||
|
const parsedJSON = $.parseJSON(error.responseText);
|
||||||
|
if (parsedJSON.errors) {
|
||||||
|
parsedError = parsedJSON.errors[0];
|
||||||
|
} else if (parsedJSON.failed) {
|
||||||
|
parsedError = parsedJSON.message;
|
||||||
|
}
|
||||||
|
} catch(ex) {
|
||||||
|
// in case the JSON doesn't parse
|
||||||
|
Ember.Logger.error(ex.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw parsedError || I18n.t('generic_error');
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import RestModel from 'discourse/models/rest';
|
import RestModel from 'discourse/models/rest';
|
||||||
import Post from 'discourse/models/post';
|
|
||||||
import Topic from 'discourse/models/topic';
|
import Topic from 'discourse/models/topic';
|
||||||
|
import { throwAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
const CLOSED = 'closed',
|
const CLOSED = 'closed',
|
||||||
SAVING = 'saving',
|
SAVING = 'saving',
|
||||||
|
@ -430,25 +430,22 @@ const Composer = RestModel.extend({
|
||||||
promise = Ember.RSVP.resolve();
|
promise = Ember.RSVP.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
post.setProperties({
|
const props = {
|
||||||
raw: this.get('reply'),
|
raw: this.get('reply'),
|
||||||
editReason: opts.editReason,
|
edit_reason: opts.editReason,
|
||||||
imageSizes: opts.imageSizes,
|
image_sizes: opts.imageSizes,
|
||||||
cooked: this.getCookedHtml()
|
cooked: this.getCookedHtml()
|
||||||
});
|
};
|
||||||
|
|
||||||
this.set('composeState', CLOSED);
|
this.set('composeState', CLOSED);
|
||||||
|
|
||||||
return promise.then(function() {
|
return promise.then(function() {
|
||||||
return post.save(function(result) {
|
return post.save(props).then(function() {
|
||||||
post.updateFromPost(result);
|
|
||||||
self.clearState();
|
self.clearState();
|
||||||
}, function (error) {
|
}).catch(throwAjaxError(function() {
|
||||||
post.set('cooked', oldCooked);
|
post.set('cooked', oldCooked);
|
||||||
self.set('composeState', OPEN);
|
self.set('composeState', OPEN);
|
||||||
const response = $.parseJSON(error.responseText);
|
}));
|
||||||
throw response && response.errors ? response.errors[0] : I18n.t('generic_error');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -473,7 +470,7 @@ const Composer = RestModel.extend({
|
||||||
let addedToStream = false;
|
let addedToStream = false;
|
||||||
|
|
||||||
// Build the post object
|
// Build the post object
|
||||||
const createdPost = Post.create({
|
const createdPost = this.store.createRecord('post', {
|
||||||
imageSizes: opts.imageSizes,
|
imageSizes: opts.imageSizes,
|
||||||
cooked: this.getCookedHtml(),
|
cooked: this.getCookedHtml(),
|
||||||
reply_count: 0,
|
reply_count: 0,
|
||||||
|
@ -489,7 +486,6 @@ const Composer = RestModel.extend({
|
||||||
moderator: user.get('moderator'),
|
moderator: user.get('moderator'),
|
||||||
admin: user.get('admin'),
|
admin: user.get('admin'),
|
||||||
yours: true,
|
yours: true,
|
||||||
newPost: true,
|
|
||||||
read: true
|
read: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -521,11 +517,7 @@ const Composer = RestModel.extend({
|
||||||
// engine, staging will just cause a blank post to render
|
// engine, staging will just cause a blank post to render
|
||||||
if (!_.isEmpty(createdPost.get('cooked'))) {
|
if (!_.isEmpty(createdPost.get('cooked'))) {
|
||||||
state = postStream.stagePost(createdPost, user);
|
state = postStream.stagePost(createdPost, user);
|
||||||
|
if (state === "alreadyStaging") { return; }
|
||||||
if(state === "alreadyStaging"){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,13 +526,12 @@ const Composer = RestModel.extend({
|
||||||
composer.set("stagedPost", state === "staged" && createdPost);
|
composer.set("stagedPost", state === "staged" && createdPost);
|
||||||
|
|
||||||
return createdPost.save().then(function(result) {
|
return createdPost.save().then(function(result) {
|
||||||
|
|
||||||
let saving = true;
|
let saving = true;
|
||||||
createdPost.updateFromJson(result);
|
|
||||||
|
|
||||||
if (topic) {
|
if (topic) {
|
||||||
// It's no longer a new post
|
// It's no longer a new post
|
||||||
createdPost.set('newPost', false);
|
topic.set('draft_sequence', result.target.draft_sequence);
|
||||||
topic.set('draft_sequence', result.draft_sequence);
|
|
||||||
postStream.commitPost(createdPost);
|
postStream.commitPost(createdPost);
|
||||||
addedToStream = true;
|
addedToStream = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -563,30 +554,13 @@ const Composer = RestModel.extend({
|
||||||
composer.set('composeState', SAVING);
|
composer.set('composeState', SAVING);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { post: result };
|
return { post: createdPost };
|
||||||
}).catch(function(error) {
|
}).catch(throwAjaxError(function() {
|
||||||
|
|
||||||
// If an error occurs
|
|
||||||
if (postStream) {
|
if (postStream) {
|
||||||
postStream.undoPost(createdPost);
|
postStream.undoPost(createdPost);
|
||||||
}
|
}
|
||||||
composer.set('composeState', OPEN);
|
composer.set('composeState', OPEN);
|
||||||
|
}));
|
||||||
// TODO extract error handling code
|
|
||||||
let parsedError;
|
|
||||||
try {
|
|
||||||
const parsedJSON = $.parseJSON(error.responseText);
|
|
||||||
if (parsedJSON.errors) {
|
|
||||||
parsedError = parsedJSON.errors[0];
|
|
||||||
} else if (parsedJSON.failed) {
|
|
||||||
parsedError = parsedJSON.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(ex) {
|
|
||||||
parsedError = "Unknown error saving post, try again. Error: " + error.status + " " + error.statusText;
|
|
||||||
}
|
|
||||||
throw parsedError;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getCookedHtml() {
|
getCookedHtml() {
|
||||||
|
|
|
@ -113,45 +113,33 @@ const Post = RestModel.extend({
|
||||||
});
|
});
|
||||||
}.property('actions_summary.@each.users', 'actions_summary.@each.count'),
|
}.property('actions_summary.@each.users', 'actions_summary.@each.count'),
|
||||||
|
|
||||||
save() {
|
afterUpdate(res) {
|
||||||
const self = this;
|
if (res.category) {
|
||||||
if (!this.get('newPost')) {
|
Discourse.Site.current().updateCategory(res.category);
|
||||||
// We're updating a post
|
}
|
||||||
return Discourse.ajax("/posts/" + (this.get('id')), {
|
},
|
||||||
type: 'PUT',
|
|
||||||
dataType: 'json',
|
updateProperties() {
|
||||||
data: {
|
return {
|
||||||
post: { raw: this.get('raw'), edit_reason: this.get('editReason') },
|
post: { raw: this.get('raw'), edit_reason: this.get('editReason') },
|
||||||
image_sizes: this.get('imageSizes')
|
image_sizes: this.get('imageSizes')
|
||||||
}
|
};
|
||||||
}).then(function(result) {
|
},
|
||||||
// If we received a category update, update it
|
|
||||||
self.set('version', result.post.version);
|
|
||||||
if (result.category) Discourse.Site.current().updateCategory(result.category);
|
|
||||||
return Discourse.Post.create(result.post);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
createProperties() {
|
||||||
// We're saving a post
|
|
||||||
const data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
|
const data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
|
||||||
data.reply_to_post_number = this.get('reply_to_post_number');
|
data.reply_to_post_number = this.get('reply_to_post_number');
|
||||||
data.image_sizes = this.get('imageSizes');
|
data.image_sizes = this.get('imageSizes');
|
||||||
data.nested_post = true;
|
|
||||||
|
|
||||||
const metaData = this.get('metaData');
|
const metaData = this.get('metaData');
|
||||||
|
|
||||||
// Put the metaData into the request
|
// Put the metaData into the request
|
||||||
if (metaData) {
|
if (metaData) {
|
||||||
data.meta_data = {};
|
data.meta_data = {};
|
||||||
Ember.keys(metaData).forEach(function(key) { data.meta_data[key] = metaData.get(key); });
|
Ember.keys(metaData).forEach(function(key) { data.meta_data[key] = metaData.get(key); });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Discourse.ajax("/posts", {
|
return data;
|
||||||
type: 'POST',
|
|
||||||
data: data
|
|
||||||
}).then(function(result) {
|
|
||||||
return Discourse.Post.create(result.post);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Expands the first post's content, if embedded and shortened.
|
// Expands the first post's content, if embedded and shortened.
|
||||||
|
@ -266,50 +254,6 @@ const Post = RestModel.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
Updates a post from a JSON packet. This is normally done after the post is saved to refresh any
|
|
||||||
attributes.
|
|
||||||
**/
|
|
||||||
updateFromJson(obj) {
|
|
||||||
if (!obj) return;
|
|
||||||
|
|
||||||
let skip, oldVal;
|
|
||||||
|
|
||||||
// Update all the properties
|
|
||||||
const post = this;
|
|
||||||
_.each(obj, function(val,key) {
|
|
||||||
if (key !== 'actions_summary'){
|
|
||||||
oldVal = post[key];
|
|
||||||
skip = false;
|
|
||||||
|
|
||||||
if (val && val !== oldVal) {
|
|
||||||
|
|
||||||
if (key === "reply_to_user" && val && oldVal) {
|
|
||||||
skip = val.username === oldVal.username || Em.get(val, "username") === Em.get(oldVal, "username");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!skip) {
|
|
||||||
post.set(key, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rebuild actions summary
|
|
||||||
this.set('actions_summary', Em.A());
|
|
||||||
if (obj.actions_summary) {
|
|
||||||
const lookup = Em.Object.create();
|
|
||||||
_.each(obj.actions_summary,function(a) {
|
|
||||||
a.post = post;
|
|
||||||
a.actionType = Discourse.Site.current().postActionTypeById(a.id);
|
|
||||||
const actionSummary = Discourse.ActionSummary.create(a);
|
|
||||||
post.get('actions_summary').pushObject(actionSummary);
|
|
||||||
lookup.set(a.actionType.get('name_key'), actionSummary);
|
|
||||||
});
|
|
||||||
this.set('actionByName', lookup);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Load replies to this post
|
// Load replies to this post
|
||||||
loadReplies() {
|
loadReplies() {
|
||||||
if(this.get('loadingReplies')){
|
if(this.get('loadingReplies')){
|
||||||
|
|
|
@ -1,22 +1,52 @@
|
||||||
import Presence from 'discourse/mixins/presence';
|
import Presence from 'discourse/mixins/presence';
|
||||||
|
|
||||||
const RestModel = Ember.Object.extend(Presence, {
|
const RestModel = Ember.Object.extend(Presence, {
|
||||||
update(attrs) {
|
isNew: Ember.computed.equal('__state', 'new'),
|
||||||
const self = this,
|
isCreated: Ember.computed.equal('__state', 'created'),
|
||||||
type = this.get('__type');
|
|
||||||
|
|
||||||
const munge = this.__munge;
|
afterUpdate: Ember.K,
|
||||||
return this.store.update(type, this.get('id'), attrs).then(function(result) {
|
|
||||||
if (result && result[type]) {
|
update(props) {
|
||||||
Object.keys(result).forEach(function(k) {
|
props = props || this.updateProperties();
|
||||||
attrs[k] = result[k];
|
|
||||||
|
const type = this.get('__type'),
|
||||||
|
store = this.get('store');
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
return store.update(type, this.get('id'), props).then(function(res) {
|
||||||
|
self.setProperties(self.__munge(res.payload || res.responseJson));
|
||||||
|
self.afterUpdate(res);
|
||||||
|
return res;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
self.setProperties(munge(attrs));
|
|
||||||
return result;
|
_saveNew(props) {
|
||||||
|
props = props || this.createProperties();
|
||||||
|
|
||||||
|
const type = this.get('__type'),
|
||||||
|
store = this.get('store'),
|
||||||
|
adapter = store.adapterFor(type);
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
return adapter.createRecord(store, type, props).then(function(res) {
|
||||||
|
if (!res) { throw "Received no data back from createRecord"; }
|
||||||
|
self.setProperties(self.__munge(res.payload));
|
||||||
|
|
||||||
|
self.set('__state', 'created');
|
||||||
|
|
||||||
|
res.target = self;
|
||||||
|
return res;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createProperties() {
|
||||||
|
throw "You must overwrite `createProperties()` before saving a record";
|
||||||
|
},
|
||||||
|
|
||||||
|
save(props) {
|
||||||
|
return this.get('isNew') ? this._saveNew(props) : this.update(props);
|
||||||
|
},
|
||||||
|
|
||||||
destroyRecord() {
|
destroyRecord() {
|
||||||
const type = this.get('__type');
|
const type = this.get('__type');
|
||||||
return this.store.destroyRecord(type, this);
|
return this.store.destroyRecord(type, this);
|
||||||
|
@ -34,7 +64,7 @@ RestModel.reopenClass({
|
||||||
args = args || {};
|
args = args || {};
|
||||||
if (!args.store) {
|
if (!args.store) {
|
||||||
const container = Discourse.__container__;
|
const container = Discourse.__container__;
|
||||||
Ember.warn('Use `store.createRecord` to create records instead of `.create()`');
|
// Ember.warn('Use `store.createRecord` to create records instead of `.create()`');
|
||||||
args.store = container.lookup('store:main');
|
args.store = container.lookup('store:main');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,26 +16,23 @@ export default Ember.Object.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
findAll(type) {
|
findAll(type) {
|
||||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
|
||||||
const self = this;
|
const self = this;
|
||||||
return adapter.findAll(this, type).then(function(result) {
|
return this.adapterFor(type).findAll(this, type).then(function(result) {
|
||||||
return self._resultSet(type, result);
|
return self._resultSet(type, result);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Mostly for legacy, things like TopicList without ResultSets
|
// Mostly for legacy, things like TopicList without ResultSets
|
||||||
findFiltered(type, findArgs) {
|
findFiltered(type, findArgs) {
|
||||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
|
||||||
const self = this;
|
const self = this;
|
||||||
return adapter.find(this, type, findArgs).then(function(result) {
|
return this.adapterFor(type).find(this, type, findArgs).then(function(result) {
|
||||||
return self._build(type, result);
|
return self._build(type, result);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
find(type, findArgs) {
|
find(type, findArgs) {
|
||||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
|
||||||
const self = this;
|
const self = this;
|
||||||
return adapter.find(this, type, findArgs).then(function(result) {
|
return this.adapterFor(type).find(this, type, findArgs).then(function(result) {
|
||||||
if (typeof findArgs === "object") {
|
if (typeof findArgs === "object") {
|
||||||
return self._resultSet(type, result);
|
return self._resultSet(type, result);
|
||||||
} else {
|
} else {
|
||||||
|
@ -64,8 +61,7 @@ export default Ember.Object.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
update(type, id, attrs) {
|
update(type, id, attrs) {
|
||||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
return this.adapterFor(type).update(this, type, id, attrs, function(result) {
|
||||||
return adapter.update(this, type, id, attrs, function(result) {
|
|
||||||
if (result && result[type] && result[type].id) {
|
if (result && result[type] && result[type].id) {
|
||||||
const oldRecord = _identityMap[type][id];
|
const oldRecord = _identityMap[type][id];
|
||||||
delete _identityMap[type][id];
|
delete _identityMap[type][id];
|
||||||
|
@ -81,8 +77,7 @@ export default Ember.Object.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyRecord(type, record) {
|
destroyRecord(type, record) {
|
||||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
return this.adapterFor(type).destroyRecord(this, type, record).then(function(result) {
|
||||||
return adapter.destroyRecord(this, type, record).then(function(result) {
|
|
||||||
const forType = _identityMap[type];
|
const forType = _identityMap[type];
|
||||||
if (forType) { delete forType[record.get('id')]; }
|
if (forType) { delete forType[record.get('id')]; }
|
||||||
return result;
|
return result;
|
||||||
|
@ -101,6 +96,7 @@ export default Ember.Object.extend({
|
||||||
_build(type, obj) {
|
_build(type, obj) {
|
||||||
obj.store = this;
|
obj.store = this;
|
||||||
obj.__type = type;
|
obj.__type = type;
|
||||||
|
obj.__state = obj.id ? "created" : "new";
|
||||||
|
|
||||||
const klass = this.container.lookupFactory('model:' + type) || RestModel;
|
const klass = this.container.lookupFactory('model:' + type) || RestModel;
|
||||||
const model = klass.create(obj);
|
const model = klass.create(obj);
|
||||||
|
@ -111,6 +107,10 @@ export default Ember.Object.extend({
|
||||||
return model;
|
return model;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
adapterFor(type) {
|
||||||
|
return this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||||
|
},
|
||||||
|
|
||||||
_hydrate(type, obj) {
|
_hydrate(type, obj) {
|
||||||
if (!obj) { throw "Can't hydrate " + type + " of `null`"; }
|
if (!obj) { throw "Can't hydrate " + type + " of `null`"; }
|
||||||
if (!obj.id) { throw "Can't hydrate " + type + " without an `id`"; }
|
if (!obj.id) { throw "Can't hydrate " + type + " without an `id`"; }
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
//= require ./discourse/helpers/register-unbound
|
//= require ./discourse/helpers/register-unbound
|
||||||
//= require ./discourse/mixins/scrolling
|
//= require ./discourse/mixins/scrolling
|
||||||
//= require_tree ./discourse/mixins
|
//= require_tree ./discourse/mixins
|
||||||
|
//= require ./discourse/lib/ajax-error
|
||||||
//= require ./discourse/lib/markdown
|
//= require ./discourse/lib/markdown
|
||||||
//= require ./discourse/lib/search-for-term
|
//= require ./discourse/lib/search-for-term
|
||||||
//= require ./discourse/lib/user-search
|
//= require ./discourse/lib/user-search
|
||||||
|
|
|
@ -100,6 +100,8 @@ test("Create a Reply", () => {
|
||||||
test("Edit the first post", () => {
|
test("Edit the first post", () => {
|
||||||
visit("/t/internationalization-localization/280");
|
visit("/t/internationalization-localization/280");
|
||||||
|
|
||||||
|
ok(!exists('.topic-post:eq(0) .post-info.edits'), 'it has no edits icon at first');
|
||||||
|
|
||||||
click('.topic-post:eq(0) button[data-action=showMoreActions]');
|
click('.topic-post:eq(0) button[data-action=showMoreActions]');
|
||||||
click('.topic-post:eq(0) button[data-action=edit]');
|
click('.topic-post:eq(0) button[data-action=edit]');
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
|
@ -111,6 +113,7 @@ test("Edit the first post", () => {
|
||||||
click('#reply-control button.create');
|
click('#reply-control button.create');
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
ok(!exists('#wmd-input'), 'it closes the composer');
|
ok(!exists('#wmd-input'), 'it closes the composer');
|
||||||
|
ok(exists('.topic-post:eq(0) .post-info.edits'), 'it has the edits icon');
|
||||||
ok(find('#topic-title h1').text().indexOf('This is the new text for the title') !== -1, 'it shows the new title');
|
ok(find('#topic-title h1').text().indexOf('This is the new text for the title') !== -1, 'it shows the new title');
|
||||||
ok(find('.topic-post:eq(0) .cooked').text().indexOf('This is the new text for the post') !== -1, 'it updates the post');
|
ok(find('.topic-post:eq(0) .cooked').text().indexOf('This is the new text for the post') !== -1, 'it updates the post');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default {
|
export default {
|
||||||
"/posts/398": {"id":398,"name":"Uwe Keim","username":"uwe_keim","avatar_template":"/user_avatar/meta.discourse.org/uwe_keim/{size}/5697.png","uploaded_avatar_id":5697,"created_at":"2013-02-05T21:29:00.280Z","cooked":"<p>Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?</p>","post_number":1,"post_type":1,"updated_at":"2013-02-05T21:29:00.280Z","like_count":0,"reply_count":1,"reply_to_post_number":null,"quote_count":0,"avg_time":25,"incoming_link_count":314,"reads":475,"score":1702.25,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Uwe Keim","primary_group_name":null,"version":2,"can_edit":true,"can_delete":false,"can_recover":true,"user_title":null,"raw":"Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?","actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":255,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false}
|
"/posts/398": {"id":398,"name":"Uwe Keim","username":"uwe_keim","avatar_template":"/user_avatar/meta.discourse.org/uwe_keim/{size}/5697.png","uploaded_avatar_id":5697,"created_at":"2013-02-05T21:29:00.280Z","cooked":"<p>Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?</p>","post_number":1,"post_type":1,"updated_at":"2013-02-05T21:29:00.280Z","like_count":0,"reply_count":1,"reply_to_post_number":null,"quote_count":0,"avg_time":25,"incoming_link_count":314,"reads":475,"score":1702.25,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Uwe Keim","primary_group_name":null,"version":1,"can_edit":true,"can_delete":false,"can_recover":true,"user_title":null,"raw":"Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?","actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":255,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,15 +2,21 @@ function parsePostData(query) {
|
||||||
const result = {};
|
const result = {};
|
||||||
query.split("&").forEach(function(part) {
|
query.split("&").forEach(function(part) {
|
||||||
const item = part.split("=");
|
const item = part.split("=");
|
||||||
result[item[0]] = decodeURIComponent(item[1]).replace(/\+/g, ' ');
|
const firstSeg = decodeURIComponent(item[0]);
|
||||||
|
const m = /^([^\[]+)\[([^\]]+)\]/.exec(firstSeg);
|
||||||
|
|
||||||
|
const val = decodeURIComponent(item[1]).replace(/\+/g, ' ');
|
||||||
|
if (m) {
|
||||||
|
result[m[1]] = result[m[1]] || {};
|
||||||
|
result[m[1]][m[2]] = val;
|
||||||
|
} else {
|
||||||
|
result[firstSeg] = val;
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clone(obj) {
|
|
||||||
return JSON.parse(JSON.stringify(obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
function response(code, obj) {
|
function response(code, obj) {
|
||||||
if (typeof code === "object") {
|
if (typeof code === "object") {
|
||||||
obj = code;
|
obj = code;
|
||||||
|
@ -122,7 +128,10 @@ export default function() {
|
||||||
this.put('/posts/:post_id/recover', success);
|
this.put('/posts/:post_id/recover', success);
|
||||||
|
|
||||||
this.put('/posts/:post_id', (request) => {
|
this.put('/posts/:post_id', (request) => {
|
||||||
return response({ post: {id: request.params.post_id, version: 2 } });
|
const data = parsePostData(request.requestBody);
|
||||||
|
data.post.id = request.params.post_id;
|
||||||
|
data.post.version = 2;
|
||||||
|
return response(200, data.post);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.put('/t/:slug/:id', (request) => {
|
this.put('/t/:slug/:id', (request) => {
|
||||||
|
@ -157,9 +166,15 @@ export default function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.post('/widgets', function(request) {
|
||||||
|
const widget = parsePostData(request.requestBody).widget;
|
||||||
|
widget.id = 100;
|
||||||
|
return response(200, {widget});
|
||||||
|
});
|
||||||
|
|
||||||
this.put('/widgets/:widget_id', function(request) {
|
this.put('/widgets/:widget_id', function(request) {
|
||||||
const w = _widgets.findBy('id', parseInt(request.params.widget_id));
|
const widget = parsePostData(request.requestBody).widget;
|
||||||
return response({ widget: clone(w) });
|
return response({ widget });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.get('/widgets', function(request) {
|
this.get('/widgets', function(request) {
|
||||||
|
|
|
@ -28,6 +28,21 @@ test('update', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('save new', function() {
|
||||||
|
const store = createStore();
|
||||||
|
const widget = store.createRecord('widget');
|
||||||
|
|
||||||
|
ok(widget.get('isNew'), 'it is a new record');
|
||||||
|
ok(!widget.get('isCreated'), 'it is not created');
|
||||||
|
|
||||||
|
widget.save({ name: 'Evil Widget' }).then(function() {
|
||||||
|
ok(widget.get('id'), 'it has an id');
|
||||||
|
ok(widget.get('name'), 'Evil Widget');
|
||||||
|
ok(widget.get('isCreated'), 'it is created');
|
||||||
|
ok(!widget.get('isNew'), 'it is no longer new');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('destroyRecord', function() {
|
test('destroyRecord', function() {
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
store.find('widget', 123).then(function(widget) {
|
store.find('widget', 123).then(function(widget) {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import createStore from 'helpers/create-store';
|
||||||
test('createRecord', function() {
|
test('createRecord', function() {
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
const widget = store.createRecord('widget', {id: 111, name: 'hello'});
|
const widget = store.createRecord('widget', {id: 111, name: 'hello'});
|
||||||
|
|
||||||
|
ok(!widget.get('isNew'), 'it is not a new record');
|
||||||
equal(widget.get('name'), 'hello');
|
equal(widget.get('name'), 'hello');
|
||||||
equal(widget.get('id'), 111);
|
equal(widget.get('id'), 111);
|
||||||
});
|
});
|
||||||
|
@ -13,6 +15,7 @@ test('createRecord without an `id`', function() {
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
const widget = store.createRecord('widget', {name: 'hello'});
|
const widget = store.createRecord('widget', {name: 'hello'});
|
||||||
|
|
||||||
|
ok(widget.get('isNew'), 'it is a new record');
|
||||||
ok(!widget.get('id'), 'there is no id');
|
ok(!widget.get('id'), 'there is no id');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,6 +24,7 @@ test('createRecord without attributes', function() {
|
||||||
const widget = store.createRecord('widget');
|
const widget = store.createRecord('widget');
|
||||||
|
|
||||||
ok(!widget.get('id'), 'there is no id');
|
ok(!widget.get('id'), 'there is no id');
|
||||||
|
ok(widget.get('isNew'), 'it is a new record');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createRecord with a record as attributes returns that record from the map', function() {
|
test('createRecord with a record as attributes returns that record from the map', function() {
|
||||||
|
@ -36,6 +40,7 @@ test('find', function() {
|
||||||
store.find('widget', 123).then(function(w) {
|
store.find('widget', 123).then(function(w) {
|
||||||
equal(w.get('name'), 'Trout Lure');
|
equal(w.get('name'), 'Trout Lure');
|
||||||
equal(w.get('id'), 123);
|
equal(w.get('id'), 123);
|
||||||
|
ok(!w.get('isNew'), 'found records are not new');
|
||||||
|
|
||||||
// A second find by id returns the same object
|
// A second find by id returns the same object
|
||||||
store.find('widget', 123).then(function(w2) {
|
store.find('widget', 123).then(function(w2) {
|
||||||
|
@ -70,6 +75,7 @@ test('findAll', function() {
|
||||||
store.findAll('widget').then(function(result) {
|
store.findAll('widget').then(function(result) {
|
||||||
equal(result.get('length'), 2);
|
equal(result.get('length'), 2);
|
||||||
const w = result.findBy('id', 124);
|
const w = result.findBy('id', 124);
|
||||||
|
ok(!w.get('isNew'), 'found records are not new');
|
||||||
equal(w.get('name'), 'Evil Repellant');
|
equal(w.get('name'), 'Evil Repellant');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -437,9 +437,10 @@ var bootbox = window.bootbox || (function(document, $) {
|
||||||
|
|
||||||
// wire up button handlers
|
// wire up button handlers
|
||||||
div.on('click', '.modal-footer a', function(e) {
|
div.on('click', '.modal-footer a', function(e) {
|
||||||
|
var self = this;
|
||||||
Ember.run(function() {
|
Ember.run(function() {
|
||||||
|
|
||||||
var handler = $(this).data("handler"),
|
var handler = $(self).data("handler"),
|
||||||
cb = callbacks[handler],
|
cb = callbacks[handler],
|
||||||
hideModal = null;
|
hideModal = null;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue