ES6ify some of the remaining files

This commit is contained in:
Robin Ward 2015-08-07 15:08:27 -04:00
parent 378087727f
commit b7e6eaa961
96 changed files with 684 additions and 720 deletions

View file

@ -1,13 +1,14 @@
import { bufferedProperty } from 'discourse/mixins/buffered-content';
import UserField from 'admin/models/user-field'; import UserField from 'admin/models/user-field';
import { bufferedProperty } from 'discourse/mixins/buffered-content';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
import { propertyEqual } from 'discourse/lib/computed';
export default Ember.Component.extend(bufferedProperty('userField'), { export default Ember.Component.extend(bufferedProperty('userField'), {
editing: Ember.computed.empty('userField.id'), editing: Ember.computed.empty('userField.id'),
classNameBindings: [':user-field'], classNameBindings: [':user-field'],
cantMoveUp: Discourse.computed.propertyEqual('userField', 'firstField'), cantMoveUp: propertyEqual('userField', 'firstField'),
cantMoveDown: Discourse.computed.propertyEqual('userField', 'lastField'), cantMoveDown: propertyEqual('userField', 'lastField'),
userFieldsDescription: function() { userFieldsDescription: function() {
return I18n.t('admin.user_fields.description'); return I18n.t('admin.user_fields.description');

View file

@ -1,13 +1,14 @@
import BufferedContent from 'discourse/mixins/buffered-content'; import BufferedContent from 'discourse/mixins/buffered-content';
import ScrollTop from 'discourse/mixins/scroll-top'; import ScrollTop from 'discourse/mixins/scroll-top';
import SiteSetting from 'admin/models/site-setting'; import SiteSetting from 'admin/models/site-setting';
import { propertyNotEqual } from 'discourse/lib/computed';
const CustomTypes = ['bool', 'enum', 'list', 'url_list', 'host_list']; const CustomTypes = ['bool', 'enum', 'list', 'url_list', 'host_list'];
export default Ember.Component.extend(BufferedContent, ScrollTop, { export default Ember.Component.extend(BufferedContent, ScrollTop, {
classNameBindings: [':row', ':setting', 'setting.overridden', 'typeClass'], classNameBindings: [':row', ':setting', 'setting.overridden', 'typeClass'],
content: Ember.computed.alias('setting'), content: Ember.computed.alias('setting'),
dirty: Discourse.computed.propertyNotEqual('buffered.value', 'setting.value'), dirty: propertyNotEqual('buffered.value', 'setting.value'),
validationMessage: null, validationMessage: null,
preview: function() { preview: function() {

View file

@ -1,5 +1,6 @@
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
import BufferedContent from 'discourse/mixins/buffered-content'; import BufferedContent from 'discourse/mixins/buffered-content';
import { propertyNotEqual } from 'discourse/lib/computed';
export default Ember.ObjectController.extend(BufferedContent, { export default Ember.ObjectController.extend(BufferedContent, {
needs: ['admin-badges'], needs: ['admin-badges'],
@ -12,7 +13,7 @@ export default Ember.ObjectController.extend(BufferedContent, {
protectedSystemFields: Em.computed.alias('controllers.admin-badges.protectedSystemFields'), protectedSystemFields: Em.computed.alias('controllers.admin-badges.protectedSystemFields'),
readOnly: Ember.computed.alias('buffered.system'), readOnly: Ember.computed.alias('buffered.system'),
showDisplayName: Discourse.computed.propertyNotEqual('name', 'displayName'), showDisplayName: propertyNotEqual('name', 'displayName'),
canEditDescription: Em.computed.none('buffered.translatedDescription'), canEditDescription: Em.computed.none('buffered.translatedDescription'),
_resetSaving: function() { _resetSaving: function() {

View file

@ -1,3 +1,5 @@
import { url } from 'discourse/lib/computed';
const sections = ['css', 'header', 'top', 'footer', 'head-tag', 'body-tag', const sections = ['css', 'header', 'top', 'footer', 'head-tag', 'body-tag',
'mobile-css', 'mobile-header', 'mobile-top', 'mobile-footer', 'mobile-css', 'mobile-header', 'mobile-top', 'mobile-footer',
'embedded-css']; 'embedded-css'];
@ -12,8 +14,8 @@ export default Ember.Controller.extend(activeSections, {
maximized: false, maximized: false,
section: null, section: null,
previewUrl: Discourse.computed.url("model.key", "/?preview-style=%@"), previewUrl: url("model.key", "/?preview-style=%@"),
downloadUrl: Discourse.computed.url('model.id', '/admin/size_customizations/%@'), downloadUrl: url('model.id', '/admin/size_customizations/%@'),
mobile: function() { mobile: function() {
return this.get('section').startsWith('mobile-'); return this.get('section').startsWith('mobile-');
@ -33,8 +35,8 @@ export default Ember.Controller.extend(activeSections, {
needs: ['adminCustomizeCssHtml'], needs: ['adminCustomizeCssHtml'],
undoPreviewUrl: Discourse.computed.url('/?preview-style='), undoPreviewUrl: url('/?preview-style='),
defaultStyleUrl: Discourse.computed.url('/?preview-style=default'), defaultStyleUrl: url('/?preview-style=default'),
actions: { actions: {
save() { save() {

View file

@ -1,17 +1,12 @@
/** import { setting } from 'discourse/lib/computed';
This controller supports the default interface when you enter the admin section.
@class AdminDashboardController // This controller supports the default interface when you enter the admin section.
@extends Ember.Controller
@namespace Discourse
@module Discourse
**/
export default Ember.Controller.extend({ export default Ember.Controller.extend({
loading: true, loading: true,
versionCheck: null, versionCheck: null,
problemsCheckMinutes: 1, problemsCheckMinutes: 1,
showVersionChecks: Discourse.computed.setting('version_checks'), showVersionChecks: setting('version_checks'),
foundProblems: function() { foundProblems: function() {
return(Discourse.User.currentProp('admin') && this.get('problems') && this.get('problems').length > 0); return(Discourse.User.currentProp('admin') && this.get('problems') && this.get('problems').length > 0);

View file

@ -1,4 +1,5 @@
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
import { propertyEqual } from 'discourse/lib/computed';
export default Em.ObjectController.extend({ export default Em.ObjectController.extend({
needs: ['adminGroupsType'], needs: ['adminGroupsType'],
@ -15,7 +16,7 @@ export default Em.ObjectController.extend({
}.property("limit", "user_count"), }.property("limit", "user_count"),
showingFirst: Em.computed.lte("currentPage", 1), showingFirst: Em.computed.lte("currentPage", 1),
showingLast: Discourse.computed.propertyEqual("currentPage", "totalPages"), showingLast: propertyEqual("currentPage", "totalPages"),
aliasLevelOptions: function() { aliasLevelOptions: function() {
return [ return [

View file

@ -1,15 +1,16 @@
import ObjectController from 'discourse/controllers/object'; import ObjectController from 'discourse/controllers/object';
import CanCheckEmails from 'discourse/mixins/can-check-emails'; import CanCheckEmails from 'discourse/mixins/can-check-emails';
import { propertyNotEqual, setting } from 'discourse/lib/computed';
export default ObjectController.extend(CanCheckEmails, { export default ObjectController.extend(CanCheckEmails, {
editingTitle: false, editingTitle: false,
originalPrimaryGroupId: null, originalPrimaryGroupId: null,
availableGroups: null, availableGroups: null,
showApproval: Discourse.computed.setting('must_approve_users'), showApproval: setting('must_approve_users'),
showBadges: Discourse.computed.setting('enable_badges'), showBadges: setting('enable_badges'),
primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'model.primary_group_id'), primaryGroupDirty: propertyNotEqual('originalPrimaryGroupId', 'model.primary_group_id'),
automaticGroups: function() { automaticGroups: function() {
return this.get("model.automaticGroups").map((g) => g.name).join(", "); return this.get("model.automaticGroups").map((g) => g.name).join(", ");

View file

@ -1,3 +1,5 @@
import { i18n } from 'discourse/lib/computed';
export default Ember.ArrayController.extend({ export default Ember.ArrayController.extend({
query: null, query: null,
showEmails: false, showEmails: false,
@ -9,7 +11,7 @@ export default Ember.ArrayController.extend({
queryPending: Em.computed.equal('query', 'pending'), queryPending: Em.computed.equal('query', 'pending'),
queryHasApproval: Em.computed.or('queryNew', 'queryPending'), queryHasApproval: Em.computed.or('queryNew', 'queryPending'),
showApproval: Em.computed.and('siteSettings.must_approve_users', 'queryHasApproval'), showApproval: Em.computed.and('siteSettings.must_approve_users', 'queryHasApproval'),
searchHint: Discourse.computed.i18n('search_hint'), searchHint: i18n('search_hint'),
hasSelection: Em.computed.gt('selectedCount', 0), hasSelection: Em.computed.gt('selectedCount', 0),
selectedCount: function() { selectedCount: function() {

View file

@ -1,3 +1,4 @@
import { propertyNotEqual } from 'discourse/lib/computed';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
const AdminUser = Discourse.User.extend({ const AdminUser = Discourse.User.extend({
@ -144,7 +145,7 @@ const AdminUser = Discourse.User.extend({
this.set('originalTrustLevel', this.get('trust_level')); this.set('originalTrustLevel', this.get('trust_level'));
}, },
dirty: Discourse.computed.propertyNotEqual('originalTrustLevel', 'trustLevel.id'), dirty: propertyNotEqual('originalTrustLevel', 'trustLevel.id'),
saveTrustLevel() { saveTrustLevel() {
return Discourse.ajax("/admin/users/" + this.id + "/trust_level", { return Discourse.ajax("/admin/users/" + this.id + "/trust_level", {

View file

@ -1,7 +1,8 @@
import round from "discourse/lib/round"; import round from "discourse/lib/round";
import { fmt } from 'discourse/lib/computed';
const Report = Discourse.Model.extend({ const Report = Discourse.Model.extend({
reportUrl: Discourse.computed.fmt("type", "/admin/reports/%@"), reportUrl: fmt("type", "/admin/reports/%@"),
valueAt(numDaysAgo) { valueAt(numDaysAgo) {
if (this.data) { if (this.data) {

View file

@ -1,9 +1,10 @@
import RestModel from 'discourse/models/rest'; import RestModel from 'discourse/models/rest';
import { i18n } from 'discourse/lib/computed';
const UserField = RestModel.extend(); const UserField = RestModel.extend();
const UserFieldType = Ember.Object.extend({ const UserFieldType = Ember.Object.extend({
name: Discourse.computed.i18n('id', 'admin.user_fields.field_types.%@') name: i18n('id', 'admin.user_fields.field_types.%@')
}); });
UserField.reopenClass({ UserField.reopenClass({

View file

@ -1,5 +1,6 @@
import StringBuffer from 'discourse/mixins/string-buffer'; import StringBuffer from 'discourse/mixins/string-buffer';
import { iconHTML } from 'discourse/helpers/fa-icon'; import { iconHTML } from 'discourse/helpers/fa-icon';
import { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
export default Ember.Component.extend(StringBuffer, { export default Ember.Component.extend(StringBuffer, {
tagName: 'section', tagName: 'section',
@ -57,7 +58,7 @@ export default Ember.Component.extend(StringBuffer, {
buffer.push("<div class='post-action'>" + buffer.push("<div class='post-action'>" +
iconHTML('fa-trash-o') + '&nbsp;' + iconHTML('fa-trash-o') + '&nbsp;' +
Discourse.Utilities.tinyAvatar(post.get('postDeletedBy.avatar_template'), {title: post.get('postDeletedBy.username')}) + 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'))) +
"</div>"); "</div>");
} }
}, },

View file

@ -1,8 +1,9 @@
import { setting } from 'discourse/lib/computed';
var get = Ember.get; var get = Ember.get;
export default Ember.Component.extend({ export default Ember.Component.extend({
classNameBindings: ['category::no-category', 'categories:has-drop','categoryStyle'], classNameBindings: ['category::no-category', 'categories:has-drop','categoryStyle'],
categoryStyle: Discourse.computed.setting('category_style'), categoryStyle: setting('category_style'),
tagName: 'li', tagName: 'li',

View file

@ -1,6 +1,7 @@
import { setting } from 'discourse/lib/computed';
import { buildCategoryPanel } from 'discourse/components/edit-category-panel'; import { buildCategoryPanel } from 'discourse/components/edit-category-panel';
export default buildCategoryPanel('settings', { export default buildCategoryPanel('settings', {
emailInEnabled: Discourse.computed.setting('email_in'), emailInEnabled: setting('email_in'),
showPositionInput: Discourse.computed.setting('fixed_category_positions'), showPositionInput: setting('fixed_category_positions'),
}); });

View file

@ -1,3 +1,5 @@
import { propertyEqual } from 'discourse/lib/computed';
export default Em.Component.extend({ export default Em.Component.extend({
tagName: 'li', tagName: 'li',
classNameBindings: ['active', 'tabClassName'], classNameBindings: ['active', 'tabClassName'],
@ -6,7 +8,7 @@ export default Em.Component.extend({
return 'edit-category-' + this.get('tab'); return 'edit-category-' + this.get('tab');
}.property('tab'), }.property('tab'),
active: Discourse.computed.propertyEqual('selectedTab', 'tab'), active: propertyEqual('selectedTab', 'tab'),
title: function() { title: function() {
return I18n.t('category.' + this.get('tab').replace('-', '_')); return I18n.t('category.' + this.get('tab').replace('-', '_'));

View file

@ -1,3 +1,5 @@
import { setting } from 'discourse/lib/computed';
export default Ember.Component.extend({ export default Ember.Component.extend({
classNames: ["title"], classNames: ["title"],
@ -13,10 +15,10 @@ export default Ember.Component.extend({
return Discourse.Mobile.mobileView && !Ember.isBlank(this.get('mobileBigLogoUrl')); return Discourse.Mobile.mobileView && !Ember.isBlank(this.get('mobileBigLogoUrl'));
}.property(), }.property(),
smallLogoUrl: Discourse.computed.setting('logo_small_url'), smallLogoUrl: setting('logo_small_url'),
bigLogoUrl: Discourse.computed.setting('logo_url'), bigLogoUrl: setting('logo_url'),
mobileBigLogoUrl: Discourse.computed.setting('mobile_logo_url'), mobileBigLogoUrl: setting('mobile_logo_url'),
title: Discourse.computed.setting('title'), title: setting('title'),
click: function(e) { click: function(e) {
// if they want to open in a new tab, let it so // if they want to open in a new tab, let it so

View file

@ -1,6 +1,8 @@
import { setting } from 'discourse/lib/computed';
const PosterNameComponent = Em.Component.extend({ const PosterNameComponent = Em.Component.extend({
classNames: ['names', 'trigger-user-card'], classNames: ['names', 'trigger-user-card'],
displayNameOnPosts: Discourse.computed.setting('display_name_on_posts'), displayNameOnPosts: setting('display_name_on_posts'),
// sanitize name for comparison // sanitize name for comparison
sanitizeName(name){ sanitizeName(name){

View file

@ -1,3 +1,5 @@
import { relativeAge } from 'discourse/lib/formatter';
const icons = { const icons = {
'closed.enabled': 'lock', 'closed.enabled': 'lock',
'closed.disabled': 'unlock-alt', 'closed.disabled': 'unlock-alt',
@ -19,7 +21,7 @@ export function actionDescription(actionCode, createdAt) {
const ac = this.get(actionCode); const ac = this.get(actionCode);
if (ac) { if (ac) {
const dt = new Date(this.get(createdAt)); 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(); return I18n.t(`action_codes.${ac}`, {when}).htmlSafe();
} }
}.property(actionCode, createdAt); }.property(actionCode, createdAt);

View file

@ -1,8 +1,9 @@
import { propertyEqual } from 'discourse/lib/computed';
import { actionDescription } from "discourse/components/small-action"; import { actionDescription } from "discourse/components/small-action";
export default Ember.Component.extend({ export default Ember.Component.extend({
classNameBindings: [":item", "item.hidden", "item.deleted", "moderatorAction"], 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"), actionDescription: actionDescription("item.action_code", "item.created_at"),
actions: { actions: {

View file

@ -1,6 +1,8 @@
import { fmt } from 'discourse/lib/computed';
export default Ember.Component.extend({ export default Ember.Component.extend({
classNameBindings: [':user-field', 'field.field_type'], 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() { noneLabel: function() {
if (!this.get('field.required')) { if (!this.get('field.required')) {

View file

@ -1,7 +1,9 @@
import { url } from 'discourse/lib/computed';
export default Ember.Component.extend({ export default Ember.Component.extend({
classNames: ['user-small'], classNames: ['user-small'],
userPath: Discourse.computed.url('user.username', '/users/%@'), userPath: url('user.username', '/users/%@'),
name: function() { name: function() {
const name = this.get('user.name'); const name = this.get('user.name');

View file

@ -1,3 +1,4 @@
import { setting } from 'discourse/lib/computed';
import Presence from 'discourse/mixins/presence'; import Presence from 'discourse/mixins/presence';
export default Ember.ObjectController.extend(Presence, { export default Ember.ObjectController.extend(Presence, {
@ -8,7 +9,7 @@ export default Ember.ObjectController.extend(Presence, {
showEditReason: false, showEditReason: false,
editReason: null, editReason: null,
maxTitleLength: Discourse.computed.setting('max_topic_title_length'), maxTitleLength: setting('max_topic_title_length'),
scopedCategoryId: null, scopedCategoryId: null,
similarTopics: null, similarTopics: null,
similarTopicsMessage: null, similarTopicsMessage: null,

View file

@ -1,5 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality'; import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller'; import DiscourseController from 'discourse/controllers/controller';
import { setting } from 'discourse/lib/computed';
export default DiscourseController.extend(ModalFunctionality, { export default DiscourseController.extend(ModalFunctionality, {
needs: ['login'], needs: ['login'],
@ -16,10 +17,10 @@ export default DiscourseController.extend(ModalFunctionality, {
userFields: null, userFields: null,
hasAuthOptions: Em.computed.notEmpty('authOptions'), hasAuthOptions: Em.computed.notEmpty('authOptions'),
canCreateLocal: Discourse.computed.setting('enable_local_logins'), canCreateLocal: setting('enable_local_logins'),
showCreateForm: Em.computed.or('hasAuthOptions', 'canCreateLocal'), showCreateForm: Em.computed.or('hasAuthOptions', 'canCreateLocal'),
maxUsernameLength: Discourse.computed.setting('max_username_length'), maxUsernameLength: setting('max_username_length'),
minUsernameLength: Discourse.computed.setting('min_username_length'), minUsernameLength: setting('min_username_length'),
resetForm() { resetForm() {
// We wrap the fields in a structure so we can assign a value // We wrap the fields in a structure so we can assign a value

View file

@ -1,3 +1,5 @@
import { propertyEqual } from 'discourse/lib/computed';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
me: Discourse.computed.propertyEqual('model.user.id', 'currentUser.id') me: propertyEqual('model.user.id', 'currentUser.id')
}); });

View file

@ -1,6 +1,7 @@
import DiscoveryController from 'discourse/controllers/discovery'; import DiscoveryController from 'discourse/controllers/discovery';
import { queryParams } from 'discourse/controllers/discovery-sortable'; import { queryParams } from 'discourse/controllers/discovery-sortable';
import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection'; import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection';
import { endWith } from 'discourse/lib/computed';
const controllerOpts = { const controllerOpts = {
needs: ['discovery'], needs: ['discovery'],
@ -102,8 +103,8 @@ const controllerOpts = {
hasTopics: Em.computed.gt('model.topics.length', 0), hasTopics: Em.computed.gt('model.topics.length', 0),
allLoaded: Em.computed.empty('model.more_topics_url'), allLoaded: Em.computed.empty('model.more_topics_url'),
latest: Discourse.computed.endWith('model.filter', 'latest'), latest: endWith('model.filter', 'latest'),
new: Discourse.computed.endWith('model.filter', 'new'), new: endWith('model.filter', 'new'),
top: Em.computed.notEmpty('period'), top: Em.computed.notEmpty('period'),
yearly: Em.computed.equal('period', 'yearly'), yearly: Em.computed.equal('period', 'yearly'),
quarterly: Em.computed.equal('period', 'quarterly'), quarterly: Em.computed.equal('period', 'quarterly'),

View file

@ -1,7 +1,4 @@
import ObjectController from 'discourse/controllers/object'; export default Ember.Controller.extend({
// The basic controller for a group
export default ObjectController.extend({
counts: null, counts: null,
showing: null, showing: null,
@ -10,4 +7,3 @@ export default ObjectController.extend({
showingIndex: Em.computed.equal('showing', 'index'), showingIndex: Em.computed.equal('showing', 'index'),
showingMembers: Em.computed.equal('showing', 'members') showingMembers: Em.computed.equal('showing', 'members')
}); });

View file

@ -1,11 +1,13 @@
export default Ember.ObjectController.extend({ export default Ember.Controller.extend({
loading: false, loading: false,
limit: null,
offset: null,
actions: { actions: {
loadMore() { loadMore() {
if (this.get("loading")) { return; } if (this.get("loading")) { return; }
// we've reached the end // 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); this.set("loading", true);

View file

@ -1,6 +1,7 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality'; import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller'; import DiscourseController from 'discourse/controllers/controller';
import showModal from 'discourse/lib/show-modal'; import showModal from 'discourse/lib/show-modal';
import { setting } from 'discourse/lib/computed';
// This is happening outside of the app via popup // This is happening outside of the app via popup
const AuthErrors = const AuthErrors =
@ -13,7 +14,7 @@ export default DiscourseController.extend(ModalFunctionality, {
loggingIn: false, loggingIn: false,
loggedIn: false, loggedIn: false,
canLoginLocal: Discourse.computed.setting('enable_local_logins'), canLoginLocal: setting('enable_local_logins'),
loginRequired: Em.computed.alias('controllers.application.loginRequired'), loginRequired: Em.computed.alias('controllers.application.loginRequired'),
resetForm: function() { resetForm: function() {

View file

@ -1,7 +1,8 @@
import NavigationDefaultController from 'discourse/controllers/navigation/default'; import NavigationDefaultController from 'discourse/controllers/navigation/default';
import { setting } from 'discourse/lib/computed';
export default NavigationDefaultController.extend({ export default NavigationDefaultController.extend({
subcategoryListSetting: Discourse.computed.setting('show_subcategory_list'), subcategoryListSetting: setting('show_subcategory_list'),
showingParentCategory: Em.computed.none('category.parentCategory'), showingParentCategory: Em.computed.none('category.parentCategory'),
showingSubcategoryList: Em.computed.and('subcategoryListSetting', 'showingParentCategory'), showingSubcategoryList: Em.computed.and('subcategoryListSetting', 'showingParentCategory'),

View file

@ -1,5 +1,7 @@
import { url } from 'discourse/lib/computed';
export default Ember.ArrayController.extend({ export default Ember.ArrayController.extend({
needs: ['header'], needs: ['header'],
loadingNotifications: Em.computed.alias('controllers.header.loadingNotifications'), loadingNotifications: Em.computed.alias('controllers.header.loadingNotifications'),
myNotificationsUrl: Discourse.computed.url('/my/notifications') myNotificationsUrl: url('/my/notifications')
}); });

View file

@ -1,14 +1,15 @@
import { setting } from 'discourse/lib/computed';
import ObjectController from 'discourse/controllers/object'; import ObjectController from 'discourse/controllers/object';
import CanCheckEmails from 'discourse/mixins/can-check-emails'; import CanCheckEmails from 'discourse/mixins/can-check-emails';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
export default ObjectController.extend(CanCheckEmails, { export default ObjectController.extend(CanCheckEmails, {
allowAvatarUpload: Discourse.computed.setting('allow_uploaded_avatars'), allowAvatarUpload: setting('allow_uploaded_avatars'),
allowUserLocale: Discourse.computed.setting('allow_user_locale'), allowUserLocale: setting('allow_user_locale'),
ssoOverridesAvatar: Discourse.computed.setting('sso_overrides_avatar'), ssoOverridesAvatar: setting('sso_overrides_avatar'),
allowBackgrounds: Discourse.computed.setting('allow_profile_backgrounds'), allowBackgrounds: setting('allow_profile_backgrounds'),
editHistoryVisible: Discourse.computed.setting('edit_history_visible_to_public'), editHistoryVisible: setting('edit_history_visible_to_public'),
selectedCategories: function(){ selectedCategories: function(){
return [].concat(this.get("model.watchedCategories"), return [].concat(this.get("model.watchedCategories"),
@ -40,7 +41,7 @@ export default ObjectController.extend(CanCheckEmails, {
cannotDeleteAccount: Em.computed.not('can_delete_account'), cannotDeleteAccount: Em.computed.not('can_delete_account'),
deleteDisabled: Em.computed.or('saving', 'deleting', 'cannotDeleteAccount'), deleteDisabled: Em.computed.or('saving', 'deleting', 'cannotDeleteAccount'),
canEditName: Discourse.computed.setting('enable_names'), canEditName: setting('enable_names'),
nameInstructions: function() { nameInstructions: function() {
return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions'); return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions');

View file

@ -1,13 +1,6 @@
import { propertyEqual } from 'discourse/lib/computed';
import ObjectController from 'discourse/controllers/object'; 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({ export default ObjectController.extend({
taken: false, taken: false,
saving: false, saving: false,
@ -17,7 +10,7 @@ export default ObjectController.extend({
newEmailEmpty: Em.computed.empty('newEmail'), newEmailEmpty: Em.computed.empty('newEmail'),
saveDisabled: Em.computed.or('saving', 'newEmailEmpty', 'taken', 'unchanged'), saveDisabled: Em.computed.or('saving', 'newEmailEmpty', 'taken', 'unchanged'),
unchanged: Discourse.computed.propertyEqual('newEmailLower', 'email'), unchanged: propertyEqual('newEmailLower', 'email'),
newEmailLower: function() { newEmailLower: function() {
return this.get('newEmail').toLowerCase(); return this.get('newEmail').toLowerCase();

View file

@ -1,3 +1,4 @@
import { setting, propertyEqual } from 'discourse/lib/computed';
import Presence from 'discourse/mixins/presence'; import Presence from 'discourse/mixins/presence';
import ObjectController from 'discourse/controllers/object'; import ObjectController from 'discourse/controllers/object';
@ -8,11 +9,11 @@ export default ObjectController.extend(Presence, {
errorMessage: null, errorMessage: null,
newUsername: null, newUsername: null,
maxLength: Discourse.computed.setting('max_username_length'), maxLength: setting('max_username_length'),
minLength: Discourse.computed.setting('min_username_length'), minLength: setting('min_username_length'),
newUsernameEmpty: Em.computed.empty('newUsername'), newUsernameEmpty: Em.computed.empty('newUsername'),
saveDisabled: Em.computed.or('saving', 'newUsernameEmpty', 'taken', 'unchanged', 'errorMessage'), saveDisabled: Em.computed.or('saving', 'newUsernameEmpty', 'taken', 'unchanged', 'errorMessage'),
unchanged: Discourse.computed.propertyEqual('newUsername', 'username'), unchanged: propertyEqual('newUsername', 'username'),
checkTaken: function() { checkTaken: function() {
if( this.get('newUsername') && this.get('newUsername').length < this.get('minLength') ) { if( this.get('newUsername') && this.get('newUsername').length < this.get('minLength') ) {

View file

@ -1,3 +1,4 @@
import { propertyEqual } from 'discourse/lib/computed';
import BufferedContent from 'discourse/mixins/buffered-content'; import BufferedContent from 'discourse/mixins/buffered-content';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
@ -21,7 +22,7 @@ export default Ember.Controller.extend(BufferedContent, {
post: Ember.computed.alias('model'), post: Ember.computed.alias('model'),
currentlyEditing: Ember.computed.alias('controllers.queued-posts.editing'), currentlyEditing: Ember.computed.alias('controllers.queued-posts.editing'),
editing: Discourse.computed.propertyEqual('model', 'currentlyEditing'), editing: propertyEqual('model', 'currentlyEditing'),
_confirmDelete: updateState('rejected', {deleteUser: true}), _confirmDelete: updateState('rejected', {deleteUser: true}),

View file

@ -1,11 +1,12 @@
import Sharing from 'discourse/lib/sharing'; import Sharing from 'discourse/lib/sharing';
import { longDateNoYear } from 'discourse/lib/formatter';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
needs: ['topic'], needs: ['topic'],
title: Ember.computed.alias('controllers.topic.model.title'), title: Ember.computed.alias('controllers.topic.model.title'),
displayDate: function() { displayDate: function() {
return Discourse.Formatter.longDateNoYear(new Date(this.get('date'))); return longDateNoYear(new Date(this.get('date')));
}.property('date'), }.property('date'),
// Close the share controller // Close the share controller

View file

@ -1,3 +1,5 @@
import { url } from 'discourse/lib/computed';
export default Ember.ArrayController.extend({ export default Ember.ArrayController.extend({
needs: ['application', 'header'], needs: ['application', 'header'],
@ -8,7 +10,7 @@ export default Ember.ArrayController.extend({
return Discourse.SiteSettings.faq_url ? Discourse.SiteSettings.faq_url : Discourse.getURL('/faq'); return Discourse.SiteSettings.faq_url ? Discourse.SiteSettings.faq_url : Discourse.getURL('/faq');
}.property(), }.property(),
badgesUrl: Discourse.computed.url('/badges'), badgesUrl: url('/badges'),
showKeyboardShortcuts: function(){ showKeyboardShortcuts: function(){
return !Discourse.Mobile.mobileView && !this.capabilities.touch; return !Discourse.Mobile.mobileView && !this.capabilities.touch;

View file

@ -3,6 +3,7 @@ import BufferedContent from 'discourse/mixins/buffered-content';
import SelectedPostsCount from 'discourse/mixins/selected-posts-count'; import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
import { spinnerHTML } from 'discourse/helpers/loading-spinner'; import { spinnerHTML } from 'discourse/helpers/loading-spinner';
import Topic from 'discourse/models/topic'; import Topic from 'discourse/models/topic';
import { setting } from 'discourse/lib/computed';
export default ObjectController.extend(SelectedPostsCount, BufferedContent, { export default ObjectController.extend(SelectedPostsCount, BufferedContent, {
multiSelect: false, multiSelect: false,
@ -18,7 +19,7 @@ export default ObjectController.extend(SelectedPostsCount, BufferedContent, {
firstPostExpanded: false, firstPostExpanded: false,
retrying: false, retrying: false,
maxTitleLength: Discourse.computed.setting('max_topic_title_length'), maxTitleLength: setting('max_topic_title_length'),
contextChanged: function() { contextChanged: function() {
this.set('controllers.search.searchContext', this.get('model.searchContext')); this.set('controllers.search.searchContext', this.get('model.searchContext'));

View file

@ -1,5 +1,6 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality'; import ModalFunctionality from 'discourse/mixins/modal-functionality';
import DiscourseController from 'discourse/controllers/controller'; import DiscourseController from 'discourse/controllers/controller';
import { setting } from 'discourse/lib/computed';
export default DiscourseController.extend(ModalFunctionality, { export default DiscourseController.extend(ModalFunctionality, {
remote: Em.computed.not("local"), remote: Em.computed.not("local"),
@ -13,7 +14,7 @@ export default DiscourseController.extend(ModalFunctionality, {
}); });
}.on('init'), }.on('init'),
maxSize: Discourse.computed.setting('max_attachment_size_kb'), maxSize: setting('max_attachment_size_kb'),
allowLocal: Em.computed.gt('maxSize', 0), allowLocal: Em.computed.gt('maxSize', 0),
actions: { actions: {

View file

@ -1,3 +1,5 @@
import { propertyNotEqual, setting } from 'discourse/lib/computed';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
needs: ['topic', 'application'], needs: ['topic', 'application'],
visible: false, visible: false,
@ -16,10 +18,10 @@ export default Ember.Controller.extend({
viewingTopic: Em.computed.match('controllers.application.currentPath', /^topic\./), viewingTopic: Em.computed.match('controllers.application.currentPath', /^topic\./),
viewingAdmin: Em.computed.match('controllers.application.currentPath', /^admin\./), viewingAdmin: Em.computed.match('controllers.application.currentPath', /^admin\./),
showFilter: Em.computed.and('viewingTopic', 'postStream.hasNoFilters', 'enoughPostsForFiltering'), 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), hasUserFilters: Em.computed.gt('postStream.userFilters.length', 0),
isSuspended: Em.computed.notEmpty('user.suspend_reason'), isSuspended: Em.computed.notEmpty('user.suspend_reason'),
showBadges: Discourse.computed.setting('enable_badges'), showBadges: setting('enable_badges'),
showMoreBadges: Em.computed.gt('moreBadgesCount', 0), showMoreBadges: Em.computed.gt('moreBadgesCount', 0),
showDelete: Em.computed.and("viewingAdmin", "showName", "user.canBeDeleted"), showDelete: Em.computed.and("viewingAdmin", "showName", "user.canBeDeleted"),

View file

@ -1,7 +1,7 @@
export default Ember.ObjectController.extend({ export default Ember.Controller.extend({
needs: ["application"], needs: ["application"],
_showFooter: function() { _showFooter: function() {
this.set("controllers.application.showFooter", !this.get("canLoadMore")); this.set("controllers.application.showFooter", !this.get("model.canLoadMore"));
}.observes("canLoadMore") }.observes("model.canLoadMore")
}); });

View file

@ -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 const safe = Handlebars.SafeString;
var registerUnbound = require('discourse/helpers/register-unbound', null, null, true).default;
var avatarTemplate = require('discourse/lib/avatar-template', null, null, true).default;
Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) { Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
if (Em.isEmpty(user)) { if (Em.isEmpty(user)) {
return new safe("<div class='avatar-placeholder'></div>"); return new safe("<div class='avatar-placeholder'></div>");
} }
var username = Em.get(user, 'username'); const username = Em.get(user, 'username');
if (arguments.length < 4) { uploadId = Em.get(user, 'uploaded_avatar_id'); } 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 })); return new safe(Discourse.Utilities.avatarImg({ size: size, avatarTemplate: avatar }));
}, 'username', 'uploaded_avatar_id', 'avatar_template'); }, 'username', 'uploaded_avatar_id', 'avatar_template');
@ -24,30 +24,30 @@ Em.Handlebars.helper('bound-avatar-template', function(avatarTemplate, size) {
}); });
registerUnbound('raw-date', function(dt) { registerUnbound('raw-date', function(dt) {
return Discourse.Formatter.longDate(new Date(dt)); return longDate(new Date(dt));
}); });
registerUnbound('age-with-tooltip', function(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) { registerUnbound('number', function(orig, params) {
orig = parseInt(orig, 10); orig = parseInt(orig, 10);
if (isNaN(orig)) { orig = 0; } if (isNaN(orig)) { orig = 0; }
var title = orig; let title = orig;
if (params.numberKey) { if (params.numberKey) {
title = I18n.t(params.numberKey, { number: orig }); title = I18n.t(params.numberKey, { number: orig });
} }
var classNames = 'number'; let classNames = 'number';
if (params['class']) { if (params['class']) {
classNames += ' ' + params['class']; classNames += ' ' + params['class'];
} }
var result = "<span class='" + classNames + "'"; let result = "<span class='" + classNames + "'";
// Round off the thousands to one decimal place // Round off the thousands to one decimal place
var n = Discourse.Formatter.number(orig); const n = number(orig);
if (n !== title) { if (n !== title) {
result += " title='" + Handlebars.Utils.escapeExpression(title) + "'"; result += " title='" + Handlebars.Utils.escapeExpression(title) + "'";
} }

View file

@ -1,3 +1,5 @@
import { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
export default Ember.Handlebars.makeBoundHelper(function(dt) { export default Ember.Handlebars.makeBoundHelper(function(dt) {
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(new Date(dt), {format: 'medium', title: true })); return new Handlebars.SafeString(autoUpdatingRelativeAge(new Date(dt), {format: 'medium', title: true }));
}); });

View file

@ -1,6 +1,7 @@
import { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
import registerUnbound from 'discourse/helpers/register-unbound'; import registerUnbound from 'discourse/helpers/register-unbound';
registerUnbound('format-age', function(dt) { registerUnbound('format-age', function(dt) {
dt = new Date(dt); dt = new Date(dt);
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(dt)); return new Handlebars.SafeString(autoUpdatingRelativeAge(dt));
}); });

View file

@ -1,4 +1,5 @@
import registerUnbound from 'discourse/helpers/register-unbound'; import registerUnbound from 'discourse/helpers/register-unbound';
import { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
/** /**
Display logic for dates. It is unbound in Ember but will use jQuery to Display logic for dates. It is unbound in Ember but will use jQuery to
@ -21,6 +22,6 @@ registerUnbound('format-date', function(val, params) {
if (val) { if (val) {
var date = new Date(val); var date = new Date(val);
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(date, {format: format, title: title, leaveAgo: leaveAgo})); return new Handlebars.SafeString(autoUpdatingRelativeAge(date, {format: format, title: title, leaveAgo: leaveAgo}));
} }
}); });

View file

@ -7,5 +7,28 @@ export default {
require(entry, null, null, true); require(entry, null, null, true);
} }
}); });
// TODO: Once things have migrated remove these
if (!Discourse.hasOwnProperty('computed')) {
const computed = require('discourse/lib/computed');
Object.defineProperty(Discourse, 'computed', {
get: function() {
Ember.warn('DEPRECATION: `Discourse.computed` is deprecated, import the functions as needed.');
return computed;
}
});
}
if (!Discourse.hasOwnProperty('Formatter')) {
const Formatter = require('discourse/lib/formatter');
Object.defineProperty(Discourse, 'Formatter', {
get: function() {
Ember.warn('DEPRECATION: `Discourse.Formatter` is deprecated, import the formatters as needed.');
return Formatter;
}
});
}
} }
}; };

View file

@ -1,13 +1,14 @@
import { cleanDOM } from 'discourse/routes/discourse'; import { cleanDOM } from 'discourse/routes/discourse';
import PageTracker from 'discourse/lib/page-tracker';
export default { export default {
name: "page-tracking", name: "page-tracking",
after: 'register-discourse-location', after: 'register-discourse-location',
initialize: function(container) { initialize(container) {
// Tell our AJAX system to track a page transition // 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() { router.on('willTransition', function() {
Discourse.viewTrackingRequired(); Discourse.viewTrackingRequired();
}); });
@ -16,7 +17,7 @@ export default {
Em.run.scheduleOnce('afterRender', Ember.Route, cleanDOM); Em.run.scheduleOnce('afterRender', Ember.Route, cleanDOM);
}); });
var pageTracker = Discourse.PageTracker.current(); const pageTracker = PageTracker.current();
pageTracker.start(); pageTracker.start();
// Out of the box, Discourse tries to track google analytics // Out of the box, Discourse tries to track google analytics

View file

@ -1,11 +1,11 @@
/** import { updateRelativeAge } from 'discourse/lib/formatter';
Updates the relative ages of dates on the screen.
**/ // Updates the relative ages of dates on the screen.
export default { export default {
name: "relative-ages", name: "relative-ages",
initialize: function() { initialize: function() {
setInterval(function(){ setInterval(function(){
Discourse.Formatter.updateRelativeAge($('.relative-date')); updateRelativeAge($('.relative-date'));
}, 60 * 1000); }, 60 * 1000);
} }
}; };

View file

@ -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();
}
};

View file

@ -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();
}

View file

@ -1,3 +1,4 @@
import PageTracker from 'discourse/lib/page-tracker';
let primaryTab = false; let primaryTab = false;
let liveEnabled = false; let liveEnabled = false;
@ -79,7 +80,7 @@ function setupNotifications() {
document.addEventListener("scroll", resetIdle); document.addEventListener("scroll", resetIdle);
} }
window.addEventListener("mouseover", resetIdle); window.addEventListener("mouseover", resetIdle);
Discourse.PageTracker.on("change", resetIdle); PageTracker.on("change", resetIdle);
} }
function resetIdle() { function resetIdle() {

View file

@ -1,38 +1,29 @@
/** // Track visible elemnts on the screen.
Track visible elemnts on the screen. const Eyeline = function Eyeline(selector) {
@class Eyeline
@namespace Discourse
@module Discourse
@uses RSVP.EventTarget
**/
Discourse.Eyeline = function Eyeline(selector) {
this.selector = selector; this.selector = selector;
}; };
/** Eyeline.prototype.update = function() {
Call this whenever you want to consider what is being seen by the browser if (Ember.Test) { return; }
@method update const docViewTop = $(window).scrollTop(),
**/ windowHeight = $(window).height(),
Discourse.Eyeline.prototype.update = function() { docViewBottom = docViewTop + windowHeight,
var docViewTop = $(window).scrollTop(), $elements = $(this.selector),
windowHeight = $(window).height(), bottomOffset = $elements.last().offset(),
docViewBottom = docViewTop + windowHeight, self = this;
$elements = $(this.selector),
atBottom = false,
bottomOffset = $elements.last().offset(),
self = this;
let atBottom = false;
if (bottomOffset) { if (bottomOffset) {
atBottom = (bottomOffset.top <= docViewBottom) && (bottomOffset.top >= docViewTop); atBottom = (bottomOffset.top <= docViewBottom) && (bottomOffset.top >= docViewTop);
} }
return $elements.each(function(i, elem) { return $elements.each(function(i, elem) {
var $elem = $(elem), const $elem = $(elem),
elemTop = $elem.offset().top, elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height(), elemBottom = elemTop + $elem.height();
markSeen = false;
let markSeen = false;
// Make sure the element is visible // Make sure the element is visible
if (!$elem.is(':visible')) return true; 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
Call this when we know aren't loading any more elements. Mark the rest as seen Eyeline.prototype.flushRest = function() {
if (Ember.Test) { return; }
@method flushRest const self = this;
**/
Discourse.Eyeline.prototype.flushRest = function() {
var self = this;
$(this.selector).each(function(i, elem) { $(this.selector).each(function(i, elem) {
return self.trigger('saw', { detail: $(elem) }); return self.trigger('saw', { detail: $(elem) });
}); });
}; };
RSVP.EventTarget.mixin(Discourse.Eyeline.prototype); RSVP.EventTarget.mixin(Eyeline.prototype);
export default Eyeline;

View file

@ -1,9 +1,5 @@
/* global BreakString:true */ /* global BreakString:true */
var updateRelativeAge, autoUpdatingRelativeAge, relativeAge, relativeAgeTiny,
relativeAgeMedium, relativeAgeMediumSpan, longDate, longDateNoYear, toTitleCase,
shortDate, shortDateNoYear, tinyDateYear, relativeAgeTinyShowsYear;
/* /*
* memoize.js * memoize.js
* by @philogb and @addyosmani * by @philogb and @addyosmani
@ -14,15 +10,15 @@ var updateRelativeAge, autoUpdatingRelativeAge, relativeAge, relativeAgeTiny,
* *
* modified with cap by Sam * modified with cap by Sam
*/ */
var cappedMemoize = function ( fn, max ) { function cappedMemoize(fn, max) {
fn.maxMemoize = max; fn.maxMemoize = max;
fn.memoizeLength = 0; fn.memoizeLength = 0;
return function () { return function () {
var args = Array.prototype.slice.call(arguments), const args = Array.prototype.slice.call(arguments);
hash = "", let hash = "";
i = args.length; let i = args.length;
var currentArg = null; let currentArg = null;
while (i--) { while (i--) {
currentArg = args[i]; currentArg = args[i];
hash += (currentArg === new Object(currentArg)) ? hash += (currentArg === new Object(currentArg)) ?
@ -39,44 +35,45 @@ var cappedMemoize = function ( fn, max ) {
fn.memoizeLength = 0; fn.memoizeLength = 0;
fn.memoize = {}; fn.memoize = {};
} }
var result = fn.apply(this, args); const result = fn.apply(this, args);
fn.memoize[hash] = result; fn.memoize[hash] = result;
return result; return result;
} }
}; };
}; }
var breakUp = cappedMemoize(function(str, hint){ const breakUp = cappedMemoize(function(str, hint){
return new BreakString(str).break(hint); return new BreakString(str).break(hint);
}, 100); }, 100);
export { breakUp };
shortDate = function(date){ export function shortDate(date){
return moment(date).format(I18n.t("dates.medium.date_year")); 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")); 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")); return moment(date).format(I18n.t("dates.tiny.date_year"));
}; }
// http://stackoverflow.com/questions/196972/convert-string-to-title-case-with-javascript // http://stackoverflow.com/questions/196972/convert-string-to-title-case-with-javascript
// TODO: locale support ? // TODO: locale support ?
toTitleCase = function toTitleCase(str) { export function toTitleCase(str) {
return str.replace(/\w\S*/g, function(txt){ return str.replace(/\w\S*/g, function(txt){
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}); });
}; }
longDate = function(dt) { export function longDate(dt) {
if (!dt) return; if (!dt) return;
return moment(dt).longDate(); return moment(dt).longDate();
}; }
// suppress year, if current year // suppress year, if current year
longDateNoYear = function(dt) { export function longDateNoYear(dt) {
if (!dt) return; if (!dt) return;
if ((new Date()).getFullYear() !== dt.getFullYear()) { if ((new Date()).getFullYear() !== dt.getFullYear()) {
@ -84,26 +81,25 @@ longDateNoYear = function(dt) {
} else { } else {
return moment(dt).format(I18n.t("dates.long_date_without_year")); return moment(dt).format(I18n.t("dates.long_date_without_year"));
} }
}; }
updateRelativeAge = function(elems) { export function updateRelativeAge(elems) {
// jQuery .each // jQuery .each
elems.each(function(){ elems.each(function(){
var $this = $(this); const $this = $(this);
$this.html(relativeAge(new Date($this.data('time')), {format: $this.data('format'), wrapInSpan: false})); $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) return "";
if (+date === +new Date(0)) return ""; if (+date === +new Date(0)) return "";
options = options || {}; options = options || {};
var format = options.format || "tiny"; let format = options.format || "tiny";
var append = ""; let append = "";
if (format === 'medium') {
if(format === 'medium') {
append = " date"; append = " date";
if(options.leaveAgo) { if(options.leaveAgo) {
format = 'medium-with-ago'; format = 'medium-with-ago';
@ -111,7 +107,7 @@ autoUpdatingRelativeAge = function(date,options) {
options.wrapInSpan = false; options.wrapInSpan = false;
} }
var relAge = relativeAge(date, options); const relAge = relativeAge(date, options);
if (format === 'tiny' && relativeAgeTinyShowsYear(relAge)) { if (format === 'tiny' && relativeAgeTinyShowsYear(relAge)) {
append += " with-year"; append += " with-year";
@ -122,16 +118,16 @@ autoUpdatingRelativeAge = function(date,options) {
} }
return "<span class='relative-date" + append + "' data-time='" + date.getTime() + "' data-format='" + format + "'>" + relAge + "</span>"; return "<span class='relative-date" + append + "' data-time='" + date.getTime() + "' data-format='" + format + "'>" + relAge + "</span>";
}; }
relativeAgeTiny = function(date){ function relativeAgeTiny(date){
var format = "tiny"; const format = "tiny";
var distance = Math.round((new Date() - date) / 1000); const distance = Math.round((new Date() - date) / 1000);
var distanceInMinutes = Math.round(distance / 60.0); const distanceInMinutes = Math.round(distance / 60.0);
var formatted; let formatted;
var t = function(key,opts){ const t = function(key,opts){
return I18n.t("dates." + format + "." + key, opts); return I18n.t("dates." + format + "." + key, opts);
}; };
@ -168,22 +164,21 @@ relativeAgeTiny = function(date){
} }
return formatted; return formatted;
}; }
/* /*
* Returns true if the given tiny date string includes the year. * Returns true if the given tiny date string includes the year.
* Useful for checking if the string isn't so tiny. * Useful for checking if the string isn't so tiny.
*/ */
relativeAgeTinyShowsYear = function(relativeAgeString) { function relativeAgeTinyShowsYear(relativeAgeString) {
return relativeAgeString.match(/'[\d]{2}$/); return relativeAgeString.match(/'[\d]{2}$/);
}; }
relativeAgeMediumSpan = function(distance, leaveAgo) { function relativeAgeMediumSpan(distance, leaveAgo) {
var formatted, distanceInMinutes; let formatted;
const distanceInMinutes = Math.round(distance / 60.0);
distanceInMinutes = Math.round(distance / 60.0); const t = function(key, opts){
var t = function(key, opts){
return I18n.t("dates.medium" + (leaveAgo?"_with_ago":"") + "." + key, opts); return I18n.t("dates.medium" + (leaveAgo?"_with_ago":"") + "." + key, opts);
}; };
@ -205,24 +200,22 @@ relativeAgeMediumSpan = function(distance, leaveAgo) {
break; break;
} }
return formatted || '&mdash'; return formatted || '&mdash';
}; }
relativeAgeMedium = function(date, options){ function relativeAgeMedium(date, options) {
var displayDate, fiveDaysAgo, oneMinuteAgo, fullReadable, leaveAgo; const wrapInSpan = options.wrapInSpan !== false;
var wrapInSpan = options.wrapInSpan !== false; const leaveAgo = options.leaveAgo;
const distance = Math.round((new Date() - date) / 1000);
leaveAgo = options.leaveAgo;
var distance = Math.round((new Date() - date) / 1000);
if (!date) { if (!date) {
return "&mdash;"; return "&mdash;";
} }
fullReadable = longDate(date); const fullReadable = longDate(date);
displayDate = ""; const fiveDaysAgo = 432000;
fiveDaysAgo = 432000; const oneMinuteAgo = 60;
oneMinuteAgo = 60;
let displayDate = "";
if (distance < oneMinuteAgo) { if (distance < oneMinuteAgo) {
displayDate = I18n.t("now"); displayDate = I18n.t("now");
} else if (distance > fiveDaysAgo) { } else if (distance > fiveDaysAgo) {
@ -239,12 +232,12 @@ relativeAgeMedium = function(date, options){
} else { } else {
return displayDate; return displayDate;
} }
}; }
// mostly lifted from rails with a few amendments // mostly lifted from rails with a few amendments
relativeAge = function(date, options) { export function relativeAge(date, options) {
options = options || {}; options = options || {};
var format = options.format || "tiny"; const format = options.format || "tiny";
if(format === "tiny") { if(format === "tiny") {
return relativeAgeTiny(date, options); return relativeAgeTiny(date, options);
@ -255,10 +248,10 @@ relativeAge = function(date, options) {
} }
return "UNKNOWN FORMAT"; return "UNKNOWN FORMAT";
}; }
var number = function(val) { export function number(val) {
var formattedNumber; let formattedNumber;
val = parseInt(val, 10); val = parseInt(val, 10);
if (isNaN(val)) val = 0; if (isNaN(val)) val = 0;
@ -272,17 +265,5 @@ var number = function(val) {
return I18n.t("number.short.thousands", {number: formattedNumber}); return I18n.t("number.short.thousands", {number: formattedNumber});
} }
return val.toString(); 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
};

View file

@ -1,3 +1,5 @@
import Singleton from 'discourse/mixins/singleton';
/** /**
Called whenever the "page" changes. This allows us to set up analytics Called whenever the "page" changes. This allows us to set up analytics
and other tracking. and other tracking.
@ -5,12 +7,12 @@
To get notified when the page changes, you can install a hook like so: To get notified when the page changes, you can install a hook like so:
```javascript ```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); 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() { start: function() {
if (this.get('started')) { return; } if (this.get('started')) { return; }
@ -30,4 +32,6 @@ Discourse.PageTracker = Ember.Object.extend(Ember.Evented, {
this.set('started', true); this.set('started', true);
} }
}); });
Discourse.PageTracker.reopenClass(Discourse.Singleton); PageTracker.reopenClass(Singleton);
export default PageTracker;

View file

@ -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 import Singleton from 'discourse/mixins/singleton';
@extends Ember.Object
@namespace Discourse
@module Discourse
**/
var PAUSE_UNLESS_SCROLLED = 1000 * 60 * 3, const PAUSE_UNLESS_SCROLLED = 1000 * 60 * 3,
MAX_TRACKING_TIME = 1000 * 60 * 6; MAX_TRACKING_TIME = 1000 * 60 * 6;
Discourse.ScreenTrack = Ember.Object.extend({ const ScreenTrack = Ember.Object.extend({
init: function() { init() {
this.reset(); this.reset();
}, },
start: function(topicId, topicController) { start(topicId, topicController) {
var currentTopicId = this.get('topicId'); const currentTopicId = this.get('topicId');
if (currentTopicId && (currentTopicId !== topicId)) { if (currentTopicId && (currentTopicId !== topicId)) {
this.tick(); this.tick();
this.flush(); this.flush();
@ -27,7 +22,7 @@ Discourse.ScreenTrack = Ember.Object.extend({
// Create an interval timer if we don't have one. // Create an interval timer if we don't have one.
if (!this.get('interval')) { if (!this.get('interval')) {
var self = this; const self = this;
this.set('interval', setInterval(function () { this.set('interval', setInterval(function () {
self.tick(); self.tick();
}, 1000)); }, 1000));
@ -39,7 +34,7 @@ Discourse.ScreenTrack = Ember.Object.extend({
this.set('topicController', topicController); this.set('topicController', topicController);
}, },
stop: function() { stop() {
if(!this.get('topicId')) { if(!this.get('topicId')) {
// already stopped no need to "extra stop" // already stopped no need to "extra stop"
return; return;
@ -56,19 +51,19 @@ Discourse.ScreenTrack = Ember.Object.extend({
} }
}, },
track: function(elementId, postNumber) { track(elementId, postNumber) {
this.get('timings')["#" + elementId] = { this.get('timings')["#" + elementId] = {
time: 0, time: 0,
postNumber: postNumber postNumber: postNumber
}; };
}, },
stopTracking: function(elementId) { stopTracking(elementId) {
delete this.get('timings')['#' + elementId]; delete this.get('timings')['#' + elementId];
}, },
// Reset our timers // Reset our timers
reset: function() { reset() {
this.setProperties({ this.setProperties({
lastTick: new Date().getTime(), lastTick: new Date().getTime(),
lastScrolled: 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()); this.set('lastScrolled', new Date().getTime());
}, },
flush: function() { flush() {
if (this.get('cancelled')) { return; } if (this.get('cancelled')) { return; }
// We don't log anything unless we're logged in // We don't log anything unless we're logged in
if (!Discourse.User.current()) return; if (!Discourse.User.current()) return;
var newTimings = {}, const newTimings = {},
totalTimings = this.get('totalTimings'), totalTimings = this.get('totalTimings'),
self = this; self = this;
@ -105,14 +100,14 @@ Discourse.ScreenTrack = Ember.Object.extend({
timing.time = 0; timing.time = 0;
}); });
var topicId = parseInt(this.get('topicId'), 10), const topicId = parseInt(this.get('topicId'), 10);
highestSeen = 0; let highestSeen = 0;
_.each(newTimings, function(time,postNumber) { _.each(newTimings, function(time,postNumber) {
highestSeen = Math.max(highestSeen, parseInt(postNumber, 10)); highestSeen = Math.max(highestSeen, parseInt(postNumber, 10));
}); });
var highestSeenByTopic = Discourse.Session.currentProp('highestSeenByTopic'); const highestSeenByTopic = Discourse.Session.currentProp('highestSeenByTopic');
if ((highestSeenByTopic[topicId] || 0) < highestSeen) { if ((highestSeenByTopic[topicId] || 0) < highestSeen) {
highestSeenByTopic[topicId] = highestSeen; highestSeenByTopic[topicId] = highestSeen;
} }
@ -132,9 +127,9 @@ Discourse.ScreenTrack = Ember.Object.extend({
'X-SILENCE-LOGGER': 'true' 'X-SILENCE-LOGGER': 'true'
} }
}).then(function(){ }).then(function(){
var controller = self.get('topicController'); const controller = self.get('topicController');
if(controller){ if(controller){
var postNumbers = Object.keys(newTimings).map(function(v){ const postNumbers = Object.keys(newTimings).map(function(v){
return parseInt(v,10); return parseInt(v,10);
}); });
controller.readPosts(topicId, postNumbers); controller.readPosts(topicId, postNumbers);
@ -146,23 +141,23 @@ Discourse.ScreenTrack = Ember.Object.extend({
this.set('lastFlush', 0); this.set('lastFlush', 0);
}, },
tick: function() { tick() {
// If the user hasn't scrolled the browser in a long time, stop tracking time read // 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) { if (sinceScrolled > PAUSE_UNLESS_SCROLLED) {
return; 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('lastFlush', this.get('lastFlush') + diff);
this.set('lastTick', new Date().getTime()); this.set('lastTick', new Date().getTime());
var totalTimings = this.get('totalTimings'), timings = this.get('timings'); const totalTimings = this.get('totalTimings'), timings = this.get('timings');
var nextFlush = Discourse.SiteSettings.flush_timings_secs * 1000; const nextFlush = Discourse.SiteSettings.flush_timings_secs * 1000;
// rush new post numbers // 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]; return !totalTimings[t.postNumber];
}); });
@ -174,15 +169,15 @@ Discourse.ScreenTrack = Ember.Object.extend({
if (!Discourse.get("hasFocus")) return; if (!Discourse.get("hasFocus")) return;
this.set('topicTime', this.get('topicTime') + diff); this.set('topicTime', this.get('topicTime') + diff);
var docViewTop = $(window).scrollTop() + $('header').height(), const docViewTop = $(window).scrollTop() + $('header').height(),
docViewBottom = docViewTop + $(window).height(); docViewBottom = docViewTop + $(window).height();
// TODO: Eyeline has a smarter more accurate function here. It's bad to do jQuery // 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. // in a model like component, so we should refactor this out later.
_.each(this.get('timings'),function(timing,id) { _.each(this.get('timings'),function(timing,id) {
var $element = $(id); const $element = $(id);
if ($element.length === 1) { if ($element.length === 1) {
var elemTop = $element.offset().top, const elemTop = $element.offset().top,
elemBottom = elemTop + $element.height(); elemBottom = elemTop + $element.height();
// If part of the element is on the screen, increase the counter // 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;

View file

@ -1,6 +1,8 @@
import { propertyEqual, setting } from 'discourse/lib/computed';
export default Ember.Mixin.create({ export default Ember.Mixin.create({
isOwnEmail: Discourse.computed.propertyEqual("model.id", "currentUser.id"), isOwnEmail: propertyEqual("model.id", "currentUser.id"),
showEmailOnProfile: Discourse.computed.setting("show_email_on_profile"), showEmailOnProfile: setting("show_email_on_profile"),
canStaffCheckEmails: Em.computed.and("showEmailOnProfile", "currentUser.staff"), canStaffCheckEmails: Em.computed.and("showEmailOnProfile", "currentUser.staff"),
canAdminCheckEmails: Em.computed.alias("currentUser.admin"), canAdminCheckEmails: Em.computed.alias("currentUser.admin"),
canCheckEmails: Em.computed.or("isOwnEmail", "canStaffCheckEmails", "canAdminCheckEmails"), canCheckEmails: Em.computed.or("isOwnEmail", "canStaffCheckEmails", "canAdminCheckEmails"),

View file

@ -1,5 +1,8 @@
import Eyeline from 'discourse/lib/eyeline';
import Scrolling from 'discourse/mixins/scrolling';
// Provides the ability to load more items for a view which is scrolled to the bottom. // 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, { export default Ember.Mixin.create(Ember.ViewTargetActionSupport, Scrolling, {
scrolled: function() { scrolled: function() {
const eyeline = this.get('eyeline'); const eyeline = this.get('eyeline');
@ -7,7 +10,7 @@ export default Em.Mixin.create(Ember.ViewTargetActionSupport, Discourse.Scrollin
}, },
_bindEyeline: function() { _bindEyeline: function() {
const eyeline = new Discourse.Eyeline(this.get('eyelineSelector') + ":last"); const eyeline = new Eyeline(this.get('eyelineSelector') + ":last");
this.set('eyeline', eyeline); this.set('eyeline', eyeline);
eyeline.on('sawBottom', () => this.send('loadMore')); eyeline.on('sawBottom', () => this.send('loadMore'));
this.bindScrolling(); this.bindScrolling();

View file

@ -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);
}
};

View file

@ -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;

View file

@ -9,7 +9,7 @@
// Define your class and apply the Mixin // Define your class and apply the Mixin
User = Ember.Object.extend({}); User = Ember.Object.extend({});
User.reopenClass(Discourse.Singleton); User.reopenClass(Singleton);
// Retrieve the current instance: // Retrieve the current instance:
var instance = User.current(); var instance = User.current();
@ -35,7 +35,7 @@
// Define your class and apply the Mixin // Define your class and apply the Mixin
Foot = Ember.Object.extend({}); Foot = Ember.Object.extend({});
Foot.reopenClass(Discourse.Singleton, { Foot.reopenClass(Singleton, {
createCurrent: function() { createCurrent: function() {
return Foot.create({toes: 5}); return Foot.create({toes: 5});
} }
@ -44,51 +44,28 @@
console.log(Foot.currentProp('toes')); // 5 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({
/** current() {
Returns the current singleton instance of the class.
@method current
@returns {Ember.Object} the instance of the singleton
**/
current: function() {
if (!this._current) { if (!this._current) {
this._current = this.createCurrent(); this._current = this.createCurrent();
} }
return this._current; return this._current;
}, },
/** /**
How the singleton instance is created. This can be overridden How the singleton instance is created. This can be overridden
with logic for creating (or even returning null) your instance. with logic for creating (or even returning null) your instance.
By default it just calls `create` with an empty object. 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({}); return this.create({});
}, },
/** // Returns OR sets a property on the singleton instance.
Returns or sets a property on the singleton instance. currentProp(property, value) {
@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) {
var instance = this.current(); var instance = this.current();
if (!instance) { return; } if (!instance) { return; }
@ -100,14 +77,9 @@ Discourse.Singleton = Em.Mixin.create({
} }
}, },
/** resetCurrent(val) {
Resets the current singleton. Useful in testing.
@method resetCurrent
**/
resetCurrent: function(val) {
this._current = val; this._current = val;
} }
}); });
export default Singleton;

View file

@ -1,15 +1,9 @@
/** import Post from 'discourse/models/post';
A data model for flagged/deleted posts.
@class AdminPost export default Post.extend({
@extends Discourse.Post
@namespace Discourse
@module Discourse
**/
Discourse.AdminPost = Discourse.Post.extend({
_attachCategory: function () { _attachCategory: function () {
var categoryId = this.get("category_id"); const categoryId = this.get("category_id");
if (categoryId) { if (categoryId) {
this.set("category", Discourse.Category.findById(categoryId)); this.set("category", Discourse.Category.findById(categoryId));
} }

View file

@ -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')
});

View file

@ -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')
});

View file

@ -1,11 +1,4 @@
/** import { toTitleCase } from 'discourse/lib/formatter';
A data model representing a navigation item on the list views
@class NavItem
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
const NavItem = Discourse.Model.extend({ const NavItem = Discourse.Model.extend({
@ -22,7 +15,7 @@ const NavItem = Discourse.Model.extend({
if (categoryName) { if (categoryName) {
name = 'category'; name = 'category';
extra.categoryName = Discourse.Formatter.toTitleCase(categoryName); extra.categoryName = toTitleCase(categoryName);
} }
return I18n.t("filters." + name.replace("/", ".") + ".title", extra); return I18n.t("filters." + name.replace("/", ".") + ".title", extra);
}.property('categoryName', 'name', 'count'), }.property('categoryName', 'name', 'count'),

View file

@ -1,6 +1,7 @@
import RestModel from 'discourse/models/rest'; import RestModel from 'discourse/models/rest';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
import ActionSummary from 'discourse/models/action-summary'; import ActionSummary from 'discourse/models/action-summary';
import { url, fmt, propertyEqual } from 'discourse/lib/computed';
const Post = RestModel.extend({ const Post = RestModel.extend({
@ -57,7 +58,7 @@ const Post = RestModel.extend({
return (this.get('post_number') === 1) ? url + "/1" : url; return (this.get('post_number') === 1) ? url + "/1" : url;
}.property('post_number', 'url'), }.property('post_number', 'url'),
usernameUrl: Discourse.computed.url('username', '/users/%@'), usernameUrl: url('username', '/users/%@'),
showUserReplyTab: function() { showUserReplyTab: function() {
return this.get('reply_to_user') && ( return this.get('reply_to_user') && (
@ -66,9 +67,9 @@ const Post = RestModel.extend({
); );
}.property('reply_to_user', 'reply_to_post_number', 'post_number'), }.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), hasHistory: Em.computed.gt('version', 1),
postElementId: Discourse.computed.fmt('post_number', 'post_%@'), postElementId: fmt('post_number', 'post_%@'),
canViewRawEmail: function() { canViewRawEmail: function() {
return this.get("user_id") === Discourse.User.currentProp("id") || Discourse.User.currentProp('staff'); return this.get("user_id") === Discourse.User.currentProp("id") || Discourse.User.currentProp('staff');

View file

@ -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 // A data model representing current session data. You can put transient
// data here you might want later. It is not stored or serialized anywhere. // data here you might want later. It is not stored or serialized anywhere.
var Session = Discourse.Model.extend({ const Session = RestModel.extend({
init: function() { init: function() {
this.set('highestSeenByTopic', {}); this.set('highestSeenByTopic', {});
} }
}); });
Session.reopenClass(Discourse.Singleton); Session.reopenClass(Singleton);
export default Session; export default Session;

View file

@ -1,4 +1,6 @@
import Archetype from 'discourse/models/archetype';
import PostActionType from 'discourse/models/post-action-type'; import PostActionType from 'discourse/models/post-action-type';
import Singleton from 'discourse/mixins/singleton';
const Site = Discourse.Model.extend({ 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`. // The current singleton will retrieve its attributes from the `PreloadStore`.
createCurrent() { createCurrent() {
return Discourse.Site.create(PreloadStore.get('site')); return Site.create(PreloadStore.get('site'));
}, },
create() { create() {
@ -137,7 +139,8 @@ Site.reopenClass(Discourse.Singleton, {
if (result.archetypes) { if (result.archetypes) {
result.archetypes = _.map(result.archetypes,function(a) { result.archetypes = _.map(result.archetypes,function(a) {
return Discourse.Archetype.create(a); a.site = result;
return Archetype.create(a);
}); });
} }

View file

@ -1,5 +1,7 @@
import { flushMap } from 'discourse/models/store'; import { flushMap } from 'discourse/models/store';
import RestModel from 'discourse/models/rest'; import RestModel from 'discourse/models/rest';
import { propertyEqual } from 'discourse/lib/computed';
import { longDate } from 'discourse/lib/formatter';
const Topic = RestModel.extend({ const Topic = RestModel.extend({
message: null, message: null,
@ -20,8 +22,8 @@ const Topic = RestModel.extend({
}.property('bumped_at', 'createdAt'), }.property('bumped_at', 'createdAt'),
bumpedAtTitle: function() { bumpedAtTitle: function() {
return I18n.t('first_post') + ": " + Discourse.Formatter.longDate(this.get('createdAt')) + "\n" + return I18n.t('first_post') + ": " + longDate(this.get('createdAt')) + "\n" +
I18n.t('last_post') + ": " + Discourse.Formatter.longDate(this.get('bumpedAt')); I18n.t('last_post') + ": " + longDate(this.get('bumpedAt'));
}.property('bumpedAt'), }.property('bumpedAt'),
createdAt: function() { createdAt: function() {
@ -364,7 +366,7 @@ const Topic = RestModel.extend({
return( e && e.substr(e.length - 8,8) === '&hellip;' ); return( e && e.substr(e.length - 8,8) === '&hellip;' );
}.property('excerpt'), }.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') canClearPin: Em.computed.and('pinned', 'readLastPost')
}); });

View file

@ -0,0 +1,6 @@
import RestModel from 'discourse/models/rest';
import { fmt } from 'discourse/lib/computed';
export default RestModel.extend({
detailedName: fmt('id', 'name', '%@ - %@')
});

View file

@ -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', '%@ - %@')
});

View file

@ -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')
});

View file

@ -1,39 +1,37 @@
var UserActionTypes = { import RestModel from 'discourse/models/rest';
likes_given: 1, import { url } from 'discourse/lib/computed';
likes_received: 2,
bookmarks: 3, const UserActionTypes = {
topics: 4, likes_given: 1,
posts: 5, likes_received: 2,
replies: 6, bookmarks: 3,
mentions: 7, topics: 4,
quotes: 9, posts: 5,
edits: 11, replies: 6,
messages_sent: 12, mentions: 7,
messages_received: 13, quotes: 9,
pending: 14 edits: 11,
}, messages_sent: 12,
InvertedActionTypes = {}; messages_received: 13,
pending: 14
};
const InvertedActionTypes = {};
_.each(UserActionTypes, function (k, v) { _.each(UserActionTypes, function (k, v) {
InvertedActionTypes[k] = v; InvertedActionTypes[k] = v;
}); });
Discourse.UserAction = Discourse.Model.extend({ const UserAction = RestModel.extend({
_attachCategory: function() { _attachCategory: function() {
var categoryId = this.get('category_id'); const categoryId = this.get('category_id');
if (categoryId) { if (categoryId) {
this.set('category', Discourse.Category.findById(categoryId)); this.set('category', Discourse.Category.findById(categoryId));
} }
}.on('init'), }.on('init'),
/**
Return an i18n key we will use for the description text of a user action.
@property descriptionKey
**/
descriptionKey: function() { 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 (action === null || Discourse.UserAction.TO_SHOW.indexOf(action) >= 0) {
if (this.get('isPM')) { if (this.get('isPM')) {
return this.get('sameUser') ? 'sent_by_you' : 'sent_by_user'; 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'), presentName: Em.computed.any('name', 'username'),
targetDisplayName: Em.computed.any('target_name', 'target_username'), targetDisplayName: Em.computed.any('target_name', 'target_username'),
actingDisplayName: Em.computed.any('acting_name', 'acting_username'), actingDisplayName: Em.computed.any('acting_name', 'acting_username'),
targetUserUrl: Discourse.computed.url('target_username', '/users/%@'), targetUserUrl: url('target_username', '/users/%@'),
usernameLower: function() { usernameLower: function() {
return this.get('username').toLowerCase(); return this.get('username').toLowerCase();
}.property('username'), }.property('username'),
userUrl: Discourse.computed.url('usernameLower', '/users/%@'), userUrl: url('usernameLower', '/users/%@'),
postUrl: function() { postUrl: function() {
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('post_number')); 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'), removableBookmark: Em.computed.and('bookmarkType', 'sameUser'),
addChild: function(action) { addChild: function(action) {
var groups = this.get("childGroups"); let groups = this.get("childGroups");
if (!groups) { if (!groups) {
groups = { groups = {
likes: Discourse.UserActionGroup.create({ icon: "fa fa-heart" }), likes: Discourse.UserActionGroup.create({ icon: "fa fa-heart" }),
@ -113,7 +111,7 @@ Discourse.UserAction = Discourse.Model.extend({
} }
this.set("childGroups", groups); this.set("childGroups", groups);
var bucket = (function() { const bucket = (function() {
switch (action.action_type) { switch (action.action_type) {
case UserActionTypes.likes_given: case UserActionTypes.likes_given:
case UserActionTypes.likes_received: case UserActionTypes.likes_received:
@ -124,15 +122,15 @@ Discourse.UserAction = Discourse.Model.extend({
return "bookmarks"; return "bookmarks";
} }
})(); })();
var current = groups[bucket]; const current = groups[bucket];
if (current) { if (current) {
current.push(action); current.push(action);
} }
}, },
children: function() { children: function() {
var g = this.get("childGroups"); const g = this.get("childGroups");
var rval = []; let rval = [];
if (g) { if (g) {
rval = [g.likes, g.stars, g.edits, g.bookmarks].filter(function(i) { rval = [g.likes, g.stars, g.edits, g.bookmarks].filter(function(i) {
return i.get("items") && i.get("items").length > 0; 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) { collapseStream: function(stream) {
var uniq = {}, const uniq = {};
collapsed = [], const collapsed = [];
pos = 0; let pos = 0;
stream.forEach(function(item) { stream.forEach(function(item) {
var key = "" + item.topic_id + "-" + item.post_number; const key = "" + item.topic_id + "-" + item.post_number;
var found = uniq[key]; const found = uniq[key];
if (found === void 0) { if (found === void 0) {
var current; let current;
if (Discourse.UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) { if (Discourse.UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) {
current = Discourse.UserAction.create(item); current = UserAction.create(item);
item.switchToActing(); item.switchToActing();
current.addChild(item); current.addChild(item);
} else { } else {
@ -177,7 +175,7 @@ Discourse.UserAction.reopenClass({
collapsed[pos] = current; collapsed[pos] = current;
pos += 1; pos += 1;
} else { } else {
if (Discourse.UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) { if (UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) {
item.switchToActing(); item.switchToActing();
collapsed[found].addChild(item); collapsed[found].addChild(item);
} else { } else {
@ -208,3 +206,5 @@ Discourse.UserAction.reopenClass({
] ]
}); });
export default UserAction;

View file

@ -1,12 +1,7 @@
/** import { url } from 'discourse/lib/computed';
Represents a user's stream import AdminPost from 'discourse/models/admin-post';
@class UserPostsStream export default Discourse.Model.extend({
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.UserPostsStream = Discourse.Model.extend({
loaded: false, loaded: false,
_initialize: function () { _initialize: function () {
@ -17,9 +12,9 @@ Discourse.UserPostsStream = Discourse.Model.extend({
}); });
}.on("init"), }.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(); } if (this.get("loaded") && this.get("filter") === filter) { return Ember.RSVP.resolve(); }
this.setProperties({ this.setProperties({
@ -32,15 +27,15 @@ Discourse.UserPostsStream = Discourse.Model.extend({
return this.findItems(); return this.findItems();
}, },
findItems: function () { findItems() {
var self = this; const self = this;
if (this.get("loading") || !this.get("canLoadMore")) { return Ember.RSVP.reject(); } if (this.get("loading") || !this.get("canLoadMore")) { return Ember.RSVP.reject(); }
this.set("loading", true); this.set("loading", true);
return Discourse.ajax(this.get("url"), { cache: false }).then(function (result) { return Discourse.ajax(this.get("url"), { cache: false }).then(function (result) {
if (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.get("content").pushObjects(posts);
self.setProperties({ self.setProperties({
loaded: true, loaded: true,

View file

@ -1,3 +1,4 @@
import { url } from 'discourse/lib/computed';
import RestModel from 'discourse/models/rest'; import RestModel from 'discourse/models/rest';
export default RestModel.extend({ export default RestModel.extend({
@ -22,7 +23,7 @@ export default RestModel.extend({
return filter; return filter;
}.property('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) { filterBy(filter) {
this.setProperties({ filter, itemsLoaded: 0, content: [], lastLoadedUrl: null }); this.setProperties({ filter, itemsLoaded: 0, content: [], lastLoadedUrl: null });

View file

@ -1,5 +1,10 @@
import { url } from 'discourse/lib/computed';
import RestModel from 'discourse/models/rest'; import RestModel from 'discourse/models/rest';
import avatarTemplate from 'discourse/lib/avatar-template'; 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({ const User = RestModel.extend({
@ -10,24 +15,12 @@ const User = RestModel.extend({
hasNotPosted: Em.computed.not("hasPosted"), hasNotPosted: Em.computed.not("hasPosted"),
canBeDeleted: Em.computed.and("can_be_deleted", "hasNotPosted"), canBeDeleted: Em.computed.and("can_be_deleted", "hasNotPosted"),
/**
The user's stream
@property stream
@type {Discourse.UserStream}
**/
stream: function() { stream: function() {
return Discourse.UserStream.create({ user: this }); return UserStream.create({ user: this });
}.property(), }.property(),
/**
The user's posts stream
@property postsStream
@type {Discourse.UserPostsStream}
**/
postsStream: function() { postsStream: function() {
return Discourse.UserPostsStream.create({ user: this }); return UserPostsStream.create({ user: this });
}.property(), }.property(),
/** /**
@ -89,7 +82,7 @@ const User = RestModel.extend({
@property adminPath @property adminPath
@type {String} @type {String}
**/ **/
adminPath: Discourse.computed.url('username_lower', "/admin/users/%@"), adminPath: url('username_lower', "/admin/users/%@"),
/** /**
This user's username in lowercase. This user's username in lowercase.
@ -123,7 +116,7 @@ const User = RestModel.extend({
}.property('suspended_till'), }.property('suspended_till'),
suspendedTillDate: function() { suspendedTillDate: function() {
return Discourse.Formatter.longDate(this.get('suspended_till')); return longDate(this.get('suspended_till'));
}.property('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. // Find a `Discourse.User` for a given username.
findByUsername: function(username, options) { findByUsername: function(username, options) {
const user = Discourse.User.create({username: username}); const user = User.create({username: username});
return user.findDetails(options); return user.findDetails(options);
}, },
// TODO: Use app.register and junk Discourse.Singleton // TODO: Use app.register and junk Singleton
createCurrent: function() { createCurrent: function() {
var userJson = PreloadStore.get('currentUser'); var userJson = PreloadStore.get('currentUser');
if (userJson) { if (userJson) {

View file

@ -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')
});

View file

@ -1,3 +1,4 @@
import { setting } from 'discourse/lib/computed';
import showModal from 'discourse/lib/show-modal'; import showModal from 'discourse/lib/show-modal';
import OpenComposer from "discourse/mixins/open-composer"; import OpenComposer from "discourse/mixins/open-composer";
@ -13,7 +14,7 @@ function unlessReadOnly(method) {
const ApplicationRoute = Discourse.Route.extend(OpenComposer, { const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
siteTitle: Discourse.computed.setting('title'), siteTitle: setting('title'),
actions: { actions: {
_collectTitleTokens(tokens) { _collectTitleTokens(tokens) {

View file

@ -1,3 +1,4 @@
import ScreenTrack from 'discourse/lib/screen-track';
import { queryParams } from 'discourse/controllers/discovery-sortable'; import { queryParams } from 'discourse/controllers/discovery-sortable';
// A helper to build a topic route for a filter // A helper to build a topic route for a filter
@ -82,7 +83,7 @@ export default function(filter, extras) {
model(data, transition) { model(data, transition) {
// attempt to stop early cause we need this to be called before .sync // 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), const findOpts = filterQueryParams(transition.queryParams),
extras = { cached: this.isPoppedState(transition) }; extras = { cached: this.isPoppedState(transition) };

View file

@ -1,3 +1,5 @@
import ScreenTrack from 'discourse/lib/screen-track';
let isTransitioning = false, let isTransitioning = false,
scheduledReplace = null, scheduledReplace = null,
lastScrollPos = null; lastScrollPos = null;
@ -185,7 +187,7 @@ const TopicRoute = Discourse.Route.extend({
topicController.set('multiSelect', false); topicController.set('multiSelect', false);
topicController.unsubscribe(); topicController.unsubscribe();
this.controllerFor('composer').set('topic', null); this.controllerFor('composer').set('topic', null);
Discourse.ScreenTrack.current().stop(); ScreenTrack.current().stop();
const headerController = this.controllerFor('header'); const headerController = this.controllerFor('header');
if (headerController) { if (headerController) {
@ -226,7 +228,7 @@ const TopicRoute = Discourse.Route.extend({
this.controllerFor('topic-progress').set('model', model); this.controllerFor('topic-progress').set('model', model);
// We reset screen tracking every time a topic is entered // 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);
} }
}); });

View file

@ -1,5 +1,5 @@
{{#each p in model.content}} {{#each p in model.content}}
<div {{bind-attr class=":item p.hidden p.deleted moderator_action"}}> <div {{bind-attr class=":item p.hidden p.deleted p.moderator_action"}}>
<div class="clearfix info"> <div class="clearfix info">
<a href="{{unbound p.usernameUrl}}" class="avatar-link"> <a href="{{unbound p.usernameUrl}}" class="avatar-link">
<div class="avatar-wrapper"> <div class="avatar-wrapper">

View file

@ -1,13 +1,15 @@
import ScreenTrack from 'discourse/lib/screen-track';
export default Discourse.GroupedView.extend({ export default Discourse.GroupedView.extend({
templateName: 'embedded-post', templateName: 'embedded-post',
classNames: ['reply'], classNames: ['reply'],
_startTracking: function() { _startTracking: function() {
const post = this.get('content'); const post = this.get('content');
Discourse.ScreenTrack.current().track(this.get('elementId'), post.get('post_number')); ScreenTrack.current().track(this.get('elementId'), post.get('post_number'));
}.on('didInsertElement'), }.on('didInsertElement'),
_stopTracking: function() { _stopTracking: function() {
Discourse.ScreenTrack.current().stopTracking(this.get('elementId')); ScreenTrack.current().stopTracking(this.get('elementId'));
}.on('willDestroyElement') }.on('willDestroyElement')
}); });

View file

@ -1,3 +1,5 @@
import { fmt } from 'discourse/lib/computed';
export default Ember.Object.extend({ export default Ember.Object.extend({
tagName: "td", tagName: "td",
ratio: function() { ratio: function() {
@ -26,6 +28,6 @@ export default Ember.Object.extend({
return ''; return '';
}.property(), }.property(),
likesHeat: Discourse.computed.fmt('ratioText', 'heatmap-%@'), likesHeat: fmt('ratioText', 'heatmap-%@'),
}); });

View file

@ -1,3 +1,6 @@
import ScreenTrack from 'discourse/lib/screen-track';
import { number } from 'discourse/lib/formatter';
const DAY = 60 * 50 * 1000; const DAY = 60 * 50 * 1000;
const PostView = Discourse.GroupedView.extend(Ember.Evented, { const PostView = Discourse.GroupedView.extend(Ember.Evented, {
@ -178,7 +181,7 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, {
// don't display badge counts on category badge & oneboxes (unless when explicitely stated) // don't display badge counts on category badge & oneboxes (unless when explicitely stated)
if ($link.hasClass("track-link") || if ($link.hasClass("track-link") ||
$link.closest('.badge-category,.onebox-result,.onebox-body').length === 0) { $link.closest('.badge-category,.onebox-result,.onebox-body').length === 0) {
$link.append("<span class='badge badge-notification clicks' title='" + I18n.t("topic_map.clicks", {count: lc.clicks}) + "'>" + Discourse.Formatter.number(lc.clicks) + "</span>"); $link.append("<span class='badge badge-notification clicks' title='" + I18n.t("topic_map.clicks", {count: lc.clicks}) + "'>" + number(lc.clicks) + "</span>");
} }
} }
}); });
@ -263,7 +266,7 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, {
}, },
_destroyedPostView: function() { _destroyedPostView: function() {
Discourse.ScreenTrack.current().stopTracking(this.get('elementId')); ScreenTrack.current().stopTracking(this.get('elementId'));
}.on('willDestroyElement'), }.on('willDestroyElement'),
_postViewInserted: function() { _postViewInserted: function() {
@ -272,7 +275,7 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, {
this._showLinkCounts(); this._showLinkCounts();
Discourse.ScreenTrack.current().track($post.prop('id'), postNumber); ScreenTrack.current().track($post.prop('id'), postNumber);
this.trigger('postViewInserted', $post); this.trigger('postViewInserted', $post);

View file

@ -3,8 +3,9 @@ import AddArchetypeClass from 'discourse/mixins/add-archetype-class';
import ClickTrack from 'discourse/lib/click-track'; import ClickTrack from 'discourse/lib/click-track';
import { listenForViewEvent } from 'discourse/lib/app-events'; import { listenForViewEvent } from 'discourse/lib/app-events';
import { categoryBadgeHTML } from 'discourse/helpers/category-link'; import { categoryBadgeHTML } from 'discourse/helpers/category-link';
import Scrolling from 'discourse/mixins/scrolling';
const TopicView = Discourse.View.extend(AddCategoryClass, AddArchetypeClass, Discourse.Scrolling, { const TopicView = Discourse.View.extend(AddCategoryClass, AddArchetypeClass, Scrolling, {
templateName: 'topic', templateName: 'topic',
topicBinding: 'controller.model', topicBinding: 'controller.model',

View file

@ -1,5 +1,5 @@
import { setting } from 'discourse/lib/computed';
import CleansUp from 'discourse/mixins/cleans-up'; import CleansUp from 'discourse/mixins/cleans-up';
import afterTransition from 'discourse/lib/after-transition'; import afterTransition from 'discourse/lib/after-transition';
const clickOutsideEventName = "mousedown.outside-user-card", const clickOutsideEventName = "mousedown.outside-user-card",
@ -9,7 +9,7 @@ const clickOutsideEventName = "mousedown.outside-user-card",
export default Discourse.View.extend(CleansUp, { export default Discourse.View.extend(CleansUp, {
elementId: 'user-card', elementId: 'user-card',
classNameBindings: ['controller.visible:show', 'controller.showBadges', 'controller.hasCardBadgeImage'], classNameBindings: ['controller.visible:show', 'controller.showBadges', 'controller.hasCardBadgeImage'],
allowBackgrounds: Discourse.computed.setting('allow_profile_backgrounds'), allowBackgrounds: setting('allow_profile_backgrounds'),
addBackground: function() { addBackground: function() {
const url = this.get('controller.user.card_background'); const url = this.get('controller.user.card_background');

View file

@ -8,10 +8,14 @@
//= require ./discourse/lib/load-script //= require ./discourse/lib/load-script
//= require ./discourse/lib/notification-levels //= require ./discourse/lib/notification-levels
//= require ./discourse/lib/app-events //= require ./discourse/lib/app-events
//= require ./discourse/lib/avatar-template
//= require ./discourse/helpers/i18n //= require ./discourse/helpers/i18n
//= require ./discourse/helpers/fa-icon //= require ./discourse/helpers/fa-icon
//= require ./discourse/helpers/register-unbound
//= require ./discourse/lib/ember_compat_handlebars //= require ./discourse/lib/ember_compat_handlebars
//= require ./discourse/lib/computed //= require ./discourse/lib/computed
//= require ./discourse/lib/formatter
//= require ./discourse/lib/eyeline
//= require ./discourse/helpers/register-unbound //= require ./discourse/helpers/register-unbound
//= require ./discourse/mixins/scrolling //= require ./discourse/mixins/scrolling
//= require_tree ./discourse/mixins //= require_tree ./discourse/mixins
@ -36,7 +40,7 @@
//= require ./discourse/models/post-stream //= require ./discourse/models/post-stream
//= require ./discourse/models/topic-details //= require ./discourse/models/topic-details
//= require ./discourse/models/topic //= require ./discourse/models/topic
//= require ./discourse/models/user_action //= require ./discourse/models/user-action
//= require ./discourse/models/composer //= require ./discourse/models/composer
//= require ./discourse/controllers/controller //= require ./discourse/controllers/controller
//= require ./discourse/controllers/discovery-sortable //= require ./discourse/controllers/discovery-sortable
@ -58,6 +62,7 @@
//= require ./discourse/lib/link-mentions //= require ./discourse/lib/link-mentions
//= require ./discourse/views/composer //= require ./discourse/views/composer
//= require ./discourse/lib/show-modal //= require ./discourse/lib/show-modal
//= require ./discourse/lib/screen-track
//= require ./discourse/routes/discourse //= require ./discourse/routes/discourse
//= require ./discourse/routes/build-topic-route //= require ./discourse/routes/build-topic-route
//= require ./discourse/routes/restricted-user //= require ./discourse/routes/restricted-user

View file

@ -1,4 +1,4 @@
moduleFor('controller:admin-user-badges', 'Admin User Badges Controller', { moduleFor('controller:admin-user-badges', {
needs: ['controller:adminUser'] needs: ['controller:adminUser']
}); });

View file

@ -15,7 +15,7 @@ function setTemplates(lookupTemplateStrings) {
}); });
} }
module("Resolver", { module("lib:resolver", {
setup: function() { setup: function() {
originalTemplates = Ember.TEMPLATES; originalTemplates = Ember.TEMPLATES;
Ember.TEMPLATES = {}; Ember.TEMPLATES = {};

View file

@ -1,6 +1,6 @@
import avatarTemplate from 'discourse/lib/avatar-template'; import avatarTemplate from 'discourse/lib/avatar-template';
module('avatarTemplate'); module('lib:avatar-template');
test("avatarTemplate", function(){ test("avatarTemplate", function(){
var oldCDN = Discourse.CDN; var oldCDN = Discourse.CDN;

View file

@ -1,4 +1,4 @@
module("categoryBadgeHTML"); module("lib:category-link");
import { categoryBadgeHTML } from "discourse/helpers/category-link"; import { categoryBadgeHTML } from "discourse/helpers/category-link";

View file

@ -4,7 +4,7 @@ var windowOpen,
win, win,
redirectTo; redirectTo;
module("ClickTrack", { module("lib:click-track", {
setup: function() { setup: function() {
// Prevent any of these tests from navigating away // Prevent any of these tests from navigating away

View file

@ -1,4 +1,6 @@
module("Discourse.Computed", { import { setting, propertyEqual, propertyNotEqual, fmt, i18n, url } from 'discourse/lib/computed';
module("lib:computed", {
setup: function() { setup: function() {
sandbox.stub(I18n, "t", function(scope) { sandbox.stub(I18n, "t", function(scope) {
return "%@ translated: " + scope; return "%@ translated: " + scope;
@ -12,8 +14,8 @@ module("Discourse.Computed", {
test("setting", function() { test("setting", function() {
var t = Em.Object.extend({ var t = Em.Object.extend({
vehicle: Discourse.computed.setting('vehicle'), vehicle: setting('vehicle'),
missingProp: Discourse.computed.setting('madeUpThing') missingProp: setting('madeUpThing')
}).create(); }).create();
Discourse.SiteSettings.vehicle = "airplane"; Discourse.SiteSettings.vehicle = "airplane";
@ -23,7 +25,7 @@ test("setting", function() {
test("propertyEqual", function() { test("propertyEqual", function() {
var t = Em.Object.extend({ var t = Em.Object.extend({
same: Discourse.computed.propertyEqual('cookies', 'biscuits') same: propertyEqual('cookies', 'biscuits')
}).create({ }).create({
cookies: 10, cookies: 10,
biscuits: 10 biscuits: 10
@ -36,7 +38,7 @@ test("propertyEqual", function() {
test("propertyNotEqual", function() { test("propertyNotEqual", function() {
var t = Em.Object.extend({ var t = Em.Object.extend({
diff: Discourse.computed.propertyNotEqual('cookies', 'biscuits') diff: propertyNotEqual('cookies', 'biscuits')
}).create({ }).create({
cookies: 10, cookies: 10,
biscuits: 10 biscuits: 10
@ -50,8 +52,8 @@ test("propertyNotEqual", function() {
test("fmt", function() { test("fmt", function() {
var t = Em.Object.extend({ var t = Em.Object.extend({
exclaimyUsername: Discourse.computed.fmt('username', "!!! %@ !!!"), exclaimyUsername: fmt('username', "!!! %@ !!!"),
multiple: Discourse.computed.fmt('username', 'mood', "%@ is %@") multiple: fmt('username', 'mood', "%@ is %@")
}).create({ }).create({
username: 'eviltrout', username: 'eviltrout',
mood: "happy" mood: "happy"
@ -69,8 +71,8 @@ test("fmt", function() {
test("i18n", function() { test("i18n", function() {
var t = Em.Object.extend({ var t = Em.Object.extend({
exclaimyUsername: Discourse.computed.i18n('username', "!!! %@ !!!"), exclaimyUsername: i18n('username', "!!! %@ !!!"),
multiple: Discourse.computed.i18n('username', 'mood', "%@ is %@") multiple: i18n('username', 'mood', "%@ is %@")
}).create({ }).create({
username: 'eviltrout', username: 'eviltrout',
mood: "happy" mood: "happy"
@ -90,7 +92,7 @@ test("url", function() {
var t, testClass; var t, testClass;
testClass = Em.Object.extend({ testClass = Em.Object.extend({
userUrl: Discourse.computed.url('username', "/users/%@") userUrl: url('username', "/users/%@")
}); });
t = testClass.create({ username: 'eviltrout' }); t = testClass.create({ username: 'eviltrout' });

View file

@ -1,6 +1,8 @@
var clock; var clock;
module("Discourse.Formatter", { import { relativeAge, autoUpdatingRelativeAge, updateRelativeAge, breakUp, number } from 'discourse/lib/formatter';
module("lib:formatter", {
setup: function() { setup: function() {
clock = sinon.useFakeTimers(new Date(2012,11,31,12,0).getTime()); clock = sinon.useFakeTimers(new Date(2012,11,31,12,0).getTime());
}, },
@ -17,7 +19,7 @@ var mins_ago = function(mins){
}; };
var formatMins = function(mins) { var formatMins = function(mins) {
return Discourse.Formatter.relativeAge(mins_ago(mins), {format: format, leaveAgo: leaveAgo}); return relativeAge(mins_ago(mins), {format: format, leaveAgo: leaveAgo});
}; };
var formatHours = function(hours) { var formatHours = function(hours) {
@ -141,26 +143,24 @@ test("formating tiny dates", function() {
Discourse.SiteSettings.relative_date_duration = originalValue; Discourse.SiteSettings.relative_date_duration = originalValue;
}); });
module("Discourse.Formatter");
test("autoUpdatingRelativeAge", function() { test("autoUpdatingRelativeAge", function() {
var d = moment().subtract(1, 'day').toDate(); var d = moment().subtract(1, 'day').toDate();
var $elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d)); var $elem = $(autoUpdatingRelativeAge(d));
equal($elem.data('format'), "tiny"); equal($elem.data('format'), "tiny");
equal($elem.data('time'), d.getTime()); equal($elem.data('time'), d.getTime());
equal($elem.attr('title'), undefined); equal($elem.attr('title'), undefined);
$elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d, {title: true})); $elem = $(autoUpdatingRelativeAge(d, {title: true}));
equal($elem.attr('title'), moment(d).longDate()); equal($elem.attr('title'), moment(d).longDate());
$elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d,{format: 'medium', title: true, leaveAgo: true})); $elem = $(autoUpdatingRelativeAge(d,{format: 'medium', title: true, leaveAgo: true}));
equal($elem.data('format'), "medium-with-ago"); equal($elem.data('format'), "medium-with-ago");
equal($elem.data('time'), d.getTime()); equal($elem.data('time'), d.getTime());
equal($elem.attr('title'), moment(d).longDate()); equal($elem.attr('title'), moment(d).longDate());
equal($elem.html(), '1 day ago'); equal($elem.html(), '1 day ago');
$elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d,{format: 'medium'})); $elem = $(autoUpdatingRelativeAge(d,{format: 'medium'}));
equal($elem.data('format'), "medium"); equal($elem.data('format'), "medium");
equal($elem.data('time'), d.getTime()); equal($elem.data('time'), d.getTime());
equal($elem.attr('title'), undefined); equal($elem.attr('title'), undefined);
@ -170,25 +170,25 @@ test("autoUpdatingRelativeAge", function() {
test("updateRelativeAge", function(){ test("updateRelativeAge", function(){
var d = new Date(); var d = new Date();
var $elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d)); var $elem = $(autoUpdatingRelativeAge(d));
$elem.data('time', d.getTime() - 2 * 60 * 1000); $elem.data('time', d.getTime() - 2 * 60 * 1000);
Discourse.Formatter.updateRelativeAge($elem); updateRelativeAge($elem);
equal($elem.html(), "2m"); equal($elem.html(), "2m");
d = new Date(); d = new Date();
$elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d, {format: 'medium', leaveAgo: true})); $elem = $(autoUpdatingRelativeAge(d, {format: 'medium', leaveAgo: true}));
$elem.data('time', d.getTime() - 2 * 60 * 1000); $elem.data('time', d.getTime() - 2 * 60 * 1000);
Discourse.Formatter.updateRelativeAge($elem); updateRelativeAge($elem);
equal($elem.html(), "2 mins ago"); equal($elem.html(), "2 mins ago");
}); });
test("breakUp", function(){ test("breakUp", function(){
var b = function(s,hint){ return Discourse.Formatter.breakUp(s,hint); }; var b = function(s,hint){ return breakUp(s,hint); };
equal(b("hello"), "hello"); equal(b("hello"), "hello");
equal(b("helloworld"), "helloworld"); equal(b("helloworld"), "helloworld");
@ -201,9 +201,9 @@ test("breakUp", function(){
}); });
test("number", function() { test("number", function() {
equal(Discourse.Formatter.number(123), "123", "it returns a string version of the number"); equal(number(123), "123", "it returns a string version of the number");
equal(Discourse.Formatter.number("123"), "123", "it works with a string command"); equal(number("123"), "123", "it works with a string command");
equal(Discourse.Formatter.number(NaN), "0", "it returns 0 for NaN"); equal(number(NaN), "0", "it returns 0 for NaN");
equal(Discourse.Formatter.number(3333), "3.3k", "it abbreviates thousands"); equal(number(3333), "3.3k", "it abbreviates thousands");
equal(Discourse.Formatter.number(2499999), "2.5M", "it abbreviates millions"); equal(number(2499999), "2.5M", "it abbreviates millions");
}); });

View file

@ -1,4 +1,4 @@
module("SelectedPostsCount"); module("mixin:selected-posts-count");
import SelectedPostsCount from 'discourse/mixins/selected-posts-count'; import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
import Topic from 'discourse/models/topic'; import Topic from 'discourse/models/topic';

View file

@ -1,8 +1,10 @@
module("Discourse.Singleton"); import Singleton from 'discourse/mixins/singleton';
module("mixin:singleton");
test("current", function() { test("current", function() {
var DummyModel = Ember.Object.extend({}); var DummyModel = Ember.Object.extend({});
DummyModel.reopenClass(Discourse.Singleton); DummyModel.reopenClass(Singleton);
var current = DummyModel.current(); var current = DummyModel.current();
present(current, 'current returns the current instance'); present(current, 'current returns the current instance');
@ -12,7 +14,7 @@ test("current", function() {
test("currentProp reading", function() { test("currentProp reading", function() {
var DummyModel = Ember.Object.extend({}); var DummyModel = Ember.Object.extend({});
DummyModel.reopenClass(Discourse.Singleton); DummyModel.reopenClass(Singleton);
var current = DummyModel.current(); var current = DummyModel.current();
blank(DummyModel.currentProp('evil'), 'by default attributes are blank'); blank(DummyModel.currentProp('evil'), 'by default attributes are blank');
@ -22,7 +24,7 @@ test("currentProp reading", function() {
test("currentProp writing", function() { test("currentProp writing", function() {
var DummyModel = Ember.Object.extend({}); var DummyModel = Ember.Object.extend({});
DummyModel.reopenClass(Discourse.Singleton); DummyModel.reopenClass(Singleton);
blank(DummyModel.currentProp('adventure'), 'by default attributes are blank'); blank(DummyModel.currentProp('adventure'), 'by default attributes are blank');
var result = DummyModel.currentProp('adventure', 'time'); var result = DummyModel.currentProp('adventure', 'time');
@ -38,7 +40,7 @@ test("currentProp writing", function() {
test("createCurrent", function() { test("createCurrent", function() {
var Shoe = Ember.Object.extend({}); var Shoe = Ember.Object.extend({});
Shoe.reopenClass(Discourse.Singleton, { Shoe.reopenClass(Singleton, {
createCurrent: function() { createCurrent: function() {
return Shoe.create({toes: 5}); return Shoe.create({toes: 5});
} }
@ -50,7 +52,7 @@ test("createCurrent", function() {
test("createCurrent that returns null", function() { test("createCurrent that returns null", function() {
var Missing = Ember.Object.extend({}); var Missing = Ember.Object.extend({});
Missing.reopenClass(Discourse.Singleton, { Missing.reopenClass(Singleton, {
createCurrent: function() { createCurrent: function() {
return null; return null;
} }

View file

@ -1,8 +1,8 @@
import Session from "discourse/models/session"; import Session from "discourse/models/session";
module("Discourse.Session"); module("model:session");
test('highestSeenByTopic', function() { test('highestSeenByTopic', function() {
var session = Session.current(); const session = Session.current();
deepEqual(session.get('highestSeenByTopic'), {}, "by default it returns an empty object"); deepEqual(session.get('highestSeenByTopic'), {}, "by default it returns an empty object");
}); });

View file

@ -79,6 +79,7 @@ var origDebounce = Ember.run.debounce,
createPretendServer = require('helpers/create-pretender', null, null, false).default, createPretendServer = require('helpers/create-pretender', null, null, false).default,
fixtures = require('fixtures/site_fixtures', null, null, false).default, fixtures = require('fixtures/site_fixtures', null, null, false).default,
flushMap = require('discourse/models/store', null, null, false).flushMap, flushMap = require('discourse/models/store', null, null, false).flushMap,
ScrollingDOMMethods = require('discourse/mixins/scrolling', null, null, false).ScrollingDOMMethods,
server; server;
function dup(obj) { function dup(obj) {
@ -92,6 +93,7 @@ QUnit.testStart(function(ctx) {
Discourse.SiteSettings = dup(Discourse.SiteSettingsOriginal); Discourse.SiteSettings = dup(Discourse.SiteSettingsOriginal);
Discourse.BaseUri = "/"; Discourse.BaseUri = "/";
Discourse.BaseUrl = "localhost"; Discourse.BaseUrl = "localhost";
Discourse.Session.resetCurrent();
Discourse.User.resetCurrent(); Discourse.User.resetCurrent();
Discourse.Site.resetCurrent(Discourse.Site.create(dup(fixtures['site.json'].site))); Discourse.Site.resetCurrent(Discourse.Site.create(dup(fixtures['site.json'].site)));
@ -103,8 +105,8 @@ QUnit.testStart(function(ctx) {
PreloadStore.reset(); PreloadStore.reset();
window.sandbox = sinon.sandbox.create(); window.sandbox = sinon.sandbox.create();
window.sandbox.stub(Discourse.ScrollingDOMMethods, "bindOnScroll"); window.sandbox.stub(ScrollingDOMMethods, "bindOnScroll");
window.sandbox.stub(Discourse.ScrollingDOMMethods, "unbindOnScroll"); window.sandbox.stub(ScrollingDOMMethods, "unbindOnScroll");
// Don't debounce in test unless we're testing debouncing // Don't debounce in test unless we're testing debouncing
if (ctx.module.indexOf('debounce') === -1) { if (ctx.module.indexOf('debounce') === -1) {