Clean up discourse.js

This commit is contained in:
Robin Ward 2013-02-26 14:54:43 -05:00
parent b9ccf4d09c
commit 1caf1e6b45
21 changed files with 818 additions and 537 deletions

View file

@ -4,7 +4,7 @@
@method buildRoutes @method buildRoutes
@for Discourse.AdminRoute @for Discourse.AdminRoute
**/ **/
Discourse.buildRoutes(function() { Discourse.Route.buildRoutes(function() {
this.resource('admin', { path: '/admin' }, function() { this.resource('admin', { path: '/admin' }, function() {
this.route('dashboard', { path: '/' }); this.route('dashboard', { path: '/' });
this.route('site_settings', { path: '/site_settings' }); this.route('site_settings', { path: '/site_settings' });

View file

@ -1,31 +1,28 @@
/*global Modernizr:true*/ /*global Modernizr:true*/
/*global assetPath:true*/ /*global assetPath:true*/
var csrf_token; /**
The main Discourse Application
@class Discourse
@extends Ember.Application
**/
Discourse = Ember.Application.createWithMixins({ Discourse = Ember.Application.createWithMixins({
rootElement: '#main', rootElement: '#main',
// Data we want to remember for a short period // Data we want to remember for a short period
transient: Em.Object.create(), transient: Em.Object.create(),
// Whether the app has focus or not
hasFocus: true, hasFocus: true,
// Are we currently scrolling?
scrolling: false, scrolling: false,
// The highest seen post number by topic // The highest seen post number by topic
highestSeenByTopic: {}, highestSeenByTopic: {},
logoSmall: (function() { titleChanged: function() {
var logo;
logo = Discourse.SiteSettings.logo_small_url;
if (logo && logo.length > 1) {
return "<img src='" + logo + "' width='33' height='33'>";
} else {
return "<i class='icon-home'></i>";
}
}).property(),
titleChanged: (function() {
var title; var title;
title = ""; title = "";
if (this.get('title')) { if (this.get('title')) {
@ -37,51 +34,45 @@ Discourse = Ember.Application.createWithMixins({
title = "(*) " + title; title = "(*) " + title;
} }
// chrome bug workaround see: http://stackoverflow.com/questions/2952384/changing-the-window-title-when-focussing-the-window-doesnt-work-in-chrome // chrome bug workaround see: http://stackoverflow.com/questions/2952384/changing-the-window-title-when-focussing-the-window-doesnt-work-in-chrome
window.setTimeout((function() { window.setTimeout(function() {
document.title = "."; document.title = ".";
document.title = title; document.title = title;
}), 200); }, 200);
}).observes('title', 'hasFocus', 'notify'), }.observes('title', 'hasFocus', 'notify'),
currentUserChanged: (function() { currentUserChanged: function() {
var bus, user;
bus = Discourse.MessageBus;
// We don't want to receive any previous user notifications // We don't want to receive any previous user notifications
var bus = Discourse.MessageBus;
bus.unsubscribe("/notification/*"); bus.unsubscribe("/notification/*");
bus.callbackInterval = Discourse.SiteSettings.anon_polling_interval; bus.callbackInterval = Discourse.SiteSettings.anon_polling_interval;
bus.enableLongPolling = false; bus.enableLongPolling = false;
user = this.get('currentUser');
var user = this.get('currentUser');
if (user) { if (user) {
bus.callbackInterval = Discourse.SiteSettings.polling_interval; bus.callbackInterval = Discourse.SiteSettings.polling_interval;
bus.enableLongPolling = true; bus.enableLongPolling = true;
if (user.admin) { if (user.admin) {
bus.subscribe("/flagged_counts", function(data) { bus.subscribe("/flagged_counts", function(data) {
return user.set('site_flagged_posts_count', data.total); user.set('site_flagged_posts_count', data.total);
}); });
} }
return bus.subscribe("/notification/" + user.id, (function(data) { bus.subscribe("/notification/" + user.id, (function(data) {
user.set('unread_notifications', data.unread_notifications); user.set('unread_notifications', data.unread_notifications);
return user.set('unread_private_messages', data.unread_private_messages); user.set('unread_private_messages', data.unread_private_messages);
}), user.notification_channel_position); }), user.notification_channel_position);
} }
}).observes('currentUser'), }.observes('currentUser'),
notifyTitle: function() {
return this.set('notify', true);
},
// Browser aware replaceState // The classes of buttons to show on a post
replaceState: function(path) { postButtons: function() {
if (window.history && return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
window.history.pushState && return (i.replace(/\+/, '').capitalize());
window.history.replaceState && });
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)) { }.property('Discourse.SiteSettings.post_menu'),
if (window.location.pathname !== path) {
return history.replaceState({ notifyTitle: function() {
path: path this.set('notify', true);
}, null, path);
}
}
}, },
openComposer: function(opts) { openComposer: function(opts) {
@ -90,291 +81,108 @@ Discourse = Ember.Application.createWithMixins({
if (composer) composer.open(opts); if (composer) composer.open(opts);
}, },
// Like router.route, but allow full urls rather than relative one /**
// HERE BE HACKS - uses the ember container for now until we can do this nicer. Establishes global DOM events and bindings via jQuery.
routeTo: function(path) {
var newMatches, newTopicId, oldMatches, oldTopicId, opts, router, topicController, topicRegexp;
path = path.replace(/https?\:\/\/[^\/]+/, '');
// If we're in the same topic, don't push the state
topicRegexp = /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/;
newMatches = topicRegexp.exec(path);
newTopicId = newMatches ? newMatches[2] : null;
if (newTopicId) {
oldMatches = topicRegexp.exec(window.location.pathname);
if ((oldTopicId = oldMatches ? oldMatches[2] : void 0) && (oldTopicId === newTopicId)) {
Discourse.replaceState(path);
topicController = Discourse.__container__.lookup('controller:topic');
opts = {
trackVisit: false
};
if (newMatches[3]) {
opts.nearPost = newMatches[3];
}
topicController.get('content').loadPosts(opts);
return;
}
}
// Be wary of looking up the router. In this case, we have links in our
// HTML, say form compiled markdown posts, that need to be routed.
router = Discourse.__container__.lookup('router:main');
router.router.updateURL(path);
return router.handleURL(path);
},
// The classes of buttons to show on a post
postButtons: (function() {
return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
return "" + (i.replace(/\+/, '').capitalize());
});
}).property('Discourse.SiteSettings.post_menu'),
@method bindDOMEvents
**/
bindDOMEvents: function() { bindDOMEvents: function() {
var $html, hasTouch, var $html, hasTouch;
_this = this;
$html = $('html');
/* Add the discourse touch event */ $html = $('html');
hasTouch = false; hasTouch = false;
if ($html.hasClass('touch')) { if ($html.hasClass('touch')) {
hasTouch = true; hasTouch = true;
} }
if (Modernizr.prefixed("MaxTouchPoints", navigator) > 1) { if (Modernizr.prefixed("MaxTouchPoints", navigator) > 1) {
hasTouch = true; hasTouch = true;
} }
if (hasTouch) { if (hasTouch) {
$html.addClass('discourse-touch'); $html.addClass('discourse-touch');
this.touch = true; this.touch = true;
this.hasTouch = true; this.hasTouch = true;
} else { } else {
$html.addClass('discourse-no-touch'); $html.addClass('discourse-no-touch');
this.touch = false; this.touch = false;
} }
$('#main').on('click.discourse', '[data-not-implemented=true]', function(e) { $('#main').on('click.discourse', '[data-not-implemented=true]', function(e) {
e.preventDefault(); e.preventDefault();
alert(Em.String.i18n('not_implemented')); alert(Em.String.i18n('not_implemented'));
return false; return false;
}); });
$('#main').on('click.discourse', 'a', function(e) { $('#main').on('click.discourse', 'a', function(e) {
var $currentTarget, href; if (e.isDefaultPrevented() || e.metaKey || e.ctrlKey) return;
if (e.isDefaultPrevented() || e.metaKey || e.ctrlKey) {
return; var $currentTarget = $(e.currentTarget);
} var href = $currentTarget.attr('href');
$currentTarget = $(e.currentTarget); if (!href) return;
href = $currentTarget.attr('href'); if (href === '#') return;
if (href === void 0) { if ($currentTarget.attr('target')) return;
return; if ($currentTarget.data('auto-route')) return;
} if ($currentTarget.hasClass('lightbox')) return;
if (href === '#') { if (href.indexOf("mailto:") === 0) return;
return; if (href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i"))) return;
}
if ($currentTarget.attr('target')) {
return;
}
if ($currentTarget.data('auto-route')) {
return;
}
if ($currentTarget.hasClass('lightbox')) {
return;
}
if (href.indexOf("mailto:") === 0) {
return;
}
if (href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i"))) {
return;
}
e.preventDefault(); e.preventDefault();
_this.routeTo(href); Discourse.URL.routeTo(href);
return false; return false;
}); });
return $(window).focus(function() {
_this.set('hasFocus', true); $(window).focus(function() {
return _this.set('notify', false); Discourse.set('hasFocus', true);
Discourse.set('notify', false);
}).blur(function() { }).blur(function() {
return _this.set('hasFocus', false); Discourse.set('hasFocus', false);
});
// Add a CSRF token to all AJAX requests
var csrfToken = $('meta[name=csrf-token]').attr('content');
jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
if (!options.crossDomain) {
xhr.setRequestHeader('X-CSRF-Token', csrfToken);
}
}); });
}, },
/**
Log the current user out of Discourse
@method logout
**/
logout: function() { logout: function() {
var username,
_this = this;
username = this.get('currentUser.username');
Discourse.KeyValueStore.abandonLocal(); Discourse.KeyValueStore.abandonLocal();
return jQuery.ajax("/session/" + username, { return jQuery.ajax("/session/" + this.get('currentUser.username'), {
type: 'DELETE', type: 'DELETE',
success: function(result) { success: function(result) {
/* To keep lots of our variables unbound, we can handle a redirect on logging out. // To keep lots of our variables unbound, we can handle a redirect on logging out.
*/ window.location.reload();
return window.location.reload();
} }
}); });
}, },
/* fancy probes in ember
*/
insertProbes: function() {
var topLevel;
if (typeof console === "undefined" || console === null) {
return;
}
topLevel = function(fn, name) {
return window.probes.measure(fn, {
name: name,
before: function(data, owner, args) {
if (owner) {
return window.probes.clear();
}
},
after: function(data, owner, args) {
var ary, f, n, v, _ref;
if (owner && data.time > 10) {
f = function(name, data) {
if (data && data.count) {
return "" + name + " - " + data.count + " calls " + ((data.time + 0.0).toFixed(2)) + "ms";
}
};
if (console && console.group) {
console.group(f(name, data));
} else {
console.log("");
console.log(f(name, data));
}
ary = [];
_ref = window.probes;
for (n in _ref) {
v = _ref[n];
if (n === name || v.time < 1) {
continue;
}
ary.push({
k: n,
v: v
});
}
ary.sortBy(function(item) {
if (item.v && item.v.time) {
return -item.v.time;
} else {
return 0;
}
}).each(function(item) {
var output = f("" + item.k, item.v);
if (output) {
return console.log(output);
}
});
if (typeof console !== "undefined" && console !== null) {
if (typeof console.groupEnd === "function") {
console.groupEnd();
}
}
return window.probes.clear();
}
}
});
};
Ember.View.prototype.renderToBuffer = window.probes.measure(Ember.View.prototype.renderToBuffer, "renderToBuffer");
Discourse.routeTo = topLevel(Discourse.routeTo, "Discourse.routeTo");
Ember.run.end = topLevel(Ember.run.end, "Ember.run.end");
},
authenticationComplete: function(options) { authenticationComplete: function(options) {
// TODO, how to dispatch this to the view without the container? // TODO, how to dispatch this to the view without the container?
var loginView; var loginView;
loginView = Discourse.__container__.lookup('controller:modal').get('currentView'); loginView = Discourse.__container__.lookup('controller:modal').get('currentView');
return loginView.authenticationComplete(options); return loginView.authenticationComplete(options);
}, },
buildRoutes: function(builder) {
var oldBuilder;
oldBuilder = Discourse.routeBuilder;
Discourse.routeBuilder = function() {
if (oldBuilder) {
oldBuilder.call(this);
}
return builder.call(this);
};
},
start: function() { start: function() {
this.bindDOMEvents(); Discourse.bindDOMEvents();
Discourse.SiteSettings = PreloadStore.getStatic('siteSettings'); Discourse.SiteSettings = PreloadStore.getStatic('siteSettings');
Discourse.MessageBus.start(); Discourse.MessageBus.start();
Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus); Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus);
Discourse.insertProbes();
// subscribe to any site customizations that are loaded // Developer specific functions
$('link.custom-css').each(function() { Discourse.Development.setupProbes();
var id, split, stylesheet, Discourse.Development.observeLiveChanges();
_this = this;
split = this.href.split("/");
id = split[split.length - 1].split(".css")[0];
stylesheet = this;
return Discourse.MessageBus.subscribe("/file-change/" + id, function(data) {
var orig, sp;
if (!$(stylesheet).data('orig')) {
$(stylesheet).data('orig', stylesheet.href);
} }
orig = $(stylesheet).data('orig');
sp = orig.split(".css?");
stylesheet.href = sp[0] + ".css?" + data;
});
});
$('header.custom').each(function() {
var header;
header = $(this);
return Discourse.MessageBus.subscribe("/header-change/" + ($(this).data('key')), function(data) {
return header.html(data);
});
}); });
// possibly move this to dev only Discourse.Router = Discourse.Router.reopen({ location: 'discourse_location' });
return Discourse.MessageBus.subscribe("/file-change", function(data) {
Ember.TEMPLATES.empty = Handlebars.compile("<div></div>");
return data.each(function(me) {
var js;
if (me === "refresh") {
return document.location.reload(true);
} else if (me.name.substr(-10) === "handlebars") {
js = me.name.replace(".handlebars", "").replace("app/assets/javascripts", "/assets");
return $LAB.script(js + "?hash=" + me.hash).wait(function() {
var templateName;
templateName = js.replace(".js", "").replace("/assets/", "");
return jQuery.each(Ember.View.views, function() {
var _this = this;
if (this.get('templateName') === templateName) {
this.set('templateName', 'empty');
this.rerender();
return Em.run.next(function() {
_this.set('templateName', templateName);
return _this.rerender();
});
}
});
});
} else {
return $('link').each(function() {
if (this.href.match(me.name) && me.hash) {
if (!$(this).data('orig')) {
$(this).data('orig', this.href);
}
this.href = $(this).data('orig') + "&hash=" + me.hash;
}
});
}
});
});
}
});
Discourse.Router = Discourse.Router.reopen({
location: 'discourse_location'
});
// since we have no jquery-rails these days, hook up csrf token
csrf_token = $('meta[name=csrf-token]').attr('content');
jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
if (!options.crossDomain) {
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
}
});

View file

@ -88,7 +88,7 @@ Discourse.ClickTrack = {
topic_id: topicId, topic_id: topicId,
redirect: false redirect: false
}); });
Discourse.routeTo(href); Discourse.URL.routeTo(href);
return false; return false;
} }

