diff --git a/Gemfile.lock b/Gemfile.lock index 725ea9068b..d10b146ad8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,7 +145,7 @@ GEM thor (~> 0.15) libv8 (3.16.14.7) listen (0.7.3) - logster (0.8.4.1.pre) + logster (0.8.4.5.pre) lru_redux (1.1.0) mail (2.5.4) mime-types (~> 1.16) diff --git a/app/assets/javascripts/discourse/adapters/build-plugin.js.es6 b/app/assets/javascripts/admin/adapters/build-plugin.js.es6 similarity index 100% rename from app/assets/javascripts/discourse/adapters/build-plugin.js.es6 rename to app/assets/javascripts/admin/adapters/build-plugin.js.es6 diff --git a/app/assets/javascripts/discourse/adapters/user-field.js.es6 b/app/assets/javascripts/admin/adapters/customization-base.js.es6 similarity index 100% rename from app/assets/javascripts/discourse/adapters/user-field.js.es6 rename to app/assets/javascripts/admin/adapters/customization-base.js.es6 diff --git a/app/assets/javascripts/admin/adapters/site-text-type.js.es6 b/app/assets/javascripts/admin/adapters/site-text-type.js.es6 new file mode 100644 index 0000000000..b547b06f3c --- /dev/null +++ b/app/assets/javascripts/admin/adapters/site-text-type.js.es6 @@ -0,0 +1,2 @@ +import CustomizationBase from 'admin/adapters/customization-base'; +export default CustomizationBase; diff --git a/app/assets/javascripts/admin/adapters/site-text.js.es6 b/app/assets/javascripts/admin/adapters/site-text.js.es6 new file mode 100644 index 0000000000..b547b06f3c --- /dev/null +++ b/app/assets/javascripts/admin/adapters/site-text.js.es6 @@ -0,0 +1,2 @@ +import CustomizationBase from 'admin/adapters/customization-base'; +export default CustomizationBase; diff --git a/app/assets/javascripts/admin/adapters/user-field.js.es6 b/app/assets/javascripts/admin/adapters/user-field.js.es6 new file mode 100644 index 0000000000..b547b06f3c --- /dev/null +++ b/app/assets/javascripts/admin/adapters/user-field.js.es6 @@ -0,0 +1,2 @@ +import CustomizationBase from 'admin/adapters/customization-base'; +export default CustomizationBase; diff --git a/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 b/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 index bccec30932..d10280daf7 100644 --- a/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 +++ b/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 @@ -1,13 +1,14 @@ -import { bufferedProperty } from 'discourse/mixins/buffered-content'; import UserField from 'admin/models/user-field'; +import { bufferedProperty } from 'discourse/mixins/buffered-content'; import { popupAjaxError } from 'discourse/lib/ajax-error'; +import { propertyEqual } from 'discourse/lib/computed'; export default Ember.Component.extend(bufferedProperty('userField'), { editing: Ember.computed.empty('userField.id'), classNameBindings: [':user-field'], - cantMoveUp: Discourse.computed.propertyEqual('userField', 'firstField'), - cantMoveDown: Discourse.computed.propertyEqual('userField', 'lastField'), + cantMoveUp: propertyEqual('userField', 'firstField'), + cantMoveDown: propertyEqual('userField', 'lastField'), userFieldsDescription: function() { return I18n.t('admin.user_fields.description'); diff --git a/app/assets/javascripts/admin/components/customize-link.js.es6 b/app/assets/javascripts/admin/components/customize-link.js.es6 new file mode 100644 index 0000000000..79d8cc26f4 --- /dev/null +++ b/app/assets/javascripts/admin/components/customize-link.js.es6 @@ -0,0 +1,10 @@ +export default Ember.Component.extend({ + router: function() { + return this.container.lookup('router:main'); + }.property(), + + active: function() { + const id = this.get('customization.id'); + return this.get('router.url').indexOf(`/customize/css_html/${id}/css`) !== -1; + }.property('router.url', 'customization.id') +}); diff --git a/app/assets/javascripts/admin/components/group-member.js.es6 b/app/assets/javascripts/admin/components/group-member.js.es6 new file mode 100644 index 0000000000..abf0f2984a --- /dev/null +++ b/app/assets/javascripts/admin/components/group-member.js.es6 @@ -0,0 +1,9 @@ +export default Ember.Component.extend({ + classNames: ["item"], + + actions: { + remove() { + this.sendAction('removeAction', this.get('member')); + } + } +}); diff --git a/app/assets/javascripts/admin/components/site-setting.js.es6 b/app/assets/javascripts/admin/components/site-setting.js.es6 index c7375af4fc..10925e8a2e 100644 --- a/app/assets/javascripts/admin/components/site-setting.js.es6 +++ b/app/assets/javascripts/admin/components/site-setting.js.es6 @@ -1,20 +1,21 @@ import BufferedContent from 'discourse/mixins/buffered-content'; import ScrollTop from 'discourse/mixins/scroll-top'; import SiteSetting from 'admin/models/site-setting'; +import { propertyNotEqual } from 'discourse/lib/computed'; const CustomTypes = ['bool', 'enum', 'list', 'url_list', 'host_list']; export default Ember.Component.extend(BufferedContent, ScrollTop, { classNameBindings: [':row', ':setting', 'setting.overridden', 'typeClass'], content: Ember.computed.alias('setting'), - dirty: Discourse.computed.propertyNotEqual('buffered.value', 'setting.value'), + dirty: propertyNotEqual('buffered.value', 'setting.value'), validationMessage: null, preview: function() { const preview = this.get('setting.preview'); if (preview) { return new Handlebars.SafeString("
{{i18n 'admin.customize.about'}}
diff --git a/app/assets/javascripts/admin/templates/customize-css-html-show.hbs b/app/assets/javascripts/admin/templates/customize-css-html-show.hbs new file mode 100644 index 0000000000..95b82ba97b --- /dev/null +++ b/app/assets/javascripts/admin/templates/customize-css-html-show.hbs @@ -0,0 +1,75 @@ +{{i18n 'admin.customize.about'}}
-{{/if}} diff --git a/app/assets/javascripts/admin/templates/group.hbs b/app/assets/javascripts/admin/templates/group.hbs index d17167a66f..6260d12aab 100644 --- a/app/assets/javascripts/admin/templates/group.hbs +++ b/app/assets/javascripts/admin/templates/group.hbs @@ -1,31 +1,33 @@{{model.description}}
+ +{{#if model.markdown}} + {{pagedown-editor value=model.value}} +{{/if}} +{{#if model.plainText}} + {{textarea value=model.value class="plain"}} +{{/if}} +{{#if model.html}} + {{ace-editor content=model.value mode="html"}} +{{/if}} +{{#if model.css}} + {{ace-editor content=model.value mode="css"}} +{{/if}} + +{{description}}
- -{{#if markdown}} - {{pagedown-editor value=value}} -{{/if}} -{{#if plainText}} - {{textarea value=value class="plain"}} -{{/if}} -{{#if html}} - {{ace-editor content=value mode="html"}} -{{/if}} -{{#if css}} - {{ace-editor content=value mode="css"}} -{{/if}} - -");
buffer.push(formattedLogs);
@@ -38,13 +38,13 @@ export default Discourse.View.extend({
buffer.push("" + I18n.t("admin.backups.logs.none") + "
");
}
// add a loading indicator
- if (this.get("controller.status.isOperationRunning")) {
+ if (this.get("controller.status.model.isOperationRunning")) {
buffer.push(renderSpinner('small'));
}
},
_forceScrollToBottom: function() {
- var $div = this.$()[0];
+ const $div = this.$()[0];
$div.scrollTop = $div.scrollHeight;
}.on("didInsertElement")
diff --git a/app/assets/javascripts/admin/views/admin-customize.js.es6 b/app/assets/javascripts/admin/views/admin-customize.js.es6
new file mode 100644
index 0000000000..faca47026f
--- /dev/null
+++ b/app/assets/javascripts/admin/views/admin-customize.js.es6
@@ -0,0 +1,18 @@
+/*global Mousetrap:true */
+
+export default Ember.View.extend({
+ classNames: ['customize'],
+
+ _init: function() {
+ var controller = this.get('controller');
+ Mousetrap.bindGlobal('mod+s', function() {
+ controller.send("save");
+ return false;
+ });
+ }.on("didInsertElement"),
+
+ _cleanUp: function() {
+ Mousetrap.unbindGlobal('mod+s');
+ }.on("willDestroyElement")
+
+});
diff --git a/app/assets/javascripts/admin/views/admin_customize_view.js b/app/assets/javascripts/admin/views/admin_customize_view.js
deleted file mode 100644
index 37ecfaceb1..0000000000
--- a/app/assets/javascripts/admin/views/admin_customize_view.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/*global Mousetrap:true */
-
-/**
- A view to handle site customizations
-
- @class AdminCustomizeView
- @extends Discourse.View
- @namespace Discourse
- @module Discourse
-**/
-Discourse.AdminCustomizeView = Discourse.View.extend({
- templateName: 'admin/templates/customize',
- classNames: ['customize'],
- selected: 'stylesheet',
- mobile: false,
-
- stylesheetActive: Em.computed.equal('selected', 'stylesheet'),
- headerActive: Em.computed.equal('selected', 'header'),
- topActive: Em.computed.equal('selected', 'top'),
- footerActive: Em.computed.equal('selected', 'footer'),
- headTagActive: Em.computed.equal('selected', 'head_tag'),
- bodyTagActive: Em.computed.equal('selected', 'body_tag'),
-
- mobileStylesheetActive: Em.computed.equal('selected', 'mobile_stylesheet'),
- mobileHeaderActive: Em.computed.equal('selected', 'mobile_header'),
- mobileTopActive: Em.computed.equal('selected', 'mobile_top'),
- mobileFooterActive: Em.computed.equal('selected', 'mobile_footer'),
-
- actions: {
- toggleMobile: function() {
- // auto-select best tab
- var tab = this.get("selected");
- if (/_tag$/.test(tab)) { tab = "stylesheet"; }
- if (this.get("mobile")) { tab = tab.replace("mobile_", ""); }
- else { tab = "mobile_" + tab; }
- this.set("selected", tab);
- // toggle mobile
- this.toggleProperty("mobile");
- },
-
- select: function(tab) {
- this.set('selected', tab);
- },
-
- toggleMaximize: function() {
- this.set("maximized", !this.get("maximized"));
-
- Em.run.scheduleOnce('afterRender', this, function(){
- $('.ace-wrapper').each(function(){
- $(this).data("editor").resize();
- });
- });
- },
- },
-
- _init: function() {
- var controller = this.get('controller');
- Mousetrap.bindGlobal('mod+s', function() {
- controller.send("save");
- return false;
- });
- }.on("didInsertElement"),
-
- _cleanUp: function() {
- Mousetrap.unbindGlobal('mod+s');
- }.on("willDestroyElement")
-
-});
diff --git a/app/assets/javascripts/admin/views/group-member.js.es6 b/app/assets/javascripts/admin/views/group-member.js.es6
deleted file mode 100644
index 7889fd59cf..0000000000
--- a/app/assets/javascripts/admin/views/group-member.js.es6
+++ /dev/null
@@ -1,4 +0,0 @@
-export default Discourse.View.extend({
- classNames: ["item"],
- templateName: "admin/templates/group_member"
-});
diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js
index 2bddc91c2f..70a5af9e4a 100644
--- a/app/assets/javascripts/discourse.js
+++ b/app/assets/javascripts/discourse.js
@@ -140,3 +140,18 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
// TODO: Remove this, it is in for backwards compatibiltiy with plugins
Discourse.HasCurrentUser = {};
+
+function proxyDep(propName, moduleFunc, msg) {
+ if (Discourse.hasOwnProperty(propName)) { return; }
+ Object.defineProperty(Discourse, propName, {
+ get: function() {
+ msg = msg || "import the module";
+ Ember.warn("DEPRECATION: `Discourse." + propName + "` is deprecated, " + msg + ".");
+ return moduleFunc();
+ }
+ });
+}
+
+proxyDep('computed', function() { return require('discourse/lib/computed') });
+proxyDep('Formatter', function() { return require('discourse/lib/formatter') });
+proxyDep('PageTracker', function() { return require('discourse/lib/page-tracker').default });
diff --git a/app/assets/javascripts/discourse/adapters/rest.js.es6 b/app/assets/javascripts/discourse/adapters/rest.js.es6
index 5a64730aba..5e427a8658 100644
--- a/app/assets/javascripts/discourse/adapters/rest.js.es6
+++ b/app/assets/javascripts/discourse/adapters/rest.js.es6
@@ -1,4 +1,4 @@
-const ADMIN_MODELS = ['plugin'];
+const ADMIN_MODELS = ['plugin', 'site-customization'];
export function Result(payload, responseJson) {
this.payload = payload;
diff --git a/app/assets/javascripts/discourse/components/actions-summary.js.es6 b/app/assets/javascripts/discourse/components/actions-summary.js.es6
index 31c1459527..21c2076aa9 100644
--- a/app/assets/javascripts/discourse/components/actions-summary.js.es6
+++ b/app/assets/javascripts/discourse/components/actions-summary.js.es6
@@ -1,5 +1,6 @@
import StringBuffer from 'discourse/mixins/string-buffer';
import { iconHTML } from 'discourse/helpers/fa-icon';
+import { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
export default Ember.Component.extend(StringBuffer, {
tagName: 'section',
@@ -57,7 +58,7 @@ export default Ember.Component.extend(StringBuffer, {
buffer.push("" +
iconHTML('fa-trash-o') + ' ' +
Discourse.Utilities.tinyAvatar(post.get('postDeletedBy.avatar_template'), {title: post.get('postDeletedBy.username')}) +
- Discourse.Formatter.autoUpdatingRelativeAge(new Date(post.get('postDeletedAt'))) +
+ autoUpdatingRelativeAge(new Date(post.get('postDeletedAt'))) +
"");
}
},
diff --git a/app/assets/javascripts/discourse/components/category-drop.js.es6 b/app/assets/javascripts/discourse/components/category-drop.js.es6
index bb63d0bbf9..696991853c 100644
--- a/app/assets/javascripts/discourse/components/category-drop.js.es6
+++ b/app/assets/javascripts/discourse/components/category-drop.js.es6
@@ -1,8 +1,9 @@
+import { setting } from 'discourse/lib/computed';
var get = Ember.get;
export default Ember.Component.extend({
classNameBindings: ['category::no-category', 'categories:has-drop','categoryStyle'],
- categoryStyle: Discourse.computed.setting('category_style'),
+ categoryStyle: setting('category_style'),
tagName: 'li',
diff --git a/app/assets/javascripts/discourse/components/date-picker.js.es6 b/app/assets/javascripts/discourse/components/date-picker.js.es6
new file mode 100644
index 0000000000..c3e92ad474
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/date-picker.js.es6
@@ -0,0 +1,30 @@
+/* global Pikaday:true */
+import loadScript from "discourse/lib/load-script";
+
+export default Em.Component.extend({
+ tagName: "input",
+ classNames: ["date-picker"],
+ _picker: null,
+
+ _loadDatePicker: function() {
+ const self = this,
+ input = this.$()[0];
+
+ loadScript("/javascripts/pikaday.js").then(function() {
+ self._picker = new Pikaday({
+ field: input,
+ format: "YYYY-MM-DD",
+ defaultDate: moment().add(1, "day").toDate(),
+ minDate: new Date(),
+ onSelect: function(date) {
+ self.set("value", moment(date).format("YYYY-MM-DD"));
+ },
+ });
+ });
+ }.on("didInsertElement"),
+
+ _destroy: function() {
+ this._picker = null;
+ }.on("willDestroyElement"),
+
+});
diff --git a/app/assets/javascripts/discourse/components/dropdown-button.js.es6 b/app/assets/javascripts/discourse/components/dropdown-button.js.es6
index 3747452e35..99d211bbe3 100644
--- a/app/assets/javascripts/discourse/components/dropdown-button.js.es6
+++ b/app/assets/javascripts/discourse/components/dropdown-button.js.es6
@@ -24,8 +24,11 @@ export default Ember.Component.extend(StringBuffer, {
}.on('willDestroyElement'),
renderString(buffer) {
+ const title = this.get('title');
+ if (title) {
+ buffer.push("" + title + "
");
+ }
- buffer.push("" + this.get('title') + "
");
buffer.push("");
diff --git a/app/assets/javascripts/discourse/components/edit-category-settings.js.es6 b/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
index 3bba6b1b24..049157609e 100644
--- a/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
+++ b/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
@@ -1,6 +1,7 @@
+import { setting } from 'discourse/lib/computed';
import { buildCategoryPanel } from 'discourse/components/edit-category-panel';
export default buildCategoryPanel('settings', {
- emailInEnabled: Discourse.computed.setting('email_in'),
- showPositionInput: Discourse.computed.setting('fixed_category_positions'),
+ emailInEnabled: setting('email_in'),
+ showPositionInput: setting('fixed_category_positions'),
});
diff --git a/app/assets/javascripts/discourse/components/edit-category-tab.js.es6 b/app/assets/javascripts/discourse/components/edit-category-tab.js.es6
index 72c95192ac..9e47066067 100644
--- a/app/assets/javascripts/discourse/components/edit-category-tab.js.es6
+++ b/app/assets/javascripts/discourse/components/edit-category-tab.js.es6
@@ -1,3 +1,5 @@
+import { propertyEqual } from 'discourse/lib/computed';
+
export default Em.Component.extend({
tagName: 'li',
classNameBindings: ['active', 'tabClassName'],
@@ -6,7 +8,7 @@ export default Em.Component.extend({
return 'edit-category-' + this.get('tab');
}.property('tab'),
- active: Discourse.computed.propertyEqual('selectedTab', 'tab'),
+ active: propertyEqual('selectedTab', 'tab'),
title: function() {
return I18n.t('category.' + this.get('tab').replace('-', '_'));
diff --git a/app/assets/javascripts/discourse/components/home-logo.js.es6 b/app/assets/javascripts/discourse/components/home-logo.js.es6
index 83ca9ec8da..d4b429ef03 100644
--- a/app/assets/javascripts/discourse/components/home-logo.js.es6
+++ b/app/assets/javascripts/discourse/components/home-logo.js.es6
@@ -1,3 +1,5 @@
+import { setting } from 'discourse/lib/computed';
+
export default Ember.Component.extend({
classNames: ["title"],
@@ -13,10 +15,10 @@ export default Ember.Component.extend({
return Discourse.Mobile.mobileView && !Ember.isBlank(this.get('mobileBigLogoUrl'));
}.property(),
- smallLogoUrl: Discourse.computed.setting('logo_small_url'),
- bigLogoUrl: Discourse.computed.setting('logo_url'),
- mobileBigLogoUrl: Discourse.computed.setting('mobile_logo_url'),
- title: Discourse.computed.setting('title'),
+ smallLogoUrl: setting('logo_small_url'),
+ bigLogoUrl: setting('logo_url'),
+ mobileBigLogoUrl: setting('mobile_logo_url'),
+ title: setting('title'),
click: function(e) {
// if they want to open in a new tab, let it so
diff --git a/app/assets/javascripts/discourse/components/notification-item.js.es6 b/app/assets/javascripts/discourse/components/notification-item.js.es6
index d4692458cb..f0add4c2f3 100644
--- a/app/assets/javascripts/discourse/components/notification-item.js.es6
+++ b/app/assets/javascripts/discourse/components/notification-item.js.es6
@@ -30,9 +30,9 @@ export default Ember.Component.extend({
}
if (it.get('notification_type') === INVITED_TYPE) {
- return Discourse.getURL('/my/invited');
+ return Discourse.getURL('/users/' + it.get('data.display_username'));
}
- }.property("notification.data.{badge_id,badge_name}", "model.slug", "model.topic_id", "model.post_number"),
+ }.property("notification.data.{badge_id,badge_name,display_username}", "model.slug", "model.topic_id", "model.post_number"),
description: function() {
const badgeName = this.get("notification.data.badge_name");
diff --git a/app/assets/javascripts/discourse/components/poster-name.js.es6 b/app/assets/javascripts/discourse/components/poster-name.js.es6
index 3c5738cac4..5a2a7bd8ea 100644
--- a/app/assets/javascripts/discourse/components/poster-name.js.es6
+++ b/app/assets/javascripts/discourse/components/poster-name.js.es6
@@ -1,6 +1,8 @@
+import { setting } from 'discourse/lib/computed';
+
const PosterNameComponent = Em.Component.extend({
classNames: ['names', 'trigger-user-card'],
- displayNameOnPosts: Discourse.computed.setting('display_name_on_posts'),
+ displayNameOnPosts: setting('display_name_on_posts'),
// sanitize name for comparison
sanitizeName(name){
diff --git a/app/assets/javascripts/discourse/components/small-action.js.es6 b/app/assets/javascripts/discourse/components/small-action.js.es6
index 4457c6fb49..54ef41b9fa 100644
--- a/app/assets/javascripts/discourse/components/small-action.js.es6
+++ b/app/assets/javascripts/discourse/components/small-action.js.es6
@@ -1,3 +1,5 @@
+import { relativeAge } from 'discourse/lib/formatter';
+
const icons = {
'closed.enabled': 'lock',
'closed.disabled': 'unlock-alt',
@@ -19,7 +21,7 @@ export function actionDescription(actionCode, createdAt) {
const ac = this.get(actionCode);
if (ac) {
const dt = new Date(this.get(createdAt));
- const when = Discourse.Formatter.relativeAge(dt, {format: 'medium-with-ago'});
+ const when = relativeAge(dt, {format: 'medium-with-ago'});
return I18n.t(`action_codes.${ac}`, {when}).htmlSafe();
}
}.property(actionCode, createdAt);
diff --git a/app/assets/javascripts/discourse/components/stream-item.js.es6 b/app/assets/javascripts/discourse/components/stream-item.js.es6
index 9a29d87438..562ac59f03 100644
--- a/app/assets/javascripts/discourse/components/stream-item.js.es6
+++ b/app/assets/javascripts/discourse/components/stream-item.js.es6
@@ -1,8 +1,9 @@
+import { propertyEqual } from 'discourse/lib/computed';
import { actionDescription } from "discourse/components/small-action";
export default Ember.Component.extend({
classNameBindings: [":item", "item.hidden", "item.deleted", "moderatorAction"],
- moderatorAction: Discourse.computed.propertyEqual("item.post_type", "site.post_types.moderator_action"),
+ moderatorAction: propertyEqual("item.post_type", "site.post_types.moderator_action"),
actionDescription: actionDescription("item.action_code", "item.created_at"),
actions: {
diff --git a/app/assets/javascripts/discourse/components/user-field.js.es6 b/app/assets/javascripts/discourse/components/user-field.js.es6
index 625067622e..86caf3b5d6 100644
--- a/app/assets/javascripts/discourse/components/user-field.js.es6
+++ b/app/assets/javascripts/discourse/components/user-field.js.es6
@@ -1,6 +1,8 @@
+import { fmt } from 'discourse/lib/computed';
+
export default Ember.Component.extend({
classNameBindings: [':user-field', 'field.field_type'],
- layoutName: Discourse.computed.fmt('field.field_type', 'components/user-fields/%@'),
+ layoutName: fmt('field.field_type', 'components/user-fields/%@'),
noneLabel: function() {
if (!this.get('field.required')) {
diff --git a/app/assets/javascripts/discourse/components/user-small.js.es6 b/app/assets/javascripts/discourse/components/user-small.js.es6
index b89c5d01a4..9dc0699381 100644
--- a/app/assets/javascripts/discourse/components/user-small.js.es6
+++ b/app/assets/javascripts/discourse/components/user-small.js.es6
@@ -1,7 +1,9 @@
+import { url } from 'discourse/lib/computed';
+
export default Ember.Component.extend({
classNames: ['user-small'],
- userPath: Discourse.computed.url('user.username', '/users/%@'),
+ userPath: url('user.username', '/users/%@'),
name: function() {
const name = this.get('user.name');
diff --git a/app/assets/javascripts/discourse/controllers/application.js.es6 b/app/assets/javascripts/discourse/controllers/application.js.es6
index 6017af3214..2f0b4cec5f 100644
--- a/app/assets/javascripts/discourse/controllers/application.js.es6
+++ b/app/assets/javascripts/discourse/controllers/application.js.es6
@@ -1,4 +1,5 @@
export default Ember.Controller.extend({
+ showTop: true,
showFooter: false,
styleCategory: null,
diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6
index c4f1ac21b4..f724a6cbce 100644
--- a/app/assets/javascripts/discourse/controllers/composer.js.es6
+++ b/app/assets/javascripts/discourse/controllers/composer.js.es6
@@ -1,3 +1,4 @@
+import { setting } from 'discourse/lib/computed';
import Presence from 'discourse/mixins/presence';
export default Ember.ObjectController.extend(Presence, {
@@ -8,7 +9,7 @@ export default Ember.ObjectController.extend(Presence, {
showEditReason: false,
editReason: null,
- maxTitleLength: Discourse.computed.setting('max_topic_title_length'),
+ maxTitleLength: setting('max_topic_title_length'),
scopedCategoryId: null,
similarTopics: null,
similarTopicsMessage: null,
diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6
index a668d4cc88..d8995ad6fe 100644
--- a/app/assets/javascripts/discourse/controllers/create-account.js.es6
+++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6
@@ -1,5 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller';
+import { setting } from 'discourse/lib/computed';
export default DiscourseController.extend(ModalFunctionality, {
needs: ['login'],
@@ -16,10 +17,10 @@ export default DiscourseController.extend(ModalFunctionality, {
userFields: null,
hasAuthOptions: Em.computed.notEmpty('authOptions'),
- canCreateLocal: Discourse.computed.setting('enable_local_logins'),
+ canCreateLocal: setting('enable_local_logins'),
showCreateForm: Em.computed.or('hasAuthOptions', 'canCreateLocal'),
- maxUsernameLength: Discourse.computed.setting('max_username_length'),
- minUsernameLength: Discourse.computed.setting('min_username_length'),
+ maxUsernameLength: setting('max_username_length'),
+ minUsernameLength: setting('min_username_length'),
resetForm() {
// We wrap the fields in a structure so we can assign a value
diff --git a/app/assets/javascripts/discourse/controllers/directory-item.js.es6 b/app/assets/javascripts/discourse/controllers/directory-item.js.es6
index 8858982408..2d09b3824f 100644
--- a/app/assets/javascripts/discourse/controllers/directory-item.js.es6
+++ b/app/assets/javascripts/discourse/controllers/directory-item.js.es6
@@ -1,3 +1,5 @@
+import { propertyEqual } from 'discourse/lib/computed';
+
export default Ember.Controller.extend({
- me: Discourse.computed.propertyEqual('model.user.id', 'currentUser.id')
+ me: propertyEqual('model.user.id', 'currentUser.id')
});
diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6
index 71a316ef02..ac534b9618 100644
--- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6
+++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6
@@ -1,6 +1,7 @@
import DiscoveryController from 'discourse/controllers/discovery';
import { queryParams } from 'discourse/controllers/discovery-sortable';
import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection';
+import { endWith } from 'discourse/lib/computed';
const controllerOpts = {
needs: ['discovery'],
@@ -102,8 +103,8 @@ const controllerOpts = {
hasTopics: Em.computed.gt('model.topics.length', 0),
allLoaded: Em.computed.empty('model.more_topics_url'),
- latest: Discourse.computed.endWith('model.filter', 'latest'),
- new: Discourse.computed.endWith('model.filter', 'new'),
+ latest: endWith('model.filter', 'latest'),
+ new: endWith('model.filter', 'new'),
top: Em.computed.notEmpty('period'),
yearly: Em.computed.equal('period', 'yearly'),
quarterly: Em.computed.equal('period', 'quarterly'),
diff --git a/app/assets/javascripts/discourse/controllers/group.js.es6 b/app/assets/javascripts/discourse/controllers/group.js.es6
index b7c7c18a63..dcd408673d 100644
--- a/app/assets/javascripts/discourse/controllers/group.js.es6
+++ b/app/assets/javascripts/discourse/controllers/group.js.es6
@@ -1,7 +1,4 @@
-import ObjectController from 'discourse/controllers/object';
-
-// The basic controller for a group
-export default ObjectController.extend({
+export default Ember.Controller.extend({
counts: null,
showing: null,
@@ -10,4 +7,3 @@ export default ObjectController.extend({
showingIndex: Em.computed.equal('showing', 'index'),
showingMembers: Em.computed.equal('showing', 'members')
});
-
diff --git a/app/assets/javascripts/discourse/controllers/group/members.js.es6 b/app/assets/javascripts/discourse/controllers/group/members.js.es6
index ca218875fb..794b5856a7 100644
--- a/app/assets/javascripts/discourse/controllers/group/members.js.es6
+++ b/app/assets/javascripts/discourse/controllers/group/members.js.es6
@@ -1,11 +1,13 @@
-export default Ember.ObjectController.extend({
+export default Ember.Controller.extend({
loading: false,
+ limit: null,
+ offset: null,
actions: {
loadMore() {
if (this.get("loading")) { return; }
// we've reached the end
- if (this.get("model.members.length") >= this.get("user_count")) { return; }
+ if (this.get("model.members.length") >= this.get("model.user_count")) { return; }
this.set("loading", true);
diff --git a/app/assets/javascripts/discourse/controllers/invite.js.es6 b/app/assets/javascripts/discourse/controllers/invite.js.es6
index 4529672277..048f984658 100644
--- a/app/assets/javascripts/discourse/controllers/invite.js.es6
+++ b/app/assets/javascripts/discourse/controllers/invite.js.es6
@@ -14,7 +14,7 @@ export default ObjectController.extend(Presence, ModalFunctionality, {
}.property(),
disabled: function() {
- if (this.get('saving')) return true;
+ if (this.get('model.saving')) return true;
if (this.blank('emailOrUsername')) return true;
const emailOrUsername = this.get('emailOrUsername').trim();
// when inviting to forum, email must be valid
@@ -22,14 +22,14 @@ export default ObjectController.extend(Presence, ModalFunctionality, {
// normal users (not admin) can't invite users to private topic via email
if (!this.get('isAdmin') && this.get('isPrivateTopic') && Discourse.Utilities.emailValid(emailOrUsername)) return true;
// when invting to private topic via email, group name must be specified
- if (this.get('isPrivateTopic') && this.blank('groupNames') && Discourse.Utilities.emailValid(emailOrUsername)) return true;
+ if (this.get('isPrivateTopic') && this.blank('model.groupNames') && Discourse.Utilities.emailValid(emailOrUsername)) return true;
if (this.get('model.details.can_invite_to')) return false;
return false;
- }.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'groupNames', 'saving'),
+ }.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'model.groupNames', 'model.saving'),
buttonTitle: function() {
- return this.get('saving') ? I18n.t('topic.inviting') : I18n.t('topic.invite_reply.action');
- }.property('saving'),
+ return this.get('model.saving') ? I18n.t('topic.inviting') : I18n.t('topic.invite_reply.action');
+ }.property('model.saving'),
// We are inviting to a topic if the model isn't the current user.
// The current user would mean we are inviting to the forum in general.
@@ -117,8 +117,8 @@ export default ObjectController.extend(Presence, ModalFunctionality, {
// Reset the modal to allow a new user to be invited.
reset() {
- this.setProperties({
- emailOrUsername: null,
+ this.set('emailOrUsername', null);
+ this.get('model').setProperties({
groupNames: null,
error: false,
saving: false,
@@ -131,13 +131,14 @@ export default ObjectController.extend(Presence, ModalFunctionality, {
createInvite() {
if (this.get('disabled')) { return; }
- const groupNames = this.get('groupNames'),
- userInvitedController = this.get('controllers.user-invited-show');
+ const groupNames = this.get('model.groupNames'),
+ userInvitedController = this.get('controllers.user-invited-show'),
+ model = this.get('model');
- this.setProperties({ saving: true, error: false });
+ model.setProperties({ saving: true, error: false });
return this.get('model').createInvite(this.get('emailOrUsername').trim(), groupNames).then(result => {
- this.setProperties({ saving: false, finished: true });
+ model.setProperties({ saving: false, finished: true });
if (!this.get('invitingToTopic')) {
Discourse.Invite.findInvitedBy(this.currentUser, userInvitedController.get('filter')).then(invite_model => {
userInvitedController.set('model', invite_model);
@@ -146,7 +147,7 @@ export default ObjectController.extend(Presence, ModalFunctionality, {
} else if (this.get('isMessage') && result && result.user) {
this.get('model.details.allowed_users').pushObject(result.user);
}
- }).catch(() => this.setProperties({ saving: false, error: true }));
+ }).catch(() => model.setProperties({ saving: false, error: true }));
}
}
diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6
index 641b6bc838..6d39640e5b 100644
--- a/app/assets/javascripts/discourse/controllers/login.js.es6
+++ b/app/assets/javascripts/discourse/controllers/login.js.es6
@@ -1,6 +1,7 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller';
import showModal from 'discourse/lib/show-modal';
+import { setting } from 'discourse/lib/computed';
// This is happening outside of the app via popup
const AuthErrors =
@@ -13,7 +14,7 @@ export default DiscourseController.extend(ModalFunctionality, {
loggingIn: false,
loggedIn: false,
- canLoginLocal: Discourse.computed.setting('enable_local_logins'),
+ canLoginLocal: setting('enable_local_logins'),
loginRequired: Em.computed.alias('controllers.application.loginRequired'),
resetForm: function() {
diff --git a/app/assets/javascripts/discourse/controllers/navigation/category.js.es6 b/app/assets/javascripts/discourse/controllers/navigation/category.js.es6
index 4abaf1da27..c21ed81341 100644
--- a/app/assets/javascripts/discourse/controllers/navigation/category.js.es6
+++ b/app/assets/javascripts/discourse/controllers/navigation/category.js.es6
@@ -1,7 +1,8 @@
import NavigationDefaultController from 'discourse/controllers/navigation/default';
+import { setting } from 'discourse/lib/computed';
export default NavigationDefaultController.extend({
- subcategoryListSetting: Discourse.computed.setting('show_subcategory_list'),
+ subcategoryListSetting: setting('show_subcategory_list'),
showingParentCategory: Em.computed.none('category.parentCategory'),
showingSubcategoryList: Em.computed.and('subcategoryListSetting', 'showingParentCategory'),
diff --git a/app/assets/javascripts/discourse/controllers/notifications.js.es6 b/app/assets/javascripts/discourse/controllers/notifications.js.es6
index f0ae8ad42e..e76d84e968 100644
--- a/app/assets/javascripts/discourse/controllers/notifications.js.es6
+++ b/app/assets/javascripts/discourse/controllers/notifications.js.es6
@@ -1,5 +1,7 @@
+import { url } from 'discourse/lib/computed';
+
export default Ember.ArrayController.extend({
needs: ['header'],
loadingNotifications: Em.computed.alias('controllers.header.loadingNotifications'),
- myNotificationsUrl: Discourse.computed.url('/my/notifications')
+ myNotificationsUrl: url('/my/notifications')
});
diff --git a/app/assets/javascripts/discourse/controllers/preferences.js.es6 b/app/assets/javascripts/discourse/controllers/preferences.js.es6
index 0cc11e05e9..cacbd4b9e1 100644
--- a/app/assets/javascripts/discourse/controllers/preferences.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences.js.es6
@@ -1,14 +1,15 @@
+import { setting } from 'discourse/lib/computed';
import ObjectController from 'discourse/controllers/object';
import CanCheckEmails from 'discourse/mixins/can-check-emails';
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default ObjectController.extend(CanCheckEmails, {
- allowAvatarUpload: Discourse.computed.setting('allow_uploaded_avatars'),
- allowUserLocale: Discourse.computed.setting('allow_user_locale'),
- ssoOverridesAvatar: Discourse.computed.setting('sso_overrides_avatar'),
- allowBackgrounds: Discourse.computed.setting('allow_profile_backgrounds'),
- editHistoryVisible: Discourse.computed.setting('edit_history_visible_to_public'),
+ allowAvatarUpload: setting('allow_uploaded_avatars'),
+ allowUserLocale: setting('allow_user_locale'),
+ ssoOverridesAvatar: setting('sso_overrides_avatar'),
+ allowBackgrounds: setting('allow_profile_backgrounds'),
+ editHistoryVisible: setting('edit_history_visible_to_public'),
selectedCategories: function(){
return [].concat(this.get("model.watchedCategories"),
@@ -40,7 +41,7 @@ export default ObjectController.extend(CanCheckEmails, {
cannotDeleteAccount: Em.computed.not('can_delete_account'),
deleteDisabled: Em.computed.or('saving', 'deleting', 'cannotDeleteAccount'),
- canEditName: Discourse.computed.setting('enable_names'),
+ canEditName: setting('enable_names'),
nameInstructions: function() {
return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions');
diff --git a/app/assets/javascripts/discourse/controllers/preferences/email.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/email.js.es6
index 50ee7857d2..547a9adbd7 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/email.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/email.js.es6
@@ -1,13 +1,6 @@
+import { propertyEqual } from 'discourse/lib/computed';
import ObjectController from 'discourse/controllers/object';
-/**
- This controller supports actions related to updating one's email address
-
- @class PreferencesEmailController
- @extends ObjectController
- @namespace Discourse
- @module Discourse
-**/
export default ObjectController.extend({
taken: false,
saving: false,
@@ -17,7 +10,7 @@ export default ObjectController.extend({
newEmailEmpty: Em.computed.empty('newEmail'),
saveDisabled: Em.computed.or('saving', 'newEmailEmpty', 'taken', 'unchanged'),
- unchanged: Discourse.computed.propertyEqual('newEmailLower', 'email'),
+ unchanged: propertyEqual('newEmailLower', 'email'),
newEmailLower: function() {
return this.get('newEmail').toLowerCase();
diff --git a/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/username.js.es6
index ed6699ad4a..f74250976b 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/username.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/username.js.es6
@@ -1,3 +1,4 @@
+import { setting, propertyEqual } from 'discourse/lib/computed';
import Presence from 'discourse/mixins/presence';
import ObjectController from 'discourse/controllers/object';
@@ -8,11 +9,11 @@ export default ObjectController.extend(Presence, {
errorMessage: null,
newUsername: null,
- maxLength: Discourse.computed.setting('max_username_length'),
- minLength: Discourse.computed.setting('min_username_length'),
+ maxLength: setting('max_username_length'),
+ minLength: setting('min_username_length'),
newUsernameEmpty: Em.computed.empty('newUsername'),
saveDisabled: Em.computed.or('saving', 'newUsernameEmpty', 'taken', 'unchanged', 'errorMessage'),
- unchanged: Discourse.computed.propertyEqual('newUsername', 'username'),
+ unchanged: propertyEqual('newUsername', 'username'),
checkTaken: function() {
if( this.get('newUsername') && this.get('newUsername').length < this.get('minLength') ) {
diff --git a/app/assets/javascripts/discourse/controllers/queued-post.js.es6 b/app/assets/javascripts/discourse/controllers/queued-post.js.es6
index 100b0b1586..a285537491 100644
--- a/app/assets/javascripts/discourse/controllers/queued-post.js.es6
+++ b/app/assets/javascripts/discourse/controllers/queued-post.js.es6
@@ -1,3 +1,4 @@
+import { propertyEqual } from 'discourse/lib/computed';
import BufferedContent from 'discourse/mixins/buffered-content';
import { popupAjaxError } from 'discourse/lib/ajax-error';
@@ -21,7 +22,7 @@ export default Ember.Controller.extend(BufferedContent, {
post: Ember.computed.alias('model'),
currentlyEditing: Ember.computed.alias('controllers.queued-posts.editing'),
- editing: Discourse.computed.propertyEqual('model', 'currentlyEditing'),
+ editing: propertyEqual('model', 'currentlyEditing'),
_confirmDelete: updateState('rejected', {deleteUser: true}),
diff --git a/app/assets/javascripts/discourse/controllers/search.js.es6 b/app/assets/javascripts/discourse/controllers/search.js.es6
index 9c44f0b2a9..82f893f050 100644
--- a/app/assets/javascripts/discourse/controllers/search.js.es6
+++ b/app/assets/javascripts/discourse/controllers/search.js.es6
@@ -1,14 +1,15 @@
import Presence from 'discourse/mixins/presence';
import searchForTerm from 'discourse/lib/search-for-term';
-var _dontSearch = false;
+let _dontSearch = false;
export default Em.Controller.extend(Presence, {
+ typeFilter: null,
contextType: function(key, value){
if(arguments.length > 1) {
// a bit hacky, consider cleaning this up, need to work through all observers though
- var context = $.extend({}, this.get('searchContext'));
+ const context = $.extend({}, this.get('searchContext'));
context.type = value;
this.set('searchContext', context);
}
@@ -29,8 +30,8 @@ export default Em.Controller.extend(Presence, {
return null;
}
- var url = '/search?q=' + encodeURIComponent(this.get('term'));
- var searchContext = this.get('searchContext');
+ let url = '/search?q=' + encodeURIComponent(this.get('term'));
+ const searchContext = this.get('searchContext');
if (this.get('searchContextEnabled') && searchContext) {
url += encodeURIComponent(" " + searchContext.type + ":" + searchContext.id);
@@ -41,14 +42,14 @@ export default Em.Controller.extend(Presence, {
}.property('searchContext','term','searchContextEnabled'),
fullSearchUrl: function(){
- var url = this.get('fullSearchUrlRelative');
+ const url = this.get('fullSearchUrlRelative');
if (url) {
return Discourse.getURL(url);
}
}.property('fullSearchUrlRelative'),
searchContextDescription: function(){
- var ctx = this.get('searchContext');
+ const ctx = this.get('searchContext');
if (ctx) {
switch(Em.get(ctx, 'type')) {
case 'topic':
@@ -71,7 +72,7 @@ export default Em.Controller.extend(Presence, {
// If we need to perform another search
newSearchNeeded: function() {
this.set('noResults', false);
- var term = (this.get('term') || '').trim();
+ const term = (this.get('term') || '').trim();
if (term.length >= Discourse.SiteSettings.min_search_term_length) {
this.set('loading', true);
@@ -82,23 +83,32 @@ export default Em.Controller.extend(Presence, {
this.set('selectedIndex', 0);
}.observes('term', 'typeFilter'),
- searchTerm: function(term, typeFilter) {
- var self = this;
+ searchTerm(term, typeFilter) {
+ const self = this;
- var context;
- if(this.get('searchContextEnabled')){
- context = this.get('searchContext');
+ // for cancelling debounced search
+ if (this._cancelSearch){
+ this._cancelSearch = null;
+ return;
}
- searchForTerm(term, {
- typeFilter: typeFilter,
- searchContext: context,
+ if (this._search) {
+ this._search.abort();
+ }
+
+ const searchContext = this.get('searchContextEnabled') ? this.get('searchContext') : null;
+
+ this._search = searchForTerm(term, {
+ typeFilter,
+ searchContext,
fullSearchUrl: this.get('fullSearchUrl')
- }).then(function(results) {
+ });
+
+ this._search.then(function(results) {
self.setProperties({ noResults: !results, content: results });
+ }).finally(function() {
self.set('loading', false);
- }).catch(function() {
- self.set('loading', false);
+ self._search = null;
});
},
@@ -112,22 +122,36 @@ export default Em.Controller.extend(Presence, {
}.observes('term'),
actions: {
- fullSearch: function() {
- var url = this.get('fullSearchUrlRelative');
+ fullSearch() {
+ const self = this;
+
+ if (this._search) {
+ this._search.abort();
+ }
+
+ // maybe we are debounced and delayed
+ // stop that as well
+ this._cancelSearch = true;
+ Em.run.later(function(){
+ self._cancelSearch = false;
+ }, 400);
+
+ const url = this.get('fullSearchUrlRelative');
if (url) {
Discourse.URL.routeTo(url);
}
},
- moreOfType: function(type) {
+
+ moreOfType(type) {
this.set('typeFilter', type);
},
- cancelType: function() {
+ cancelType() {
this.cancelTypeFilter();
}
},
- cancelTypeFilter: function() {
+ cancelTypeFilter() {
this.set('typeFilter', null);
}
});
diff --git a/app/assets/javascripts/discourse/controllers/share.js.es6 b/app/assets/javascripts/discourse/controllers/share.js.es6
index 2e662dc217..fa1c3ff390 100644
--- a/app/assets/javascripts/discourse/controllers/share.js.es6
+++ b/app/assets/javascripts/discourse/controllers/share.js.es6
@@ -1,11 +1,12 @@
import Sharing from 'discourse/lib/sharing';
+import { longDateNoYear } from 'discourse/lib/formatter';
export default Ember.Controller.extend({
needs: ['topic'],
title: Ember.computed.alias('controllers.topic.model.title'),
displayDate: function() {
- return Discourse.Formatter.longDateNoYear(new Date(this.get('date')));
+ return longDateNoYear(new Date(this.get('date')));
}.property('date'),
// Close the share controller
diff --git a/app/assets/javascripts/discourse/controllers/site-map.js.es6 b/app/assets/javascripts/discourse/controllers/site-map.js.es6
index af9ccd08c4..61192eb575 100644
--- a/app/assets/javascripts/discourse/controllers/site-map.js.es6
+++ b/app/assets/javascripts/discourse/controllers/site-map.js.es6
@@ -1,3 +1,5 @@
+import { url } from 'discourse/lib/computed';
+
export default Ember.ArrayController.extend({
needs: ['application', 'header'],
@@ -8,7 +10,7 @@ export default Ember.ArrayController.extend({
return Discourse.SiteSettings.faq_url ? Discourse.SiteSettings.faq_url : Discourse.getURL('/faq');
}.property(),
- badgesUrl: Discourse.computed.url('/badges'),
+ badgesUrl: url('/badges'),
showKeyboardShortcuts: function(){
return !Discourse.Mobile.mobileView && !this.capabilities.touch;
diff --git a/app/assets/javascripts/discourse/controllers/topic-unsubscribe.js.es6 b/app/assets/javascripts/discourse/controllers/topic-unsubscribe.js.es6
new file mode 100644
index 0000000000..a2611ebdcb
--- /dev/null
+++ b/app/assets/javascripts/discourse/controllers/topic-unsubscribe.js.es6
@@ -0,0 +1,7 @@
+export default Ember.Controller.extend({
+
+ stopNotificiationsText: function() {
+ return I18n.t("topic.unsubscribe.stop_notifications", { title: this.get("model.fancyTitle") });
+ }.property("model.fancyTitle"),
+
+});
diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6
index 7dbce6dbb4..5b9e0ae778 100644
--- a/app/assets/javascripts/discourse/controllers/topic.js.es6
+++ b/app/assets/javascripts/discourse/controllers/topic.js.es6
@@ -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 { setting } from 'discourse/lib/computed';
export default ObjectController.extend(SelectedPostsCount, BufferedContent, {
multiSelect: false,
@@ -18,7 +19,7 @@ export default ObjectController.extend(SelectedPostsCount, BufferedContent, {
firstPostExpanded: false,
retrying: false,
- maxTitleLength: Discourse.computed.setting('max_topic_title_length'),
+ maxTitleLength: setting('max_topic_title_length'),
contextChanged: function() {
this.set('controllers.search.searchContext', this.get('model.searchContext'));
@@ -285,8 +286,8 @@ export default ObjectController.extend(SelectedPostsCount, BufferedContent, {
self.rollbackBuffer();
self.set('editingTopic', false);
}).catch(function(error) {
- if (error && error.responseText) {
- bootbox.alert($.parseJSON(error.responseText).errors[0]);
+ if (error && error.jqXHR && error.jqXHR.responseText) {
+ bootbox.alert($.parseJSON(error.jqXHR.responseText).errors[0]);
} else {
bootbox.alert(I18n.t('generic_error'));
}
diff --git a/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 b/app/assets/javascripts/discourse/controllers/upload-customization.js.es6
index 09158f5375..d7a980a0b4 100644
--- a/app/assets/javascripts/discourse/controllers/upload-customization.js.es6
+++ b/app/assets/javascripts/discourse/controllers/upload-customization.js.es6
@@ -2,25 +2,19 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
export default Ember.Controller.extend(ModalFunctionality, {
notReady: Em.computed.not('ready'),
-
- needs: ['admin-customize-css-html'],
-
- title: "hi",
+ needs: ['adminCustomizeCssHtml'],
ready: function() {
- let parsed;
try {
- parsed = JSON.parse(this.get('customizationFile'));
+ const parsed = JSON.parse(this.get('customizationFile'));
+ return !!parsed["site_customization"];
} catch (e) {
return false;
}
-
- return !!parsed["site_customization"];
}.property('customizationFile'),
actions: {
createCustomization: function() {
- const self = this;
const object = JSON.parse(this.get('customizationFile')).site_customization;
// Slight fixup before creating object
@@ -28,24 +22,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
delete object.id;
delete object.key;
- const customization = Discourse.SiteCustomization.create(object);
-
- this.set('loading', true);
- customization.save().then(function(customization) {
- self.send('closeModal');
- self.set('loading', false);
-
- const parentController = self.get('controllers.admin-customize-css-html');
- parentController.pushObject(customization);
- parentController.set('selectedItem', customization);
- }).catch(function(xhr) {
- self.set('loading', false);
- if (xhr.responseJSON) {
- bootbox.alert(xhr.responseJSON.errors.join("
"));
- } else {
- bootbox.alert(I18n.t('generic_error'));
- }
- });
+ const controller = this.get('controllers.adminCustomizeCssHtml');
+ controller.send('newCustomization', object);
}
}
diff --git a/app/assets/javascripts/discourse/controllers/upload-selector.js.es6 b/app/assets/javascripts/discourse/controllers/upload-selector.js.es6
index edd2d56943..be9d21dafb 100644
--- a/app/assets/javascripts/discourse/controllers/upload-selector.js.es6
+++ b/app/assets/javascripts/discourse/controllers/upload-selector.js.es6
@@ -1,5 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller';
+import { setting } from 'discourse/lib/computed';
export default DiscourseController.extend(ModalFunctionality, {
remote: Em.computed.not("local"),
@@ -13,7 +14,7 @@ export default DiscourseController.extend(ModalFunctionality, {
});
}.on('init'),
- maxSize: Discourse.computed.setting('max_attachment_size_kb'),
+ maxSize: setting('max_attachment_size_kb'),
allowLocal: Em.computed.gt('maxSize', 0),
actions: {
diff --git a/app/assets/javascripts/discourse/controllers/user-card.js.es6 b/app/assets/javascripts/discourse/controllers/user-card.js.es6
index 2dc7dde29f..3279137dbf 100644
--- a/app/assets/javascripts/discourse/controllers/user-card.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-card.js.es6
@@ -1,3 +1,5 @@
+import { propertyNotEqual, setting } from 'discourse/lib/computed';
+
export default Ember.Controller.extend({
needs: ['topic', 'application'],
visible: false,
@@ -16,10 +18,10 @@ export default Ember.Controller.extend({
viewingTopic: Em.computed.match('controllers.application.currentPath', /^topic\./),
viewingAdmin: Em.computed.match('controllers.application.currentPath', /^admin\./),
showFilter: Em.computed.and('viewingTopic', 'postStream.hasNoFilters', 'enoughPostsForFiltering'),
- showName: Discourse.computed.propertyNotEqual('user.name', 'user.username'),
+ showName: propertyNotEqual('user.name', 'user.username'),
hasUserFilters: Em.computed.gt('postStream.userFilters.length', 0),
isSuspended: Em.computed.notEmpty('user.suspend_reason'),
- showBadges: Discourse.computed.setting('enable_badges'),
+ showBadges: setting('enable_badges'),
showMoreBadges: Em.computed.gt('moreBadgesCount', 0),
showDelete: Em.computed.and("viewingAdmin", "showName", "user.canBeDeleted"),
diff --git a/app/assets/javascripts/discourse/controllers/user-posts.js.es6 b/app/assets/javascripts/discourse/controllers/user-posts.js.es6
index dd2f58f695..e06143db48 100644
--- a/app/assets/javascripts/discourse/controllers/user-posts.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-posts.js.es6
@@ -1,7 +1,7 @@
-export default Ember.ObjectController.extend({
+export default Ember.Controller.extend({
needs: ["application"],
_showFooter: function() {
- this.set("controllers.application.showFooter", !this.get("canLoadMore"));
- }.observes("canLoadMore")
+ this.set("controllers.application.showFooter", !this.get("model.canLoadMore"));
+ }.observes("model.canLoadMore")
});
diff --git a/app/assets/javascripts/discourse/controllers/user.js.es6 b/app/assets/javascripts/discourse/controllers/user.js.es6
index 60fd3fc060..419e6f8a2f 100644
--- a/app/assets/javascripts/discourse/controllers/user.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user.js.es6
@@ -14,12 +14,6 @@ export default ObjectController.extend(CanCheckEmails, {
collapsedInfo: Em.computed.not('indexStream'),
- websiteName: function() {
- var website = this.get('model.website');
- if (Em.isEmpty(website)) { return; }
- return website.split("/")[2];
- }.property('model.website'),
-
linkWebsite: Em.computed.not('model.isBasic'),
removeNoFollow: function() {
diff --git a/app/assets/javascripts/discourse/helpers/application_helpers.js b/app/assets/javascripts/discourse/helpers/application.js.es6
similarity index 64%
rename from app/assets/javascripts/discourse/helpers/application_helpers.js
rename to app/assets/javascripts/discourse/helpers/application.js.es6
index 291e598b14..35c4f64936 100644
--- a/app/assets/javascripts/discourse/helpers/application_helpers.js
+++ b/app/assets/javascripts/discourse/helpers/application.js.es6
@@ -1,17 +1,17 @@
-var safe = Handlebars.SafeString;
+import registerUnbound from 'discourse/helpers/register-unbound';
+import avatarTemplate from 'discourse/lib/avatar-template';
+import { longDate, autoUpdatingRelativeAge, number } from 'discourse/lib/formatter';
-// TODO: Remove me when ES6ified
-var registerUnbound = require('discourse/helpers/register-unbound', null, null, true).default;
-var avatarTemplate = require('discourse/lib/avatar-template', null, null, true).default;
+const safe = Handlebars.SafeString;
Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
if (Em.isEmpty(user)) {
return new safe("");
}
- var username = Em.get(user, 'username');
+ const username = Em.get(user, 'username');
if (arguments.length < 4) { uploadId = Em.get(user, 'uploaded_avatar_id'); }
- var avatar = Em.get(user, 'avatar_template') || avatarTemplate(username, uploadId);
+ const avatar = Em.get(user, 'avatar_template') || avatarTemplate(username, uploadId);
return new safe(Discourse.Utilities.avatarImg({ size: size, avatarTemplate: avatar }));
}, 'username', 'uploaded_avatar_id', 'avatar_template');
@@ -24,30 +24,30 @@ Em.Handlebars.helper('bound-avatar-template', function(avatarTemplate, size) {
});
registerUnbound('raw-date', function(dt) {
- return Discourse.Formatter.longDate(new Date(dt));
+ return longDate(new Date(dt));
});
registerUnbound('age-with-tooltip', function(dt) {
- return new safe(Discourse.Formatter.autoUpdatingRelativeAge(new Date(dt), {title: true}));
+ return new safe(autoUpdatingRelativeAge(new Date(dt), {title: true}));
});
registerUnbound('number', function(orig, params) {
orig = parseInt(orig, 10);
if (isNaN(orig)) { orig = 0; }
- var title = orig;
+ let title = orig;
if (params.numberKey) {
title = I18n.t(params.numberKey, { number: orig });
}
- var classNames = 'number';
+ let classNames = 'number';
if (params['class']) {
classNames += ' ' + params['class'];
}
- var result = "#reply-control .wmd-preview img:not(.thumbnail), .cooked img:not(.thumbnail) {' + style + '}').appendTo('head');
+ }
+};
diff --git a/app/assets/javascripts/discourse/initializers/page-tracking.js.es6 b/app/assets/javascripts/discourse/initializers/page-tracking.js.es6
index 1899879943..791e1983e6 100644
--- a/app/assets/javascripts/discourse/initializers/page-tracking.js.es6
+++ b/app/assets/javascripts/discourse/initializers/page-tracking.js.es6
@@ -1,13 +1,14 @@
import { cleanDOM } from 'discourse/routes/discourse';
+import PageTracker from 'discourse/lib/page-tracker';
export default {
name: "page-tracking",
after: 'register-discourse-location',
- initialize: function(container) {
+ initialize(container) {
// Tell our AJAX system to track a page transition
- var router = container.lookup('router:main');
+ const router = container.lookup('router:main');
router.on('willTransition', function() {
Discourse.viewTrackingRequired();
});
@@ -16,7 +17,7 @@ export default {
Em.run.scheduleOnce('afterRender', Ember.Route, cleanDOM);
});
- var pageTracker = Discourse.PageTracker.current();
+ const pageTracker = PageTracker.current();
pageTracker.start();
// Out of the box, Discourse tries to track google analytics
diff --git a/app/assets/javascripts/discourse/initializers/relative-ages.js.es6 b/app/assets/javascripts/discourse/initializers/relative-ages.js.es6
index 263f8d1981..aec67ad3fb 100644
--- a/app/assets/javascripts/discourse/initializers/relative-ages.js.es6
+++ b/app/assets/javascripts/discourse/initializers/relative-ages.js.es6
@@ -1,11 +1,11 @@
-/**
- Updates the relative ages of dates on the screen.
-**/
+import { updateRelativeAge } from 'discourse/lib/formatter';
+
+// Updates the relative ages of dates on the screen.
export default {
name: "relative-ages",
initialize: function() {
setInterval(function(){
- Discourse.Formatter.updateRelativeAge($('.relative-date'));
+ updateRelativeAge($('.relative-date'));
}, 60 * 1000);
}
};
diff --git a/app/assets/javascripts/discourse/lib/computed.js b/app/assets/javascripts/discourse/lib/computed.js
deleted file mode 100644
index 9cabb58990..0000000000
--- a/app/assets/javascripts/discourse/lib/computed.js
+++ /dev/null
@@ -1,128 +0,0 @@
-Discourse.computed = {
-
- /**
- Returns whether two properties are equal to each other.
-
- @method propertyEqual
- @params {String} p1 the first property
- @params {String} p2 the second property
- @return {Function} computedProperty function
- **/
- propertyEqual: function(p1, p2) {
- return Em.computed(function() {
- return this.get(p1) === this.get(p2);
- }).property(p1, p2);
- },
-
- /**
- Returns whether two properties are not equal to each other.
-
- @method propertyNotEqual
- @params {String} p1 the first property
- @params {String} p2 the second property
- @return {Function} computedProperty function
- **/
- propertyNotEqual: function(p1, p2) {
- return Em.computed(function() {
- return this.get(p1) !== this.get(p2);
- }).property(p1, p2);
- },
-
- /**
- Returns i18n version of a string based on a property.
-
- @method i18n
- @params {String} properties* to format
- @params {String} format the i18n format string
- @return {Function} computedProperty function
- **/
- i18n: function() {
- var args = Array.prototype.slice.call(arguments, 0);
- var format = args.pop();
- var computed = Em.computed(function() {
- var self = this;
- return I18n.t(format.fmt.apply(format, args.map(function (a) {
- return self.get(a);
- })));
- });
- return computed.property.apply(computed, args);
- },
-
- /**
- Uses an Ember String `fmt` call to format a string. See:
- http://emberjs.com/api/classes/Em.String.html#method_fmt
-
- @method fmt
- @params {String} properties* to format
- @params {String} format the format string
- @return {Function} computedProperty function
- **/
- fmt: function() {
- var args = Array.prototype.slice.call(arguments, 0);
- var format = args.pop();
- var computed = Em.computed(function() {
- var self = this;
- return format.fmt.apply(format, args.map(function (a) {
- return self.get(a);
- }));
- });
- return computed.property.apply(computed, args);
- },
-
- /**
- Creates a URL using Discourse.getURL. It takes a fmt string just like
- fmt does.
-
- @method url
- @params {String} properties* to format
- @params {String} format the format string for the URL
- @return {Function} computedProperty function returning a URL
- **/
- url: function() {
- var args = Array.prototype.slice.call(arguments, 0);
- var format = args.pop();
- var computed = Em.computed(function() {
- var self = this;
- return Discourse.getURL(format.fmt.apply(format, args.map(function (a) {
- return self.get(a);
- })));
- });
- return computed.property.apply(computed, args);
- },
-
- /**
- Returns whether properties end with a string
-
- @method endWith
- @params {String} properties* to check
- @params {String} substring the substring
- @return {Function} computedProperty function
- **/
- endWith: function() {
- var args = Array.prototype.slice.call(arguments, 0);
- var substring = args.pop();
- var computed = Em.computed(function() {
- var self = this;
- return _.all(args.map(function(a) { return self.get(a); }), function(s) {
- var position = s.length - substring.length,
- lastIndex = s.lastIndexOf(substring);
- return lastIndex !== -1 && lastIndex === position;
- });
- });
- return computed.property.apply(computed, args);
- },
-
- /**
- Creates a property from a SiteSetting. In the future the plan is for them to
- be able to update when changed.
-
- @method setting
- @param {String} name of site setting
- **/
- setting: function(name) {
- return Em.computed(function() {
- return Discourse.SiteSettings[name];
- }).property();
- }
-
-};
diff --git a/app/assets/javascripts/discourse/lib/computed.js.es6 b/app/assets/javascripts/discourse/lib/computed.js.es6
new file mode 100644
index 0000000000..b1b7186b0e
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/computed.js.es6
@@ -0,0 +1,124 @@
+/**
+ Returns whether two properties are equal to each other.
+
+ @method propertyEqual
+ @params {String} p1 the first property
+ @params {String} p2 the second property
+ @return {Function} computedProperty function
+**/
+export function propertyEqual(p1, p2) {
+ return Em.computed(function() {
+ return this.get(p1) === this.get(p2);
+ }).property(p1, p2);
+}
+
+/**
+ Returns whether two properties are not equal to each other.
+
+ @method propertyNotEqual
+ @params {String} p1 the first property
+ @params {String} p2 the second property
+ @return {Function} computedProperty function
+**/
+export function propertyNotEqual(p1, p2) {
+ return Em.computed(function() {
+ return this.get(p1) !== this.get(p2);
+ }).property(p1, p2);
+}
+
+/**
+ Returns i18n version of a string based on a property.
+
+ @method i18n
+ @params {String} properties* to format
+ @params {String} format the i18n format string
+ @return {Function} computedProperty function
+**/
+export function i18n() {
+ const args = Array.prototype.slice.call(arguments, 0);
+ const format = args.pop();
+ const computed = Em.computed(function() {
+ const self = this;
+ return I18n.t(format.fmt.apply(format, args.map(function (a) {
+ return self.get(a);
+ })));
+ });
+ return computed.property.apply(computed, args);
+}
+
+/**
+ Uses an Ember String `fmt` call to format a string. See:
+ http://emberjs.com/api/classes/Em.String.html#method_fmt
+
+ @method fmt
+ @params {String} properties* to format
+ @params {String} format the format string
+ @return {Function} computedProperty function
+**/
+export function fmt() {
+ const args = Array.prototype.slice.call(arguments, 0);
+ const format = args.pop();
+ const computed = Em.computed(function() {
+ const self = this;
+ return format.fmt.apply(format, args.map(function (a) {
+ return self.get(a);
+ }));
+ });
+ return computed.property.apply(computed, args);
+}
+
+/**
+ Creates a URL using Discourse.getURL. It takes a fmt string just like
+ fmt does.
+
+ @method url
+ @params {String} properties* to format
+ @params {String} format the format string for the URL
+ @return {Function} computedProperty function returning a URL
+**/
+export function url() {
+ const args = Array.prototype.slice.call(arguments, 0);
+ const format = args.pop();
+ const computed = Em.computed(function() {
+ const self = this;
+ return Discourse.getURL(format.fmt.apply(format, args.map(function (a) {
+ return self.get(a);
+ })));
+ });
+ return computed.property.apply(computed, args);
+}
+
+/**
+ Returns whether properties end with a string
+
+ @method endWith
+ @params {String} properties* to check
+ @params {String} substring the substring
+ @return {Function} computedProperty function
+**/
+export function endWith() {
+ const args = Array.prototype.slice.call(arguments, 0);
+ const substring = args.pop();
+ const computed = Em.computed(function() {
+ const self = this;
+ return _.all(args.map(function(a) { return self.get(a); }), function(s) {
+ const position = s.length - substring.length,
+ lastIndex = s.lastIndexOf(substring);
+ return lastIndex !== -1 && lastIndex === position;
+ });
+ });
+ return computed.property.apply(computed, args);
+}
+
+/**
+ Creates a property from a SiteSetting. In the future the plan is for them to
+ be able to update when changed.
+
+ @method setting
+ @param {String} name of site setting
+**/
+export function setting(name) {
+ return Em.computed(function() {
+ return Discourse.SiteSettings[name];
+ }).property();
+}
diff --git a/app/assets/javascripts/discourse/lib/desktop-notifications.js.es6 b/app/assets/javascripts/discourse/lib/desktop-notifications.js.es6
index ce67ff9dfd..5bdcf40d86 100644
--- a/app/assets/javascripts/discourse/lib/desktop-notifications.js.es6
+++ b/app/assets/javascripts/discourse/lib/desktop-notifications.js.es6
@@ -1,3 +1,4 @@
+import PageTracker from 'discourse/lib/page-tracker';
let primaryTab = false;
let liveEnabled = false;
@@ -79,7 +80,7 @@ function setupNotifications() {
document.addEventListener("scroll", resetIdle);
}
window.addEventListener("mouseover", resetIdle);
- Discourse.PageTracker.on("change", resetIdle);
+ PageTracker.on("change", resetIdle);
}
function resetIdle() {
diff --git a/app/assets/javascripts/discourse/lib/eyeline.js b/app/assets/javascripts/discourse/lib/eyeline.js.es6
similarity index 58%
rename from app/assets/javascripts/discourse/lib/eyeline.js
rename to app/assets/javascripts/discourse/lib/eyeline.js.es6
index ded17866ec..8d40841afd 100644
--- a/app/assets/javascripts/discourse/lib/eyeline.js
+++ b/app/assets/javascripts/discourse/lib/eyeline.js.es6
@@ -1,38 +1,29 @@
-/**
- Track visible elemnts on the screen.
-
- @class Eyeline
- @namespace Discourse
- @module Discourse
- @uses RSVP.EventTarget
-**/
-Discourse.Eyeline = function Eyeline(selector) {
+// Track visible elemnts on the screen.
+const Eyeline = function Eyeline(selector) {
this.selector = selector;
};
-/**
- Call this whenever you want to consider what is being seen by the browser
+Eyeline.prototype.update = function() {
+ if (Ember.testing) { return; }
- @method update
-**/
-Discourse.Eyeline.prototype.update = function() {
- var docViewTop = $(window).scrollTop(),
- windowHeight = $(window).height(),
- docViewBottom = docViewTop + windowHeight,
- $elements = $(this.selector),
- atBottom = false,
- bottomOffset = $elements.last().offset(),
- self = this;
+ const docViewTop = $(window).scrollTop(),
+ windowHeight = $(window).height(),
+ docViewBottom = docViewTop + windowHeight,
+ $elements = $(this.selector),
+ bottomOffset = $elements.last().offset(),
+ self = this;
+ let atBottom = false;
if (bottomOffset) {
atBottom = (bottomOffset.top <= docViewBottom) && (bottomOffset.top >= docViewTop);
}
return $elements.each(function(i, elem) {
- var $elem = $(elem),
- elemTop = $elem.offset().top,
- elemBottom = elemTop + $elem.height(),
- markSeen = false;
+ const $elem = $(elem),
+ elemTop = $elem.offset().top,
+ elemBottom = elemTop + $elem.height();
+
+ let markSeen = false;
// Make sure the element is visible
if (!$elem.is(':visible')) return true;
@@ -67,18 +58,15 @@ Discourse.Eyeline.prototype.update = function() {
};
-/**
- Call this when we know aren't loading any more elements. Mark the rest as seen
-
- @method flushRest
-**/
-Discourse.Eyeline.prototype.flushRest = function() {
- var self = this;
+// Call this when we know aren't loading any more elements. Mark the rest as seen
+Eyeline.prototype.flushRest = function() {
+ if (Ember.testing) { return; }
+ const self = this;
$(this.selector).each(function(i, elem) {
return self.trigger('saw', { detail: $(elem) });
});
};
-RSVP.EventTarget.mixin(Discourse.Eyeline.prototype);
-
+RSVP.EventTarget.mixin(Eyeline.prototype);
+export default Eyeline;
diff --git a/app/assets/javascripts/discourse/lib/formatter.js b/app/assets/javascripts/discourse/lib/formatter.js.es6
similarity index 72%
rename from app/assets/javascripts/discourse/lib/formatter.js
rename to app/assets/javascripts/discourse/lib/formatter.js.es6
index 1b3db5baad..a2cdaefb40 100644
--- a/app/assets/javascripts/discourse/lib/formatter.js
+++ b/app/assets/javascripts/discourse/lib/formatter.js.es6
@@ -1,9 +1,5 @@
/* global BreakString:true */
-var updateRelativeAge, autoUpdatingRelativeAge, relativeAge, relativeAgeTiny,
- relativeAgeMedium, relativeAgeMediumSpan, longDate, longDateNoYear, toTitleCase,
- shortDate, shortDateNoYear, tinyDateYear, relativeAgeTinyShowsYear;
-
/*
* memoize.js
* by @philogb and @addyosmani
@@ -14,15 +10,15 @@ var updateRelativeAge, autoUpdatingRelativeAge, relativeAge, relativeAgeTiny,
*
* modified with cap by Sam
*/
-var cappedMemoize = function ( fn, max ) {
+function cappedMemoize(fn, max) {
fn.maxMemoize = max;
fn.memoizeLength = 0;
return function () {
- var args = Array.prototype.slice.call(arguments),
- hash = "",
- i = args.length;
- var currentArg = null;
+ const args = Array.prototype.slice.call(arguments);
+ let hash = "";
+ let i = args.length;
+ let currentArg = null;
while (i--) {
currentArg = args[i];
hash += (currentArg === new Object(currentArg)) ?
@@ -39,44 +35,45 @@ var cappedMemoize = function ( fn, max ) {
fn.memoizeLength = 0;
fn.memoize = {};
}
- var result = fn.apply(this, args);
+ const result = fn.apply(this, args);
fn.memoize[hash] = result;
return result;
}
};
-};
+}
-var breakUp = cappedMemoize(function(str, hint){
+const breakUp = cappedMemoize(function(str, hint){
return new BreakString(str).break(hint);
}, 100);
+export { breakUp };
-shortDate = function(date){
+export function shortDate(date){
return moment(date).format(I18n.t("dates.medium.date_year"));
-};
+}
-shortDateNoYear = function(date) {
+function shortDateNoYear(date) {
return moment(date).format(I18n.t("dates.tiny.date_month"));
-};
+}
-tinyDateYear = function(date) {
+function tinyDateYear(date) {
return moment(date).format(I18n.t("dates.tiny.date_year"));
-};
+}
// http://stackoverflow.com/questions/196972/convert-string-to-title-case-with-javascript
// TODO: locale support ?
-toTitleCase = function toTitleCase(str) {
+export function toTitleCase(str) {
return str.replace(/\w\S*/g, function(txt){
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
-};
+}
-longDate = function(dt) {
+export function longDate(dt) {
if (!dt) return;
return moment(dt).longDate();
-};
+}
// suppress year, if current year
-longDateNoYear = function(dt) {
+export function longDateNoYear(dt) {
if (!dt) return;
if ((new Date()).getFullYear() !== dt.getFullYear()) {
@@ -84,26 +81,25 @@ longDateNoYear = function(dt) {
} else {
return moment(dt).format(I18n.t("dates.long_date_without_year"));
}
-};
+}
-updateRelativeAge = function(elems) {
+export function updateRelativeAge(elems) {
// jQuery .each
elems.each(function(){
- var $this = $(this);
+ const $this = $(this);
$this.html(relativeAge(new Date($this.data('time')), {format: $this.data('format'), wrapInSpan: false}));
});
-};
+}
-autoUpdatingRelativeAge = function(date,options) {
+export function autoUpdatingRelativeAge(date,options) {
if (!date) return "";
if (+date === +new Date(0)) return "";
options = options || {};
- var format = options.format || "tiny";
+ let format = options.format || "tiny";
- var append = "";
-
- if(format === 'medium') {
+ let append = "";
+ if (format === 'medium') {
append = " date";
if(options.leaveAgo) {
format = 'medium-with-ago';
@@ -111,7 +107,7 @@ autoUpdatingRelativeAge = function(date,options) {
options.wrapInSpan = false;
}
- var relAge = relativeAge(date, options);
+ const relAge = relativeAge(date, options);
if (format === 'tiny' && relativeAgeTinyShowsYear(relAge)) {
append += " with-year";
@@ -122,16 +118,16 @@ autoUpdatingRelativeAge = function(date,options) {
}
return "" + relAge + "";
-};
+}
-relativeAgeTiny = function(date){
- var format = "tiny";
- var distance = Math.round((new Date() - date) / 1000);
- var distanceInMinutes = Math.round(distance / 60.0);
+function relativeAgeTiny(date){
+ const format = "tiny";
+ const distance = Math.round((new Date() - date) / 1000);
+ const distanceInMinutes = Math.round(distance / 60.0);
- var formatted;
- var t = function(key,opts){
+ let formatted;
+ const t = function(key,opts){
return I18n.t("dates." + format + "." + key, opts);
};
@@ -168,22 +164,21 @@ relativeAgeTiny = function(date){
}
return formatted;
-};
+}
/*
* Returns true if the given tiny date string includes the year.
* Useful for checking if the string isn't so tiny.
*/
-relativeAgeTinyShowsYear = function(relativeAgeString) {
+function relativeAgeTinyShowsYear(relativeAgeString) {
return relativeAgeString.match(/'[\d]{2}$/);
-};
+}
-relativeAgeMediumSpan = function(distance, leaveAgo) {
- var formatted, distanceInMinutes;
+function relativeAgeMediumSpan(distance, leaveAgo) {
+ let formatted;
+ const distanceInMinutes = Math.round(distance / 60.0);
- distanceInMinutes = Math.round(distance / 60.0);
-
- var t = function(key, opts){
+ const t = function(key, opts){
return I18n.t("dates.medium" + (leaveAgo?"_with_ago":"") + "." + key, opts);
};
@@ -205,24 +200,22 @@ relativeAgeMediumSpan = function(distance, leaveAgo) {
break;
}
return formatted || '&mdash';
-};
+}
-relativeAgeMedium = function(date, options){
- var displayDate, fiveDaysAgo, oneMinuteAgo, fullReadable, leaveAgo;
- var wrapInSpan = options.wrapInSpan !== false;
-
- leaveAgo = options.leaveAgo;
- var distance = Math.round((new Date() - date) / 1000);
+function relativeAgeMedium(date, options) {
+ const wrapInSpan = options.wrapInSpan !== false;
+ const leaveAgo = options.leaveAgo;
+ const distance = Math.round((new Date() - date) / 1000);
if (!date) {
return "—";
}
- fullReadable = longDate(date);
- displayDate = "";
- fiveDaysAgo = 432000;
- oneMinuteAgo = 60;
+ const fullReadable = longDate(date);
+ const fiveDaysAgo = 432000;
+ const oneMinuteAgo = 60;
+ let displayDate = "";
if (distance < oneMinuteAgo) {
displayDate = I18n.t("now");
} else if (distance > fiveDaysAgo) {
@@ -239,12 +232,12 @@ relativeAgeMedium = function(date, options){
} else {
return displayDate;
}
-};
+}
// mostly lifted from rails with a few amendments
-relativeAge = function(date, options) {
+export function relativeAge(date, options) {
options = options || {};
- var format = options.format || "tiny";
+ const format = options.format || "tiny";
if(format === "tiny") {
return relativeAgeTiny(date, options);
@@ -255,10 +248,10 @@ relativeAge = function(date, options) {
}
return "UNKNOWN FORMAT";
-};
+}
-var number = function(val) {
- var formattedNumber;
+export function number(val) {
+ let formattedNumber;
val = parseInt(val, 10);
if (isNaN(val)) val = 0;
@@ -272,17 +265,5 @@ var number = function(val) {
return I18n.t("number.short.thousands", {number: formattedNumber});
}
return val.toString();
-};
+}
-Discourse.Formatter = {
- longDate: longDate,
- longDateNoYear: longDateNoYear,
- relativeAge: relativeAge,
- autoUpdatingRelativeAge: autoUpdatingRelativeAge,
- updateRelativeAge: updateRelativeAge,
- toTitleCase: toTitleCase,
- shortDate: shortDate,
- breakUp: breakUp,
- cappedMemoize: cappedMemoize,
- number: number
-};
diff --git a/app/assets/javascripts/discourse/lib/key_value_store.js b/app/assets/javascripts/discourse/lib/key_value_store.js
index 17d17c76a3..ecb1651eb9 100644
--- a/app/assets/javascripts/discourse/lib/key_value_store.js
+++ b/app/assets/javascripts/discourse/lib/key_value_store.js
@@ -10,9 +10,13 @@
var safeLocalStorage;
try {
- safeLocalStorage = localStorage;
+ safeLocalStorage = localStorage;
+ if (localStorage["disableLocalStorage"] === "true") {
+ safeLocalStorage = null;
+ }
} catch(e){
// cookies disabled, we don't care
+ safeLocalStorage = null;
}
Discourse.KeyValueStore = {
diff --git a/app/assets/javascripts/discourse/lib/page_tracker.js b/app/assets/javascripts/discourse/lib/page-tracker.js.es6
similarity index 77%
rename from app/assets/javascripts/discourse/lib/page_tracker.js
rename to app/assets/javascripts/discourse/lib/page-tracker.js.es6
index 842f2d9b11..9a7dcc9c9d 100644
--- a/app/assets/javascripts/discourse/lib/page_tracker.js
+++ b/app/assets/javascripts/discourse/lib/page-tracker.js.es6
@@ -1,3 +1,5 @@
+import Singleton from 'discourse/mixins/singleton';
+
/**
Called whenever the "page" changes. This allows us to set up analytics
and other tracking.
@@ -5,12 +7,12 @@
To get notified when the page changes, you can install a hook like so:
```javascript
- Discourse.PageTracker.current().on('change', function(url, title) {
+ PageTracker.current().on('change', function(url, title) {
console.log('the page changed to: ' + url + ' and title ' + title);
});
```
**/
-Discourse.PageTracker = Ember.Object.extend(Ember.Evented, {
+const PageTracker = Ember.Object.extend(Ember.Evented, {
start: function() {
if (this.get('started')) { return; }
@@ -30,4 +32,6 @@ Discourse.PageTracker = Ember.Object.extend(Ember.Evented, {
this.set('started', true);
}
});
-Discourse.PageTracker.reopenClass(Discourse.Singleton);
+PageTracker.reopenClass(Singleton);
+
+export default PageTracker;
diff --git a/app/assets/javascripts/discourse/lib/screen_track.js b/app/assets/javascripts/discourse/lib/screen-track.js.es6
similarity index 72%
rename from app/assets/javascripts/discourse/lib/screen_track.js
rename to app/assets/javascripts/discourse/lib/screen-track.js.es6
index 595a5321ce..3d55418cfb 100644
--- a/app/assets/javascripts/discourse/lib/screen_track.js
+++ b/app/assets/javascripts/discourse/lib/screen-track.js.es6
@@ -1,23 +1,18 @@
-/**
- We use this class to track how long posts in a topic are on the screen.
+// We use this class to track how long posts in a topic are on the screen.
- @class ScreenTrack
- @extends Ember.Object
- @namespace Discourse
- @module Discourse
-**/
+import Singleton from 'discourse/mixins/singleton';
-var PAUSE_UNLESS_SCROLLED = 1000 * 60 * 3,
- MAX_TRACKING_TIME = 1000 * 60 * 6;
+const PAUSE_UNLESS_SCROLLED = 1000 * 60 * 3,
+ MAX_TRACKING_TIME = 1000 * 60 * 6;
-Discourse.ScreenTrack = Ember.Object.extend({
+const ScreenTrack = Ember.Object.extend({
- init: function() {
+ init() {
this.reset();
},
- start: function(topicId, topicController) {
- var currentTopicId = this.get('topicId');
+ start(topicId, topicController) {
+ const currentTopicId = this.get('topicId');
if (currentTopicId && (currentTopicId !== topicId)) {
this.tick();
this.flush();
@@ -27,7 +22,7 @@ Discourse.ScreenTrack = Ember.Object.extend({
// Create an interval timer if we don't have one.
if (!this.get('interval')) {
- var self = this;
+ const self = this;
this.set('interval', setInterval(function () {
self.tick();
}, 1000));
@@ -39,7 +34,7 @@ Discourse.ScreenTrack = Ember.Object.extend({
this.set('topicController', topicController);
},
- stop: function() {
+ stop() {
if(!this.get('topicId')) {
// already stopped no need to "extra stop"
return;
@@ -56,19 +51,19 @@ Discourse.ScreenTrack = Ember.Object.extend({
}
},
- track: function(elementId, postNumber) {
+ track(elementId, postNumber) {
this.get('timings')["#" + elementId] = {
time: 0,
postNumber: postNumber
};
},
- stopTracking: function(elementId) {
+ stopTracking(elementId) {
delete this.get('timings')['#' + elementId];
},
// Reset our timers
- reset: function() {
+ reset() {
this.setProperties({
lastTick: new Date().getTime(),
lastScrolled: new Date().getTime(),
@@ -80,17 +75,17 @@ Discourse.ScreenTrack = Ember.Object.extend({
});
},
- scrolled: function() {
+ scrolled() {
this.set('lastScrolled', new Date().getTime());
},
- flush: function() {
+ flush() {
if (this.get('cancelled')) { return; }
// We don't log anything unless we're logged in
if (!Discourse.User.current()) return;
- var newTimings = {},
+ const newTimings = {},
totalTimings = this.get('totalTimings'),
self = this;
@@ -105,14 +100,14 @@ Discourse.ScreenTrack = Ember.Object.extend({
timing.time = 0;
});
- var topicId = parseInt(this.get('topicId'), 10),
- highestSeen = 0;
+ const topicId = parseInt(this.get('topicId'), 10);
+ let highestSeen = 0;
_.each(newTimings, function(time,postNumber) {
highestSeen = Math.max(highestSeen, parseInt(postNumber, 10));
});
- var highestSeenByTopic = Discourse.Session.currentProp('highestSeenByTopic');
+ const highestSeenByTopic = Discourse.Session.currentProp('highestSeenByTopic');
if ((highestSeenByTopic[topicId] || 0) < highestSeen) {
highestSeenByTopic[topicId] = highestSeen;
}
@@ -132,9 +127,9 @@ Discourse.ScreenTrack = Ember.Object.extend({
'X-SILENCE-LOGGER': 'true'
}
}).then(function(){
- var controller = self.get('topicController');
+ const controller = self.get('topicController');
if(controller){
- var postNumbers = Object.keys(newTimings).map(function(v){
+ const postNumbers = Object.keys(newTimings).map(function(v){
return parseInt(v,10);
});
controller.readPosts(topicId, postNumbers);
@@ -146,23 +141,23 @@ Discourse.ScreenTrack = Ember.Object.extend({
this.set('lastFlush', 0);
},
- tick: function() {
+ tick() {
// If the user hasn't scrolled the browser in a long time, stop tracking time read
- var sinceScrolled = new Date().getTime() - this.get('lastScrolled');
+ const sinceScrolled = new Date().getTime() - this.get('lastScrolled');
if (sinceScrolled > PAUSE_UNLESS_SCROLLED) {
return;
}
- var diff = new Date().getTime() - this.get('lastTick');
+ const diff = new Date().getTime() - this.get('lastTick');
this.set('lastFlush', this.get('lastFlush') + diff);
this.set('lastTick', new Date().getTime());
- var totalTimings = this.get('totalTimings'), timings = this.get('timings');
- var nextFlush = Discourse.SiteSettings.flush_timings_secs * 1000;
+ const totalTimings = this.get('totalTimings'), timings = this.get('timings');
+ const nextFlush = Discourse.SiteSettings.flush_timings_secs * 1000;
// rush new post numbers
- var rush = _.any(_.filter(timings, function(t){return t.time>0;}), function(t){
+ const rush = _.any(_.filter(timings, function(t){return t.time>0;}), function(t){
return !totalTimings[t.postNumber];
});
@@ -174,15 +169,15 @@ Discourse.ScreenTrack = Ember.Object.extend({
if (!Discourse.get("hasFocus")) return;
this.set('topicTime', this.get('topicTime') + diff);
- var docViewTop = $(window).scrollTop() + $('header').height(),
+ const docViewTop = $(window).scrollTop() + $('header').height(),
docViewBottom = docViewTop + $(window).height();
// TODO: Eyeline has a smarter more accurate function here. It's bad to do jQuery
// in a model like component, so we should refactor this out later.
_.each(this.get('timings'),function(timing,id) {
- var $element = $(id);
+ const $element = $(id);
if ($element.length === 1) {
- var elemTop = $element.offset().top,
+ const elemTop = $element.offset().top,
elemBottom = elemTop + $element.height();
// If part of the element is on the screen, increase the counter
@@ -195,5 +190,5 @@ Discourse.ScreenTrack = Ember.Object.extend({
});
-Discourse.ScreenTrack.reopenClass(Discourse.Singleton);
-
+ScreenTrack.reopenClass(Singleton);
+export default ScreenTrack;
diff --git a/app/assets/javascripts/discourse/lib/search-for-term.js.es6 b/app/assets/javascripts/discourse/lib/search-for-term.js.es6
index e5348a6a13..2d029fdf9e 100644
--- a/app/assets/javascripts/discourse/lib/search-for-term.js.es6
+++ b/app/assets/javascripts/discourse/lib/search-for-term.js.es6
@@ -77,9 +77,13 @@ function searchForTerm(term, opts) {
};
}
- return Discourse.ajax('/search/query', { data: data }).then(function(results){
+ var promise = Discourse.ajax('/search/query', { data: data });
+
+ promise.then(function(results){
return translateResults(results, opts);
});
+
+ return promise;
}
export default searchForTerm;
diff --git a/app/assets/javascripts/discourse/lib/utilities.js b/app/assets/javascripts/discourse/lib/utilities.js
index 6d08ab9347..92c66cd53c 100644
--- a/app/assets/javascripts/discourse/lib/utilities.js
+++ b/app/assets/javascripts/discourse/lib/utilities.js
@@ -179,7 +179,7 @@ Discourse.Utilities = {
// check file size
var fileSizeKB = file.size / 1024;
- var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'];
+ var maxSizeKB = 10 * 1024; // 10MB
if (fileSizeKB > maxSizeKB) {
bootbox.alert(I18n.t('post.errors.file_too_large', { max_size_kb: maxSizeKB }));
return false;
@@ -243,7 +243,7 @@ Discourse.Utilities = {
// entity too large, usually returned from the web server
case 413:
- var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
+ var maxSizeKB = 10 * 1024; // 10 MB
bootbox.alert(I18n.t('post.errors.file_too_large', { max_size_kb: maxSizeKB }));
return;
diff --git a/app/assets/javascripts/discourse/mixins/ajax.js b/app/assets/javascripts/discourse/mixins/ajax.js
index 6817150d51..4ca8c9a131 100644
--- a/app/assets/javascripts/discourse/mixins/ajax.js
+++ b/app/assets/javascripts/discourse/mixins/ajax.js
@@ -19,6 +19,7 @@ Discourse.Ajax = Em.Mixin.create({
**/
ajax: function() {
var url, args;
+ var ajax;
if (arguments.length === 1) {
if (typeof arguments[0] === "string") {
@@ -95,22 +96,32 @@ Discourse.Ajax = Em.Mixin.create({
args.cache = false;
}
- $.ajax(Discourse.getURL(url), args);
+ ajax = $.ajax(Discourse.getURL(url), args);
};
+ var promise;
+
// For cached pages we strip out CSRF tokens, need to round trip to server prior to sending the
// request (bypass for GET, not needed)
if(args.type && args.type.toUpperCase() !== 'GET' && !Discourse.Session.currentProp('csrfToken')){
- return new Ember.RSVP.Promise(function(resolve, reject){
- $.ajax(Discourse.getURL('/session/csrf'), {cache: false})
+ promise = new Ember.RSVP.Promise(function(resolve, reject){
+ ajax = $.ajax(Discourse.getURL('/session/csrf'), {cache: false})
.success(function(result){
Discourse.Session.currentProp('csrfToken', result.csrf);
performAjax(resolve, reject);
});
});
} else {
- return new Ember.RSVP.Promise(performAjax);
+ promise = new Ember.RSVP.Promise(performAjax);
}
+
+ promise.abort = function(){
+ if (ajax) {
+ ajax.abort();
+ }
+ };
+
+ return promise;
}
});
diff --git a/app/assets/javascripts/discourse/mixins/can-check-emails.js.es6 b/app/assets/javascripts/discourse/mixins/can-check-emails.js.es6
index 22742d7451..dc802c4d04 100644
--- a/app/assets/javascripts/discourse/mixins/can-check-emails.js.es6
+++ b/app/assets/javascripts/discourse/mixins/can-check-emails.js.es6
@@ -1,6 +1,8 @@
+import { propertyEqual, setting } from 'discourse/lib/computed';
+
export default Ember.Mixin.create({
- isOwnEmail: Discourse.computed.propertyEqual("model.id", "currentUser.id"),
- showEmailOnProfile: Discourse.computed.setting("show_email_on_profile"),
+ isOwnEmail: propertyEqual("model.id", "currentUser.id"),
+ showEmailOnProfile: setting("show_email_on_profile"),
canStaffCheckEmails: Em.computed.and("showEmailOnProfile", "currentUser.staff"),
canAdminCheckEmails: Em.computed.alias("currentUser.admin"),
canCheckEmails: Em.computed.or("isOwnEmail", "canStaffCheckEmails", "canAdminCheckEmails"),
diff --git a/app/assets/javascripts/discourse/mixins/load-more.js.es6 b/app/assets/javascripts/discourse/mixins/load-more.js.es6
index 0726b13bd7..d0e63e4ba4 100644
--- a/app/assets/javascripts/discourse/mixins/load-more.js.es6
+++ b/app/assets/javascripts/discourse/mixins/load-more.js.es6
@@ -1,13 +1,16 @@
-// Provides the ability to load more items for a view which is scrolled to the bottom.
-export default Em.Mixin.create(Ember.ViewTargetActionSupport, Discourse.Scrolling, {
+import Eyeline from 'discourse/lib/eyeline';
+import Scrolling from 'discourse/mixins/scrolling';
- scrolled: function() {
+// Provides the ability to load more items for a view which is scrolled to the bottom.
+export default Ember.Mixin.create(Ember.ViewTargetActionSupport, Scrolling, {
+
+ scrolled() {
const eyeline = this.get('eyeline');
if (eyeline) { eyeline.update(); }
},
_bindEyeline: function() {
- const eyeline = new Discourse.Eyeline(this.get('eyelineSelector') + ":last");
+ const eyeline = new Eyeline(this.get('eyelineSelector') + ":last");
this.set('eyeline', eyeline);
eyeline.on('sawBottom', () => this.send('loadMore'));
this.bindScrolling();
diff --git a/app/assets/javascripts/discourse/mixins/scrolling.js b/app/assets/javascripts/discourse/mixins/scrolling.js
deleted file mode 100644
index c9473c155e..0000000000
--- a/app/assets/javascripts/discourse/mixins/scrolling.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- This mixin adds support for being notified every time the browser window
- is scrolled.
-
- @class Scrolling
- @extends Ember.Mixin
- @namespace Discourse
- @module Discourse
-**/
-
-Discourse.Scrolling = Em.Mixin.create({
-
- /**
- Begin watching for scroll events. By default they will be called at max every 100ms.
- call with {debounce: N} for a diff time
-
- @method bindScrolling
- */
- bindScrolling: function(opts) {
- opts = opts || {debounce: 100};
-
- // So we can not call the scrolled event while transitioning
- var router = Discourse.__container__.lookup('router:main').router;
-
- var self = this,
- onScrollMethod = function() {
- if (router.activeTransition) { return; }
- return Em.run.scheduleOnce('afterRender', self, 'scrolled');
- };
-
- if (opts.debounce) {
- onScrollMethod = Discourse.debounce(onScrollMethod, opts.debounce);
- }
-
- Discourse.ScrollingDOMMethods.bindOnScroll(onScrollMethod, opts.name);
- Em.run.scheduleOnce('afterRender', onScrollMethod);
- },
-
- /**
- Stop watching for scroll events.
-
- @method unbindScrolling
- */
- unbindScrolling: function(name) {
- Discourse.ScrollingDOMMethods.unbindOnScroll(name);
- }
-
-});
-
-
-/**
- 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
- easier.
-
- @class ScrollingDOMMethods
- @module Discourse
-**/
-Discourse.ScrollingDOMMethods = {
-
- bindOnScroll: function(onScrollMethod, name) {
- name = name || 'default';
- $(document).bind('touchmove.discourse-' + name, onScrollMethod);
- $(window).bind('scroll.discourse-' + name, onScrollMethod);
- },
-
- unbindOnScroll: function(name) {
- name = name || 'default';
- $(window).unbind('scroll.discourse-' + name);
- $(document).unbind('touchmove.discourse-' + name);
- }
-
-};
diff --git a/app/assets/javascripts/discourse/mixins/scrolling.js.es6 b/app/assets/javascripts/discourse/mixins/scrolling.js.es6
new file mode 100644
index 0000000000..459bbd7119
--- /dev/null
+++ b/app/assets/javascripts/discourse/mixins/scrolling.js.es6
@@ -0,0 +1,50 @@
+/**
+ 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
+ easier.
+**/
+const ScrollingDOMMethods = {
+ bindOnScroll: function(onScrollMethod, name) {
+ name = name || 'default';
+ $(document).bind('touchmove.discourse-' + name, onScrollMethod);
+ $(window).bind('scroll.discourse-' + name, onScrollMethod);
+ },
+
+ unbindOnScroll: function(name) {
+ name = name || 'default';
+ $(window).unbind('scroll.discourse-' + name);
+ $(document).unbind('touchmove.discourse-' + name);
+ }
+};
+
+const Scrolling = Ember.Mixin.create({
+
+ // Begin watching for scroll events. By default they will be called at max every 100ms.
+ // call with {debounce: N} for a diff time
+ bindScrolling: function(opts) {
+ opts = opts || {debounce: 100};
+
+ // So we can not call the scrolled event while transitioning
+ const router = Discourse.__container__.lookup('router:main').router;
+
+ const self = this;
+ var onScrollMethod = function() {
+ if (router.activeTransition) { return; }
+ return Em.run.scheduleOnce('afterRender', self, 'scrolled');
+ };
+
+ if (opts.debounce) {
+ onScrollMethod = Discourse.debounce(onScrollMethod, opts.debounce);
+ }
+
+ ScrollingDOMMethods.bindOnScroll(onScrollMethod, opts.name);
+ Em.run.scheduleOnce('afterRender', onScrollMethod);
+ },
+
+ unbindScrolling: function(name) {
+ ScrollingDOMMethods.unbindOnScroll(name);
+ }
+});
+
+export { ScrollingDOMMethods };
+export default Scrolling;
diff --git a/app/assets/javascripts/discourse/mixins/singleton.js b/app/assets/javascripts/discourse/mixins/singleton.js.es6
similarity index 61%
rename from app/assets/javascripts/discourse/mixins/singleton.js
rename to app/assets/javascripts/discourse/mixins/singleton.js.es6
index 73ce7a33e7..3bd27813e5 100644
--- a/app/assets/javascripts/discourse/mixins/singleton.js
+++ b/app/assets/javascripts/discourse/mixins/singleton.js.es6
@@ -9,7 +9,7 @@
// Define your class and apply the Mixin
User = Ember.Object.extend({});
- User.reopenClass(Discourse.Singleton);
+ User.reopenClass(Singleton);
// Retrieve the current instance:
var instance = User.current();
@@ -35,7 +35,7 @@
// Define your class and apply the Mixin
Foot = Ember.Object.extend({});
- Foot.reopenClass(Discourse.Singleton, {
+ Foot.reopenClass(Singleton, {
createCurrent: function() {
return Foot.create({toes: 5});
}
@@ -44,51 +44,28 @@
console.log(Foot.currentProp('toes')); // 5
```
-
- @class Discourse.Singleton
- @extends Ember.Mixin
- @namespace Discourse
- @module Discourse
**/
-Discourse.Singleton = Em.Mixin.create({
+const Singleton = Ember.Mixin.create({
- /**
- Returns the current singleton instance of the class.
-
- @method current
- @returns {Ember.Object} the instance of the singleton
- **/
- current: function() {
+ current() {
if (!this._current) {
this._current = this.createCurrent();
}
-
return this._current;
},
-
/**
How the singleton instance is created. This can be overridden
with logic for creating (or even returning null) your instance.
By default it just calls `create` with an empty object.
-
- @method createCurrent
- @returns {Ember.Object} the instance that will be your singleton
**/
- createCurrent: function() {
+ createCurrent() {
return this.create({});
},
- /**
- Returns or sets a property on the singleton instance.
-
- @method currentProp
- @param {String} property the property we want to get or set
- @param {String} value the optional value to set the property to
- @returns the value of the property
- **/
- currentProp: function(property, value) {
+ // Returns OR sets a property on the singleton instance.
+ currentProp(property, value) {
var instance = this.current();
if (!instance) { return; }
@@ -100,14 +77,9 @@ Discourse.Singleton = Em.Mixin.create({
}
},
- /**
- Resets the current singleton. Useful in testing.
-
- @method resetCurrent
- **/
- resetCurrent: function(val) {
+ resetCurrent(val) {
this._current = val;
}
});
-
+export default Singleton;
diff --git a/app/assets/javascripts/discourse/models/admin_post.js b/app/assets/javascripts/discourse/models/admin-post.js.es6
similarity index 73%
rename from app/assets/javascripts/discourse/models/admin_post.js
rename to app/assets/javascripts/discourse/models/admin-post.js.es6
index 8db9dfec69..4504fcbe24 100644
--- a/app/assets/javascripts/discourse/models/admin_post.js
+++ b/app/assets/javascripts/discourse/models/admin-post.js.es6
@@ -1,15 +1,9 @@
-/**
- A data model for flagged/deleted posts.
+import Post from 'discourse/models/post';
- @class AdminPost
- @extends Discourse.Post
- @namespace Discourse
- @module Discourse
-**/
-Discourse.AdminPost = Discourse.Post.extend({
+export default Post.extend({
_attachCategory: function () {
- var categoryId = this.get("category_id");
+ const categoryId = this.get("category_id");
if (categoryId) {
this.set("category", Discourse.Category.findById(categoryId));
}
diff --git a/app/assets/javascripts/discourse/models/archetype.js b/app/assets/javascripts/discourse/models/archetype.js
deleted file mode 100644
index cfbc88c418..0000000000
--- a/app/assets/javascripts/discourse/models/archetype.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- A data model for archetypes such as polls, tasks, etc.
-
- @class Archetype
- @extends Discourse.Model
- @namespace Discourse
- @module Discourse
-**/
-Discourse.Archetype = Discourse.Model.extend({
-
- hasOptions: Em.computed.gt('options.length', 0),
-
- site: function() {
- return Discourse.Site.current();
- }.property(),
-
- isDefault: Discourse.computed.propertyEqual('id', 'site.default_archetype'),
- notDefault: Em.computed.not('isDefault')
-
-});
-
-
diff --git a/app/assets/javascripts/discourse/models/archetype.js.es6 b/app/assets/javascripts/discourse/models/archetype.js.es6
new file mode 100644
index 0000000000..3931858f75
--- /dev/null
+++ b/app/assets/javascripts/discourse/models/archetype.js.es6
@@ -0,0 +1,8 @@
+import { propertyEqual } from 'discourse/lib/computed';
+import RestModel from 'discourse/models/rest';
+
+export default RestModel.extend({
+ hasOptions: Em.computed.gt('options.length', 0),
+ isDefault: propertyEqual('id', 'site.default_archetype'),
+ notDefault: Em.computed.not('isDefault')
+});
diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6
index edde5df3c6..e58f62362e 100644
--- a/app/assets/javascripts/discourse/models/composer.js.es6
+++ b/app/assets/javascripts/discourse/models/composer.js.es6
@@ -101,12 +101,13 @@ const Composer = RestModel.extend({
actionTitle: function() {
const topic = this.get('topic');
- let postLink, topicLink;
+ let postLink, topicLink, usernameLink;
if (topic) {
const postNumber = this.get('post.post_number');
postLink = "" +
I18n.t("post.post_number", { number: postNumber }) + "";
topicLink = " " + (Handlebars.Utils.escapeExpression(topic.get('title'))) + "";
+ usernameLink = "" + this.get('post.username') + "";
}
let postDescription;
@@ -116,7 +117,8 @@ const Composer = RestModel.extend({
postDescription = I18n.t('post.' + this.get('action'), {
link: postLink,
replyAvatar: Discourse.Utilities.tinyAvatar(post.get('avatar_template')),
- username: this.get('post.username')
+ username: this.get('post.username'),
+ usernameLink
});
if (!Discourse.Mobile.mobileView) {
@@ -491,15 +493,17 @@ const Composer = RestModel.extend({
this.set('composeState', CLOSED);
+ var rollback = throwAjaxError(function(){
+ post.set('cooked', oldCooked);
+ self.set('composeState', OPEN);
+ });
+
return promise.then(function() {
return post.save(props).then(function(result) {
self.clearState();
return result;
- }).catch(throwAjaxError(function() {
- post.set('cooked', oldCooked);
- self.set('composeState', OPEN);
- }));
- });
+ }).catch(rollback);
+ }).catch(rollback);
},
serialize(serializer, dest) {
diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6
index 984941e89f..b79db164f4 100644
--- a/app/assets/javascripts/discourse/models/group.js.es6
+++ b/app/assets/javascripts/discourse/models/group.js.es6
@@ -50,14 +50,7 @@ const Group = Discourse.Model.extend({
type: "PUT",
data: { usernames: usernames }
}).then(function() {
- // reload member list
self.findMembers();
- }).catch(function(error) {
- if (error && error.responseText) {
- bootbox.alert($.parseJSON(error.responseText).errors[0]);
- } else {
- bootbox.alert(I18n.t('generic_error'));
- }
});
},
diff --git a/app/assets/javascripts/discourse/models/nav-item.js.es6 b/app/assets/javascripts/discourse/models/nav-item.js.es6
index def6a3430a..d4e5ef228b 100644
--- a/app/assets/javascripts/discourse/models/nav-item.js.es6
+++ b/app/assets/javascripts/discourse/models/nav-item.js.es6
@@ -1,11 +1,4 @@
-/**
- A data model representing a navigation item on the list views
-
- @class NavItem
- @extends Discourse.Model
- @namespace Discourse
- @module Discourse
-**/
+import { toTitleCase } from 'discourse/lib/formatter';
const NavItem = Discourse.Model.extend({
@@ -22,7 +15,7 @@ const NavItem = Discourse.Model.extend({
if (categoryName) {
name = 'category';
- extra.categoryName = Discourse.Formatter.toTitleCase(categoryName);
+ extra.categoryName = toTitleCase(categoryName);
}
return I18n.t("filters." + name.replace("/", ".") + ".title", extra);
}.property('categoryName', 'name', 'count'),
diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6
index a678573077..7f6ccf38d8 100644
--- a/app/assets/javascripts/discourse/models/post.js.es6
+++ b/app/assets/javascripts/discourse/models/post.js.es6
@@ -1,6 +1,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';
const Post = RestModel.extend({
@@ -57,7 +58,7 @@ const Post = RestModel.extend({
return (this.get('post_number') === 1) ? url + "/1" : url;
}.property('post_number', 'url'),
- usernameUrl: Discourse.computed.url('username', '/users/%@'),
+ usernameUrl: url('username', '/users/%@'),
showUserReplyTab: function() {
return this.get('reply_to_user') && (
@@ -66,9 +67,9 @@ const Post = RestModel.extend({
);
}.property('reply_to_user', 'reply_to_post_number', 'post_number'),
- topicOwner: Discourse.computed.propertyEqual('topic.details.created_by.id', 'user_id'),
+ topicOwner: propertyEqual('topic.details.created_by.id', 'user_id'),
hasHistory: Em.computed.gt('version', 1),
- postElementId: Discourse.computed.fmt('post_number', 'post_%@'),
+ postElementId: fmt('post_number', 'post_%@'),
canViewRawEmail: function() {
return this.get("user_id") === Discourse.User.currentProp("id") || Discourse.User.currentProp('staff');
diff --git a/app/assets/javascripts/discourse/models/session.js.es6 b/app/assets/javascripts/discourse/models/session.js.es6
index 894dc6b2fd..f875493393 100644
--- a/app/assets/javascripts/discourse/models/session.js.es6
+++ b/app/assets/javascripts/discourse/models/session.js.es6
@@ -1,11 +1,13 @@
+import RestModel from 'discourse/models/rest';
+import Singleton from 'discourse/mixins/singleton';
+
// A data model representing current session data. You can put transient
// data here you might want later. It is not stored or serialized anywhere.
-var Session = Discourse.Model.extend({
+const Session = RestModel.extend({
init: function() {
this.set('highestSeenByTopic', {});
}
});
-Session.reopenClass(Discourse.Singleton);
-
+Session.reopenClass(Singleton);
export default Session;
diff --git a/app/assets/javascripts/discourse/models/site.js.es6 b/app/assets/javascripts/discourse/models/site.js.es6
index e3466eada0..226e1ed811 100644
--- a/app/assets/javascripts/discourse/models/site.js.es6
+++ b/app/assets/javascripts/discourse/models/site.js.es6
@@ -1,4 +1,6 @@
+import Archetype from 'discourse/models/archetype';
import PostActionType from 'discourse/models/post-action-type';
+import Singleton from 'discourse/mixins/singleton';
const Site = Discourse.Model.extend({
@@ -85,11 +87,11 @@ const Site = Discourse.Model.extend({
}
});
-Site.reopenClass(Discourse.Singleton, {
+Site.reopenClass(Singleton, {
// The current singleton will retrieve its attributes from the `PreloadStore`.
createCurrent() {
- return Discourse.Site.create(PreloadStore.get('site'));
+ return Site.create(PreloadStore.get('site'));
},
create() {
@@ -137,7 +139,8 @@ Site.reopenClass(Discourse.Singleton, {
if (result.archetypes) {
result.archetypes = _.map(result.archetypes,function(a) {
- return Discourse.Archetype.create(a);
+ a.site = result;
+ return Archetype.create(a);
});
}
diff --git a/app/assets/javascripts/discourse/models/store.js.es6 b/app/assets/javascripts/discourse/models/store.js.es6
index cda112cefb..a9aa86f294 100644
--- a/app/assets/javascripts/discourse/models/store.js.es6
+++ b/app/assets/javascripts/discourse/models/store.js.es6
@@ -116,6 +116,12 @@ export default Ember.Object.extend({
},
destroyRecord(type, record) {
+ // If the record is new, don't perform an Ajax call
+ if (record.get('isNew')) {
+ removeMap(type, record.get('id'));
+ return Ember.RSVP.Promise.resolve(true);
+ }
+
return this.adapterFor(type).destroyRecord(this, type, record).then(function(result) {
removeMap(type, record.get('id'));
return result;
diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6
index 2455087411..0fab68fa10 100644
--- a/app/assets/javascripts/discourse/models/topic.js.es6
+++ b/app/assets/javascripts/discourse/models/topic.js.es6
@@ -1,5 +1,7 @@
import { flushMap } from 'discourse/models/store';
import RestModel from 'discourse/models/rest';
+import { propertyEqual } from 'discourse/lib/computed';
+import { longDate } from 'discourse/lib/formatter';
const Topic = RestModel.extend({
message: null,
@@ -20,8 +22,8 @@ const Topic = RestModel.extend({
}.property('bumped_at', 'createdAt'),
bumpedAtTitle: function() {
- return I18n.t('first_post') + ": " + Discourse.Formatter.longDate(this.get('createdAt')) + "\n" +
- I18n.t('last_post') + ": " + Discourse.Formatter.longDate(this.get('bumpedAt'));
+ return I18n.t('first_post') + ": " + longDate(this.get('createdAt')) + "\n" +
+ I18n.t('last_post') + ": " + longDate(this.get('bumpedAt'));
}.property('bumpedAt'),
createdAt: function() {
@@ -364,7 +366,7 @@ const Topic = RestModel.extend({
return( e && e.substr(e.length - 8,8) === '…' );
}.property('excerpt'),
- readLastPost: Discourse.computed.propertyEqual('last_read_post_number', 'highest_post_number'),
+ readLastPost: propertyEqual('last_read_post_number', 'highest_post_number'),
canClearPin: Em.computed.and('pinned', 'readLastPost')
});
diff --git a/app/assets/javascripts/discourse/models/trust-level.js.es6 b/app/assets/javascripts/discourse/models/trust-level.js.es6
new file mode 100644
index 0000000000..f51eda37e3
--- /dev/null
+++ b/app/assets/javascripts/discourse/models/trust-level.js.es6
@@ -0,0 +1,6 @@
+import RestModel from 'discourse/models/rest';
+import { fmt } from 'discourse/lib/computed';
+
+export default RestModel.extend({
+ detailedName: fmt('id', 'name', '%@ - %@')
+});
diff --git a/app/assets/javascripts/discourse/models/trust_level.js b/app/assets/javascripts/discourse/models/trust_level.js
deleted file mode 100644
index 8694010119..0000000000
--- a/app/assets/javascripts/discourse/models/trust_level.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- Represents a user's trust level in the system
-
- @class TrustLevel
- @extends Discourse.Model
- @namespace Discourse
- @module Discourse
-**/
-Discourse.TrustLevel = Discourse.Model.extend({
- detailedName: Discourse.computed.fmt('id', 'name', '%@ - %@')
-});
diff --git a/app/assets/javascripts/discourse/models/user-action-stat.js.es6 b/app/assets/javascripts/discourse/models/user-action-stat.js.es6
new file mode 100644
index 0000000000..073c303247
--- /dev/null
+++ b/app/assets/javascripts/discourse/models/user-action-stat.js.es6
@@ -0,0 +1,21 @@
+import RestModel from 'discourse/models/rest';
+import UserAction from 'discourse/models/user-action';
+import { i18n } from 'discourse/lib/computed';
+
+export default RestModel.extend({
+
+ isPM: function() {
+ const actionType = this.get('action_type');
+ return actionType === UserAction.TYPES.messages_sent ||
+ actionType === UserAction.TYPES.messages_received;
+ }.property('action_type'),
+
+ description: i18n('action_type', 'user_action_groups.%@'),
+
+ isResponse: function() {
+ const actionType = this.get('action_type');
+ return actionType === UserAction.TYPES.replies ||
+ actionType === UserAction.TYPES.quotes;
+ }.property('action_type')
+
+});
diff --git a/app/assets/javascripts/discourse/models/user_action.js b/app/assets/javascripts/discourse/models/user-action.js.es6
similarity index 81%
rename from app/assets/javascripts/discourse/models/user_action.js
rename to app/assets/javascripts/discourse/models/user-action.js.es6
index 2c5060258d..2d273c43e1 100644
--- a/app/assets/javascripts/discourse/models/user_action.js
+++ b/app/assets/javascripts/discourse/models/user-action.js.es6
@@ -1,39 +1,37 @@
-var UserActionTypes = {
- likes_given: 1,
- likes_received: 2,
- bookmarks: 3,
- topics: 4,
- posts: 5,
- replies: 6,
- mentions: 7,
- quotes: 9,
- edits: 11,
- messages_sent: 12,
- messages_received: 13,
- pending: 14
- },
- InvertedActionTypes = {};
+import RestModel from 'discourse/models/rest';
+import { url } from 'discourse/lib/computed';
+
+const UserActionTypes = {
+ likes_given: 1,
+ likes_received: 2,
+ bookmarks: 3,
+ topics: 4,
+ posts: 5,
+ replies: 6,
+ mentions: 7,
+ quotes: 9,
+ edits: 11,
+ messages_sent: 12,
+ messages_received: 13,
+ pending: 14
+};
+const InvertedActionTypes = {};
_.each(UserActionTypes, function (k, v) {
InvertedActionTypes[k] = v;
});
-Discourse.UserAction = Discourse.Model.extend({
+const UserAction = RestModel.extend({
_attachCategory: function() {
- var categoryId = this.get('category_id');
+ const categoryId = this.get('category_id');
if (categoryId) {
this.set('category', Discourse.Category.findById(categoryId));
}
}.on('init'),
- /**
- Return an i18n key we will use for the description text of a user action.
-
- @property descriptionKey
- **/
descriptionKey: function() {
- var action = this.get('action_type');
+ const action = this.get('action_type');
if (action === null || Discourse.UserAction.TO_SHOW.indexOf(action) >= 0) {
if (this.get('isPM')) {
return this.get('sameUser') ? 'sent_by_you' : 'sent_by_user';
@@ -74,13 +72,13 @@ Discourse.UserAction = Discourse.Model.extend({
presentName: Em.computed.any('name', 'username'),
targetDisplayName: Em.computed.any('target_name', 'target_username'),
actingDisplayName: Em.computed.any('acting_name', 'acting_username'),
- targetUserUrl: Discourse.computed.url('target_username', '/users/%@'),
+ targetUserUrl: url('target_username', '/users/%@'),
usernameLower: function() {
return this.get('username').toLowerCase();
}.property('username'),
- userUrl: Discourse.computed.url('usernameLower', '/users/%@'),
+ userUrl: url('usernameLower', '/users/%@'),
postUrl: function() {
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('post_number'));
@@ -102,7 +100,7 @@ Discourse.UserAction = Discourse.Model.extend({
removableBookmark: Em.computed.and('bookmarkType', 'sameUser'),
addChild: function(action) {
- var groups = this.get("childGroups");
+ let groups = this.get("childGroups");
if (!groups) {
groups = {
likes: Discourse.UserActionGroup.create({ icon: "fa fa-heart" }),
@@ -113,7 +111,7 @@ Discourse.UserAction = Discourse.Model.extend({
}
this.set("childGroups", groups);
- var bucket = (function() {
+ const bucket = (function() {
switch (action.action_type) {
case UserActionTypes.likes_given:
case UserActionTypes.likes_received:
@@ -124,15 +122,15 @@ Discourse.UserAction = Discourse.Model.extend({
return "bookmarks";
}
})();
- var current = groups[bucket];
+ const current = groups[bucket];
if (current) {
current.push(action);
}
},
children: function() {
- var g = this.get("childGroups");
- var rval = [];
+ const g = this.get("childGroups");
+ let rval = [];
if (g) {
rval = [g.likes, g.stars, g.edits, g.bookmarks].filter(function(i) {
return i.get("items") && i.get("items").length > 0;
@@ -154,20 +152,20 @@ Discourse.UserAction = Discourse.Model.extend({
}
});
-Discourse.UserAction.reopenClass({
+UserAction.reopenClass({
collapseStream: function(stream) {
- var uniq = {},
- collapsed = [],
- pos = 0;
+ const uniq = {};
+ const collapsed = [];
+ let pos = 0;
stream.forEach(function(item) {
- var key = "" + item.topic_id + "-" + item.post_number;
- var found = uniq[key];
+ const key = "" + item.topic_id + "-" + item.post_number;
+ const found = uniq[key];
if (found === void 0) {
- var current;
+ let current;
if (Discourse.UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) {
- current = Discourse.UserAction.create(item);
+ current = UserAction.create(item);
item.switchToActing();
current.addChild(item);
} else {
@@ -177,7 +175,7 @@ Discourse.UserAction.reopenClass({
collapsed[pos] = current;
pos += 1;
} else {
- if (Discourse.UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) {
+ if (UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) {
item.switchToActing();
collapsed[found].addChild(item);
} else {
@@ -208,3 +206,5 @@ Discourse.UserAction.reopenClass({
]
});
+
+export default UserAction;
diff --git a/app/assets/javascripts/discourse/models/user_posts_stream.js b/app/assets/javascripts/discourse/models/user-posts-stream.js.es6
similarity index 67%
rename from app/assets/javascripts/discourse/models/user_posts_stream.js
rename to app/assets/javascripts/discourse/models/user-posts-stream.js.es6
index e00646cc12..8d7a750d8b 100644
--- a/app/assets/javascripts/discourse/models/user_posts_stream.js
+++ b/app/assets/javascripts/discourse/models/user-posts-stream.js.es6
@@ -1,12 +1,7 @@
-/**
- Represents a user's stream
+import { url } from 'discourse/lib/computed';
+import AdminPost from 'discourse/models/admin-post';
- @class UserPostsStream
- @extends Discourse.Model
- @namespace Discourse
- @module Discourse
-**/
-Discourse.UserPostsStream = Discourse.Model.extend({
+export default Discourse.Model.extend({
loaded: false,
_initialize: function () {
@@ -17,9 +12,9 @@ Discourse.UserPostsStream = Discourse.Model.extend({
});
}.on("init"),
- url: Discourse.computed.url("user.username_lower", "filter", "itemsLoaded", "/posts/%@/%@?offset=%@"),
+ url: url("user.username_lower", "filter", "itemsLoaded", "/posts/%@/%@?offset=%@"),
- filterBy: function (filter) {
+ filterBy(filter) {
if (this.get("loaded") && this.get("filter") === filter) { return Ember.RSVP.resolve(); }
this.setProperties({
@@ -32,15 +27,15 @@ Discourse.UserPostsStream = Discourse.Model.extend({
return this.findItems();
},
- findItems: function () {
- var self = this;
+ findItems() {
+ const self = this;
if (this.get("loading") || !this.get("canLoadMore")) { return Ember.RSVP.reject(); }
this.set("loading", true);
return Discourse.ajax(this.get("url"), { cache: false }).then(function (result) {
if (result) {
- var posts = result.map(function (post) { return Discourse.AdminPost.create(post); });
+ const posts = result.map(function (post) { return AdminPost.create(post); });
self.get("content").pushObjects(posts);
self.setProperties({
loaded: true,
diff --git a/app/assets/javascripts/discourse/models/user-stream.js.es6 b/app/assets/javascripts/discourse/models/user-stream.js.es6
index 1870d484d6..71d1bba89c 100644
--- a/app/assets/javascripts/discourse/models/user-stream.js.es6
+++ b/app/assets/javascripts/discourse/models/user-stream.js.es6
@@ -1,3 +1,4 @@
+import { url } from 'discourse/lib/computed';
import RestModel from 'discourse/models/rest';
export default RestModel.extend({
@@ -22,7 +23,7 @@ export default RestModel.extend({
return filter;
}.property('filter'),
- baseUrl: Discourse.computed.url('itemsLoaded', 'user.username_lower', '/user_actions.json?offset=%@&username=%@'),
+ baseUrl: url('itemsLoaded', 'user.username_lower', '/user_actions.json?offset=%@&username=%@'),
filterBy(filter) {
this.setProperties({ filter, itemsLoaded: 0, content: [], lastLoadedUrl: null });
diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6
index 8e9c8e5350..279073957c 100644
--- a/app/assets/javascripts/discourse/models/user.js.es6
+++ b/app/assets/javascripts/discourse/models/user.js.es6
@@ -1,5 +1,10 @@
+import { url } from 'discourse/lib/computed';
import RestModel from 'discourse/models/rest';
import avatarTemplate from 'discourse/lib/avatar-template';
+import UserStream from 'discourse/models/user-stream';
+import UserPostsStream from 'discourse/models/user-posts-stream';
+import Singleton from 'discourse/mixins/singleton';
+import { longDate } from 'discourse/lib/formatter';
const User = RestModel.extend({
@@ -10,24 +15,12 @@ const User = RestModel.extend({
hasNotPosted: Em.computed.not("hasPosted"),
canBeDeleted: Em.computed.and("can_be_deleted", "hasNotPosted"),
- /**
- The user's stream
-
- @property stream
- @type {Discourse.UserStream}
- **/
stream: function() {
- return Discourse.UserStream.create({ user: this });
+ return UserStream.create({ user: this });
}.property(),
- /**
- The user's posts stream
-
- @property postsStream
- @type {Discourse.UserPostsStream}
- **/
postsStream: function() {
- return Discourse.UserPostsStream.create({ user: this });
+ return UserPostsStream.create({ user: this });
}.property(),
/**
@@ -63,7 +56,7 @@ const User = RestModel.extend({
/**
This user's profile background(in CSS).
- @property websiteName
+ @property profileBackground
@type {String}
**/
profileBackground: function() {
@@ -89,7 +82,7 @@ const User = RestModel.extend({
@property adminPath
@type {String}
**/
- adminPath: Discourse.computed.url('username_lower', "/admin/users/%@"),
+ adminPath: url('username_lower', "/admin/users/%@"),
/**
This user's username in lowercase.
@@ -123,7 +116,7 @@ const User = RestModel.extend({
}.property('suspended_till'),
suspendedTillDate: function() {
- return Discourse.Formatter.longDate(this.get('suspended_till'));
+ return longDate(this.get('suspended_till'));
}.property('suspended_till'),
/**
@@ -434,15 +427,15 @@ const User = RestModel.extend({
});
-User.reopenClass(Discourse.Singleton, {
+User.reopenClass(Singleton, {
// Find a `Discourse.User` for a given username.
findByUsername: function(username, options) {
- const user = Discourse.User.create({username: username});
+ const user = User.create({username: username});
return user.findDetails(options);
},
- // TODO: Use app.register and junk Discourse.Singleton
+ // TODO: Use app.register and junk Singleton
createCurrent: function() {
var userJson = PreloadStore.get('currentUser');
if (userJson) {
diff --git a/app/assets/javascripts/discourse/models/user_action_stat.js b/app/assets/javascripts/discourse/models/user_action_stat.js
deleted file mode 100644
index 942f22e960..0000000000
--- a/app/assets/javascripts/discourse/models/user_action_stat.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- A data model representing a statistic on a UserAction
-
- @class UserActionStat
- @extends Discourse.Model
- @namespace Discourse
- @module Discourse
-**/
-Discourse.UserActionStat = Discourse.Model.extend({
-
- isPM: function() {
- var actionType = this.get('action_type');
- return actionType === Discourse.UserAction.TYPES.messages_sent ||
- actionType === Discourse.UserAction.TYPES.messages_received;
- }.property('action_type'),
-
- description: Discourse.computed.i18n('action_type', 'user_action_groups.%@'),
-
- isResponse: function() {
- var actionType = this.get('action_type');
- return actionType === Discourse.UserAction.TYPES.replies ||
- actionType === Discourse.UserAction.TYPES.quotes;
- }.property('action_type')
-
-});
diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6
index 779822ee39..152aabadde 100644
--- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6
+++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6
@@ -10,6 +10,7 @@ export default function() {
this.route('fromParamsNear', { path: '/:nearPost' });
});
this.resource('topicBySlug', { path: '/t/:slug' });
+ this.route('topicUnsubscribe', { path: '/t/:slug/:id/unsubscribe' });
this.resource('discovery', { path: '/' }, function() {
// top
diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6
index f0af569b02..a923dd1797 100644
--- a/app/assets/javascripts/discourse/routes/application.js.es6
+++ b/app/assets/javascripts/discourse/routes/application.js.es6
@@ -1,3 +1,4 @@
+import { setting } from 'discourse/lib/computed';
import showModal from 'discourse/lib/show-modal';
import OpenComposer from "discourse/mixins/open-composer";
@@ -13,7 +14,7 @@ function unlessReadOnly(method) {
const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
- siteTitle: Discourse.computed.setting('title'),
+ siteTitle: setting('title'),
actions: {
_collectTitleTokens(tokens) {
@@ -74,7 +75,7 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
}
exceptionController.setProperties({ lastTransition: transition, thrown: err });
- this.transitionTo('exception');
+ this.intermediateTransitionTo('exception');
},
showLogin: unlessReadOnly('handleShowLogin'),
diff --git a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6
index 1f5dcd7621..69cd2e5c22 100644
--- a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6
+++ b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6
@@ -1,3 +1,4 @@
+import ScreenTrack from 'discourse/lib/screen-track';
import { queryParams } from 'discourse/controllers/discovery-sortable';
// A helper to build a topic route for a filter
@@ -82,7 +83,7 @@ export default function(filter, extras) {
model(data, transition) {
// attempt to stop early cause we need this to be called before .sync
- Discourse.ScreenTrack.current().stop();
+ ScreenTrack.current().stop();
const findOpts = filterQueryParams(transition.queryParams),
extras = { cached: this.isPoppedState(transition) };
diff --git a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6
index bfb8468cd7..6de3d07c54 100644
--- a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6
+++ b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6
@@ -2,32 +2,32 @@
export default Discourse.Route.extend({
// Avoid default model hook
- model: function(p) { return p; },
+ model(params) { return params; },
- setupController: function(controller, params) {
+ setupController(controller, params) {
params = params || {};
params.track_visit = true;
- var topic = this.modelFor('topic'),
- postStream = topic.get('postStream');
- var topicController = this.controllerFor('topic'),
- topicProgressController = this.controllerFor('topic-progress'),
- composerController = this.controllerFor('composer');
+ const self = this,
+ topic = this.modelFor('topic'),
+ postStream = topic.get('postStream'),
+ topicController = this.controllerFor('topic'),
+ topicProgressController = this.controllerFor('topic-progress'),
+ composerController = this.controllerFor('composer');
// I sincerely hope no topic gets this many posts
if (params.nearPost === "last") { params.nearPost = 999999999; }
- var self = this;
params.forceLoad = true;
- postStream.refresh(params).then(function () {
+ postStream.refresh(params).then(function () {
// TODO we are seeing errors where closest post is null and this is exploding
// we need better handling and logging for this condition.
// The post we requested might not exist. Let's find the closest post
- var closestPost = postStream.closestPostForPostNumber(params.nearPost || 1),
- closest = closestPost.get('post_number'),
- progress = postStream.progressIndexOfPost(closestPost);
+ const closestPost = postStream.closestPostForPostNumber(params.nearPost || 1),
+ closest = closestPost.get('post_number'),
+ progress = postStream.progressIndexOfPost(closestPost);
topicController.setProperties({
'model.currentPost': closest,
@@ -43,6 +43,7 @@ export default Discourse.Route.extend({
Ember.run.scheduleOnce('afterRender', function() {
self.appEvents.trigger('post:highlight', closest);
});
+
Discourse.URL.jumpToPost(closest);
if (topic.present('draft')) {
diff --git a/app/assets/javascripts/discourse/routes/topic-unsubscribe.js.es6 b/app/assets/javascripts/discourse/routes/topic-unsubscribe.js.es6
new file mode 100644
index 0000000000..10e77c47b4
--- /dev/null
+++ b/app/assets/javascripts/discourse/routes/topic-unsubscribe.js.es6
@@ -0,0 +1,23 @@
+import PostStream from "discourse/models/post-stream";
+
+export default Discourse.Route.extend({
+ model(params) {
+ const topic = this.store.createRecord("topic", { id: params.id });
+ return PostStream.loadTopicView(params.id).then(json => {
+ topic.updateFromJson(json);
+ return topic;
+ });
+ },
+
+ afterModel(topic) {
+ // hide the notification reason text
+ topic.set("details.notificationReasonText", null);
+ },
+
+ actions: {
+ didTransition() {
+ this.controllerFor("application").set("showFooter", true);
+ return true;
+ }
+ }
+});
diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6
index 7d5f253341..6d730f058d 100644
--- a/app/assets/javascripts/discourse/routes/topic.js.es6
+++ b/app/assets/javascripts/discourse/routes/topic.js.es6
@@ -1,3 +1,5 @@
+import ScreenTrack from 'discourse/lib/screen-track';
+
let isTransitioning = false,
scheduledReplace = null,
lastScrollPos = null;
@@ -185,7 +187,7 @@ const TopicRoute = Discourse.Route.extend({
topicController.set('multiSelect', false);
topicController.unsubscribe();
this.controllerFor('composer').set('topic', null);
- Discourse.ScreenTrack.current().stop();
+ ScreenTrack.current().stop();
const headerController = this.controllerFor('header');
if (headerController) {
@@ -226,7 +228,7 @@ const TopicRoute = Discourse.Route.extend({
this.controllerFor('topic-progress').set('model', model);
// We reset screen tracking every time a topic is entered
- Discourse.ScreenTrack.current().start(model.get('id'), controller);
+ ScreenTrack.current().start(model.get('id'), controller);
}
});
diff --git a/app/assets/javascripts/discourse/templates/application.hbs b/app/assets/javascripts/discourse/templates/application.hbs
index 7eb87f79e3..9d92d2063f 100644
--- a/app/assets/javascripts/discourse/templates/application.hbs
+++ b/app/assets/javascripts/discourse/templates/application.hbs
@@ -2,7 +2,9 @@
- {{custom-html "top"}}
+ {{#if showTop}}
+ {{custom-html "top"}}
+ {{/if}}
{{global-notice}}
{{outlet}}
diff --git a/app/assets/javascripts/discourse/templates/badges/show.hbs b/app/assets/javascripts/discourse/templates/badges/show.hbs
index ed72176c52..42a13208c2 100644
--- a/app/assets/javascripts/discourse/templates/badges/show.hbs
+++ b/app/assets/javascripts/discourse/templates/badges/show.hbs
@@ -36,7 +36,7 @@
{{/link-to}}
{{#if ub.post_number}}
- {{ub.topic.title}}
+ {{{ub.topic.fancyTitle}}}
{{/if}}
{{/each}}
diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs
index 3a8bd239ed..42b2323f6a 100644
--- a/app/assets/javascripts/discourse/templates/header.hbs
+++ b/app/assets/javascripts/discourse/templates/header.hbs
@@ -12,6 +12,9 @@
{{/unless}}
{{#if view.renderDropdowns}}
+
+ {{plugin-outlet "header-before-dropdowns"}}
+
{{render "search"}}
{{render "notifications" notifications}}
diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs
index 82f0e59d79..7a02b2a778 100644
--- a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs
+++ b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs
@@ -38,7 +38,7 @@
{{footerMessage}}
- {{#if can_create_topic}}{{i18n 'topic.suggest_create_topic'}}{{/if}}
+ {{#if model.can_create_topic}}{{i18n 'topic.suggest_create_topic'}}{{/if}}
{{else}}
{{#if top}}
diff --git a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs
index f52e48ba43..972acfa16a 100644
--- a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs
+++ b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs
@@ -14,6 +14,7 @@
{{category-link content.category}}
{{/unless}}
+ {{plugin-outlet "topic-list-tags"}}
{{raw "list/posts-count-column" topic=content tagName="div"}}
diff --git a/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs b/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs
index 07b6430795..d6360b86de 100644
--- a/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs
@@ -35,7 +35,7 @@
{{{pinMessage}}}
{{fa-icon "clock-o"}}
- {{input type="date" value=model.pinnedInCategoryUntil}}
+ {{date-picker value=model.pinnedInCategoryUntil}}
{{d-button action="pin" icon="thumb-tack" label="topic.feature.pin" class="btn-primary" disabled=pinDisabled}}
@@ -56,7 +56,7 @@
{{i18n "topic.feature_topic.pin_globally"}}
{{fa-icon "clock-o"}}
- {{input type="date" value=model.pinnedGloballyUntil}}
+ {{date-picker value=model.pinnedGloballyUntil}}
{{d-button action="pinGlobally" icon="thumb-tack" label="topic.feature.pin_globally" class="btn-primary" disabled=pinGloballyDisabled}}
diff --git a/app/assets/javascripts/discourse/templates/modal/invite.hbs b/app/assets/javascripts/discourse/templates/modal/invite.hbs
index 5984aa86ab..cba7a89b4e 100644
--- a/app/assets/javascripts/discourse/templates/modal/invite.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/invite.hbs
@@ -1,11 +1,11 @@
- {{#if error}}
+ {{#if model.error}}
{{errorMessage}}
{{/if}}
- {{#if finished}}
+ {{#if model.finished}}
{{{successMessage}}}
{{else}}
@@ -20,12 +20,12 @@
{{/if}}
{{#if showGroups}}
- {{group-selector groupFinder=groupFinder groupNames=groupNames placeholderKey="topic.invite_private.group_name"}}
+ {{group-selector groupFinder=groupFinder groupNames=model.groupNames placeholderKey="topic.invite_private.group_name"}}
{{/if}}
{{/if}}