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 { bufferedProperty } from 'discourse/mixins/buffered-content';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { propertyEqual } from 'discourse/lib/computed';
export default Ember.Component.extend(bufferedProperty('userField'), {
editing: Ember.computed.empty('userField.id'),
classNameBindings: [':user-field'],
cantMoveUp: Discourse.computed.propertyEqual('userField', 'firstField'),
cantMoveDown: Discourse.computed.propertyEqual('userField', 'lastField'),
cantMoveUp: propertyEqual('userField', 'firstField'),
cantMoveDown: propertyEqual('userField', 'lastField'),
userFieldsDescription: function() {
return I18n.t('admin.user_fields.description');

View File

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

View File

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

View File

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

View File

@ -1,17 +1,12 @@
/**
This controller supports the default interface when you enter the admin section.
import { setting } from 'discourse/lib/computed';
@class AdminDashboardController
@extends Ember.Controller
@namespace Discourse
@module Discourse
**/
// This controller supports the default interface when you enter the admin section.
export default Ember.Controller.extend({
loading: true,
versionCheck: null,
problemsCheckMinutes: 1,
showVersionChecks: Discourse.computed.setting('version_checks'),
showVersionChecks: setting('version_checks'),
foundProblems: function() {
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 { propertyEqual } from 'discourse/lib/computed';
export default Em.ObjectController.extend({
needs: ['adminGroupsType'],
@ -15,7 +16,7 @@ export default Em.ObjectController.extend({
}.property("limit", "user_count"),
showingFirst: Em.computed.lte("currentPage", 1),
showingLast: Discourse.computed.propertyEqual("currentPage", "totalPages"),
showingLast: propertyEqual("currentPage", "totalPages"),
aliasLevelOptions: function() {
return [

View File

@ -1,15 +1,16 @@
import ObjectController from 'discourse/controllers/object';
import CanCheckEmails from 'discourse/mixins/can-check-emails';
import { propertyNotEqual, setting } from 'discourse/lib/computed';
export default ObjectController.extend(CanCheckEmails, {
editingTitle: false,
originalPrimaryGroupId: null,
availableGroups: null,
showApproval: Discourse.computed.setting('must_approve_users'),
showBadges: Discourse.computed.setting('enable_badges'),
showApproval: setting('must_approve_users'),
showBadges: setting('enable_badges'),
primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'model.primary_group_id'),
primaryGroupDirty: propertyNotEqual('originalPrimaryGroupId', 'model.primary_group_id'),
automaticGroups: function() {
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({
query: null,
showEmails: false,
@ -9,7 +11,7 @@ export default Ember.ArrayController.extend({
queryPending: Em.computed.equal('query', 'pending'),
queryHasApproval: Em.computed.or('queryNew', 'queryPending'),
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),
selectedCount: function() {

View File

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

View File

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

View File

@ -1,9 +1,10 @@
import RestModel from 'discourse/models/rest';
import { i18n } from 'discourse/lib/computed';
const UserField = RestModel.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({

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,9 @@
import { propertyEqual } from 'discourse/lib/computed';
import { actionDescription } from "discourse/components/small-action";
export default Ember.Component.extend({
classNameBindings: [":item", "item.hidden", "item.deleted", "moderatorAction"],
moderatorAction: Discourse.computed.propertyEqual("item.post_type", "site.post_types.moderator_action"),
moderatorAction: propertyEqual("item.post_type", "site.post_types.moderator_action"),
actionDescription: actionDescription("item.action_code", "item.created_at"),
actions: {

View File

@ -1,6 +1,8 @@
import { fmt } from 'discourse/lib/computed';
export default Ember.Component.extend({
classNameBindings: [':user-field', 'field.field_type'],
layoutName: Discourse.computed.fmt('field.field_type', 'components/user-fields/%@'),
layoutName: fmt('field.field_type', 'components/user-fields/%@'),
noneLabel: function() {
if (!this.get('field.required')) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,13 @@
export default Ember.ObjectController.extend({
export default Ember.Controller.extend({
loading: false,
limit: null,
offset: null,
actions: {
loadMore() {
if (this.get("loading")) { return; }
// we've reached the end
if (this.get("model.members.length") >= this.get("user_count")) { return; }
if (this.get("model.members.length") >= this.get("model.user_count")) { return; }
this.set("loading", true);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,6 @@
import { propertyEqual } from 'discourse/lib/computed';
import ObjectController from 'discourse/controllers/object';
/**
This controller supports actions related to updating one's email address
@class PreferencesEmailController
@extends ObjectController
@namespace Discourse
@module Discourse
**/
export default ObjectController.extend({
taken: false,
saving: false,
@ -17,7 +10,7 @@ export default ObjectController.extend({
newEmailEmpty: Em.computed.empty('newEmail'),
saveDisabled: Em.computed.or('saving', 'newEmailEmpty', 'taken', 'unchanged'),
unchanged: Discourse.computed.propertyEqual('newEmailLower', 'email'),
unchanged: propertyEqual('newEmailLower', 'email'),
newEmailLower: function() {
return this.get('newEmail').toLowerCase();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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
var registerUnbound = require('discourse/helpers/register-unbound', null, null, true).default;
var avatarTemplate = require('discourse/lib/avatar-template', null, null, true).default;
const safe = Handlebars.SafeString;
Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
if (Em.isEmpty(user)) {
return new safe("<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'); }
var avatar = Em.get(user, 'avatar_template') || avatarTemplate(username, uploadId);
const avatar = Em.get(user, 'avatar_template') || avatarTemplate(username, uploadId);
return new safe(Discourse.Utilities.avatarImg({ size: size, avatarTemplate: avatar }));
}, 'username', 'uploaded_avatar_id', 'avatar_template');
@ -24,30 +24,30 @@ Em.Handlebars.helper('bound-avatar-template', function(avatarTemplate, size) {
});
registerUnbound('raw-date', function(dt) {
return Discourse.Formatter.longDate(new Date(dt));
return longDate(new Date(dt));
});
registerUnbound('age-with-tooltip', function(dt) {
return new safe(Discourse.Formatter.autoUpdatingRelativeAge(new Date(dt), {title: true}));
return new safe(autoUpdatingRelativeAge(new Date(dt), {title: true}));
});
registerUnbound('number', function(orig, params) {
orig = parseInt(orig, 10);
if (isNaN(orig)) { orig = 0; }
var title = orig;
let title = orig;
if (params.numberKey) {
title = I18n.t(params.numberKey, { number: orig });
}
var classNames = 'number';
let classNames = 'number';
if (params['class']) {
classNames += ' ' + params['class'];
}
var result = "<span class='" + classNames + "'";
let result = "<span class='" + classNames + "'";
// Round off the thousands to one decimal place
var n = Discourse.Formatter.number(orig);
const n = number(orig);
if (n !== 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) {
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';
registerUnbound('format-age', function(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 { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
/**
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) {
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);
}
});
// 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 PageTracker from 'discourse/lib/page-tracker';
export default {
name: "page-tracking",
after: 'register-discourse-location',
initialize: function(container) {
initialize(container) {
// Tell our AJAX system to track a page transition
var router = container.lookup('router:main');
const router = container.lookup('router:main');
router.on('willTransition', function() {
Discourse.viewTrackingRequired();
});
@ -16,7 +17,7 @@ export default {
Em.run.scheduleOnce('afterRender', Ember.Route, cleanDOM);
});
var pageTracker = Discourse.PageTracker.current();
const pageTracker = PageTracker.current();
pageTracker.start();
// Out of the box, Discourse tries to track google analytics

View File

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

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 liveEnabled = false;
@ -79,7 +80,7 @@ function setupNotifications() {
document.addEventListener("scroll", resetIdle);
}
window.addEventListener("mouseover", resetIdle);
Discourse.PageTracker.on("change", resetIdle);
PageTracker.on("change", resetIdle);
}
function resetIdle() {

View File

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

View File

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

View File

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

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

View File

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

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.
export default Em.Mixin.create(Ember.ViewTargetActionSupport, Discourse.Scrolling, {
export default Ember.Mixin.create(Ember.ViewTargetActionSupport, Scrolling, {
scrolled: function() {
const eyeline = this.get('eyeline');
@ -7,7 +10,7 @@ export default Em.Mixin.create(Ember.ViewTargetActionSupport, Discourse.Scrollin
},
_bindEyeline: function() {
const eyeline = new Discourse.Eyeline(this.get('eyelineSelector') + ":last");
const eyeline = new Eyeline(this.get('eyelineSelector') + ":last");
this.set('eyeline', eyeline);
eyeline.on('sawBottom', () => this.send('loadMore'));
this.bindScrolling();

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

View File

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

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

@ -1,3 +1,4 @@
import { url } from 'discourse/lib/computed';
import RestModel from 'discourse/models/rest';
export default RestModel.extend({
@ -22,7 +23,7 @@ export default RestModel.extend({
return filter;
}.property('filter'),
baseUrl: Discourse.computed.url('itemsLoaded', 'user.username_lower', '/user_actions.json?offset=%@&username=%@'),
baseUrl: url('itemsLoaded', 'user.username_lower', '/user_actions.json?offset=%@&username=%@'),
filterBy(filter) {
this.setProperties({ filter, itemsLoaded: 0, content: [], lastLoadedUrl: null });

View File

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

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 OpenComposer from "discourse/mixins/open-composer";
@ -13,7 +14,7 @@ function unlessReadOnly(method) {
const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
siteTitle: Discourse.computed.setting('title'),
siteTitle: setting('title'),
actions: {
_collectTitleTokens(tokens) {

View File

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

View File

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

View File

@ -1,5 +1,5 @@
{{#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">
<a href="{{unbound p.usernameUrl}}" class="avatar-link">
<div class="avatar-wrapper">

View File

@ -1,13 +1,15 @@
import ScreenTrack from 'discourse/lib/screen-track';
export default Discourse.GroupedView.extend({
templateName: 'embedded-post',
classNames: ['reply'],
_startTracking: function() {
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'),
_stopTracking: function() {
Discourse.ScreenTrack.current().stopTracking(this.get('elementId'));
ScreenTrack.current().stopTracking(this.get('elementId'));
}.on('willDestroyElement')
});

View File

@ -1,3 +1,5 @@
import { fmt } from 'discourse/lib/computed';
export default Ember.Object.extend({
tagName: "td",
ratio: function() {
@ -26,6 +28,6 @@ export default Ember.Object.extend({
return '';
}.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 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)
if ($link.hasClass("track-link") ||
$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() {
Discourse.ScreenTrack.current().stopTracking(this.get('elementId'));
ScreenTrack.current().stopTracking(this.get('elementId'));
}.on('willDestroyElement'),
_postViewInserted: function() {
@ -272,7 +275,7 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, {
this._showLinkCounts();
Discourse.ScreenTrack.current().track($post.prop('id'), postNumber);
ScreenTrack.current().track($post.prop('id'), postNumber);
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 { listenForViewEvent } from 'discourse/lib/app-events';
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',
topicBinding: 'controller.model',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
var clock;
module("Discourse.Formatter", {
import { relativeAge, autoUpdatingRelativeAge, updateRelativeAge, breakUp, number } from 'discourse/lib/formatter';
module("lib:formatter", {
setup: function() {
clock = sinon.useFakeTimers(new Date(2012,11,31,12,0).getTime());
},
@ -17,7 +19,7 @@ var mins_ago = 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) {
@ -141,26 +143,24 @@ test("formating tiny dates", function() {
Discourse.SiteSettings.relative_date_duration = originalValue;
});
module("Discourse.Formatter");
test("autoUpdatingRelativeAge", function() {
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('time'), d.getTime());
equal($elem.attr('title'), undefined);
$elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d, {title: true}));
$elem = $(autoUpdatingRelativeAge(d, {title: true}));
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('time'), d.getTime());
equal($elem.attr('title'), moment(d).longDate());
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('time'), d.getTime());
equal($elem.attr('title'), undefined);
@ -170,25 +170,25 @@ test("autoUpdatingRelativeAge", function() {
test("updateRelativeAge", function(){
var d = new Date();
var $elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d));
var $elem = $(autoUpdatingRelativeAge(d));
$elem.data('time', d.getTime() - 2 * 60 * 1000);
Discourse.Formatter.updateRelativeAge($elem);
updateRelativeAge($elem);
equal($elem.html(), "2m");
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);
Discourse.Formatter.updateRelativeAge($elem);
updateRelativeAge($elem);
equal($elem.html(), "2 mins ago");
});
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("helloworld"), "helloworld");
@ -201,9 +201,9 @@ test("breakUp", function(){
});
test("number", function() {
equal(Discourse.Formatter.number(123), "123", "it returns a string version of the number");
equal(Discourse.Formatter.number("123"), "123", "it works with a string command");
equal(Discourse.Formatter.number(NaN), "0", "it returns 0 for NaN");
equal(Discourse.Formatter.number(3333), "3.3k", "it abbreviates thousands");
equal(Discourse.Formatter.number(2499999), "2.5M", "it abbreviates millions");
equal(number(123), "123", "it returns a string version of the number");
equal(number("123"), "123", "it works with a string command");
equal(number(NaN), "0", "it returns 0 for NaN");
equal(number(3333), "3.3k", "it abbreviates thousands");
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 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() {
var DummyModel = Ember.Object.extend({});
DummyModel.reopenClass(Discourse.Singleton);
DummyModel.reopenClass(Singleton);
var current = DummyModel.current();
present(current, 'current returns the current instance');
@ -12,7 +14,7 @@ test("current", function() {
test("currentProp reading", function() {
var DummyModel = Ember.Object.extend({});
DummyModel.reopenClass(Discourse.Singleton);
DummyModel.reopenClass(Singleton);
var current = DummyModel.current();
blank(DummyModel.currentProp('evil'), 'by default attributes are blank');
@ -22,7 +24,7 @@ test("currentProp reading", function() {
test("currentProp writing", function() {
var DummyModel = Ember.Object.extend({});
DummyModel.reopenClass(Discourse.Singleton);
DummyModel.reopenClass(Singleton);
blank(DummyModel.currentProp('adventure'), 'by default attributes are blank');
var result = DummyModel.currentProp('adventure', 'time');
@ -38,7 +40,7 @@ test("currentProp writing", function() {
test("createCurrent", function() {
var Shoe = Ember.Object.extend({});
Shoe.reopenClass(Discourse.Singleton, {
Shoe.reopenClass(Singleton, {
createCurrent: function() {
return Shoe.create({toes: 5});
}
@ -50,7 +52,7 @@ test("createCurrent", function() {
test("createCurrent that returns null", function() {
var Missing = Ember.Object.extend({});
Missing.reopenClass(Discourse.Singleton, {
Missing.reopenClass(Singleton, {
createCurrent: function() {
return null;
}

View File

@ -1,8 +1,8 @@
import Session from "discourse/models/session";
module("Discourse.Session");
module("model:session");
test('highestSeenByTopic', function() {
var session = Session.current();
const session = Session.current();
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,
fixtures = require('fixtures/site_fixtures', null, null, false).default,
flushMap = require('discourse/models/store', null, null, false).flushMap,
ScrollingDOMMethods = require('discourse/mixins/scrolling', null, null, false).ScrollingDOMMethods,
server;
function dup(obj) {
@ -92,6 +93,7 @@ QUnit.testStart(function(ctx) {
Discourse.SiteSettings = dup(Discourse.SiteSettingsOriginal);
Discourse.BaseUri = "/";
Discourse.BaseUrl = "localhost";
Discourse.Session.resetCurrent();
Discourse.User.resetCurrent();
Discourse.Site.resetCurrent(Discourse.Site.create(dup(fixtures['site.json'].site)));
@ -103,8 +105,8 @@ QUnit.testStart(function(ctx) {
PreloadStore.reset();
window.sandbox = sinon.sandbox.create();
window.sandbox.stub(Discourse.ScrollingDOMMethods, "bindOnScroll");
window.sandbox.stub(Discourse.ScrollingDOMMethods, "unbindOnScroll");
window.sandbox.stub(ScrollingDOMMethods, "bindOnScroll");
window.sandbox.stub(ScrollingDOMMethods, "unbindOnScroll");
// Don't debounce in test unless we're testing debouncing
if (ctx.module.indexOf('debounce') === -1) {