Fixes issues with composer

This commit is contained in:
Robin Ward 2015-04-30 16:04:58 -04:00
parent 14fa033288
commit ed398e65e0
16 changed files with 121 additions and 55 deletions

View file

@ -4,9 +4,8 @@ export default Ember.Component.extend({
}.observes("visible"), }.observes("visible"),
render: function(buffer){ render: function(buffer){
if(!this.get("visible")){ if (this._state !== 'inDOM' && this._state !== 'preRender') { return; }
return; if (!this.get("visible")) { return; }
}
return this._super(buffer); return this._super(buffer);
} }

View file

@ -1,6 +1,6 @@
import DiscourseController from 'discourse/controllers/controller'; import DiscourseController from 'discourse/controllers/controller';
export default DiscourseController.extend({ export default Ember.ObjectController.extend({
needs: ['modal', 'topic', 'composer-messages', 'application'], needs: ['modal', 'topic', 'composer-messages', 'application'],
replyAsNewTopicDraft: Em.computed.equal('model.draftKey', Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY), replyAsNewTopicDraft: Em.computed.equal('model.draftKey', Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY),
@ -10,6 +10,14 @@ export default DiscourseController.extend({
editReason: null, editReason: null,
maxTitleLength: Discourse.computed.setting('max_topic_title_length'), maxTitleLength: Discourse.computed.setting('max_topic_title_length'),
scopedCategoryId: null, scopedCategoryId: null,
similarTopics: null,
similarTopicsMessage: null,
lastSimilaritySearch: null,
topic: null,
// TODO: Remove this, very bad
view: null,
_initializeSimilar: function() { _initializeSimilar: function() {
this.set('similarTopics', []); this.set('similarTopics', []);
@ -183,7 +191,7 @@ export default DiscourseController.extend({
// for now handle a very narrow use case // for now handle a very narrow use case
// if we are replying to a topic AND not on the topic pop the window up // if we are replying to a topic AND not on the topic pop the window up
if (!force && composer.get('replyingToTopic')) { if (!force && composer.get('replyingToTopic')) {
const topic = this.get('topic'); const topic = this.get('model.topic');
if (!topic || topic.get('id') !== composer.get('topic.id')) if (!topic || topic.get('id') !== composer.get('topic.id'))
{ {
const message = I18n.t("composer.posting_not_on_topic"); const message = I18n.t("composer.posting_not_on_topic");
@ -285,7 +293,7 @@ export default DiscourseController.extend({
// Checks to see if a reply has been typed. // Checks to see if a reply has been typed.
// This is signaled by a keyUp event in a view. // This is signaled by a keyUp event in a view.
checkReplyLength() { checkReplyLength() {
if (this.present('model.reply')) { if (!Ember.isEmpty('model.reply')) {
// Notify the composer messages controller that a reply has been typed. Some // Notify the composer messages controller that a reply has been typed. Some
// messages only appear after typing. // messages only appear after typing.
this.get('controllers.composer-messages').typedReply(); this.get('controllers.composer-messages').typedReply();
@ -469,7 +477,7 @@ export default DiscourseController.extend({
// View a new reply we've made // View a new reply we've made
viewNewReply() { viewNewReply() {
Discourse.URL.routeTo(this.get('createdPost.url')); Discourse.URL.routeTo(this.get('model.createdPost.url'));
this.close(); this.close();
return false; return false;
}, },

View file

@ -1,4 +1,4 @@
export default Ember.Controller.extend({ export default Ember.ObjectController.extend({
needs: ['navigation/category', 'discovery/topics', 'application'], needs: ['navigation/category', 'discovery/topics', 'application'],
loading: false, loading: false,

View file

@ -116,7 +116,7 @@ var controllerOpts = {
if( category ) { if( category ) {
return I18n.t('topics.bottom.category', {category: category.get('name')}); return I18n.t('topics.bottom.category', {category: category.get('name')});
} else { } else {
var split = (this.get('filter') || '').split('/'); var split = (this.get('model.filter') || '').split('/');
if (this.get('topics.length') === 0) { if (this.get('topics.length') === 0) {
return I18n.t("topics.none." + split[0], { return I18n.t("topics.none." + split[0], {
category: split[1] category: split[1]

View file

@ -3,6 +3,7 @@ export default {
after: "message-bus", after: "message-bus",
initialize(container) { initialize(container) {
const banner = Em.Object.create(PreloadStore.get("banner")), const banner = Em.Object.create(PreloadStore.get("banner")),
site = container.lookup('site:main'); site = container.lookup('site:main');

View file

@ -60,7 +60,7 @@ Discourse.Ajax = Em.Mixin.create({
Ember.run(null, resolve, data); Ember.run(null, resolve, data);
}; };
args.error = function(xhr, textStatus) { args.error = function(xhr, textStatus, errorThrown) {
// note: for bad CSRF we don't loop an extra request right away. // note: for bad CSRF we don't loop an extra request right away.
// this allows us to eliminate the possibility of having a loop. // this allows us to eliminate the possibility of having a loop.
if (xhr.status === 403 && xhr.responseText === "['BAD CSRF']") { if (xhr.status === 403 && xhr.responseText === "['BAD CSRF']") {
@ -74,7 +74,11 @@ Discourse.Ajax = Em.Mixin.create({
xhr.jqTextStatus = textStatus; xhr.jqTextStatus = textStatus;
xhr.requestedUrl = url; xhr.requestedUrl = url;
Ember.run(null, reject, xhr); Ember.run(null, reject, {
jqXHR: xhr,
textStatus: textStatus,
errorThrown: errorThrown
});
}; };
// We default to JSON on GET. If we don't, sometimes if the server doesn't return the proper header // We default to JSON on GET. If we don't, sometimes if the server doesn't return the proper header

View file

@ -6,8 +6,8 @@ export default Ember.Mixin.create({
this.controllerFor('composer').open({ this.controllerFor('composer').open({
categoryId: controller.get('category.id'), categoryId: controller.get('category.id'),
action: Discourse.Composer.CREATE_TOPIC, action: Discourse.Composer.CREATE_TOPIC,
draftKey: controller.get('draft_key'), draftKey: controller.get('model.draft_key'),
draftSequence: controller.get('draft_sequence') draftSequence: controller.get('model.draft_sequence')
}); });
}, },

View file

@ -84,7 +84,7 @@ so I'm going to stop rendering it until we figure out what's up
</div> </div>
<!-- keep the classes here in sync with post.hbs --> <!-- keep the classes here in sync with post.hbs -->
<div class='preview-wrapper regular'> <div class='preview-wrapper regular'>
<div id='wmd-preview' {{bind-attr class="hidePreview:hidden :cooked"}}></div> <div id='wmd-preview' {{bind-attr class="model.hidePreview:hidden :cooked"}}></div>
</div> </div>
<div class="composer-bottom-right"> <div class="composer-bottom-right">
<a href="#" {{action "togglePreview"}} class='toggle-preview'>{{{model.toggleText}}}</a> <a href="#" {{action "togglePreview"}} class='toggle-preview'>{{{model.toggleText}}}</a>

View file

@ -67,7 +67,7 @@
</div> </div>
<h3> <h3>
{{footerMessage}} {{footerMessage}}
{{#if can_create_topic}}<a href='#' {{action "createTopic"}}>{{i18n 'topic.suggest_create_topic'}}</a>{{/if}} {{#if can_create_topic}}<a href {{action "createTopic"}}>{{i18n 'topic.suggest_create_topic'}}</a>{{/if}}
</h3> </h3>
{{else}} {{else}}
{{#if top}} {{#if top}}

View file

@ -282,5 +282,5 @@ const CloakedCollectionView = Ember.CollectionView.extend({
}.on('willDestroyElement') }.on('willDestroyElement')
}); });
Ember.Handlebars.helper('cloaked-collection', CloakedCollectionView); Ember.Handlebars.helper('cloaked-collection', Ember.testing ? Ember.CollectionView : CloakedCollectionView);
export default CloakedCollectionView; export default CloakedCollectionView;

View file

@ -36,7 +36,7 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
}.observes('loading'), }.observes('loading'),
postMade: function() { postMade: function() {
return this.present('controller.createdPost') ? 'created-post' : null; return this.present('model.createdPost') ? 'created-post' : null;
}.property('model.createdPost'), }.property('model.createdPost'),
refreshPreview: Discourse.debounce(function() { refreshPreview: Discourse.debounce(function() {

View file

@ -1,6 +1,7 @@
import Site from 'discourse/models/site'; import Site from 'discourse/models/site';
const notificationFixture = { function buildFixture() {
return {
notification_type: 1, //mentioned notification_type: 1, //mentioned
post_number: 1, post_number: 1,
topic_id: 1234, topic_id: 1234,
@ -10,45 +11,46 @@ const notificationFixture = {
display_username: "velesin" display_username: "velesin"
}, },
site: Site.current() site: Site.current()
}; };
}
moduleFor("controller:notification"); moduleFor("controller:notification");
test("scope property is correct", function() { test("scope property is correct", function() {
const controller = this.subject(notificationFixture); const controller = this.subject(buildFixture());
equal(controller.get("scope"), "notifications.mentioned"); equal(controller.get("scope"), "notifications.mentioned");
}); });
test("username property is correct", function() { test("username property is correct", function() {
const controller = this.subject(notificationFixture); const controller = this.subject(buildFixture());
equal(controller.get("username"), "velesin"); equal(controller.get("username"), "velesin");
}); });
test("description property returns badge name when there is one", function() { test("description property returns badge name when there is one", function() {
const fixtureWithBadgeName = _.extend({}, notificationFixture, { data: { badge_name: "badge" } }); const fixtureWithBadgeName = _.extend({}, buildFixture(), { data: { badge_name: "badge" } });
const controller = this.subject(fixtureWithBadgeName); const controller = this.subject(fixtureWithBadgeName);
equal(controller.get("description"), "badge"); equal(controller.get("description"), "badge");
}); });
test("description property returns empty string when there is no topic title", function() { test("description property returns empty string when there is no topic title", function() {
const fixtureWithEmptyTopicTitle = _.extend({}, notificationFixture, { data: { topic_title: "" } }); const fixtureWithEmptyTopicTitle = _.extend({}, buildFixture(), { data: { topic_title: "" } });
const controller = this.subject(fixtureWithEmptyTopicTitle); const controller = this.subject(fixtureWithEmptyTopicTitle);
equal(controller.get("description"), ""); equal(controller.get("description"), "");
}); });
test("description property returns topic title", function() { test("description property returns topic title", function() {
const fixtureWithTopicTitle = _.extend({}, notificationFixture, { data: { topic_title: "topic" } }); const fixtureWithTopicTitle = _.extend({}, buildFixture(), { data: { topic_title: "topic" } });
const controller = this.subject(fixtureWithTopicTitle); const controller = this.subject(fixtureWithTopicTitle);
equal(controller.get("description"), "topic"); equal(controller.get("description"), "topic");
}); });
test("url property returns badge's url when there is a badge", function() { test("url property returns badge's url when there is a badge", function() {
const fixtureWithBadge = _.extend({}, notificationFixture, { data: { badge_id: 1, badge_name: "Badge Name"} }); const fixtureWithBadge = _.extend({}, buildFixture(), { data: { badge_id: 1, badge_name: "Badge Name"} });
const controller = this.subject(fixtureWithBadge); const controller = this.subject(fixtureWithBadge);
equal(controller.get("url"), "/badges/1/badge-name"); equal(controller.get("url"), "/badges/1/badge-name");
}); });
test("url property returns topic's url when there is a topic", function() { test("url property returns topic's url when there is a topic", function() {
const controller = this.subject(notificationFixture); const controller = this.subject(buildFixture());
equal(controller.get("url"), "/t/a-slug/1234"); equal(controller.get("url"), "/t/a-slug/1234");
}); });

View file

@ -70,6 +70,8 @@ function acceptance(name, options) {
if (options && options.teardown) { if (options && options.teardown) {
options.teardown.call(this); options.teardown.call(this);
} }
Discourse.User.resetCurrent();
Discourse.Site.resetCurrent(Discourse.Site.create(fixtures['site.json'].site));
Discourse.Utilities.avatarImg = oldAvatar; Discourse.Utilities.avatarImg = oldAvatar;
Discourse.reset(); Discourse.reset();

View file

@ -1,16 +1,10 @@
var asianCategory = Discourse.Category.create({name: '确实是这样', id: 343434});
module("Discourse.NavItem", { module("Discourse.NavItem", {
setup: function() { setup: function() {
Ember.run(function() { Ember.run(function() {
const asianCategory = Discourse.Category.create({name: '确实是这样', id: 343434});
Discourse.Site.currentProp('categories').addObject(asianCategory); Discourse.Site.currentProp('categories').addObject(asianCategory);
}); });
},
teardown: function() {
Em.run(function() {
Discourse.Site.currentProp('categories').removeObject(asianCategory);
});
} }
}); });

View file

@ -19,6 +19,8 @@
var self = this, var self = this,
img = document.createElement('img'); img = document.createElement('img');
if (Ember.testing) { return; }
if (self.canvas.getContext) { if (self.canvas.getContext) {
img.crossOrigin = "anonymous"; img.crossOrigin = "anonymous";
@ -94,9 +96,7 @@
head.appendChild(favicon); head.appendChild(favicon);
} }
Favcount.VERSION = '1.5.0';
this.Favcount = Favcount; this.Favcount = Favcount;
}).call(this); }).call(this);
(function(){
Favcount.VERSION = '1.5.0';
}).call(this);

View file

@ -3,9 +3,10 @@
var isNode = typeof process !== 'undefined' && process.toString() === '[object process]'; var isNode = typeof process !== 'undefined' && process.toString() === '[object process]';
var RouteRecognizer = isNode ? require('route-recognizer')['default'] : window.RouteRecognizer; var RouteRecognizer = isNode ? require('route-recognizer')['default'] : window.RouteRecognizer;
var FakeXMLHttpRequest = isNode ? require('./bower_components/FakeXMLHttpRequest/fake_xml_http_request') : window.FakeXMLHttpRequest; var FakeXMLHttpRequest = isNode ? require('./bower_components/FakeXMLHttpRequest/fake_xml_http_request') : window.FakeXMLHttpRequest;
var slice = [].slice;
function Pretender(maps){ function Pretender(/* routeMap1, routeMap2, ...*/){
maps = maps || function(){}; maps = slice.call(arguments);
// Herein we keep track of RouteRecognizer instances // Herein we keep track of RouteRecognizer instances
// keyed by HTTP method. Feel free to add more as needed. // keyed by HTTP method. Feel free to add more as needed.
this.registry = { this.registry = {
@ -21,6 +22,7 @@ function Pretender(maps){
this.handledRequests = []; this.handledRequests = [];
this.passthroughRequests = []; this.passthroughRequests = [];
this.unhandledRequests = []; this.unhandledRequests = [];
this.requestReferences = [];
// reference the native XMLHttpRequest object so // reference the native XMLHttpRequest object so
// it can be restored later // it can be restored later
@ -34,7 +36,9 @@ function Pretender(maps){
this.running = true; this.running = true;
// trigger the route map DSL. // trigger the route map DSL.
maps.call(this); for(i=0; i < arguments.length; i++){
this.map(arguments[i]);
}
} }
function interceptor(pretender) { function interceptor(pretender) {
@ -51,8 +55,8 @@ function interceptor(pretender) {
'a pretender earlier than you intended to'); 'a pretender earlier than you intended to');
} }
if (!pretender.checkPassthrough(this)) {
FakeXMLHttpRequest.prototype.send.apply(this, arguments); FakeXMLHttpRequest.prototype.send.apply(this, arguments);
if (!pretender.checkPassthrough(this)) {
pretender.handleRequest(this); pretender.handleRequest(this);
} }
else { else {
@ -86,6 +90,9 @@ function interceptor(pretender) {
xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password); xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password);
xhr.timeout = fakeXHR.timeout; xhr.timeout = fakeXHR.timeout;
xhr.withCredentials = fakeXHR.withCredentials; xhr.withCredentials = fakeXHR.withCredentials;
for (var h in fakeXHR.requestHeaders) {
xhr.setRequestHeader(h, fakeXHR.requestHeaders[h]);
}
return xhr; return xhr;
} }
proto._passthroughCheck = function(method, arguments) { proto._passthroughCheck = function(method, arguments) {
@ -109,8 +116,8 @@ function interceptor(pretender) {
} }
function verbify(verb){ function verbify(verb){
return function(path, handler){ return function(path, handler, async){
this.register(verb, path, handler); this.register(verb, path, handler, async);
}; };
} }
@ -137,8 +144,16 @@ Pretender.prototype = {
'delete': verbify('DELETE'), 'delete': verbify('DELETE'),
patch: verbify('PATCH'), patch: verbify('PATCH'),
head: verbify('HEAD'), head: verbify('HEAD'),
register: function register(verb, path, handler){ map: function(maps){
maps.call(this);
},
register: function register(verb, path, handler, async){
if (!handler) {
throw new Error("The function you tried passing to Pretender to handle " + verb + " " + path + " is undefined or missing.");
}
handler.numberOfCalls = 0; handler.numberOfCalls = 0;
handler.async = async;
this.handlers.push(handler); this.handlers.push(handler);
var registry = this.registry[verb]; var registry = this.registry[verb];
@ -171,24 +186,65 @@ Pretender.prototype = {
if (handler) { if (handler) {
handler.handler.numberOfCalls++; handler.handler.numberOfCalls++;
var async = handler.handler.async;
this.handledRequests.push(request); this.handledRequests.push(request);
try { try {
var statusHeadersAndBody = handler.handler(request), var statusHeadersAndBody = handler.handler(request),
status = statusHeadersAndBody[0], status = statusHeadersAndBody[0],
headers = this.prepareHeaders(statusHeadersAndBody[1]), headers = this.prepareHeaders(statusHeadersAndBody[1]),
body = this.prepareBody(statusHeadersAndBody[2]); body = this.prepareBody(statusHeadersAndBody[2]),
request.respond(status, headers, body); pretender = this;
this.handledRequest(verb, path, request); this.handleResponse(request, async, function() {
request.respond(status, headers, body);
pretender.handledRequest(verb, path, request);
});
} catch (error) { } catch (error) {
this.erroredRequest(verb, path, request, error); this.erroredRequest(verb, path, request, error);
this.resolve(request);
} }
} else { } else {
this.unhandledRequests.push(request); this.unhandledRequests.push(request);
this.unhandledRequest(verb, path, request); this.unhandledRequest(verb, path, request);
} }
}, },
handleResponse: function handleResponse(request, strategy, callback) {
strategy = typeof strategy === 'function' ? strategy() : strategy;
if (strategy === false) {
callback();
} else {
var pretender = this;
pretender.requestReferences.push({
request: request,
callback: callback
});
if (strategy !== true) {
setTimeout(function() {
pretender.resolve(request);
}, typeof strategy === 'number' ? strategy : 0);
}
}
},
resolve: function resolve(request) {
for(var i = 0, len = this.requestReferences.length; i < len; i++) {
var res = this.requestReferences[i];
if (res.request === request) {
res.callback();
this.requestReferences.splice(i, 1);
break;
}
}
},
requiresManualResolution: function(verb, path) {
var handler = this._handlerFor(verb.toUpperCase(), path, {});
if (!handler) { return false; }
var async = handler.handler.async;
return typeof async === 'function' ? async() === true : async === true;
},
prepareBody: function(body) { return body; }, prepareBody: function(body) { return body; },
prepareHeaders: function(headers) { return headers; }, prepareHeaders: function(headers) { return headers; },
handledRequest: function(verb, path, request) { /* no-op */}, handledRequest: function(verb, path, request) { /* no-op */},