2013-02-21 02:15:50 +08:00
|
|
|
/*global Modernizr:true*/
|
|
|
|
(function() {
|
|
|
|
var csrf_token;
|
|
|
|
|
|
|
|
window.Discourse = Ember.Application.createWithMixins({
|
|
|
|
rootElement: '#main',
|
|
|
|
|
|
|
|
// Data we want to remember for a short period
|
|
|
|
transient: Em.Object.create(),
|
|
|
|
|
|
|
|
hasFocus: true,
|
|
|
|
scrolling: false,
|
|
|
|
|
|
|
|
// The highest seen post number by topic
|
|
|
|
highestSeenByTopic: {},
|
|
|
|
|
|
|
|
logoSmall: (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;
|
|
|
|
title = "";
|
|
|
|
if (this.get('title')) {
|
|
|
|
title += "" + (this.get('title')) + " - ";
|
|
|
|
}
|
|
|
|
title += Discourse.SiteSettings.title;
|
|
|
|
jQuery('title').text(title);
|
|
|
|
if (!this.get('hasFocus') && this.get('notify')) {
|
|
|
|
title = "(*) " + title;
|
|
|
|
}
|
|
|
|
// chrome bug workaround see: http://stackoverflow.com/questions/2952384/changing-the-window-title-when-focussing-the-window-doesnt-work-in-chrome
|
|
|
|
window.setTimeout((function() {
|
|
|
|
document.title = ".";
|
|
|
|
document.title = title;
|
|
|
|
}), 200);
|
|
|
|
}).observes('title', 'hasFocus', 'notify'),
|
|
|
|
|
|
|
|
currentUserChanged: (function() {
|
|
|
|
var bus, user;
|
|
|
|
bus = Discourse.MessageBus;
|
|
|
|
|
2013-02-24 07:23:52 +08:00
|
|
|
// We don't want to receive any previous user notifications
|
|
|
|
bus.unsubscribe("/notification/*");
|
2013-02-21 02:15:50 +08:00
|
|
|
bus.callbackInterval = Discourse.SiteSettings.anon_polling_interval;
|
|
|
|
bus.enableLongPolling = false;
|
|
|
|
user = this.get('currentUser');
|
|
|
|
if (user) {
|
|
|
|
bus.callbackInterval = Discourse.SiteSettings.polling_interval;
|
|
|
|
bus.enableLongPolling = true;
|
|
|
|
if (user.admin) {
|
|
|
|
bus.subscribe("/flagged_counts", function(data) {
|
|
|
|
return user.set('site_flagged_posts_count', data.total);
|
|
|
|
});
|
|
|
|
}
|
2013-02-24 07:23:52 +08:00
|
|
|
return bus.subscribe("/notification/" + user.id, (function(data) {
|
2013-02-21 02:15:50 +08:00
|
|
|
user.set('unread_notifications', data.unread_notifications);
|
|
|
|
return user.set('unread_private_messages', data.unread_private_messages);
|
|
|
|
}), user.notification_channel_position);
|
|
|
|
}
|
|
|
|
}).observes('currentUser'),
|
|
|
|
notifyTitle: function() {
|
|
|
|
return this.set('notify', true);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Browser aware replaceState
|
|
|
|
replaceState: function(path) {
|
|
|
|
if (window.history &&
|
|
|
|
window.history.pushState &&
|
|
|
|
window.history.replaceState &&
|
|
|
|
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)) {
|
|
|
|
if (window.location.pathname !== path) {
|
|
|
|
return history.replaceState({
|
|
|
|
path: path
|
|
|
|
}, null, path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
openComposer: function(opts) {
|
|
|
|
// TODO, remove container link
|
|
|
|
var composer = Discourse.__container__.lookup('controller:composer');
|
|
|
|
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.
|
|
|
|
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);
|
2013-02-24 07:23:52 +08:00
|
|
|
newTopicId = newMatches ? newMatches[2] : null;
|
|
|
|
if (newTopicId) {
|
2013-02-21 02:15:50 +08:00
|
|
|
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'),
|
|
|
|
|
|
|
|
bindDOMEvents: function() {
|
|
|
|
var $html, hasTouch,
|
|
|
|
_this = this;
|
|
|
|
$html = jQuery('html');
|
|
|
|
|
|
|
|
/* Add the discourse touch event */
|
|
|
|
hasTouch = false;
|
|
|
|
if ($html.hasClass('touch')) {
|
|
|
|
hasTouch = true;
|
|
|
|
}
|
|
|
|
if (Modernizr.prefixed("MaxTouchPoints", navigator) > 1) {
|
|
|
|
hasTouch = true;
|
|
|
|
}
|
|
|
|
if (hasTouch) {
|
|
|
|
$html.addClass('discourse-touch');
|
|
|
|
this.touch = true;
|
|
|
|
this.hasTouch = true;
|
|
|
|
} else {
|
|
|
|
$html.addClass('discourse-no-touch');
|
|
|
|
this.touch = false;
|
|
|
|
}
|
|
|
|
jQuery('#main').on('click.discourse', '[data-not-implemented=true]', function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
alert(Em.String.i18n('not_implemented'));
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
jQuery('#main').on('click.discourse', 'a', function(e) {
|
|
|
|
var $currentTarget, href;
|
|
|
|
if (e.isDefaultPrevented() || e.metaKey || e.ctrlKey) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$currentTarget = jQuery(e.currentTarget);
|
|
|
|
href = $currentTarget.attr('href');
|
|
|
|
if (href === void 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (href === '#') {
|
|
|
|
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();
|
|
|
|
_this.routeTo(href);
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
return jQuery(window).focus(function() {
|
|
|
|
_this.set('hasFocus', true);
|
|
|
|
return _this.set('notify', false);
|
|
|
|
}).blur(function() {
|
|
|
|
return _this.set('hasFocus', false);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
logout: function() {
|
|
|
|
var username,
|
|
|
|
_this = this;
|
|
|
|
username = this.get('currentUser.username');
|
|
|
|
Discourse.KeyValueStore.abandonLocal();
|
|
|
|
return jQuery.ajax("/session/" + username, {
|
|
|
|
type: 'DELETE',
|
|
|
|
success: function(result) {
|
|
|
|
/* To keep lots of our variables unbound, we can handle a redirect on logging out.
|
|
|
|
*/
|
|
|
|
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) {
|
2013-02-24 07:23:52 +08:00
|
|
|
var output = f("" + item.k, item.v);
|
|
|
|
if (output) {
|
2013-02-21 02:15:50 +08:00
|
|
|
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) {
|
|
|
|
// TODO, how to dispatch this to the view without the container?
|
|
|
|
var loginView;
|
|
|
|
loginView = Discourse.__container__.lookup('controller:modal').get('currentView');
|
|
|
|
return loginView.authenticationComplete(options);
|
|
|
|
},
|
|
|
|
buildRoutes: function(builder) {
|
|
|
|
var oldBuilder;
|
|
|
|
oldBuilder = Discourse.routeBuilder;
|
|
|
|
Discourse.routeBuilder = function() {
|
|
|
|
if (oldBuilder) {
|
|
|
|
oldBuilder.call(this);
|
|
|
|
}
|
|
|
|
return builder.call(this);
|
2013-02-24 07:23:52 +08:00
|
|
|
};
|
2013-02-21 02:15:50 +08:00
|
|
|
},
|
|
|
|
start: function() {
|
|
|
|
this.bindDOMEvents();
|
|
|
|
Discourse.SiteSettings = PreloadStore.getStatic('siteSettings');
|
|
|
|
Discourse.MessageBus.start();
|
|
|
|
Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus);
|
|
|
|
Discourse.insertProbes();
|
|
|
|
|
|
|
|
// subscribe to any site customizations that are loaded
|
|
|
|
jQuery('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 (!jQuery(stylesheet).data('orig')) {
|
|
|
|
jQuery(stylesheet).data('orig', stylesheet.href);
|
|
|
|
}
|
|
|
|
orig = jQuery(stylesheet).data('orig');
|
|
|
|
sp = orig.split(".css?");
|
|
|
|
stylesheet.href = sp[0] + ".css?" + data;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
jQuery('header.custom').each(function() {
|
|
|
|
var header;
|
|
|
|
header = jQuery(this);
|
|
|
|
return Discourse.MessageBus.subscribe("/header-change/" + (jQuery(this).data('key')), function(data) {
|
|
|
|
return header.html(data);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// possibly move this to dev only
|
|
|
|
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 jQuery('link').each(function() {
|
|
|
|
if (this.href.match(me.name) && me.hash) {
|
|
|
|
if (!jQuery(this).data('orig')) {
|
|
|
|
jQuery(this).data('orig', this.href);
|
|
|
|
}
|
|
|
|
this.href = jQuery(this).data('orig') + "&hash=" + me.hash;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
window.Discourse.Router = Discourse.Router.reopen({
|
|
|
|
location: 'discourse_location'
|
|
|
|
});
|
|
|
|
|
|
|
|
// since we have no jquery-rails these days, hook up csrf token
|
|
|
|
csrf_token = jQuery('meta[name=csrf-token]').attr('content');
|
|
|
|
|
|
|
|
jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
|
|
|
|
if (!options.crossDomain) {
|
|
|
|
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
}).call(this);
|