View file

@ -0,0 +1,158 @@
/**
Functions to help development of Discourse, such as inserting probes
@class Development
@namespace Discourse
@module Discourse
**/
Discourse.Development = {
/**
Set up probes for performance measurements.
@method setupProbes
**/
setupProbes: function() {
// Don't probe if we don't have a console
if (typeof console === "undefined" || console === null) return;
var topLevel = function(fn, name) {
return window.probes.measure(fn, {
name: name,
before: function(data, owner, args) {
if (owner) {
return window.probes.clear();
}
},
after: function(data, owner, args) {
var ary, f, n, v, _ref;
if (owner && data.time > 10) {
f = function(name, data) {
if (data && data.count) return name + " - " + data.count + " calls " + ((data.time + 0.0).toFixed(2)) + "ms";
};
if (console && console.group) {
console.group(f(name, data));
} else {
console.log("");
console.log(f(name, data));
}
ary = [];
_ref = window.probes;
for (n in _ref) {
v = _ref[n];
if (n === name || v.time < 1) {
continue;
}
ary.push({
k: n,
v: v
});
}
ary.sortBy(function(item) {
if (item.v && item.v.time) {
return -item.v.time;
} else {
return 0;
}
}).each(function(item) {
var output = f("" + item.k, item.v);
if (output) {
return console.log(output);
}
});
if (typeof console !== "undefined" && console !== null) {
if (typeof console.groupEnd === "function") {
console.groupEnd();
}
}
return window.probes.clear();
}
}
});
};
Ember.View.prototype.renderToBuffer = window.probes.measure(Ember.View.prototype.renderToBuffer, "renderToBuffer");
Discourse.URL.routeTo = topLevel(Discourse.URL.routeTo, "Discourse.URL.routeTo");
Ember.run.end = topLevel(Ember.run.end, "Ember.run.end");
},
/**
Use the message bus for live reloading of components for faster development.
@method observeLiveChanges
**/
observeLiveChanges: function() {
// subscribe to any site customizations that are loaded
$('link.custom-css').each(function() {
var id, split, stylesheet,
_this = this;
split = this.href.split("/");
id = split[split.length - 1].split(".css")[0];
stylesheet = this;
return Discourse.MessageBus.subscribe("/file-change/" + id, function(data) {
var orig, sp;
if (!$(stylesheet).data('orig')) {
$(stylesheet).data('orig', stylesheet.href);
}
orig = $(stylesheet).data('orig');
sp = orig.split(".css?");
stylesheet.href = sp[0] + ".css?" + data;
});
});
// Custom header changes
$('header.custom').each(function() {
var header;
header = $(this);
return Discourse.MessageBus.subscribe("/header-change/" + ($(this).data('key')), function(data) {
return header.html(data);
});
});
// Observe file changes
return Discourse.MessageBus.subscribe("/file-change", function(data) {
Ember.TEMPLATES.empty = Handlebars.compile("<div></div>");
return data.each(function(me) {
var js;
if (me === "refresh") {
return document.location.reload(true);
} else if (me.name.substr(-10) === "handlebars") {
js = me.name.replace(".handlebars", "").replace("app/assets/javascripts", "/assets");
return $LAB.script(js + "?hash=" + me.hash).wait(function() {
var templateName;
templateName = js.replace(".js", "").replace("/assets/", "");
return jQuery.each(Ember.View.views, function() {
var _this = this;
if (this.get('templateName') === templateName) {
this.set('templateName', 'empty');
this.rerender();
return Em.run.next(function() {
_this.set('templateName', templateName);
return _this.rerender();
});
}
});
});
} else {
return $('link').each(function() {
if (this.href.match(me.name) && me.hash) {
if (!$(this).data('orig')) {
$(this).data('orig', this.href);
}
this.href = $(this).data('orig') + "&hash=" + me.hash;
}
});
}
});
});
}
};

