Add ES6 support to more files

This commit is contained in:
Robin Ward 2015-08-10 17:11:27 -04:00
parent 766903c430
commit e2e3e7c0e0
78 changed files with 419 additions and 387 deletions

View File

@ -46,7 +46,6 @@
"_",
"alert",
"containsInstance",
"parseHTML",
"deepEqual",
"notEqual",
"define",

View File

@ -1,8 +1,9 @@
import DiscourseController from 'discourse/controllers/controller';
import debounce from 'discourse/lib/debounce';
export default DiscourseController.extend({
filterEmailLogs: Discourse.debounce(function() {
filterEmailLogs: debounce(function() {
var self = this;
Discourse.EmailLog.findAll(this.get("filter")).then(function(logs) {
self.set("model", logs);

View File

@ -1,7 +1,8 @@
import debounce from 'discourse/lib/debounce';
import DiscourseController from 'discourse/controllers/controller';
export default DiscourseController.extend({
filterEmailLogs: Discourse.debounce(function() {
filterEmailLogs: debounce(function() {
var self = this;
Discourse.EmailLog.findAll(this.get("filter")).then(function(logs) {
self.set("model", logs);

View File

@ -1,3 +1,4 @@
import debounce from 'discourse/lib/debounce';
import { outputExportResult } from 'discourse/lib/export-result';
import { exportEntity } from 'discourse/lib/export-csv';
@ -6,7 +7,7 @@ export default Ember.ArrayController.extend({
itemController: 'admin-log-screened-ip-address',
filter: null,
show: Discourse.debounce(function() {
show: debounce(function() {
var self = this;
self.set('loading', true);
Discourse.ScreenedIpAddress.findAll(this.get("filter")).then(function(result) {

View File

@ -1,8 +1,10 @@
import debounce from 'discourse/lib/debounce';
export default Ember.ArrayController.extend({
loading: false,
filter: null,
show: Discourse.debounce(function() {
show: debounce(function() {
var self = this;
self.set('loading', true);
Discourse.Permalink.findAll(self.get("filter")).then(function(result) {

View File

@ -1,3 +1,4 @@
import debounce from 'discourse/lib/debounce';
import Presence from 'discourse/mixins/presence';
export default Ember.ArrayController.extend(Presence, {
@ -50,7 +51,7 @@ export default Ember.ArrayController.extend(Presence, {
this.transitionToRoute("adminSiteSettingsCategory", category || "all_results");
},
filterContent: Discourse.debounce(function() {
filterContent: debounce(function() {
if (this.get("_skipBounce")) {
this.set("_skipBounce", false);
} else {

View File

@ -1,3 +1,4 @@
import debounce from 'discourse/lib/debounce';
import { i18n } from 'discourse/lib/computed';
export default Ember.ArrayController.extend({
@ -33,7 +34,7 @@ export default Ember.ArrayController.extend({
return I18n.t('admin.users.titles.' + this.get('query'));
}.property('query'),
_filterUsers: Discourse.debounce(function() {
_filterUsers: debounce(function() {
this._refreshUsers();
}, 250).observes('listFilter'),

View File

@ -1,3 +1,4 @@
import debounce from 'discourse/lib/debounce';
import { renderSpinner } from 'discourse/helpers/loading-spinner';
export default Discourse.View.extend({
@ -9,7 +10,7 @@ export default Discourse.View.extend({
this.setProperties({ formattedLogs: "", index: 0 });
},
_updateFormattedLogs: Discourse.debounce(function() {
_updateFormattedLogs: debounce(function() {
const logs = this.get("controller.model");
if (logs.length === 0) {
this._reset(); // reset the cached logs whenever the model is reset

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
export default Discourse.View.extend({
classNames: ["admin-backups"],
@ -12,7 +14,7 @@ export default Discourse.View.extend({
$link.data("auto-route", true);
}
Discourse.URL.redirectTo($link.data("href"));
DiscourseURL.redirectTo($link.data("href"));
});
}.on("didInsertElement"),

View File

@ -1,3 +1,4 @@
import DiscourseURL from 'discourse/lib/url';
import { buildCategoryPanel } from 'discourse/components/edit-category-panel';
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
@ -53,7 +54,7 @@ export default buildCategoryPanel('general', {
actions: {
showCategoryTopic() {
Discourse.URL.routeTo(this.get('category.topic_url'));
DiscourseURL.routeTo(this.get('category.topic_url'));
return false;
}
}

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
const TopicCategoryComponent = Ember.Component.extend({
needsSecondRow: Ember.computed.gt('secondRowItems.length', 0),
secondRowItems: function() { return []; }.property(),
@ -10,7 +12,7 @@ const TopicCategoryComponent = Ember.Component.extend({
jumpToTopPost() {
const topic = this.get('topic');
if (topic) {
Discourse.URL.routeTo(topic.get('firstPostUrl'));
DiscourseURL.routeTo(topic.get('firstPostUrl'));
}
}
}

View File

@ -1,3 +1,4 @@
import DiscourseURL from 'discourse/lib/url';
import { setting } from 'discourse/lib/computed';
export default Ember.Component.extend({
@ -26,7 +27,7 @@ export default Ember.Component.extend({
e.preventDefault();
Discourse.URL.routeTo('/');
DiscourseURL.routeTo('/');
return false;
}
});

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
export default Ember.Component.extend({
tagName: 'ul',
classNameBindings: [':nav', ':nav-pills'],
@ -24,7 +26,7 @@ export default Ember.Component.extend({
this.set('expanded',false);
}
$(window).off('click.navigation-bar');
Discourse.URL.appEvents.off('dom:clean', this, this.ensureDropClosed);
DiscourseURL.appEvents.off('dom:clean', this, this.ensureDropClosed);
},
actions: {
@ -33,7 +35,7 @@ export default Ember.Component.extend({
var self = this;
if (this.get('expanded')) {
Discourse.URL.appEvents.on('dom:clean', this, this.ensureDropClosed);
DiscourseURL.appEvents.on('dom:clean', this, this.ensureDropClosed);
Em.run.next(function() {

View File

@ -1,6 +1,7 @@
import Presence from 'discourse/mixins/presence';
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseURL from 'discourse/lib/url';
// Modal related to changing the ownership of posts
export default Ember.Controller.extend(Presence, SelectedPostsCount, ModalFunctionality, {
@ -43,7 +44,7 @@ export default Ember.Controller.extend(Presence, SelectedPostsCount, ModalFuncti
// success
self.send('closeModal');
self.get('topicController').send('toggleMultiSelect');
Em.run.next(function() { Discourse.URL.routeTo(result.url); });
Em.run.next(function() { DiscourseURL.routeTo(result.url); });
}, function() {
// failure
self.flash(I18n.t('topic.change_owner.error'), 'alert-error');

View File

@ -1,5 +1,8 @@
import { setting } from 'discourse/lib/computed';
import Presence from 'discourse/mixins/presence';
import DiscourseURL from 'discourse/lib/url';
import Quote from 'discourse/lib/quote';
import Draft from 'discourse/models/draft';
export default Ember.ObjectController.extend(Presence, {
needs: ['modal', 'topic', 'composer-messages', 'application'],
@ -72,7 +75,7 @@ export default Ember.ObjectController.extend(Presence, {
const composer = this;
return this.store.find('post', postId).then(function(post) {
const quote = Discourse.Quote.build(post, post.get("raw"), {raw: true, full: true});
const quote = Quote.build(post, post.get("raw"), {raw: true, full: true});
composer.appendBlockAtCursor(quote);
composer.set('model.loading', false);
});
@ -262,7 +265,7 @@ export default Ember.ObjectController.extend(Presence, {
if (!composer.get('replyingToTopic') || !disableJumpReply) {
const post = result.target;
if (post && !staged) {
Discourse.URL.routeTo(post.get('url'));
DiscourseURL.routeTo(post.get('url'));
}
}
}).catch(function(error) {
@ -278,7 +281,7 @@ export default Ember.ObjectController.extend(Presence, {
Em.run.schedule('afterRender', function() {
if (staged && !disableJumpReply) {
const postNumber = staged.get('post_number');
Discourse.URL.jumpToPost(postNumber, { skipIfOnScreen: true });
DiscourseURL.jumpToPost(postNumber, { skipIfOnScreen: true });
self.appEvents.trigger('post:highlight', postNumber);
}
});
@ -415,7 +418,7 @@ export default Ember.ObjectController.extend(Presence, {
// we need a draft sequence for the composer to work
if (opts.draftSequence === undefined) {
return Discourse.Draft.get(opts.draftKey).then(function(data) {
return Draft.get(opts.draftKey).then(function(data) {
opts.draftSequence = data.draft_sequence;
opts.draft = data.draft;
self._setModel(composerModel, opts);
@ -477,7 +480,7 @@ export default Ember.ObjectController.extend(Presence, {
// View a new reply we've made
viewNewReply() {
Discourse.URL.routeTo(this.get('model.createdPost.url'));
DiscourseURL.routeTo(this.get('model.createdPost.url'));
this.close();
return false;
},
@ -485,7 +488,7 @@ export default Ember.ObjectController.extend(Presence, {
destroyDraft() {
const key = this.get('model.draftKey');
if (key) {
Discourse.Draft.clear(key, this.get('model.draftSequence'));
Draft.clear(key, this.get('model.draftSequence'));
}
},

View File

@ -1,3 +1,4 @@
import debounce from 'discourse/lib/debounce';
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller';
import { setting } from 'discourse/lib/computed';
@ -151,7 +152,7 @@ export default DiscourseController.extend(ModalFunctionality, {
}
}.observes('emailValidation', 'accountEmail'),
fetchExistingUsername: Discourse.debounce(function() {
fetchExistingUsername: debounce(function() {
const self = this;
Discourse.User.checkUsername(null, this.get('accountEmail')).then(function(result) {
if (result.suggestion && (self.blank('accountUsername') || self.get('accountUsername') === self.get('authOptions.username'))) {
@ -227,7 +228,7 @@ export default DiscourseController.extend(ModalFunctionality, {
return !this.blank('accountUsername') && this.get('accountUsername').length >= this.get('minUsernameLength');
},
checkUsernameAvailability: Discourse.debounce(function() {
checkUsernameAvailability: debounce(function() {
const _this = this;
if (this.shouldCheckUsernameMatch()) {
return Discourse.User.checkUsername(this.get('accountUsername'), this.get('accountEmail')).then(function(result) {

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
export default Ember.ObjectController.extend({
needs: ['navigation/category', 'discovery/topics', 'application'],
loading: false,
@ -22,7 +24,7 @@ export default Ember.ObjectController.extend({
actions: {
changePeriod(p) {
Discourse.URL.routeTo(this.showMoreUrl(p));
DiscourseURL.routeTo(this.showMoreUrl(p));
}
}

View File

@ -1,5 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
import DiscourseURL from 'discourse/lib/url';
// Modal for editing / creating a category
export default ObjectController.extend(ModalFunctionality, {
@ -71,7 +72,7 @@ export default ObjectController.extend(ModalFunctionality, {
this.get('model').save().then(function(result) {
self.send('closeModal');
model.setProperties({slug: result.category.slug, id: result.category.id });
Discourse.URL.redirectTo("/c/" + Discourse.Category.slugFor(model));
DiscourseURL.redirectTo("/c/" + Discourse.Category.slugFor(model));
}).catch(function(error) {
if (error && error.responseText) {
self.flash($.parseJSON(error.responseText).errors[0], 'error');
@ -92,7 +93,7 @@ export default ObjectController.extend(ModalFunctionality, {
self.get('model').destroy().then(function(){
// success
self.send('closeModal');
Discourse.URL.redirectTo("/categories");
DiscourseURL.redirectTo("/categories");
}, function(error){
if (error && error.responseText) {

View File

@ -1,6 +1,7 @@
import Presence from 'discourse/mixins/presence';
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';
import Invite from 'discourse/models/invite';
export default ObjectController.extend(Presence, ModalFunctionality, {
needs: ['user-invited-show'],
@ -140,7 +141,7 @@ export default ObjectController.extend(Presence, ModalFunctionality, {
return this.get('model').createInvite(this.get('emailOrUsername').trim(), groupNames).then(result => {
model.setProperties({ saving: false, finished: true });
if (!this.get('invitingToTopic')) {
Discourse.Invite.findInvitedBy(this.currentUser, userInvitedController.get('filter')).then(invite_model => {
Invite.findInvitedBy(this.currentUser, userInvitedController.get('filter')).then(invite_model => {
userInvitedController.set('model', invite_model);
userInvitedController.set('totalInvites', invite_model.invites.length);
});

View File

@ -2,6 +2,7 @@ import Presence from 'discourse/mixins/presence';
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import { movePosts, mergeTopic } from 'discourse/models/topic';
import DiscourseURL from 'discourse/lib/url';
// Modal related to merging of topics
export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, Presence, {
@ -54,7 +55,7 @@ export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, P
// Posts moved
self.send('closeModal');
self.get('topicController').send('toggleMultiSelect');
Em.run.next(function() { Discourse.URL.routeTo(result.url); });
Em.run.next(function() { DiscourseURL.routeTo(result.url); });
}).catch(function() {
self.flash(I18n.t('topic.merge_topic.error'));
}).finally(function() {

View File

@ -1,6 +1,7 @@
import { setting, propertyEqual } from 'discourse/lib/computed';
import Presence from 'discourse/mixins/presence';
import ObjectController from 'discourse/controllers/object';
import DiscourseURL from 'discourse/lib/url';
export default ObjectController.extend(Presence, {
taken: false,
@ -46,7 +47,7 @@ export default ObjectController.extend(Presence, {
if (result) {
self.set('saving', true);
self.get('content').changeUsername(self.get('newUsername')).then(function() {
Discourse.URL.redirectTo("/users/" + self.get('newUsername').toLowerCase() + "/preferences");
DiscourseURL.redirectTo("/users/" + self.get('newUsername').toLowerCase() + "/preferences");
}, function() {
// error
self.set('error', true);

View File

@ -1,5 +1,6 @@
import DiscourseController from 'discourse/controllers/controller';
import loadScript from 'discourse/lib/load-script';
import Quote from 'discourse/lib/quote';
export default DiscourseController.extend({
needs: ['topic', 'composer'],
@ -114,7 +115,7 @@ export default DiscourseController.extend({
}
const buffer = this.get('buffer');
const quotedText = Discourse.Quote.build(post, buffer);
const quotedText = Quote.build(post, buffer);
composerOpts.quote = quotedText;
if (composerController.get('content.viewOpen') || composerController.get('content.viewDraft')) {
composerController.appendBlockAtCursor(quotedText.trim());

View File

@ -1,5 +1,6 @@
import Presence from 'discourse/mixins/presence';
import searchForTerm from 'discourse/lib/search-for-term';
import DiscourseURL from 'discourse/lib/url';
let _dontSearch = false;
@ -138,7 +139,7 @@ export default Em.Controller.extend(Presence, {
const url = this.get('fullSearchUrlRelative');
if (url) {
Discourse.URL.routeTo(url);
DiscourseURL.routeTo(url);
}
},

View File

@ -3,6 +3,7 @@ import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import { extractError } from 'discourse/lib/ajax-error';
import { movePosts } from 'discourse/models/topic';
import DiscourseURL from 'discourse/lib/url';
// Modal related to auto closing of topics
export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, Presence, {
@ -55,7 +56,7 @@ export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, P
// Posts moved
self.send('closeModal');
self.get('topicController').send('toggleMultiSelect');
Ember.run.next(function() { Discourse.URL.routeTo(result.url); });
Ember.run.next(function() { DiscourseURL.routeTo(result.url); });
}).catch(function(xhr) {
self.flash(extractError(xhr, I18n.t('topic.split_topic.error')));
}).finally(function() {

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
function entranceDate(dt, showTime) {
var today = new Date();
@ -51,11 +53,11 @@ export default Ember.Controller.extend({
},
enterTop: function() {
Discourse.URL.routeTo(this.get('model.url'));
DiscourseURL.routeTo(this.get('model.url'));
},
enterBottom: function() {
Discourse.URL.routeTo(this.get('model.lastPostUrl'));
DiscourseURL.routeTo(this.get('model.lastPostUrl'));
}
}
});

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
export default Ember.ObjectController.extend({
needs: ['topic'],
progressPosition: null,
@ -62,7 +64,7 @@ export default Ember.ObjectController.extend({
// Route and close the expansion
jumpTo: function(url) {
this.set('expanded', false);
Discourse.URL.routeTo(url);
DiscourseURL.routeTo(url);
},
streamPercentage: function() {

View File

@ -3,6 +3,7 @@ import BufferedContent from 'discourse/mixins/buffered-content';
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
import { spinnerHTML } from 'discourse/helpers/loading-spinner';
import Topic from 'discourse/models/topic';
import Quote from 'discourse/lib/quote';
import { setting } from 'discourse/lib/computed';
export default ObjectController.extend(SelectedPostsCount, BufferedContent, {
@ -109,7 +110,7 @@ export default ObjectController.extend(SelectedPostsCount, BufferedContent, {
replyToPost(post) {
const composerController = this.get('controllers.composer'),
quoteController = this.get('controllers.quote-button'),
quotedText = Discourse.Quote.build(quoteController.get('post'), quoteController.get('buffer')),
quotedText = Quote.build(quoteController.get('post'), quoteController.get('buffer')),
topic = post ? post.get('topic') : this.get('model');
quoteController.set('buffer', '');
@ -412,7 +413,7 @@ export default ObjectController.extend(SelectedPostsCount, BufferedContent, {
replyAsNewTopic(post) {
const composerController = this.get('controllers.composer'),
quoteController = this.get('controllers.quote-button'),
quotedText = Discourse.Quote.build(quoteController.get('post'), quoteController.get('buffer')),
quotedText = Quote.build(quoteController.get('post'), quoteController.get('buffer')),
self = this;
quoteController.deselectText();

View File

@ -1,3 +1,4 @@
import DiscourseURL from 'discourse/lib/url';
import { propertyNotEqual, setting } from 'discourse/lib/computed';
export default Ember.Controller.extend({
@ -41,7 +42,7 @@ export default Ember.Controller.extend({
// Don't show on mobile
if (Discourse.Mobile.mobileView) {
const url = "/users/" + username;
Discourse.URL.routeTo(url);
DiscourseURL.routeTo(url);
return;
}

View File

@ -1,3 +1,6 @@
import Invite from 'discourse/models/invite';
import debounce from 'discourse/lib/debounce';
// This controller handles actions related to a user's invitations
export default Ember.ObjectController.extend({
user: null,
@ -19,9 +22,9 @@ export default Ember.ObjectController.extend({
@observes searchTerm
**/
_searchTermChanged: Discourse.debounce(function() {
_searchTermChanged: debounce(function() {
var self = this;
Discourse.Invite.findInvitedBy(self.get('user'), this.get('filter'), this.get('searchTerm')).then(function (invites) {
Invite.findInvitedBy(self.get('user'), this.get('filter'), this.get('searchTerm')).then(function (invites) {
self.set('model', invites);
});
}, 250).observes('searchTerm'),
@ -57,35 +60,23 @@ export default Ember.ObjectController.extend({
actions: {
/**
Rescind a given invite
@method rescive
@param {Discourse.Invite} invite the invite to rescind.
**/
rescind: function(invite) {
rescind(invite) {
invite.rescind();
return false;
},
/**
Resend a given invite
@method reinvite
@param {Discourse.Invite} invite the invite to resend.
**/
reinvite: function(invite) {
reinvite(invite) {
invite.reinvite();
return false;
},
loadMore: function() {
loadMore() {
var self = this;
var model = self.get('model');
if (self.get('canLoadMore') && !self.get('invitesLoading')) {
self.set('invitesLoading', true);
Discourse.Invite.findInvitedBy(self.get('user'), self.get('filter'), self.get('searchTerm'), model.invites.length).then(function(invite_model) {
Invite.findInvitedBy(self.get('user'), self.get('filter'), self.get('searchTerm'), model.invites.length).then(function(invite_model) {
self.set('invitesLoading', false);
model.invites.pushObjects(invite_model.invites);
if(invite_model.invites.length === 0 || invite_model.invites.length < Discourse.SiteSettings.invites_per_page) {

View File

@ -1,3 +1,5 @@
import debounce from 'discourse/lib/debounce';
export default Ember.Controller.extend({
needs: ["application"],
queryParams: ["period", "order", "asc", "name"],
@ -8,7 +10,7 @@ export default Ember.Controller.extend({
showTimeRead: Ember.computed.equal("period", "all"),
_setName: Discourse.debounce(function() {
_setName: debounce(function() {
this.set("name", this.get("nameInput"));
}, 500).observes("nameInput"),

View File

@ -1,6 +1,25 @@
const _customizations = {};
export function getCustomHTML(key) {
const c = _customizations[key];
if (c) {
return new Handlebars.SafeString(c);
}
const html = PreloadStore.get("customHTML");
if (html && html[key] && html[key].length) {
return new Handlebars.SafeString(html[key]);
}
}
// Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`.
export function setCustomHTML(key, html) {
_customizations[key] = html;
}
Ember.HTMLBars._registerHelper('custom-html', function(params, hash, options, env) {
const name = params[0];
const html = Discourse.HTML.getCustomHTML(name);
const html = getCustomHTML(name);
if (html) { return html; }
const contextString = params[1];

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
/**
Discourse does some server side rendering of HTML, such as the `cooked` contents of
posts. The downside of this in an Ember app is the links will not go through the router.
@ -28,7 +30,7 @@ export default {
}
e.preventDefault();
Discourse.URL.routeTo(href);
DiscourseURL.routeTo(href);
return false;
});
}

View File

@ -0,0 +1,27 @@
import DiscourseURL from 'discourse/lib/url';
import Quote from 'discourse/lib/quote';
import debounce from 'discourse/lib/debounce';
function proxyDep(propName, module) {
if (Discourse.hasOwnProperty(propName)) { return; }
Object.defineProperty(Discourse, propName, {
get: function() {
Ember.warn(`DEPRECATION: \`Discourse.${propName}\` is deprecated, import the module.`);
return module;
}
});
}
export default {
name: 'es6-deprecations',
before: 'inject-objects',
initialize: function() {
// TODO: Once things have migrated remove these
proxyDep('computed', require('discourse/lib/computed'));
proxyDep('Formatter', require('discourse/lib/formatter'));
proxyDep('URL', DiscourseURL);
proxyDep('Quote', Quote);
proxyDep('debounce', debounce);
}
};

View File

@ -1,6 +1,7 @@
import Session from 'discourse/models/session';
import AppEvents from 'discourse/lib/app-events';
import Store from 'discourse/models/store';
import DiscourseURL from 'discourse/lib/url';
function inject() {
const app = arguments[0],
@ -22,7 +23,7 @@ export default {
const appEvents = AppEvents.create();
app.register('app-events:main', appEvents, { instantiate: false });
injectAll(app, 'appEvents');
Discourse.URL.appEvents = appEvents;
DiscourseURL.appEvents = appEvents;
app.register('store:main', Store);
inject(app, 'store', 'route', 'controller');

View File

@ -1,11 +1,9 @@
/*global Mousetrap:true*/
import KeyboardShortcuts from 'discourse/lib/keyboard-shortcuts';
/**
Initialize Global Keyboard Shortcuts
**/
export default {
name: "keyboard-shortcuts",
initialize: function(container) {
Discourse.KeyboardShortcuts.bindEvents(Mousetrap, container);
initialize(container) {
KeyboardShortcuts.bindEvents(Mousetrap, container);
}
};

View File

@ -1,11 +1,13 @@
import DiscourseURL from 'discourse/lib/url';
export default {
name: 'url-redirects',
initialize: function() {
// URL rewrites (usually due to refactoring)
Discourse.URL.rewrite(/^\/category\//, "/c/");
Discourse.URL.rewrite(/^\/group\//, "/groups/");
Discourse.URL.rewrite(/\/private-messages\/$/, "/messages/");
Discourse.URL.rewrite(/^\/users\/([^\/]+)\/?$/, "/users/$1/activity");
DiscourseURL.rewrite(/^\/category\//, "/c/");
DiscourseURL.rewrite(/^\/group\//, "/groups/");
DiscourseURL.rewrite(/\/private-messages\/$/, "/messages/");
DiscourseURL.rewrite(/^\/users\/([^\/]+)\/?$/, "/users/$1/activity");
}
};

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
export default {
trackClick(e) {
// cancel click if triggered as part of selection.
@ -87,7 +89,7 @@ export default {
}
// If we're on the same site, use the router and track via AJAX
if (Discourse.URL.isInternal(href) && !$link.hasClass('attachment')) {
if (DiscourseURL.isInternal(href) && !$link.hasClass('attachment')) {
Discourse.ajax("/clicks/track", {
data: {
url: href,
@ -97,7 +99,7 @@ export default {
},
dataType: 'html'
});
Discourse.URL.routeTo(href);
DiscourseURL.routeTo(href);
return false;
}
@ -106,7 +108,7 @@ export default {
var win = window.open(trackingUrl, '_blank');
win.focus();
} else {
Discourse.URL.redirectTo(trackingUrl);
DiscourseURL.redirectTo(trackingUrl);
}
return false;

View File

@ -3,9 +3,9 @@
should only be executed once (at the end of the limit counted from the last call made).
Original function will be called with the context and arguments from the last call made.
**/
Discourse.debounce = function(func, wait) {
var self, args;
var later = function() {
export default function(func, wait) {
let self, args;
const later = function() {
func.apply(self, args);
};
@ -15,4 +15,4 @@ Discourse.debounce = function(func, wait) {
Ember.run.debounce(null, later, wait);
};
};
}

View File

@ -1,3 +1,4 @@
import DiscourseURL from 'discourse/lib/url';
import PageTracker from 'discourse/lib/page-tracker';
let primaryTab = false;
@ -116,7 +117,7 @@ function onNotification(data) {
});
function clickEventHandler() {
Discourse.URL.routeTo(data.post_url);
DiscourseURL.routeTo(data.post_url);
// Cannot delay this until the page renders
// due to trigger-based permissions
window.focus();

View File

@ -1,28 +0,0 @@
var customizations = {};
Discourse.HTML = {
/**
Return a custom fragment of HTML by key. It can be registered via a plugin
using `setCustomHTML(key, html)`. This is used by a handlebars helper to find
the HTML content it wants. It will also check the `PreloadStore` for any server
side preloaded HTML.
**/
getCustomHTML: function(key) {
var c = customizations[key];
if (c) {
return new Handlebars.SafeString(c);
}
var html = PreloadStore.get("customHTML");
if (html && html[key] && html[key].length) {
return new Handlebars.SafeString(html[key]);
}
},
// Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`.
setCustomHTML: function(key, html) {
customizations[key] = html;
}
};

View File

@ -1,4 +1,6 @@
var PATH_BINDINGS = {
import DiscourseURL from 'discourse/lib/url';
const PATH_BINDINGS = {
'g h': '/',
'g l': '/latest',
'g n': '/new',
@ -55,8 +57,8 @@ var PATH_BINDINGS = {
};
Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
bindEvents: function(keyTrapper, container) {
export default {
bindEvents(keyTrapper, container) {
this.keyTrapper = keyTrapper;
this.container = container;
this._stopCallback();
@ -67,13 +69,13 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
_.each(FUNCTION_BINDINGS, this._bindToFunction, this);
},
toggleBookmark: function(){
toggleBookmark(){
this.sendToSelectedPost('toggleBookmark');
this.sendToTopicListItemView('toggleBookmark');
},
toggleBookmarkTopic: function(){
var topic = this.currentTopic();
toggleBookmarkTopic(){
const topic = this.currentTopic();
// BIG hack, need a cleaner way
if(topic && $('.posts-wrapper').length > 0) {
topic.toggleBookmark();
@ -82,7 +84,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
}
},
quoteReply: function(){
quoteReply(){
$('.topic-post.selected button.create').click();
// lazy but should work for now
setTimeout(function(){
@ -90,55 +92,55 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
}, 500);
},
goToFirstPost: function() {
goToFirstPost() {
this._jumpTo('jumpTop');
},
goToLastPost: function() {
goToLastPost() {
this._jumpTo('jumpBottom');
},
_jumpTo: function(direction) {
_jumpTo(direction) {
if ($('.container.posts').length) {
this.container.lookup('controller:topic-progress').send(direction);
}
},
replyToTopic: function() {
replyToTopic() {
this.container.lookup('controller:topic').send('replyToPost');
},
selectDown: function() {
selectDown() {
this._moveSelection(1);
},
selectUp: function() {
selectUp() {
this._moveSelection(-1);
},
goBack: function() {
goBack() {
history.back();
},
nextSection: function() {
nextSection() {
this._changeSection(1);
},
prevSection: function() {
prevSection() {
this._changeSection(-1);
},
showBuiltinSearch: function() {
showBuiltinSearch() {
if ($('#search-dropdown').is(':visible')) {
this._toggleSearch(false);
return true;
}
var currentPath = this.container.lookup('controller:application').get('currentPath'),
const currentPath = this.container.lookup('controller:application').get('currentPath'),
blacklist = [ /^discovery\.categories/ ],
whitelist = [ /^topic\./ ],
check = function(regex) { return !!currentPath.match(regex); },
showSearch = whitelist.any(check) && !blacklist.any(check);
check = function(regex) { return !!currentPath.match(regex); };
let showSearch = whitelist.any(check) && !blacklist.any(check);
// If we're viewing a topic, only intercept search if there are cloaked posts
if (showSearch && currentPath.match(/^topic\./)) {
@ -153,61 +155,61 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
return true;
},
createTopic: function() {
Discourse.__container__.lookup('controller:composer').open({action: Discourse.Composer.CREATE_TOPIC, draftKey: Discourse.Composer.CREATE_TOPIC});
createTopic() {
this.container.lookup('controller:composer').open({action: Discourse.Composer.CREATE_TOPIC, draftKey: Discourse.Composer.CREATE_TOPIC});
},
pinUnpinTopic: function() {
Discourse.__container__.lookup('controller:topic').togglePinnedState();
pinUnpinTopic() {
this.container.lookup('controller:topic').togglePinnedState();
},
toggleProgress: function() {
Discourse.__container__.lookup('controller:topic-progress').send('toggleExpansion', {highlight: true});
toggleProgress() {
this.container.lookup('controller:topic-progress').send('toggleExpansion', {highlight: true});
},
showSearch: function() {
showSearch() {
this._toggleSearch(false);
return false;
},
showSiteMap: function() {
showSiteMap() {
$('#site-map').click();
$('#site-map-dropdown a:first').focus();
},
showCurrentUser: function() {
showCurrentUser() {
$('#current-user').click();
$('#user-dropdown a:first').focus();
},
showHelpModal: function() {
Discourse.__container__.lookup('controller:application').send('showKeyboardShortcutsHelp');
showHelpModal() {
this.container.lookup('controller:application').send('showKeyboardShortcutsHelp');
},
sendToTopicListItemView: function(action){
var elem = $('tr.selected.topic-list-item.ember-view')[0];
sendToTopicListItemView(action){
const elem = $('tr.selected.topic-list-item.ember-view')[0];
if(elem){
var view = Ember.View.views[elem.id];
const view = Ember.View.views[elem.id];
view.send(action);
}
},
currentTopic: function(){
var topicController = this.container.lookup('controller:topic');
currentTopic(){
const topicController = this.container.lookup('controller:topic');
if(topicController) {
var topic = topicController.get('model');
const topic = topicController.get('model');
if(topic){
return topic;
}
}
},
sendToSelectedPost: function(action){
var container = this.container;
sendToSelectedPost(action){
const container = this.container;
// TODO: We should keep track of the post without a CSS class
var selectedPostId = parseInt($('.topic-post.selected article.boxed').data('post-id'), 10);
const selectedPostId = parseInt($('.topic-post.selected article.boxed').data('post-id'), 10);
if (selectedPostId) {
var topicController = container.lookup('controller:topic'),
const topicController = container.lookup('controller:topic'),
post = topicController.get('model.postStream.posts').findBy('id', selectedPostId);
if (post) {
topicController.send(action, post);
@ -215,24 +217,24 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
}
},
_bindToSelectedPost: function(action, binding) {
var self = this;
_bindToSelectedPost(action, binding) {
const self = this;
this.keyTrapper.bind(binding, function() {
self.sendToSelectedPost(action);
});
},
_bindToPath: function(path, binding) {
_bindToPath(path, binding) {
this.keyTrapper.bind(binding, function() {
Discourse.URL.routeTo(path);
DiscourseURL.routeTo(path);
});
},
_bindToClick: function(selector, binding) {
_bindToClick(selector, binding) {
binding = binding.split(',');
this.keyTrapper.bind(binding, function(e) {
var $sel = $(selector);
const $sel = $(selector);
// Special case: We're binding to enter.
if (e && e.keyCode === 13) {
@ -249,21 +251,21 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
});
},
_bindToFunction: function(func, binding) {
_bindToFunction(func, binding) {
if (typeof this[func] === 'function') {
this.keyTrapper.bind(binding, _.bind(this[func], this));
}
},
_moveSelection: function(direction) {
var $articles = this._findArticles();
_moveSelection(direction) {
const $articles = this._findArticles();
if (typeof $articles === 'undefined') {
return;
}
var $selected = $articles.filter('.selected'),
index = $articles.index($selected);
const $selected = $articles.filter('.selected');
let index = $articles.index($selected);
if($selected.length !== 0){ //boundries check
// loop is not allowed
@ -273,11 +275,11 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
// if nothing is selected go to the first post on screen
if ($selected.length === 0) {
var scrollTop = $(document).scrollTop();
const scrollTop = $(document).scrollTop();
index = 0;
$articles.each(function(){
var top = $(this).position().top;
const top = $(this).position().top;
if(top > scrollTop) {
return false;
}
@ -291,7 +293,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
direction = 0;
}
var $article = $articles.eq(index + direction);
const $article = $articles.eq(index + direction);
if ($article.size() > 0) {
@ -303,7 +305,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
}
if ($article.is('.topic-post')) {
var tabLoc = $article.find('a.tabLoc');
let tabLoc = $article.find('a.tabLoc');
if (tabLoc.length === 0) {
tabLoc = $('<a href class="tabLoc"></a>');
$article.prepend(tabLoc);
@ -315,19 +317,19 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
}
},
_scrollList: function($article) {
_scrollList($article) {
// Try to keep the article on screen
var pos = $article.offset();
var height = $article.height();
var scrollTop = $(window).scrollTop();
var windowHeight = $(window).height();
const pos = $article.offset();
const height = $article.height();
const scrollTop = $(window).scrollTop();
const windowHeight = $(window).height();
// skip if completely on screen
if (pos.top > scrollTop && (pos.top + height) < (scrollTop + windowHeight)) {
return;
}
var scrollPos = (pos.top + (height/2)) - (windowHeight * 0.5);
let scrollPos = (pos.top + (height/2)) - (windowHeight * 0.5);
if (scrollPos < 0) { scrollPos = 0; }
if (this._scrollAnimation) {
@ -337,8 +339,8 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
},
_findArticles: function() {
var $topicList = $('.topic-list'),
_findArticles() {
const $topicList = $('.topic-list'),
$topicArea = $('.posts-wrapper');
if ($topicArea.size() > 0) {
@ -349,8 +351,8 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
}
},
_changeSection: function(direction) {
var $sections = $('#navigation-bar li'),
_changeSection(direction) {
const $sections = $('#navigation-bar li'),
active = $('#navigation-bar li.active'),
index = $sections.index(active) + direction;
@ -359,8 +361,8 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
}
},
_stopCallback: function() {
var oldStopCallback = this.keyTrapper.stopCallback;
_stopCallback() {
const oldStopCallback = this.keyTrapper.stopCallback;
this.keyTrapper.stopCallback = function(e, element, combo) {
if ((combo === 'ctrl+f' || combo === 'command+f') && element.id === 'search-term') {
@ -371,10 +373,10 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
};
},
_toggleSearch: function(selectContext) {
_toggleSearch(selectContext) {
$('#search-button').click();
if (selectContext) {
Discourse.__container__.lookup('controller:search').set('searchContextEnabled', true);
this.container.lookup('controller:search').set('searchContextEnabled', true);
}
},
});
};

View File

@ -1,4 +1,4 @@
Discourse.Quote = {
export default {
REGEXP: /\[quote=([^\]]*)\]((?:[\s\S](?!\[quote=[^\]]*\]))*?)\[\/quote\]/im,

View File

@ -1,3 +1,5 @@
import DiscourseURL from 'discourse/lib/url';
const configs = {
"faq": "faq_url",
"tos": "tos_url",
@ -14,14 +16,14 @@ export default (page) => {
const configKey = configs[page];
if (configKey && Discourse.SiteSettings[configKey].length > 0) {
transition.abort();
Discourse.URL.redirectTo(Discourse.SiteSettings[configKey]);
DiscourseURL.redirectTo(Discourse.SiteSettings[configKey]);
}
},
activate() {
this._super();
// Scroll to an element if exists
Discourse.URL.scrollToId(document.location.hash);
DiscourseURL.scrollToId(document.location.hash);
},
model() {

View File

@ -1,25 +1,25 @@
/*global LockOn:true*/
var jumpScheduled = false,
rewrites = [];
let _jumpScheduled = false;
const rewrites = [];
Discourse.URL = Ember.Object.createWithMixins({
const DiscourseURL = Ember.Object.createWithMixins({
// Used for matching a topic
TOPIC_REGEXP: /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/,
isJumpScheduled: function() {
return jumpScheduled;
return _jumpScheduled;
},
/**
Jumps to a particular post in the stream
**/
jumpToPost: function(postNumber, opts) {
var holderId = '#post-cloak-' + postNumber;
const holderId = '#post-cloak-' + postNumber;
var offset = function(){
const offset = function(){
var $header = $('header'),
const $header = $('header'),
$title = $('#topic-title'),
windowHeight = $(window).height() - $title.height(),
expectedOffset = $title.height() - $header.find('.contents').height() + (windowHeight / 5);
@ -34,13 +34,13 @@ Discourse.URL = Ember.Object.createWithMixins({
return;
}
var lockon = new LockOn(holderId, {offsetCalculator: offset});
var holder = $(holderId);
const lockon = new LockOn(holderId, {offsetCalculator: offset});
const holder = $(holderId);
if(holder.length > 0 && opts && opts.skipIfOnScreen){
// if we are on screen skip
var elementTop = lockon.elementTop(),
const elementTop = lockon.elementTop(),
scrollTop = $(window).scrollTop(),
windowHeight = $(window).height()-offset(),
height = holder.height();
@ -73,7 +73,7 @@ Discourse.URL = Ember.Object.createWithMixins({
// while URLs are loading. For example, while a topic loads it sets `currentPost`
// which triggers a replaceState even though the topic hasn't fully loaded yet!
Em.run.next(function() {
var location = Discourse.URL.get('router.location');
const location = DiscourseURL.get('router.location');
if (location && location.replaceURL) {
location.replaceURL(path);
}
@ -85,15 +85,15 @@ Discourse.URL = Ember.Object.createWithMixins({
scrollToId: function(id) {
if (Em.isEmpty(id)) { return; }
jumpScheduled = true;
_jumpScheduled = true;
Em.run.schedule('afterRender', function() {
var $elem = $(id);
let $elem = $(id);
if ($elem.length === 0) {
$elem = $("[name='" + id.replace('#', '') + "']");
}
if ($elem.length > 0) {
$('html,body').scrollTop($elem.offset().top - $('header').height() - 15);
jumpScheduled = false;
_jumpScheduled = false;
}
});
},
@ -125,19 +125,19 @@ Discourse.URL = Ember.Object.createWithMixins({
return;
}
var oldPath = window.location.pathname;
const oldPath = window.location.pathname;
path = path.replace(/(https?\:)?\/\/[^\/]+/, '');
// handle prefixes
if (path.match(/^\//)) {
var rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri);
let rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri);
rootURL = rootURL.replace(/\/$/, '');
path = path.replace(rootURL, '');
}
// Rewrite /my/* urls
if (path.indexOf('/my/') === 0) {
var currentUser = Discourse.User.current();
const currentUser = Discourse.User.current();
if (currentUser) {
path = path.replace('/my/', '/users/' + currentUser.get('username_lower') + "/");
} else {
@ -203,40 +203,40 @@ Discourse.URL = Ember.Object.createWithMixins({
@param {String} path the path we're navigating to
**/
navigatedToPost: function(oldPath, path) {
var newMatches = this.TOPIC_REGEXP.exec(path),
const newMatches = this.TOPIC_REGEXP.exec(path),
newTopicId = newMatches ? newMatches[2] : null;
if (newTopicId) {
var oldMatches = this.TOPIC_REGEXP.exec(oldPath),
const oldMatches = this.TOPIC_REGEXP.exec(oldPath),
oldTopicId = oldMatches ? oldMatches[2] : null;
// If the topic_id is the same
if (oldTopicId === newTopicId) {
Discourse.URL.replaceState(path);
DiscourseURL.replaceState(path);
var container = Discourse.__container__,
const container = Discourse.__container__,
topicController = container.lookup('controller:topic'),
opts = {},
postStream = topicController.get('model.postStream');
if (newMatches[3]) opts.nearPost = newMatches[3];
if (path.match(/last$/)) { opts.nearPost = topicController.get('highest_post_number'); }
var closest = opts.nearPost || 1;
const closest = opts.nearPost || 1;
var self = this;
const self = this;
postStream.refresh(opts).then(function() {
topicController.setProperties({
'model.currentPost': closest,
enteredAt: new Date().getTime().toString()
});
var closestPost = postStream.closestPostForPostNumber(closest),
const closestPost = postStream.closestPostForPostNumber(closest),
progress = postStream.progressIndexOfPost(closestPost),
progressController = container.lookup('controller:topic-progress');
progressController.set('progressPosition', progress);
self.appEvents.trigger('post:highlight', closest);
}).then(function() {
Discourse.URL.jumpToPost(closest, {skipIfOnScreen: true});
DiscourseURL.jumpToPost(closest, {skipIfOnScreen: true});
});
// Abort routing, we have replaced our state.
@ -256,7 +256,7 @@ Discourse.URL = Ember.Object.createWithMixins({
@param {String} path the path we're navigating to
**/
navigatedToHome: function(oldPath, path) {
var homepage = Discourse.Utilities.defaultHomepage();
const homepage = Discourse.Utilities.defaultHomepage();
if (window.history &&
window.history.pushState &&
@ -269,14 +269,7 @@ Discourse.URL = Ember.Object.createWithMixins({
return false;
},
/**
@private
Get the origin of the current location.
This has been extracted so it can be tested.
@method origin
**/
// This has been extracted so it can be tested.
origin: function() {
return window.location.origin;
},
@ -293,15 +286,8 @@ Discourse.URL = Ember.Object.createWithMixins({
return Discourse.__container__.lookup('router:main');
}.property().volatile(),
/**
@private
Get a controller. Note that currently it uses `__container__` which is not
advised but there is no other way to access the router.
@method controllerFor
@param {String} name the name of the controller
**/
// Get a controller. Note that currently it uses `__container__` which is not
// advised but there is no other way to access the router.
controllerFor: function(name) {
return Discourse.__container__.lookup('controller:' + name);
},
@ -313,7 +299,7 @@ Discourse.URL = Ember.Object.createWithMixins({
handleURL: function(path, opts) {
opts = opts || {};
var router = this.get('router');
const router = this.get('router');
if (opts.replaceURL) {
this.replaceState(path);
@ -321,25 +307,25 @@ Discourse.URL = Ember.Object.createWithMixins({
router.router.updateURL(path);
}
var split = path.split('#'),
elementId;
const split = path.split('#');
let elementId;
if (split.length === 2) {
path = split[0];
elementId = split[1];
}
var transition = router.handleURL(path);
const transition = router.handleURL(path);
transition._discourse_intercepted = true;
transition.promise.then(function() {
if (elementId) {
jumpScheduled = true;
_jumpScheduled = true;
Em.run.next('afterRender', function() {
var offset = $('#' + elementId).offset();
const offset = $('#' + elementId).offset();
if (offset && offset.top) {
$('html, body').scrollTop(offset.top - $('header').height() - 10);
jumpScheduled = false;
_jumpScheduled = false;
}
});
}
@ -347,3 +333,5 @@ Discourse.URL = Ember.Object.createWithMixins({
}
});
export default DiscourseURL;

View File

@ -1,5 +1,7 @@
import DiscourseURL from 'discourse/lib/url';
function scrollTop() {
if (Discourse.URL.isJumpScheduled()) { return; }
if (DiscourseURL.isJumpScheduled()) { return; }
Ember.run.schedule('afterRender', function() {
$(document).scrollTop(0);
});

View File

@ -1,3 +1,5 @@
import debounce from 'discourse/lib/debounce';
/**
This object provides the DOM methods we need for our Mixin to bind to scrolling
methods in the browser. By removing them from the Mixin we can test them
@ -34,7 +36,7 @@ const Scrolling = Ember.Mixin.create({
};
if (opts.debounce) {
onScrollMethod = Discourse.debounce(onScrollMethod, opts.debounce);
onScrollMethod = debounce(onScrollMethod, opts.debounce);
}
ScrollingDOMMethods.bindOnScroll(onScrollMethod, opts.name);

View File

@ -1,6 +1,8 @@
import RestModel from 'discourse/models/rest';
import Topic from 'discourse/models/topic';
import { throwAjaxError } from 'discourse/lib/ajax-error';
import Quote from 'discourse/lib/quote';
import Draft from 'discourse/models/draft';
const CLOSED = 'closed',
SAVING = 'saving',
@ -274,7 +276,7 @@ const Composer = RestModel.extend({
**/
replyLength: function() {
let reply = this.get('reply') || "";
while (Discourse.Quote.REGEXP.test(reply)) { reply = reply.replace(Discourse.Quote.REGEXP, ""); }
while (Quote.REGEXP.test(reply)) { reply = reply.replace(Quote.REGEXP, ""); }
return reply.replace(/\s+/img, " ").trim().length;
}.property('reply'),
@ -662,7 +664,7 @@ const Composer = RestModel.extend({
}
// try to save the draft
return Discourse.Draft.save(this.get('draftKey'), this.get('draftSequence'), data)
return Draft.save(this.get('draftKey'), this.get('draftSequence'), data)
.then(function() {
composer.set('draftStatus', I18n.t('composer.saved_draft_tip'));
}).catch(function() {
@ -706,7 +708,7 @@ Composer.reopenClass({
}
} catch (error) {
draft = null;
Discourse.Draft.clear(draftKey, draftSequence);
Draft.clear(draftKey, draftSequence);
}
if (draft && ((draft.title && draft.title !== '') || (draft.reply && draft.reply !== ''))) {
return this.open({

View File

@ -1,16 +1,8 @@
/**
A data model representing a draft post
const Draft = Discourse.Model.extend();
@class Draft
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Draft = Discourse.Model.extend({});
Draft.reopenClass({
Discourse.Draft.reopenClass({
clear: function(key, sequence) {
clear(key, sequence) {
return Discourse.ajax("/draft.json", {
type: 'DELETE',
data: {
@ -20,19 +12,19 @@ Discourse.Draft.reopenClass({
});
},
get: function(key) {
get(key) {
return Discourse.ajax('/draft.json', {
data: { draft_key: key },
dataType: 'json'
});
},
getLocal: function(key, current) {
getLocal(key, current) {
// TODO: implement this
return current;
},
save: function(key, sequence, data) {
save(key, sequence, data) {
data = typeof data === "string" ? data : JSON.stringify(data);
return Discourse.ajax("/draft.json", {
type: 'POST',
@ -45,3 +37,5 @@ Discourse.Draft.reopenClass({
}
});
export default Draft;

View File

@ -1,15 +1,6 @@
/**
A data model representing an Invite
const Invite = Discourse.Model.extend({
@class Invite
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Invite = Discourse.Model.extend({
rescind: function() {
rescind() {
Discourse.ajax('/invites', {
type: 'DELETE',
data: { email: this.get('email') }
@ -17,7 +8,7 @@ Discourse.Invite = Discourse.Model.extend({
this.set('rescinded', true);
},
reinvite: function() {
reinvite() {
Discourse.ajax('/invites/reinvite', {
type: 'POST',
data: { email: this.get('email') }
@ -27,9 +18,9 @@ Discourse.Invite = Discourse.Model.extend({
});
Discourse.Invite.reopenClass({
Invite.reopenClass({
create: function() {
create() {
var result = this._super.apply(this, arguments);
if (result.user) {
result.user = Discourse.User.create(result.user);
@ -37,7 +28,7 @@ Discourse.Invite.reopenClass({
return result;
},
findInvitedBy: function(user, filter, search, offset) {
findInvitedBy(user, filter, search, offset) {
if (!user) { return Em.RSVP.resolve(); }
var data = {};
@ -45,9 +36,9 @@ Discourse.Invite.reopenClass({
if (!Em.isNone(search)) { data.search = search; }
data.offset = offset || 0;
return Discourse.ajax("/users/" + user.get('username_lower') + "/invited.json", {data: data}).then(function (result) {
return Discourse.ajax("/users/" + user.get('username_lower') + "/invited.json", {data}).then(function (result) {
result.invites = result.invites.map(function (i) {
return Discourse.Invite.create(i);
return Invite.create(i);
});
return Em.Object.create(result);
@ -55,3 +46,5 @@ Discourse.Invite.reopenClass({
}
});
export default Invite;

View File

@ -1,3 +1,4 @@
import DiscourseURL from 'discourse/lib/url';
import RestModel from 'discourse/models/rest';
function calcDayDiff(p1, p2) {
@ -159,7 +160,7 @@ const PostStream = RestModel.extend({
const posts = this.get('posts');
if (posts.length > 1) {
const secondPostNum = posts[1].get('post_number');
Discourse.URL.jumpToPost(secondPostNum);
DiscourseURL.jumpToPost(secondPostNum);
}
},

View File

@ -2,6 +2,7 @@ import RestModel from 'discourse/models/rest';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import ActionSummary from 'discourse/models/action-summary';
import { url, fmt, propertyEqual } from 'discourse/lib/computed';
import Quote from 'discourse/lib/quote';
const Post = RestModel.extend({
@ -418,7 +419,7 @@ Post.reopenClass({
loadQuote(postId) {
return Discourse.ajax("/posts/" + postId + ".json").then(function (result) {
const post = Discourse.Post.create(result);
return Discourse.Quote.build(post, post.get('raw'), {raw: true, full: true});
return Quote.build(post, post.get('raw'), {raw: true, full: true});
});
},

View File

@ -1,35 +0,0 @@
// this allows you to track the selected item in an array, ghetto for now
Discourse.SelectableArray = Em.ArrayProxy.extend({
init: function() {
this.content = [];
this._super();
},
selectIndex: function(index){
this.select(this[index]);
},
select: function(selected){
_.each(this.content,function(item){
if(item === selected){
Em.set(item, "active", true);
} else {
if (item.get("active")) {
Em.set(item, "active", false);
}
}
});
this.set("active", selected);
},
removeObject: function(object) {
if(object === this.get("active")){
this.set("active", null);
Em.set(object, "active", false);
}
this._super(object);
}
});

View File

@ -1,4 +1,5 @@
import Topic from 'discourse/models/topic';
import DiscourseURL from 'discourse/lib/url';
export default Discourse.Route.extend({
model: function(params) {
@ -6,6 +7,6 @@ export default Discourse.Route.extend({
},
afterModel: function(result) {
Discourse.URL.routeTo(result.url, { replaceURL: true });
DiscourseURL.routeTo(result.url, { replaceURL: true });
}
});

View File

@ -1,3 +1,6 @@
import DiscourseURL from 'discourse/lib/url';
import Draft from 'discourse/models/draft';
// This route is used for retrieving a topic based on params
export default Discourse.Route.extend({
@ -43,12 +46,11 @@ export default Discourse.Route.extend({
Ember.run.scheduleOnce('afterRender', function() {
self.appEvents.trigger('post:highlight', closest);
});
Discourse.URL.jumpToPost(closest);
DiscourseURL.jumpToPost(closest);
if (topic.present('draft')) {
composerController.open({
draft: Discourse.Draft.getLocal(topic.get('draft_key'), topic.get('draft')),
draft: Draft.getLocal(topic.get('draft_key'), topic.get('draft')),
draftKey: topic.get('draft_key'),
draftSequence: topic.get('draft_sequence'),
topic: topic,

View File

@ -1,4 +1,5 @@
import ScreenTrack from 'discourse/lib/screen-track';
import DiscourseURL from 'discourse/lib/url';
let isTransitioning = false,
scheduledReplace = null,
@ -128,7 +129,7 @@ const TopicRoute = Discourse.Route.extend({
_replaceUnlessScrolling(url) {
const currentPos = parseInt($(document).scrollTop(), 10);
if (currentPos === lastScrollPos) {
Discourse.URL.replaceState(url);
DiscourseURL.replaceState(url);
return;
}
lastScrollPos = currentPos;

View File

@ -1,3 +1,5 @@
import Draft from 'discourse/models/draft';
export default Discourse.Route.extend({
model() {
return this.modelFor("user");
@ -10,7 +12,7 @@ export default Discourse.Route.extend({
const composerController = this.controllerFor("composer");
controller.set("model", user);
if (this.currentUser) {
Discourse.Draft.get("new_private_message").then(function(data) {
Draft.get("new_private_message").then(function(data) {
if (data.draft) {
composerController.open({
draft: data.draft,

View File

@ -1,10 +1,11 @@
import Invite from 'discourse/models/invite';
import showModal from "discourse/lib/show-modal";
export default Discourse.Route.extend({
model(params) {
this.inviteFilter = params.filter;
return Discourse.Invite.findInvitedBy(this.modelFor("user"), params.filter);
return Invite.findInvitedBy(this.modelFor("user"), params.filter);
},
afterModel(model) {

View File

@ -1,3 +1,4 @@
import debounce from 'discourse/lib/debounce';
import searchForTerm from 'discourse/lib/search-for-term';
export default Discourse.View.extend({
@ -18,7 +19,7 @@ export default Discourse.View.extend({
this.set('loading', false);
}.observes('topics'),
search: Discourse.debounce(function(title) {
search: debounce(function(title) {
var self = this;
if (Em.isEmpty(title)) {
self.setProperties({ topics: null, loading: false });

View File

@ -3,6 +3,7 @@ import afterTransition from 'discourse/lib/after-transition';
import loadScript from 'discourse/lib/load-script';
import avatarTemplate from 'discourse/lib/avatar-template';
import positioningWorkaround from 'discourse/lib/safari-hacks';
import debounce from 'discourse/lib/debounce';
import { linkSeenMentions, fetchUnseenMentions } from 'discourse/lib/link-mentions';
const ComposerView = Discourse.View.extend(Ember.Evented, {
@ -40,7 +41,7 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
return this.present('model.createdPost') ? 'created-post' : null;
}.property('model.createdPost'),
refreshPreview: Discourse.debounce(function() {
refreshPreview: debounce(function() {
if (this.editor) {
this.editor.refreshPreview();
}
@ -279,7 +280,7 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
this.set('editor', this.editor);
this.loadingChanged();
const saveDraft = Discourse.debounce((function() {
const saveDraft = debounce((function() {
return self.get('controller').saveDraft();
}), 2000);

View File

@ -1,5 +1,6 @@
import ScreenTrack from 'discourse/lib/screen-track';
import { number } from 'discourse/lib/formatter';
import DiscourseURL from 'discourse/lib/url';
const DAY = 60 * 50 * 1000;
@ -199,7 +200,7 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, {
self = this;
if (Discourse.Mobile.mobileView) {
Discourse.URL.routeTo(this.get('post.topic').urlForPostNumber(replyPostNumber));
DiscourseURL.routeTo(this.get('post.topic').urlForPostNumber(replyPostNumber));
return;
}

View File

@ -9,6 +9,9 @@
//= require ./discourse/lib/notification-levels
//= require ./discourse/lib/app-events
//= require ./discourse/lib/avatar-template
//= require ./discourse/lib/url
//= require ./discourse/lib/debounce
//= require ./discourse/lib/quote
//= require ./discourse/helpers/i18n
//= require ./discourse/helpers/fa-icon
//= require ./discourse/helpers/register-unbound
@ -41,7 +44,9 @@
//= require ./discourse/models/topic-details
//= require ./discourse/models/topic
//= require ./discourse/models/user-action
//= require ./discourse/models/draft
//= require ./discourse/models/composer
//= require ./discourse/models/invite
//= require ./discourse/controllers/controller
//= require ./discourse/controllers/discovery-sortable
//= require ./discourse/controllers/object

View File

@ -1,6 +1,6 @@
//= require logster
//= require ./env
//= require ./discourse/lib/probes.js
//= require probes.js
//= require handlebars.js
//= require jquery_include.js
@ -19,6 +19,8 @@
//= require bootstrap-modal.js
//= require bootstrap-transition.js
//= require select2.js
//= require div_resizer
//= require caret_position
//= require favcount.js
//= require jquery.ba-replacetext.js
//= require jquery.ba-resize.min.js

View File

@ -80,7 +80,6 @@ module PrettyText
"app/assets/javascripts/defer/html-sanitizer-bundle.js",
"app/assets/javascripts/discourse/dialects/dialect.js",
"app/assets/javascripts/discourse/lib/utilities.js",
"app/assets/javascripts/discourse/lib/html.js",
"app/assets/javascripts/discourse/lib/markdown.js",
)

View File

@ -1,3 +1,4 @@
import DiscourseURL from 'discourse/lib/url';
import { acceptance } from "helpers/qunit-helpers";
acceptance("Category Edit", { loggedIn: true });
@ -24,7 +25,7 @@ test("Change the category color", (assert) => {
click('#save-category');
andThen(() => {
assert.ok(!visible('#discourse-modal'), 'it closes the modal');
assert.equal(Discourse.URL.redirectedTo, '/c/bug', 'it does one of the rare full page redirects');
assert.equal(DiscourseURL.redirectedTo, '/c/bug', 'it does one of the rare full page redirects');
});
});
@ -37,6 +38,6 @@ test("Change the topic template", (assert) => {
click('#save-category');
andThen(() => {
assert.ok(!visible('#discourse-modal'), 'it closes the modal');
assert.equal(Discourse.URL.redirectedTo, '/c/bug', 'it does one of the rare full page redirects');
assert.equal(DiscourseURL.redirectedTo, '/c/bug', 'it does one of the rare full page redirects');
});
});

View File

@ -1,6 +1,9 @@
var testMouseTrap;
import DiscourseURL from 'discourse/lib/url';
module("Discourse.KeyboardShortcuts", {
var testMouseTrap;
import KeyboardShortcuts from 'discourse/lib/keyboard-shortcuts';
module("lib:keyboard-shortcuts", {
setup: function() {
var _bindings = {};
@ -23,7 +26,7 @@ module("Discourse.KeyboardShortcuts", {
}
};
sandbox.stub(Discourse.URL, "routeTo");
sandbox.stub(DiscourseURL, "routeTo");
$("#qunit-fixture").html([
"<article class='topic-post selected'>",
@ -64,20 +67,20 @@ module("Discourse.KeyboardShortcuts", {
}
});
var pathBindings = Discourse.KeyboardShortcuts.PATH_BINDINGS;
var pathBindings = KeyboardShortcuts.PATH_BINDINGS;
_.each(pathBindings, function(path, binding) {
var testName = binding + " goes to " + path;
test(testName, function() {
Discourse.KeyboardShortcuts.bindEvents(testMouseTrap);
KeyboardShortcuts.bindEvents(testMouseTrap);
testMouseTrap.trigger(binding);
ok(Discourse.URL.routeTo.calledWith(path));
ok(DiscourseURL.routeTo.calledWith(path));
});
});
var clickBindings = Discourse.KeyboardShortcuts.CLICK_BINDINGS;
var clickBindings = KeyboardShortcuts.CLICK_BINDINGS;
_.each(clickBindings, function(selector, binding) {
var bindings = binding.split(",");
@ -85,7 +88,7 @@ _.each(clickBindings, function(selector, binding) {
var testName = binding + " clicks on " + selector;
test(testName, function() {
Discourse.KeyboardShortcuts.bindEvents(testMouseTrap);
KeyboardShortcuts.bindEvents(testMouseTrap);
$(selector).on("click", function() {
ok(true, selector + " was clicked");
});
@ -96,32 +99,32 @@ _.each(clickBindings, function(selector, binding) {
});
});
var functionBindings = Discourse.KeyboardShortcuts.FUNCTION_BINDINGS;
var functionBindings = KeyboardShortcuts.FUNCTION_BINDINGS;
_.each(functionBindings, function(func, binding) {
var testName = binding + " calls " + func;
test(testName, function() {
sandbox.stub(Discourse.KeyboardShortcuts, func, function() {
sandbox.stub(KeyboardShortcuts, func, function() {
ok(true, func + " is called when " + binding + " is triggered");
});
Discourse.KeyboardShortcuts.bindEvents(testMouseTrap);
KeyboardShortcuts.bindEvents(testMouseTrap);
testMouseTrap.trigger(binding);
});
});
test("selectDown calls _moveSelection with 1", function() {
var spy = sandbox.spy(Discourse.KeyboardShortcuts, '_moveSelection');
var spy = sandbox.spy(KeyboardShortcuts, '_moveSelection');
Discourse.KeyboardShortcuts.selectDown();
KeyboardShortcuts.selectDown();
ok(spy.calledWith(1), "_moveSelection is called with 1");
});
test("selectUp calls _moveSelection with -1", function() {
var spy = sandbox.spy(Discourse.KeyboardShortcuts, '_moveSelection');
var spy = sandbox.spy(KeyboardShortcuts, '_moveSelection');
Discourse.KeyboardShortcuts.selectUp();
KeyboardShortcuts.selectUp();
ok(spy.calledWith(-1), "_moveSelection is called with -1");
});
@ -131,20 +134,20 @@ test("goBack calls history.back", function() {
called = true;
});
Discourse.KeyboardShortcuts.goBack();
KeyboardShortcuts.goBack();
ok(called, "history.back is called");
});
test("nextSection calls _changeSection with 1", function() {
var spy = sandbox.spy(Discourse.KeyboardShortcuts, '_changeSection');
var spy = sandbox.spy(KeyboardShortcuts, '_changeSection');
Discourse.KeyboardShortcuts.nextSection();
KeyboardShortcuts.nextSection();
ok(spy.calledWith(1), "_changeSection is called with 1");
});
test("prevSection calls _changeSection with -1", function() {
var spy = sandbox.spy(Discourse.KeyboardShortcuts, '_changeSection');
var spy = sandbox.spy(KeyboardShortcuts, '_changeSection');
Discourse.KeyboardShortcuts.prevSection();
KeyboardShortcuts.prevSection();
ok(spy.calledWith(-1), "_changeSection is called with -1");
});

View File

@ -0,0 +1,14 @@
module("helper:custom-html");
import { getCustomHTML, setCustomHTML } from 'discourse/helpers/custom-html';
test("customHTML", function() {
blank(getCustomHTML('evil'), "there is no custom HTML for a key by default");
setCustomHTML('evil', 'trout');
equal(getCustomHTML('evil'), 'trout', 'it retrieves the custom html');
PreloadStore.store('customHTML', {cookie: 'monster'});
equal(getCustomHTML('cookie'), 'monster', 'it returns HTML fragments from the PreloadStore');
});

View File

@ -0,0 +1,8 @@
/* global Tautologistics */
export default function parseHTML(rawHtml) {
const builder = new Tautologistics.NodeHtmlParser.HtmlBuilder();
const parser = new Tautologistics.NodeHtmlParser.Parser(builder);
parser.parseComplete(rawHtml);
return builder.dom;
}

View File

@ -1,9 +0,0 @@
/* global Tautologistics */
/* exported parseHTML */
function parseHTML(rawHtml) {
var builder = new Tautologistics.NodeHtmlParser.HtmlBuilder(),
parser = new Tautologistics.NodeHtmlParser.Parser(builder);
parser.parseComplete(rawHtml);
return builder.dom;
}

View File

@ -1,3 +1,5 @@
import Quote from 'discourse/lib/quote';
module("Discourse.BBCode");
var format = function(input, expected, text) {
@ -90,7 +92,7 @@ test("quotes", function() {
});
var formatQuote = function(val, expected, text) {
equal(Discourse.Quote.build(post, val), expected, text);
equal(Quote.build(post, val), expected, text);
};
formatQuote(undefined, "", "empty string for undefined content");

View File

@ -1,5 +1,6 @@
module("lib:category-link");
import parseHTML from 'helpers/parse-html';
import { categoryBadgeHTML } from "discourse/helpers/category-link";
test("categoryBadge without a category", function() {

View File

@ -1,3 +1,4 @@
import DiscourseURL from "discourse/lib/url";
import ClickTrack from "discourse/lib/click-track";
var windowOpen,
@ -9,7 +10,7 @@ module("lib:click-track", {
// Prevent any of these tests from navigating away
win = {focus: function() { } };
redirectTo = sandbox.stub(Discourse.URL, "redirectTo");
redirectTo = sandbox.stub(DiscourseURL, "redirectTo");
sandbox.stub(Discourse, "ajax");
windowOpen = sandbox.stub(window, "open").returns(win);
sandbox.stub(win, "focus");
@ -51,7 +52,7 @@ test("it calls preventDefault when clicking on an a", function() {
sandbox.stub(clickEvent, "preventDefault");
track(clickEvent);
ok(clickEvent.preventDefault.calledOnce);
ok(Discourse.URL.redirectTo.calledOnce);
ok(DiscourseURL.redirectTo.calledOnce);
});
test("does not track clicks on back buttons", function() {
@ -70,7 +71,7 @@ test("removes the href and put it as a data attribute", function() {
equal($link.data('href'), 'http://www.google.com');
blank($link.attr('href'));
ok($link.data('auto-route'));
ok(Discourse.URL.redirectTo.calledOnce);
ok(DiscourseURL.redirectTo.calledOnce);
});
asyncTest("restores the href after a while", function() {
@ -159,20 +160,20 @@ testOpenInANewTab("it opens in a new tab on middle click", function(clickEvent)
});
test("tracks via AJAX if we're on the same site", function() {
sandbox.stub(Discourse.URL, "routeTo");
sandbox.stub(Discourse.URL, "origin").returns("http://discuss.domain.com");
sandbox.stub(DiscourseURL, "routeTo");
sandbox.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
ok(!track(generateClickEventOn('#same-site')));
ok(Discourse.ajax.calledOnce);
ok(Discourse.URL.routeTo.calledOnce);
ok(DiscourseURL.routeTo.calledOnce);
});
test("does not track via AJAX for attachments", function() {
sandbox.stub(Discourse.URL, "routeTo");
sandbox.stub(Discourse.URL, "origin").returns("http://discuss.domain.com");
sandbox.stub(DiscourseURL, "routeTo");
sandbox.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
ok(!track(generateClickEventOn('.attachment')));
ok(Discourse.URL.redirectTo.calledOnce);
ok(DiscourseURL.redirectTo.calledOnce);
});
test("tracks custom urls when opening in another window", function() {

View File

@ -1,14 +0,0 @@
module("Discourse.HTML");
var html = Discourse.HTML;
test("customHTML", function() {
blank(html.getCustomHTML('evil'), "there is no custom HTML for a key by default");
html.setCustomHTML('evil', 'trout');
equal(html.getCustomHTML('evil'), 'trout', 'it retrieves the custom html');
PreloadStore.store('customHTML', {cookie: 'monster'});
equal(html.getCustomHTML('cookie'), 'monster', 'it returns HTML fragments from the PreloadStore');
});

View File

@ -1,16 +1,18 @@
module("Discourse.URL");
import DiscourseURL from 'discourse/lib/url';
module("lib:url");
test("isInternal with a HTTP url", function() {
sandbox.stub(Discourse.URL, "origin").returns("http://eviltrout.com");
sandbox.stub(DiscourseURL, "origin").returns("http://eviltrout.com");
not(Discourse.URL.isInternal(null), "a blank URL is not internal");
ok(Discourse.URL.isInternal("/test"), "relative URLs are internal");
ok(Discourse.URL.isInternal("http://eviltrout.com/tophat"), "a url on the same host is internal");
ok(Discourse.URL.isInternal("https://eviltrout.com/moustache"), "a url on a HTTPS of the same host is internal");
not(Discourse.URL.isInternal("http://twitter.com"), "a different host is not internal");
not(DiscourseURL.isInternal(null), "a blank URL is not internal");
ok(DiscourseURL.isInternal("/test"), "relative URLs are internal");
ok(DiscourseURL.isInternal("http://eviltrout.com/tophat"), "a url on the same host is internal");
ok(DiscourseURL.isInternal("https://eviltrout.com/moustache"), "a url on a HTTPS of the same host is internal");
not(DiscourseURL.isInternal("http://twitter.com"), "a different host is not internal");
});
test("isInternal with a HTTPS url", function() {
sandbox.stub(Discourse.URL, "origin").returns("https://eviltrout.com");
ok(Discourse.URL.isInternal("http://eviltrout.com/monocle"), "HTTPS urls match HTTP urls");
sandbox.stub(DiscourseURL, "origin").returns("https://eviltrout.com");
ok(DiscourseURL.isInternal("http://eviltrout.com/monocle"), "HTTPS urls match HTTP urls");
});

View File

@ -1,5 +1,7 @@
module("Discourse.Invite");
import Invite from 'discourse/models/invite';
module("model:invite");
test("create", function() {
ok(Discourse.Invite.create(), "it can be created without arguments");
ok(Invite.create(), "it can be created without arguments");
});

View File

@ -6,7 +6,7 @@
//= require ../../app/assets/javascripts/preload_store
// probe framework first
//= require ../../app/assets/javascripts/discourse/lib/probes
//= require probes
// Externals we need to load first
//= require jquery.debug
@ -80,6 +80,7 @@ var origDebounce = Ember.run.debounce,
fixtures = require('fixtures/site_fixtures', null, null, false).default,
flushMap = require('discourse/models/store', null, null, false).flushMap,
ScrollingDOMMethods = require('discourse/mixins/scrolling', null, null, false).ScrollingDOMMethods,
_DiscourseURL = require('discourse/lib/url', null, null, false).default,
server;
function dup(obj) {
@ -97,9 +98,9 @@ QUnit.testStart(function(ctx) {
Discourse.User.resetCurrent();
Discourse.Site.resetCurrent(Discourse.Site.create(dup(fixtures['site.json'].site)));
Discourse.URL.redirectedTo = null;
Discourse.URL.redirectTo = function(url) {
Discourse.URL.redirectedTo = url;
_DiscourseURL.redirectedTo = null;
_DiscourseURL.redirectTo = function(url) {
_DiscourseURL.redirectedTo = url;
};
PreloadStore.reset();