View file

@ -0,0 +1,69 @@
/**
URL related functions.
@class URL
@namespace Discourse
@module Discourse
**/
Discourse.URL = {
/**
Browser aware replaceState. Will only be invoked if the browser supports it.
@method replaceState
@param {String} path The path we are replacing our history state with.
**/
replaceState: function(path) {
if (window.history &&
window.history.pushState &&
window.history.replaceState &&
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/) &&
(window.location.pathname !== path)) {
return history.replaceState({ path: path }, null, path);
}
},
/**
Our custom routeTo method is used to intelligently overwrite default routing
behavior.
It contains the logic necessary to route within a topic using replaceState to
keep the history intact.
Note that currently it uses `__container__` which is not advised
but there is no other way to access the router.
@method routeTo
@param {String} path The path we are routing to.
**/
routeTo: function(path) {
var newMatches, newTopicId, oldMatches, oldTopicId, opts, router, topicController, topicRegexp;
path = path.replace(/https?\:\/\/[^\/]+/, '');
console.log("route to: " + path);
// If we're in the same topic, don't push the state
topicRegexp = /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/;
newMatches = topicRegexp.exec(path);
newTopicId = newMatches ? newMatches[2] : null;
if (newTopicId) {
oldMatches = topicRegexp.exec(window.location.pathname);
if ((oldTopicId = oldMatches ? oldMatches[2] : void 0) && (oldTopicId === newTopicId)) {
Discourse.URL.replaceState(path);
topicController = Discourse.__container__.lookup('controller:topic');
opts = { trackVisit: false };
if (newMatches[3]) {
opts.nearPost = newMatches[3];
}
topicController.get('content').loadPosts(opts);
return;
}
}
// Be wary of looking up the router. In this case, we have links in our
// HTML, say form compiled markdown posts, that need to be routed.
router = Discourse.__container__.lookup('router:main');
router.router.updateURL(path);
return router.handleURL(path);
}
};

View file

@ -42,7 +42,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
} else { } else {
Discourse.set('currentUser.reply_count', Discourse.get('currentUser.reply_count') + 1); Discourse.set('currentUser.reply_count', Discourse.get('currentUser.reply_count') + 1);
} }
Discourse.routeTo(opts.post.get('url')); Discourse.URL.routeTo(opts.post.get('url'));
}, function(error) { }, function(error) {
composer.set('disableDrafts', false); composer.set('disableDrafts', false);
bootbox.alert(error); bootbox.alert(error);
@ -159,7 +159,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
// View a new reply we've made // View a new reply we've made
viewNewReply: function() { viewNewReply: function() {
Discourse.routeTo(this.get('createdPost.url')); Discourse.URL.routeTo(this.get('createdPost.url'));
this.close(); this.close();
return false; return false;
}, },

View file

@ -8,7 +8,7 @@
**/ **/
Discourse.HeaderController = Discourse.Controller.extend({ Discourse.HeaderController = Discourse.Controller.extend({
topic: null, topic: null,
showExtraInfo: false, showExtraInfo: null,
toggleStar: function() { toggleStar: function() {
var topic = this.get('topic'); var topic = this.get('topic');

View file

@ -10,7 +10,6 @@ Discourse.TopicController = Discourse.ObjectController.extend({
userFilters: new Em.Set(), userFilters: new Em.Set(),
multiSelect: false, multiSelect: false,
bestOf: false, bestOf: false,
showExtraHeaderInfo: false,
needs: ['header', 'modal', 'composer', 'quoteButton'], needs: ['header', 'modal', 'composer', 'quoteButton'],
filter: (function() { filter: (function() {
@ -42,10 +41,6 @@ Discourse.TopicController = Discourse.ObjectController.extend({
return this.get('canDeleteSelected'); return this.get('canDeleteSelected');
}).property('canDeleteSelected'), }).property('canDeleteSelected'),
showExtraHeaderInfoChanged: (function() {
this.set('controllers.header.showExtraInfo', this.get('showExtraHeaderInfo'));
}).observes('showExtraHeaderInfo'),
canDeleteSelected: (function() { canDeleteSelected: (function() {
var canDelete, selectedPosts; var canDelete, selectedPosts;
selectedPosts = this.get('selectedPosts'); selectedPosts = this.get('selectedPosts');
@ -109,11 +104,11 @@ Discourse.TopicController = Discourse.ObjectController.extend({
}, },
jumpTop: function() { jumpTop: function() {
Discourse.routeTo(this.get('content.url')); Discourse.URL.routeTo(this.get('content.url'));
}, },
jumpBottom: function() { jumpBottom: function() {
Discourse.routeTo(this.get('content.lastPostUrl')); Discourse.URL.routeTo(this.get('content.lastPostUrl'));
}, },
cancelFilter: function() { cancelFilter: function() {

View file

@ -9,7 +9,7 @@
Discourse.UserPrivateMessagesController = Discourse.ObjectController.extend({ Discourse.UserPrivateMessagesController = Discourse.ObjectController.extend({
editPreferences: function() { editPreferences: function() {
return Discourse.routeTo("/users/" + (this.get('content.username_lower')) + "/preferences"); return Discourse.URL.routeTo("/users/" + (this.get('content.username_lower')) + "/preferences");
}, },
composePrivateMessage: function() { composePrivateMessage: function() {

View file

@ -14,7 +14,7 @@ Discourse.TopicList = Discourse.Model.extend({
_this = this; _this = this;
promise = new RSVP.Promise(); promise = new RSVP.Promise();
if (moreUrl = this.get('more_topics_url')) { if (moreUrl = this.get('more_topics_url')) {
Discourse.replaceState("/" + (this.get('filter')) + "/more"); Discourse.URL.replaceState("/" + (this.get('filter')) + "/more");
jQuery.ajax(moreUrl, { jQuery.ajax(moreUrl, {
success: function(result) { success: function(result) {
var newTopics, topicIds, topics; var newTopics, topicIds, topics;

View file

@ -4,7 +4,7 @@
@method buildRoutes @method buildRoutes
@for Discourse.ApplicationRoute @for Discourse.ApplicationRoute
**/ **/
Discourse.buildRoutes(function() { Discourse.Route.buildRoutes(function() {
var router = this; var router = this;
// Topic routes // Topic routes

View file

@ -28,3 +28,14 @@ Discourse.Route = Em.Route.extend({
}); });
Discourse.Route.reopenClass({
buildRoutes: function(builder) {
var oldBuilder = Discourse.routeBuilder;
Discourse.routeBuilder = function() {
if (oldBuilder) oldBuilder.call(this);
return builder.call(this);
};
}
});

View file

@ -34,10 +34,7 @@ Discourse.TopicRoute = Discourse.Route.extend({
}, },
setupController: function(controller, model) { setupController: function(controller, model) {
var headerController; this.controllerFor('header').set('topic', model);
controller.set('showExtraHeaderInfo', false);
headerController = this.controllerFor('header');
headerController.set('topic', model);
} }
}); });

View file

@ -1,17 +1,7 @@
<div class='container'> <div class='container'>
<div class='contents clearfix'> <div class='contents clearfix'>
<div class='title'>
{{#if controller.showExtraInfo}}
{{#linkTo list.popular}}{{{Discourse.logoSmall}}}{{/linkTo}}
{{else}}
{{#linkTo list.popular}}<img src="{{unbound Discourse.SiteSettings.logo_url}}" alt="{{unbound Discourse.SiteSettings.title}}" id='site-logo'>{{/linkTo}}
{{/if}}
</div>
{{view.logoHTML}}
{{view Discourse.TopicExtraInfoView}} {{view Discourse.TopicExtraInfoView}}
<div class='panel clearfix'> <div class='panel clearfix'>

View file

@ -81,6 +81,27 @@ Discourse.HeaderView = Discourse.View.extend({
} }
}, },
/**
Display the correct logo in the header, showing a custom small icon if it exists.
@property logoHTML
**/
logoHTML: function() {
var result = "<div class='title'><a href='/'>";
if (this.get('controller.showExtraInfo')) {
var logo = Discourse.SiteSettings.logo_small_url;
if (logo && logo.length > 1) {
result += "<img src='" + logo + "' width='33' height='33'>";
} else {
result += "<i class='icon-home'></i>";
}
} else {
result += "<img src=\"" + Discourse.SiteSettings.logo_url + "\" alt=\"" + Discourse.SiteSettings.title + "\" id='site-logo'>";
}
result += "</a></div>";
return new Handlebars.SafeString(result);
}.property('controller.showExtraInfo'),
willDestroyElement: function() { willDestroyElement: function() {
$(window).unbind('scroll.discourse-dock'); $(window).unbind('scroll.discourse-dock');
return $(document).unbind('touchmove.discourse-dock'); return $(document).unbind('touchmove.discourse-dock');

View file

@ -42,7 +42,7 @@ Discourse.EditCategoryView = Discourse.ModalBodyView.extend({
showCategoryTopic: function() { showCategoryTopic: function() {
$('#discourse-modal').modal('hide'); $('#discourse-modal').modal('hide');
Discourse.routeTo(this.get('category.topic_url')); Discourse.URL.routeTo(this.get('category.topic_url'));
return false; return false;
}, },

View file

@ -36,8 +36,8 @@ Discourse.MoveSelectedView = Discourse.ModalBodyView.extend({
Discourse.Topic.movePosts(this.get('topic.id'), this.get('topicName'), postIds).then(function(result) { Discourse.Topic.movePosts(this.get('topic.id'), this.get('topicName'), postIds).then(function(result) {
if (result.success) { if (result.success) {
$('#discourse-modal').modal('hide'); $('#discourse-modal').modal('hide');
return Em.run.next(function() { Em.run.next(function() {
return Discourse.routeTo(result.url); Discourse.URL.routeTo(result.url);
}); });
} else { } else {
_this.flash(Em.String.i18n('topic.move_selected.error')); _this.flash(Em.String.i18n('topic.move_selected.error'));

View file

@ -139,13 +139,10 @@ window.Discourse.SearchView = Discourse.View.extend({
}, },
select: function() { select: function() {
var href; if (this.get('loading')) return;
if (this.get('loading')) { var href = $('#search-dropdown li.selected a').prop('href');
return;
}
href = $('#search-dropdown li.selected a').prop('href');
if (href) { if (href) {
Discourse.routeTo(href); Discourse.URL.routeTo(href);
} }
return false; return false;
} }

View file

@ -84,7 +84,7 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
postUrl += "/best_of"; postUrl += "/best_of";
} }
} }
Discourse.replaceState(postUrl); Discourse.URL.replaceState(postUrl);
// Show appropriate jump tools // Show appropriate jump tools
if (current === 1) { if (current === 1) {
@ -441,10 +441,12 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
this.docAt = title.offset().top; this.docAt = title.offset().top;
} }
} }
var headerController = this.get('controller.controllers.header');
if (this.docAt) { if (this.docAt) {
this.set('controller.showExtraHeaderInfo', offset >= this.docAt || !firstLoaded); headerController.set('showExtraInfo', offset >= this.docAt || !firstLoaded);
} else { } else {
this.set('controller.showExtraHeaderInfo', !firstLoaded); headerController.set('showExtraInfo', !firstLoaded);
} }
// there is a whole bunch of caching we could add here // there is a whole bunch of caching we could add here

View file

@ -1,5 +1,5 @@
// Version: v1.0.0-pre.2-723-g052062c // Version: v1.0.0-pre.2-756-gb26f1f0
// Last commit: 052062c (2013-02-18 19:32:17 -0800) // Last commit: b26f1f0 (2013-02-26 09:03:26 -0800)
(function() { (function() {
@ -150,8 +150,8 @@ Ember.deprecateFunc = function(message, func) {
})(); })();
// Version: v1.0.0-pre.2-723-g052062c // Version: v1.0.0-pre.2-756-gb26f1f0
// Last commit: 052062c (2013-02-18 19:32:17 -0800) // Last commit: b26f1f0 (2013-02-26 09:03:26 -0800)
(function() { (function() {
@ -297,6 +297,15 @@ Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !
*/ */
Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES; Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
/**
Determines whether Ember logs info about version of used libraries
@property LOG_VERSION
@type Boolean
@default true
*/
Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true;
/** /**
Empty function. Useful for some operations. Empty function. Useful for some operations.
@ -6123,7 +6132,6 @@ define("container",
register: function(type, name, factory, options) { register: function(type, name, factory, options) {
var fullName; var fullName;
if (type.indexOf(':') !== -1){ if (type.indexOf(':') !== -1){
options = factory; options = factory;
factory = name; factory = name;
@ -6133,15 +6141,23 @@ define("container",
fullName = type + ":" + name; fullName = type + ":" + name;
} }
this.registry.set(fullName, factory); var normalizedName = this.normalize(fullName);
this._options.set(fullName, options || {});
this.registry.set(normalizedName, factory);
this._options.set(normalizedName, options || {});
}, },
resolve: function(fullName) { resolve: function(fullName) {
return this.resolver(fullName) || this.registry.get(fullName); return this.resolver(fullName) || this.registry.get(fullName);
}, },
normalize: function(fullName) {
return fullName;
},
lookup: function(fullName, options) { lookup: function(fullName, options) {
fullName = this.normalize(fullName);
options = options || {}; options = options || {};
if (this.cache.has(fullName) && options.singleton !== false) { if (this.cache.has(fullName) && options.singleton !== false) {
@ -6270,7 +6286,8 @@ define("container",
} }
function factoryFor(container, fullName) { function factoryFor(container, fullName) {
return container.resolve(fullName); var name = container.normalize(fullName);
return container.resolve(name);
} }
function instantiate(container, fullName) { function instantiate(container, fullName) {
@ -6745,6 +6762,20 @@ Ember.Error.prototype = Ember.create(Error.prototype);
(function() {
/**
Expose RSVP implementation
@class RSVP
@namespace Ember
@constructor
*/
Ember.RSVP = requireModule('rsvp');
})();
(function() { (function() {
/** /**
@module ember @module ember
@ -11125,40 +11156,8 @@ Ember.Mixin.prototype.toString = classToString;
(function() { (function() {
/**
@module ember
@submodule ember-runtime
*/
/**
Defines a namespace that will contain an executable application. This is
very similar to a normal namespace except that it is expected to include at
least a 'ready' function which can be run to initialize the application.
Currently `Ember.Application` is very similar to `Ember.Namespace.` However,
this class may be augmented by additional frameworks so it is important to
use this instance when building new applications.
# Example Usage
```javascript
MyApp = Ember.Application.create({
VERSION: '1.0.0',
store: Ember.Store.create().from(Ember.fixtures)
});
MyApp.ready = function() {
//..init code goes here...
}
```
@class Application
@namespace Ember
@extends Ember.Namespace
*/
Ember.Application = Ember.Namespace.extend(); Ember.Application = Ember.Namespace.extend();
})(); })();
@ -11544,6 +11543,25 @@ Ember.ObjectProxy = Ember.Object.extend(
} }
}); });
Ember.ObjectProxy.reopenClass({
create: function () {
var mixin, prototype, i, l, properties, keyName;
if (arguments.length) {
prototype = this.proto();
for (i = 0, l = arguments.length; i < l; i++) {
properties = arguments[i];
for (keyName in properties) {
if (!properties.hasOwnProperty(keyName) || keyName in prototype) { continue; }
if (!mixin) mixin = {};
mixin[keyName] = null;
}
}
if (mixin) this._initMixins([mixin]);
}
return this._super.apply(this, arguments);
}
});
})(); })();
@ -11864,7 +11882,7 @@ if (ignore.length>0) {
/** /**
The NativeArray mixin contains the properties needed to to make the native The NativeArray mixin contains the properties needed to to make the native
Array support Ember.MutableArray and all of its dependent APIs. Unless you Array support Ember.MutableArray and all of its dependent APIs. Unless you
have `Ember.EXTEND_PROTOTYPES or `Ember.EXTEND_PROTOTYPES.Array` set to have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to
false, this will be applied automatically. Otherwise you can apply the mixin false, this will be applied automatically. Otherwise you can apply the mixin
at anytime by calling `Ember.NativeArray.activate`. at anytime by calling `Ember.NativeArray.activate`.
@ -12440,7 +12458,8 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
objectAtContent: function(idx) { objectAtContent: function(idx) {
var length = get(this, 'length'), var length = get(this, 'length'),
object = get(this,'arrangedContent').objectAt(idx); arrangedContent = get(this,'arrangedContent'),
object = arrangedContent && arrangedContent.objectAt(idx);
if (idx >= 0 && idx < length) { if (idx >= 0 && idx < length) {
var controllerClass = this.lookupItemController(object); var controllerClass = this.lookupItemController(object);
@ -12591,7 +12610,7 @@ Ember.$ = jQuery;
@module ember @module ember
@submodule ember-views @submodule ember-views
*/ */
if (Ember.$) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend'); var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
@ -12600,6 +12619,7 @@ var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover dro
Ember.EnumerableUtils.forEach(dragEvents, function(eventName) { Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] }; Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
}); });
}
})(); })();
@ -12616,7 +12636,8 @@ Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
// Internet Explorer prior to 9 does not allow setting innerHTML if the first element // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
// is a "zero-scope" element. This problem can be worked around by making // is a "zero-scope" element. This problem can be worked around by making
// the first node an invisible text node. We, like Modernizr, use &shy; // the first node an invisible text node. We, like Modernizr, use &shy;
var needsShy = (function(){
var needsShy = this.document && (function(){
var testEl = document.createElement('div'); var testEl = document.createElement('div');
testEl.innerHTML = "<div></div>"; testEl.innerHTML = "<div></div>";
testEl.firstChild.innerHTML = "<script></script>"; testEl.firstChild.innerHTML = "<script></script>";
@ -12626,7 +12647,7 @@ var needsShy = (function(){
// IE 8 (and likely earlier) likes to move whitespace preceeding // IE 8 (and likely earlier) likes to move whitespace preceeding
// a script tag to appear after it. This means that we can // a script tag to appear after it. This means that we can
// accidentally remove whitespace when updating a morph. // accidentally remove whitespace when updating a morph.
var movesWhitespace = (function() { var movesWhitespace = this.document && (function() {
var testEl = document.createElement('div'); var testEl = document.createElement('div');
testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value"; testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
return testEl.childNodes[0].nodeValue === 'Test:' && return testEl.childNodes[0].nodeValue === 'Test:' &&
@ -13297,7 +13318,7 @@ Ember.EventDispatcher = Ember.Object.extend(
setup: function(addedEvents) { setup: function(addedEvents) {
var event, events = { var event, events = {
touchstart : 'touchStart', touchstart : 'touchStart',
// touchmove : 'touchMove', touchmove : 'touchMove',
touchend : 'touchEnd', touchend : 'touchEnd',
touchcancel : 'touchCancel', touchcancel : 'touchCancel',
keydown : 'keyDown', keydown : 'keyDown',
@ -13308,8 +13329,7 @@ Ember.EventDispatcher = Ember.Object.extend(
contextmenu : 'contextMenu', contextmenu : 'contextMenu',
click : 'click', click : 'click',
dblclick : 'doubleClick', dblclick : 'doubleClick',
// https://github.com/emberjs/ember.js/pull/2148 mousemove : 'mouseMove',
// mousemove : 'mouseMove',
focusin : 'focusIn', focusin : 'focusIn',
focusout : 'focusOut', focusout : 'focusOut',
mouseenter : 'mouseEnter', mouseenter : 'mouseEnter',
@ -13459,8 +13479,9 @@ Ember.EventDispatcher = Ember.Object.extend(
// Add a new named queue for rendering views that happens // Add a new named queue for rendering views that happens
// after bindings have synced, and a queue for scheduling actions // after bindings have synced, and a queue for scheduling actions
// that that should occur after view rendering. // that that should occur after view rendering.
var queues = Ember.run.queues; var queues = Ember.run.queues,
queues.splice(Ember.$.inArray('actions', queues)+1, 0, 'render', 'afterRender'); indexOf = Ember.ArrayPolyfills.indexOf;
queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender');
})(); })();
@ -15706,17 +15727,24 @@ Ember.View = Ember.CoreView.extend(
// once the view has been inserted into the DOM, legal manipulations // once the view has been inserted into the DOM, legal manipulations
// are done on the DOM element. // are done on the DOM element.
function notifyMutationListeners() {
Ember.run.once(Ember.View, 'notifyMutationListeners');
}
var DOMManager = { var DOMManager = {
prepend: function(view, html) { prepend: function(view, html) {
view.$().prepend(html); view.$().prepend(html);
notifyMutationListeners();
}, },
after: function(view, html) { after: function(view, html) {
view.$().after(html); view.$().after(html);
notifyMutationListeners();
}, },
html: function(view, html) { html: function(view, html) {
view.$().html(html); view.$().html(html);
notifyMutationListeners();
}, },
replace: function(view) { replace: function(view) {
@ -15726,15 +15754,18 @@ var DOMManager = {
view._insertElementLater(function() { view._insertElementLater(function() {
Ember.$(element).replaceWith(get(view, 'element')); Ember.$(element).replaceWith(get(view, 'element'));
notifyMutationListeners();
}); });
}, },
remove: function(view) { remove: function(view) {
view.$().remove(); view.$().remove();
notifyMutationListeners();
}, },
empty: function(view) { empty: function(view) {
view.$().empty(); view.$().empty();
notifyMutationListeners();
} }
}; };
@ -15849,6 +15880,20 @@ Ember.View.reopenClass({
} }
}); });
var mutation = Ember.Object.extend(Ember.Evented).create();
Ember.View.addMutationListener = function(callback) {
mutation.on('change', callback);
};
Ember.View.removeMutationListener = function(callback) {
mutation.off('change', callback);
};
Ember.View.notifyMutationListeners = function() {
mutation.trigger('change');
};
/** /**
Global views hash Global views hash
@ -17008,15 +17053,15 @@ define("metamorph",
var K = function(){}, var K = function(){},
guid = 0, guid = 0,
document = window.document, document = this.document,
// Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, supportsRange = document && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
// Internet Explorer prior to 9 does not allow setting innerHTML if the first element // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
// is a "zero-scope" element. This problem can be worked around by making // is a "zero-scope" element. This problem can be worked around by making
// the first node an invisible text node. We, like Modernizr, use &shy; // the first node an invisible text node. We, like Modernizr, use &shy;
needsShy = (function(){ needsShy = document && (function(){
var testEl = document.createElement('div'); var testEl = document.createElement('div');
testEl.innerHTML = "<div></div>"; testEl.innerHTML = "<div></div>";
testEl.firstChild.innerHTML = "<script></script>"; testEl.firstChild.innerHTML = "<script></script>";
@ -17027,7 +17072,7 @@ define("metamorph",
// IE 8 (and likely earlier) likes to move whitespace preceeding // IE 8 (and likely earlier) likes to move whitespace preceeding
// a script tag to appear after it. This means that we can // a script tag to appear after it. This means that we can
// accidentally remove whitespace when updating a morph. // accidentally remove whitespace when updating a morph.
movesWhitespace = (function() { movesWhitespace = document && (function() {
var testEl = document.createElement('div'); var testEl = document.createElement('div');
testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value"; testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
return testEl.childNodes[0].nodeValue === 'Test:' && return testEl.childNodes[0].nodeValue === 'Test:' &&
@ -17471,7 +17516,11 @@ var objectCreate = Object.create || function(parent) {
return new F(); return new F();
}; };
var Handlebars = this.Handlebars || Ember.imports.Handlebars; var Handlebars = this.Handlebars || (Ember.imports && Ember.imports.Handlebars);
if(!Handlebars && typeof require === 'function') {
Handlebars = require('handlebars');
}
Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.[0-9](\.rc\.[23456789]+)?/)); Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.[0-9](\.rc\.[23456789]+)?/));
/** /**
@ -18131,22 +18180,30 @@ Ember.Handlebars.resolvePaths = function(options) {
var set = Ember.set, get = Ember.get; var set = Ember.set, get = Ember.get;
var Metamorph = requireModule('metamorph'); var Metamorph = requireModule('metamorph');
function notifyMutationListeners() {
Ember.run.once(Ember.View, 'notifyMutationListeners');
}
// DOMManager should just abstract dom manipulation between jquery and metamorph // DOMManager should just abstract dom manipulation between jquery and metamorph
var DOMManager = { var DOMManager = {
remove: function(view) { remove: function(view) {
view.morph.remove(); view.morph.remove();
notifyMutationListeners();
}, },
prepend: function(view, html) { prepend: function(view, html) {
view.morph.prepend(html); view.morph.prepend(html);
notifyMutationListeners();
}, },
after: function(view, html) { after: function(view, html) {
view.morph.after(html); view.morph.after(html);
notifyMutationListeners();
}, },
html: function(view, html) { html: function(view, html) {
view.morph.html(html); view.morph.html(html);
notifyMutationListeners();
}, },
// This is messed up. // This is messed up.
@ -18169,11 +18226,13 @@ var DOMManager = {
morph.replaceWith(buffer.string()); morph.replaceWith(buffer.string());
view.transitionTo('inDOM'); view.transitionTo('inDOM');
view.triggerRecursively('didInsertElement'); view.triggerRecursively('didInsertElement');
notifyMutationListeners();
}); });
}, },
empty: function(view) { empty: function(view) {
view.morph.html(""); view.morph.html("");
notifyMutationListeners();
} }
}; };
@ -23555,6 +23614,34 @@ function teardownView(route) {
(function() {
Ember.onLoad('Ember.Handlebars', function() {
var handlebarsResolve = Ember.Handlebars.resolveParams,
map = Ember.ArrayPolyfills.map,
get = Ember.get;
function resolveParams(context, params, options) {
var resolved = handlebarsResolve(context, params, options);
return map.call(resolved, unwrap);
function unwrap(object, i) {
if (params[i] === 'controller') { return object; }
if (Ember.ControllerMixin.detect(object)) {
return unwrap(get(object, 'model'));
} else {
return object;
}
}
}
Ember.Router.resolveParams = resolveParams;
});
})();
(function() { (function() {
/** /**
@module ember @module ember
@ -23564,7 +23651,7 @@ function teardownView(route) {
var get = Ember.get, set = Ember.set; var get = Ember.get, set = Ember.set;
Ember.onLoad('Ember.Handlebars', function(Handlebars) { Ember.onLoad('Ember.Handlebars', function(Handlebars) {
var resolveParams = Ember.Handlebars.resolveParams, var resolveParams = Ember.Router.resolveParams,
isSimpleClick = Ember.ViewUtils.isSimpleClick; isSimpleClick = Ember.ViewUtils.isSimpleClick;
function fullRouteName(router, name) { function fullRouteName(router, name) {
@ -23847,7 +23934,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
*/ */
Ember.onLoad('Ember.Handlebars', function(Handlebars) { Ember.onLoad('Ember.Handlebars', function(Handlebars) {
var resolveParams = Ember.Handlebars.resolveParams, var resolveParams = Ember.Router.resolveParams,
isSimpleClick = Ember.ViewUtils.isSimpleClick; isSimpleClick = Ember.ViewUtils.isSimpleClick;
var EmberHandlebars = Ember.Handlebars, var EmberHandlebars = Ember.Handlebars,
@ -24473,7 +24560,7 @@ Ember.HashLocation = Ember.Object.extend({
set(self, 'lastSetURL', null); set(self, 'lastSetURL', null);
callback(location.hash.substr(1)); callback(path);
}); });
}); });
}, },
@ -24856,6 +24943,9 @@ var get = Ember.get, set = Ember.set,
method. When you are ready for your app to be initialized, call its method. When you are ready for your app to be initialized, call its
`advanceReadiness()` method. `advanceReadiness()` method.
You can define a `ready` method on the `Ember.Application` instance, which
will be run by Ember when the application is initialized.
Because `Ember.Application` inherits from `Ember.Namespace`, any classes Because `Ember.Application` inherits from `Ember.Namespace`, any classes
you create will have useful string representations when calling `toString()`. you create will have useful string representations when calling `toString()`.
See the `Ember.Namespace` documentation for more information. See the `Ember.Namespace` documentation for more information.
@ -25050,11 +25140,13 @@ var Application = Ember.Application = Ember.Namespace.extend({
this.scheduleInitialize(); this.scheduleInitialize();
} }
if ( Ember.LOG_VERSION ) {
Ember.debug('-------------------------------'); Ember.debug('-------------------------------');
Ember.debug('Ember.VERSION : ' + Ember.VERSION); Ember.debug('Ember.VERSION : ' + Ember.VERSION);
Ember.debug('Handlebars.VERSION : ' + Ember.Handlebars.VERSION); Ember.debug('Handlebars.VERSION : ' + Ember.Handlebars.VERSION);
Ember.debug('jQuery.VERSION : ' + Ember.$().jquery); Ember.debug('jQuery.VERSION : ' + Ember.$().jquery);
Ember.debug('-------------------------------'); Ember.debug('-------------------------------');
}
}, },
/** /**
@ -25414,6 +25506,7 @@ Ember.Application.reopenClass({
Ember.Container.defaultContainer = Ember.Container.defaultContainer || container; Ember.Container.defaultContainer = Ember.Container.defaultContainer || container;
container.set = Ember.set; container.set = Ember.set;
container.normalize = normalize;
container.resolver = resolverFor(namespace); container.resolver = resolverFor(namespace);
container.optionsForType('view', { singleton: false }); container.optionsForType('view', { singleton: false });
container.optionsForType('template', { instantiate: false }); container.optionsForType('template', { instantiate: false });
@ -25453,6 +25546,7 @@ function resolverFor(namespace) {
if (type === 'template') { if (type === 'template') {
var templateName = name.replace(/\./g, '/'); var templateName = name.replace(/\./g, '/');
if (Ember.TEMPLATES[templateName]) { if (Ember.TEMPLATES[templateName]) {
return Ember.TEMPLATES[templateName]; return Ember.TEMPLATES[templateName];
} }
@ -25483,9 +25577,31 @@ function resolverFor(namespace) {
}; };
} }
Ember.runLoadHooks('Ember.Application', Ember.Application); function normalize(fullName) {
var split = fullName.split(':'),
type = split[0],
name = split[1];
if (type !== 'template') {
var result = name;
if (result.indexOf('.') > -1) {
result = result.replace(/\.(.)/g, function(m) { return m[1].toUpperCase(); });
}
if (name.indexOf('_') > -1) {
result = result.replace(/_(.)/g, function(m) { return m[1].toUpperCase(); });
}
return type + ':' + result;
} else {
return fullName;
}
}
Ember.runLoadHooks('Ember.Application', Ember.Application);
})(); })();
@ -26831,8 +26947,8 @@ Ember States
})(); })();
// Version: v1.0.0-pre.2-723-g052062c // Version: v1.0.0-pre.2-756-gb26f1f0
// Last commit: 052062c (2013-02-18 19:32:17 -0800) // Last commit: b26f1f0 (2013-02-26 09:03:26 -0800)
(function() { (function() {

View file

@ -141,6 +141,15 @@ Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !
*/ */
Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES; Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
/**
Determines whether Ember logs info about version of used libraries
@property LOG_VERSION
@type Boolean
@default true
*/
Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true;
/** /**
Empty function. Useful for some operations. Empty function. Useful for some operations.
@ -5963,7 +5972,6 @@ define("container",
register: function(type, name, factory, options) { register: function(type, name, factory, options) {
var fullName; var fullName;
if (type.indexOf(':') !== -1){ if (type.indexOf(':') !== -1){
options = factory; options = factory;
factory = name; factory = name;
@ -5973,15 +5981,23 @@ define("container",
fullName = type + ":" + name; fullName = type + ":" + name;
} }
this.registry.set(fullName, factory); var normalizedName = this.normalize(fullName);
this._options.set(fullName, options || {});
this.registry.set(normalizedName, factory);
this._options.set(normalizedName, options || {});
}, },
resolve: function(fullName) { resolve: function(fullName) {
return this.resolver(fullName) || this.registry.get(fullName); return this.resolver(fullName) || this.registry.get(fullName);
}, },
normalize: function(fullName) {
return fullName;
},
lookup: function(fullName, options) { lookup: function(fullName, options) {
fullName = this.normalize(fullName);
options = options || {}; options = options || {};
if (this.cache.has(fullName) && options.singleton !== false) { if (this.cache.has(fullName) && options.singleton !== false) {
@ -6110,7 +6126,8 @@ define("container",
} }
function factoryFor(container, fullName) { function factoryFor(container, fullName) {
return container.resolve(fullName); var name = container.normalize(fullName);
return container.resolve(name);
} }
function instantiate(container, fullName) { function instantiate(container, fullName) {
@ -6584,6 +6601,20 @@ Ember.Error.prototype = Ember.create(Error.prototype);
(function() {
/**
Expose RSVP implementation
@class RSVP
@namespace Ember
@constructor
*/
Ember.RSVP = requireModule('rsvp');
})();
(function() { (function() {
/** /**
@module ember @module ember
@ -10962,40 +10993,8 @@ Ember.Mixin.prototype.toString = classToString;
(function() { (function() {
/**
@module ember
@submodule ember-runtime
*/
/**
Defines a namespace that will contain an executable application. This is
very similar to a normal namespace except that it is expected to include at
least a 'ready' function which can be run to initialize the application.
Currently `Ember.Application` is very similar to `Ember.Namespace.` However,
this class may be augmented by additional frameworks so it is important to
use this instance when building new applications.
# Example Usage
```javascript
MyApp = Ember.Application.create({
VERSION: '1.0.0',
store: Ember.Store.create().from(Ember.fixtures)
});
MyApp.ready = function() {
//..init code goes here...
}
```
@class Application
@namespace Ember
@extends Ember.Namespace
*/
Ember.Application = Ember.Namespace.extend(); Ember.Application = Ember.Namespace.extend();
})(); })();
@ -11379,6 +11378,25 @@ Ember.ObjectProxy = Ember.Object.extend(
} }
}); });
Ember.ObjectProxy.reopenClass({
create: function () {
var mixin, prototype, i, l, properties, keyName;
if (arguments.length) {
prototype = this.proto();
for (i = 0, l = arguments.length; i < l; i++) {
properties = arguments[i];
for (keyName in properties) {
if (!properties.hasOwnProperty(keyName) || keyName in prototype) { continue; }
if (!mixin) mixin = {};
mixin[keyName] = null;
}
}
if (mixin) this._initMixins([mixin]);
}
return this._super.apply(this, arguments);
}
});
})(); })();
@ -11699,7 +11717,7 @@ if (ignore.length>0) {
/** /**
The NativeArray mixin contains the properties needed to to make the native The NativeArray mixin contains the properties needed to to make the native
Array support Ember.MutableArray and all of its dependent APIs. Unless you Array support Ember.MutableArray and all of its dependent APIs. Unless you
have `Ember.EXTEND_PROTOTYPES or `Ember.EXTEND_PROTOTYPES.Array` set to have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to
false, this will be applied automatically. Otherwise you can apply the mixin false, this will be applied automatically. Otherwise you can apply the mixin
at anytime by calling `Ember.NativeArray.activate`. at anytime by calling `Ember.NativeArray.activate`.
@ -12274,7 +12292,8 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
objectAtContent: function(idx) { objectAtContent: function(idx) {
var length = get(this, 'length'), var length = get(this, 'length'),
object = get(this,'arrangedContent').objectAt(idx); arrangedContent = get(this,'arrangedContent'),
object = arrangedContent && arrangedContent.objectAt(idx);
if (idx >= 0 && idx < length) { if (idx >= 0 && idx < length) {
var controllerClass = this.lookupItemController(object); var controllerClass = this.lookupItemController(object);
@ -12425,7 +12444,7 @@ Ember.$ = jQuery;
@module ember @module ember
@submodule ember-views @submodule ember-views
*/ */
if (Ember.$) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend'); var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
@ -12434,6 +12453,7 @@ var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover dro
Ember.EnumerableUtils.forEach(dragEvents, function(eventName) { Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] }; Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
}); });
}
})(); })();
@ -12450,7 +12470,8 @@ Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
// Internet Explorer prior to 9 does not allow setting innerHTML if the first element // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
// is a "zero-scope" element. This problem can be worked around by making // is a "zero-scope" element. This problem can be worked around by making
// the first node an invisible text node. We, like Modernizr, use &shy; // the first node an invisible text node. We, like Modernizr, use &shy;
var needsShy = (function(){
var needsShy = this.document && (function(){
var testEl = document.createElement('div'); var testEl = document.createElement('div');
testEl.innerHTML = "<div></div>"; testEl.innerHTML = "<div></div>";
testEl.firstChild.innerHTML = "<script></script>"; testEl.firstChild.innerHTML = "<script></script>";
@ -12460,7 +12481,7 @@ var needsShy = (function(){
// IE 8 (and likely earlier) likes to move whitespace preceeding // IE 8 (and likely earlier) likes to move whitespace preceeding
// a script tag to appear after it. This means that we can // a script tag to appear after it. This means that we can
// accidentally remove whitespace when updating a morph. // accidentally remove whitespace when updating a morph.
var movesWhitespace = (function() { var movesWhitespace = this.document && (function() {
var testEl = document.createElement('div'); var testEl = document.createElement('div');
testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value"; testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
return testEl.childNodes[0].nodeValue === 'Test:' && return testEl.childNodes[0].nodeValue === 'Test:' &&
@ -13131,7 +13152,7 @@ Ember.EventDispatcher = Ember.Object.extend(
setup: function(addedEvents) { setup: function(addedEvents) {
var event, events = { var event, events = {
touchstart : 'touchStart', touchstart : 'touchStart',
// touchmove : 'touchMove', touchmove : 'touchMove',
touchend : 'touchEnd', touchend : 'touchEnd',
touchcancel : 'touchCancel', touchcancel : 'touchCancel',
keydown : 'keyDown', keydown : 'keyDown',
@ -13142,7 +13163,7 @@ Ember.EventDispatcher = Ember.Object.extend(
contextmenu : 'contextMenu', contextmenu : 'contextMenu',
click : 'click', click : 'click',
dblclick : 'doubleClick', dblclick : 'doubleClick',
// mousemove : 'mouseMove', mousemove : 'mouseMove',
focusin : 'focusIn', focusin : 'focusIn',
focusout : 'focusOut', focusout : 'focusOut',
mouseenter : 'mouseEnter', mouseenter : 'mouseEnter',
@ -13290,8 +13311,9 @@ Ember.EventDispatcher = Ember.Object.extend(
// Add a new named queue for rendering views that happens // Add a new named queue for rendering views that happens
// after bindings have synced, and a queue for scheduling actions // after bindings have synced, and a queue for scheduling actions
// that that should occur after view rendering. // that that should occur after view rendering.
var queues = Ember.run.queues; var queues = Ember.run.queues,
queues.splice(Ember.$.inArray('actions', queues)+1, 0, 'render', 'afterRender'); indexOf = Ember.ArrayPolyfills.indexOf;
queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender');
})(); })();
@ -15530,17 +15552,24 @@ Ember.View = Ember.CoreView.extend(
// once the view has been inserted into the DOM, legal manipulations // once the view has been inserted into the DOM, legal manipulations
// are done on the DOM element. // are done on the DOM element.
function notifyMutationListeners() {
Ember.run.once(Ember.View, 'notifyMutationListeners');
}
var DOMManager = { var DOMManager = {
prepend: function(view, html) { prepend: function(view, html) {
view.$().prepend(html); view.$().prepend(html);
notifyMutationListeners();
}, },
after: function(view, html) { after: function(view, html) {
view.$().after(html); view.$().after(html);
notifyMutationListeners();
}, },
html: function(view, html) { html: function(view, html) {
view.$().html(html); view.$().html(html);
notifyMutationListeners();
}, },
replace: function(view) { replace: function(view) {
@ -15550,15 +15579,18 @@ var DOMManager = {
view._insertElementLater(function() { view._insertElementLater(function() {
Ember.$(element).replaceWith(get(view, 'element')); Ember.$(element).replaceWith(get(view, 'element'));
notifyMutationListeners();
}); });
}, },
remove: function(view) { remove: function(view) {
view.$().remove(); view.$().remove();
notifyMutationListeners();
}, },
empty: function(view) { empty: function(view) {
view.$().empty(); view.$().empty();
notifyMutationListeners();
} }
}; };
@ -15673,6 +15705,20 @@ Ember.View.reopenClass({
} }
}); });
var mutation = Ember.Object.extend(Ember.Evented).create();
Ember.View.addMutationListener = function(callback) {
mutation.on('change', callback);
};
Ember.View.removeMutationListener = function(callback) {
mutation.off('change', callback);
};
Ember.View.notifyMutationListeners = function() {
mutation.trigger('change');
};
/** /**
Global views hash Global views hash
@ -16831,15 +16877,15 @@ define("metamorph",
var K = function(){}, var K = function(){},
guid = 0, guid = 0,
document = window.document, document = this.document,
// Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, supportsRange = document && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
// Internet Explorer prior to 9 does not allow setting innerHTML if the first element // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
// is a "zero-scope" element. This problem can be worked around by making // is a "zero-scope" element. This problem can be worked around by making
// the first node an invisible text node. We, like Modernizr, use &shy; // the first node an invisible text node. We, like Modernizr, use &shy;
needsShy = (function(){ needsShy = document && (function(){
var testEl = document.createElement('div'); var testEl = document.createElement('div');
testEl.innerHTML = "<div></div>"; testEl.innerHTML = "<div></div>";
testEl.firstChild.innerHTML = "<script></script>"; testEl.firstChild.innerHTML = "<script></script>";
@ -16850,7 +16896,7 @@ define("metamorph",
// IE 8 (and likely earlier) likes to move whitespace preceeding // IE 8 (and likely earlier) likes to move whitespace preceeding
// a script tag to appear after it. This means that we can // a script tag to appear after it. This means that we can
// accidentally remove whitespace when updating a morph. // accidentally remove whitespace when updating a morph.
movesWhitespace = (function() { movesWhitespace = document && (function() {
var testEl = document.createElement('div'); var testEl = document.createElement('div');
testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value"; testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
return testEl.childNodes[0].nodeValue === 'Test:' && return testEl.childNodes[0].nodeValue === 'Test:' &&
@ -17294,7 +17340,10 @@ var objectCreate = Object.create || function(parent) {
return new F(); return new F();
}; };
var Handlebars = this.Handlebars || Ember.imports.Handlebars; var Handlebars = this.Handlebars || (Ember.imports && Ember.imports.Handlebars);
if(!Handlebars && typeof require === 'function') {
Handlebars = require('handlebars');
}
/** /**
@ -17953,22 +18002,30 @@ Ember.Handlebars.resolvePaths = function(options) {
var set = Ember.set, get = Ember.get; var set = Ember.set, get = Ember.get;
var Metamorph = requireModule('metamorph'); var Metamorph = requireModule('metamorph');
function notifyMutationListeners() {
Ember.run.once(Ember.View, 'notifyMutationListeners');
}
// DOMManager should just abstract dom manipulation between jquery and metamorph // DOMManager should just abstract dom manipulation between jquery and metamorph
var DOMManager = { var DOMManager = {
remove: function(view) { remove: function(view) {
view.morph.remove(); view.morph.remove();
notifyMutationListeners();
}, },
prepend: function(view, html) { prepend: function(view, html) {
view.morph.prepend(html); view.morph.prepend(html);
notifyMutationListeners();
}, },
after: function(view, html) { after: function(view, html) {
view.morph.after(html); view.morph.after(html);
notifyMutationListeners();
}, },
html: function(view, html) { html: function(view, html) {
view.morph.html(html); view.morph.html(html);
notifyMutationListeners();
}, },
// This is messed up. // This is messed up.
@ -17991,11 +18048,13 @@ var DOMManager = {
morph.replaceWith(buffer.string()); morph.replaceWith(buffer.string());
view.transitionTo('inDOM'); view.transitionTo('inDOM');
view.triggerRecursively('didInsertElement'); view.triggerRecursively('didInsertElement');
notifyMutationListeners();
}); });
}, },
empty: function(view) { empty: function(view) {
view.morph.html(""); view.morph.html("");
notifyMutationListeners();
} }
}; };
@ -23362,6 +23421,34 @@ function teardownView(route) {
(function() {
Ember.onLoad('Ember.Handlebars', function() {
var handlebarsResolve = Ember.Handlebars.resolveParams,
map = Ember.ArrayPolyfills.map,
get = Ember.get;
function resolveParams(context, params, options) {
var resolved = handlebarsResolve(context, params, options);
return map.call(resolved, unwrap);
function unwrap(object, i) {
if (params[i] === 'controller') { return object; }
if (Ember.ControllerMixin.detect(object)) {
return unwrap(get(object, 'model'));
} else {
return object;
}
}
}
Ember.Router.resolveParams = resolveParams;
});
})();
(function() { (function() {
/** /**
@module ember @module ember
@ -23371,7 +23458,7 @@ function teardownView(route) {
var get = Ember.get, set = Ember.set; var get = Ember.get, set = Ember.set;
Ember.onLoad('Ember.Handlebars', function(Handlebars) { Ember.onLoad('Ember.Handlebars', function(Handlebars) {
var resolveParams = Ember.Handlebars.resolveParams, var resolveParams = Ember.Router.resolveParams,
isSimpleClick = Ember.ViewUtils.isSimpleClick; isSimpleClick = Ember.ViewUtils.isSimpleClick;
function fullRouteName(router, name) { function fullRouteName(router, name) {
@ -23652,7 +23739,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
*/ */
Ember.onLoad('Ember.Handlebars', function(Handlebars) { Ember.onLoad('Ember.Handlebars', function(Handlebars) {
var resolveParams = Ember.Handlebars.resolveParams, var resolveParams = Ember.Router.resolveParams,
isSimpleClick = Ember.ViewUtils.isSimpleClick; isSimpleClick = Ember.ViewUtils.isSimpleClick;
var EmberHandlebars = Ember.Handlebars, var EmberHandlebars = Ember.Handlebars,
@ -24277,7 +24364,7 @@ Ember.HashLocation = Ember.Object.extend({
set(self, 'lastSetURL', null); set(self, 'lastSetURL', null);
callback(location.hash.substr(1)); callback(path);
}); });
}); });
}, },
@ -24660,6 +24747,9 @@ var get = Ember.get, set = Ember.set,
method. When you are ready for your app to be initialized, call its method. When you are ready for your app to be initialized, call its
`advanceReadiness()` method. `advanceReadiness()` method.
You can define a `ready` method on the `Ember.Application` instance, which
will be run by Ember when the application is initialized.
Because `Ember.Application` inherits from `Ember.Namespace`, any classes Because `Ember.Application` inherits from `Ember.Namespace`, any classes
you create will have useful string representations when calling `toString()`. you create will have useful string representations when calling `toString()`.
See the `Ember.Namespace` documentation for more information. See the `Ember.Namespace` documentation for more information.
@ -24854,10 +24944,13 @@ var Application = Ember.Application = Ember.Namespace.extend({
this.scheduleInitialize(); this.scheduleInitialize();
} }
if ( Ember.LOG_VERSION ) {
}
}, },
/** /**
@ -25216,6 +25309,7 @@ Ember.Application.reopenClass({
Ember.Container.defaultContainer = Ember.Container.defaultContainer || container; Ember.Container.defaultContainer = Ember.Container.defaultContainer || container;
container.set = Ember.set; container.set = Ember.set;
container.normalize = normalize;
container.resolver = resolverFor(namespace); container.resolver = resolverFor(namespace);
container.optionsForType('view', { singleton: false }); container.optionsForType('view', { singleton: false });
container.optionsForType('template', { instantiate: false }); container.optionsForType('template', { instantiate: false });
@ -25255,6 +25349,7 @@ function resolverFor(namespace) {
if (type === 'template') { if (type === 'template') {
var templateName = name.replace(/\./g, '/'); var templateName = name.replace(/\./g, '/');
if (Ember.TEMPLATES[templateName]) { if (Ember.TEMPLATES[templateName]) {
return Ember.TEMPLATES[templateName]; return Ember.TEMPLATES[templateName];
} }
@ -25284,9 +25379,31 @@ function resolverFor(namespace) {
}; };
} }
Ember.runLoadHooks('Ember.Application', Ember.Application); function normalize(fullName) {
var split = fullName.split(':'),
type = split[0],
name = split[1];
if (type !== 'template') {
var result = name;
if (result.indexOf('.') > -1) {
result = result.replace(/\.(.)/g, function(m) { return m[1].toUpperCase(); });
}
if (name.indexOf('_') > -1) {
result = result.replace(/_(.)/g, function(m) { return m[1].toUpperCase(); });
}
return type + ':' + result;
} else {
return fullName;
}
}
Ember.runLoadHooks('Ember.Application', Ember.Application);
})(); })();