Refactor: Back all modals by controllers

This commit is contained in:
Robin Ward 2013-05-30 14:12:33 -04:00
parent e0dae88885
commit 0af0a214b2
74 changed files with 1555 additions and 1382 deletions

View file

@ -155,9 +155,9 @@ Discourse = Ember.Application.createWithMixins({
},
authenticationComplete: function(options) {
// TODO, how to dispatch this to the view without the container?
var loginView = Discourse.__container__.lookup('controller:modal').get('currentView');
return loginView.authenticationComplete(options);
// TODO, how to dispatch this to the controller without the container?
var loginController = Discourse.__container__.lookup('controller:login');
return loginController.authenticationComplete(options);
},
/**

View file

@ -9,14 +9,6 @@
@module Discourse
**/
Discourse.ApplicationController = Discourse.Controller.extend({
needs: ['modal'],
showLogin: function() {
var modalController = this.get('controllers.modal');
if (modalController) {
modalController.show(Discourse.LoginView.create())
}
},
routeChanged: function(){
if (window._gaq === undefined) { return; }

View file

@ -0,0 +1,276 @@
/**
The modal for creating accounts
@class CreateAccountController
@extends Discourse.Controller
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.CreateAccountController = Discourse.Controller.extend(Discourse.ModalFunctionality, {
uniqueUsernameValidation: null,
globalNicknameExists: false,
complete: false,
accountPasswordConfirm: 0,
accountChallenge: 0,
formSubmitted: false,
submitDisabled: function() {
if (this.get('formSubmitted')) return true;
if (this.get('nameValidation.failed')) return true;
if (this.get('emailValidation.failed')) return true;
if (this.get('usernameValidation.failed')) return true;
if (this.get('passwordValidation.failed')) return true;
return false;
}.property('nameValidation.failed', 'emailValidation.failed', 'usernameValidation.failed', 'passwordValidation.failed', 'formSubmitted'),
passwordRequired: function() {
return this.blank('authOptions.auth_provider');
}.property('authOptions.auth_provider'),
// Validate the name
nameValidation: function() {
// If blank, fail without a reason
if (this.blank('accountName')) return Discourse.InputValidation.create({ failed: true });
if (this.get('accountPasswordConfirm') === 0) {
this.fetchConfirmationValue();
}
// If too short
if (this.get('accountName').length < 3) {
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.name.too_short')
});
}
// Looks good!
return Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.name.ok')
});
}.property('accountName'),
// Check the email address
emailValidation: function() {
// If blank, fail without a reason
var email;
if (this.blank('accountEmail')) {
return Discourse.InputValidation.create({
failed: true
});
}
email = this.get("accountEmail");
if ((this.get('authOptions.email') === email) && this.get('authOptions.email_valid')) {
return Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.email.authenticated', {
provider: this.get('authOptions.auth_provider')
})
});
}
if (Discourse.Utilities.emailValid(email)) {
return Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.email.ok')
});
}
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.email.invalid')
});
}.property('accountEmail'),
usernameMatch: function() {
if (this.usernameNeedsToBeValidatedWithEmail()) {
if (this.get('emailValidation.failed')) {
if (this.shouldCheckUsernameMatch()) {
return this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.enter_email')
}));
} else {
return this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ failed: true }));
}
} else if (this.shouldCheckUsernameMatch()) {
this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.checking')
}));
return this.checkUsernameAvailability();
}
}
}.observes('accountEmail'),
basicUsernameValidation: function() {
this.set('uniqueUsernameValidation', null);
// If blank, fail without a reason
if (this.blank('accountUsername')) {
return Discourse.InputValidation.create({
failed: true
});
}
// If too short
if (this.get('accountUsername').length < 3) {
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.too_short')
});
}
// If too long
if (this.get('accountUsername').length > 15) {
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.too_long')
});
}
this.checkUsernameAvailability();
// Let's check it out asynchronously
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.checking')
});
}.property('accountUsername'),
shouldCheckUsernameMatch: function() {
return !this.blank('accountUsername') && this.get('accountUsername').length > 2;
},
checkUsernameAvailability: Discourse.debounce(function() {
var _this = this;
if (this.shouldCheckUsernameMatch()) {
return Discourse.User.checkUsername(this.get('accountUsername'), this.get('accountEmail')).then(function(result) {
_this.set('globalNicknameExists', false);
if (result.available) {
if (result.global_match) {
_this.set('globalNicknameExists', true);
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.username.global_match')
}));
} else {
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.username.available')
}));
}
} else {
if (result.suggestion) {
if (result.global_match !== void 0 && result.global_match === false) {
_this.set('globalNicknameExists', true);
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.global_mismatch', result)
}));
} else {
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.not_available', result)
}));
}
} else if (result.errors) {
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: result.errors.join(' ')
}));
} else {
_this.set('globalNicknameExists', true);
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.enter_email')
}));
}
}
});
}
}, 500),
// Actually wait for the async name check before we're 100% sure we're good to go
usernameValidation: function() {
var basicValidation, uniqueUsername;
basicValidation = this.get('basicUsernameValidation');
uniqueUsername = this.get('uniqueUsernameValidation');
if (uniqueUsername) {
return uniqueUsername;
}
return basicValidation;
}.property('uniqueUsernameValidation', 'basicUsernameValidation'),
usernameNeedsToBeValidatedWithEmail: function() {
return( this.get('globalNicknameExists') || false );
},
// Validate the password
passwordValidation: function() {
var password;
if (!this.get('passwordRequired')) {
return Discourse.InputValidation.create({
ok: true
});
}
// If blank, fail without a reason
password = this.get("accountPassword");
if (this.blank('accountPassword')) {
return Discourse.InputValidation.create({ failed: true });
}
// If too short
if (password.length < 6) {
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.password.too_short')
});
}
// Looks good!
return Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.password.ok')
});
}.property('accountPassword'),
fetchConfirmationValue: function() {
var createAccountController = this;
return Discourse.ajax('/users/hp.json').then(function (json) {
createAccountController.set('accountPasswordConfirm', json.value);
createAccountController.set('accountChallenge', json.challenge.split("").reverse().join(""));
});
},
createAccount: function() {
var createAccountController = this;
this.set('formSubmitted', true);
var name = this.get('accountName');
var email = this.get('accountEmail');
var password = this.get('accountPassword');
var username = this.get('accountUsername');
var passwordConfirm = this.get('accountPasswordConfirm');
var challenge = this.get('accountChallenge');
return Discourse.User.createAccount(name, email, password, username, passwordConfirm, challenge).then(function(result) {
if (result.success) {
createAccountController.flash(result.message);
createAccountController.set('complete', true);
} else {
createAccountController.flash(result.message || Em.String.i18n('create_account.failed'), 'error');
createAccountController.set('formSubmitted', false);
}
if (result.active) {
return window.location.reload();
}
}, function() {
createAccountController.set('formSubmitted', false);
return createAccountController.flash(Em.String.i18n('create_account.failed'), 'error');
});
}
});

View file

@ -0,0 +1,194 @@
/**
Modal for editing / creating a category
@class EditCategoryController
@extends Discourse.ObjectController
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.EditCategoryController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
generalSelected: Ember.computed.equal('selectedTab', 'general'),
securitySelected: Ember.computed.equal('selectedTab', 'security'),
settingsSelected: Ember.computed.equal('selectedTab', 'settings'),
foregroundColors: ['FFFFFF', '000000'],
descriptionChanged: function() {
if (this.present('description')) {
this.set('controllers.modal.modalClass', 'edit-category-modal full');
} else {
this.set('controllers.modal.modalClass', 'edit-category-modal small');
}
}.observes('description'),
title: function() {
if (this.get('id')) return Em.String.i18n("category.edit_long");
if (this.get('isUncategorized')) return Em.String.i18n("category.edit_uncategorized");
return Em.String.i18n("category.create");
}.property('id'),
titleChanged: function() {
this.set('controllers.modal.title', this.get('title'));
}.observes('title'),
selectGeneral: function() {
this.set('selectedTab', 'general');
},
selectSecurity: function() {
this.set('selectedTab', 'security');
},
selectSettings: function() {
this.set('selectedTab', 'settings');
},
disabled: function() {
if (this.get('saving') || this.get('deleting')) return true;
if (!this.get('name')) return true;
if (!this.get('color')) return true;
return false;
}.property('name', 'color', 'deleting'),
deleteVisible: function() {
return (this.get('id') && this.get('topic_count') === 0);
}.property('id', 'topic_count'),
deleteDisabled: function() {
return (this.get('deleting') || this.get('saving') || false);
}.property('disabled', 'saving', 'deleting'),
colorStyle: function() {
return "background-color: #" + (this.get('color')) + "; color: #" + (this.get('text_color')) + ";";
}.property('color', 'text_color'),
// background colors are available as a pipe-separated string
backgroundColors: function() {
var categories = Discourse.Category.list();
return Discourse.SiteSettings.category_colors.split("|").map(function(i) { return i.toUpperCase(); }).concat(
categories.map(function(c) { return c.color.toUpperCase(); }) ).uniq();
}.property('Discourse.SiteSettings.category_colors'),
usedBackgroundColors: function() {
var categories = Discourse.Category.list();
var currentCat = this.get('model');
return categories.map(function(c) {
// If editing a category, don't include its color:
return (currentCat.get('id') && currentCat.get('color').toUpperCase() === c.color.toUpperCase()) ? null : c.color.toUpperCase();
}, this).compact();
}.property('id', 'color'),
categoryName: function() {
var name = this.get('name') || "";
return name.trim().length > 0 ? name : Em.String.i18n("preview");
}.property('name'),
buttonTitle: function() {
if (this.get('saving')) return Em.String.i18n("saving");
if (this.get('isUncategorized')) return Em.String.i18n("save");
return (this.get('id') ? Em.String.i18n("category.save") : Em.String.i18n("category.create"));
}.property('saving', 'id'),
deleteButtonTitle: function() {
return Em.String.i18n('category.delete');
}.property(),
didInsertElement: function() {
this._super();
if (this.get('id')) {
this.set('loading', true);
var categoryController = this;
// We need the topic_count to be correct, so get the most up-to-date info about this category from the server.
Discourse.Category.findBySlugOrId( this.get('slug') || this.get('id') ).then( function(cat) {
categoryController.set('category', cat);
Discourse.Site.instance().updateCategory(cat);
categoryController.set('id', categoryController.get('slug'));
categoryController.set('loading', false);
});
} else if( this.get('isUncategorized') ) {
this.set('category', Discourse.Category.uncategorizedInstance());
} else {
this.set('category', Discourse.Category.create({ color: 'AB9364', text_color: 'FFFFFF', hotness: 5 }));
}
},
showCategoryTopic: function() {
$('#discourse-modal').modal('hide');
Discourse.URL.routeTo(this.get('topic_url'));
return false;
},
addGroup: function(){
this.get('model').addGroup(this.get("selectedGroup"));
},
removeGroup: function(group){
// OBVIOUS, Ember treats this as Ember.String, we need a real string here
group = group + "";
this.get('model').removeGroup(group);
},
saveCategory: function() {
var categoryController = this;
this.set('saving', true);
if( this.get('isUncategorized') ) {
$.when(
Discourse.SiteSetting.update('uncategorized_color', this.get('color')),
Discourse.SiteSetting.update('uncategorized_text_color', this.get('text_color')),
Discourse.SiteSetting.update('uncategorized_name', this.get('name'))
).then(function(result) {
// success
$('#discourse-modal').modal('hide');
// We can't redirect to the uncategorized category on save because the slug
// might have changed.
Discourse.URL.redirectTo("/categories");
}, function(errors) {
// errors
if(errors.length === 0) errors.push(Em.String.i18n("category.save_error"));
categoryController.displayErrors(errors);
categoryController.set('saving', false);
});
} else {
this.get('model').save().then(function(result) {
// success
$('#discourse-modal').modal('hide');
Discourse.URL.redirectTo("/category/" + Discourse.Category.slugFor(result.category));
}, function(errors) {
// errors
if(errors.length === 0) errors.push(Em.String.i18n("category.creation_error"));
categoryController.displayErrors(errors);
categoryController.set('saving', false);
});
}
},
deleteCategory: function() {
var categoryController = this;
this.set('deleting', true);
$('#discourse-modal').modal('hide');
bootbox.confirm(Em.String.i18n("category.delete_confirm"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) {
if (result) {
categoryController.get('category').destroy().then(function(){
// success
Discourse.URL.redirectTo("/categories");
}, function(jqXHR){
// error
$('#discourse-modal').modal('show');
categoryController.displayErrors([Em.String.i18n("category.delete_error")]);
categoryController.set('deleting', false);
});
} else {
$('#discourse-modal').modal('show');
categoryController.set('deleting', false);
}
});
}
});

View file

@ -0,0 +1,45 @@
/**
Modal related to auto closing of topics
@class EditTopicAutoCloseController
@extends Discourse.ObjectController
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.EditTopicAutoCloseController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
setDays: function() {
if( this.get('auto_close_at') ) {
var closeTime = Date.create( this.get('auto_close_at') );
if (closeTime.isFuture()) {
this.set('auto_close_days', closeTime.daysSince());
}
} else {
this.set('auto_close_days', "");
}
}.observes('auto_close_at'),
saveAutoClose: function() {
this.setAutoClose( parseFloat(this.get('auto_close_days')) );
},
removeAutoClose: function() {
this.setAutoClose(null);
},
setAutoClose: function(days) {
var editTopicAutoCloseController = this;
Discourse.ajax({
url: "/t/" + this.get('id') + "/autoclose",
type: 'PUT',
dataType: 'json',
data: { auto_close_days: days > 0 ? days : null }
}).then(function(){
editTopicAutoCloseController.set('auto_close_at', Date.create(days + ' days from now').toJSON());
}, function (error) {
bootbox.alert(Em.String.i18n('generic_error'));
});
}
});

View file

@ -0,0 +1,78 @@
/**
This controller supports actions related to flagging
@class FlagController
@extends Discourse.ObjectController
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
// trick to bind user / post to flag
boundFlags: function() {
var _this = this;
var original = this.get('flagsAvailable');
if(original){
return $.map(original, function(v){
var b = Discourse.BoundPostActionType.create(v);
b.set('post', _this.get('model'));
return b;
});
}
}.property('flagsAvailable.@each'),
changePostActionType: function(action) {
if (this.get('postActionTypeId') === action.id) return false;
this.get('boundFlags').setEach('selected', false);
action.set('selected', true);
this.set('postActionTypeId', action.id);
this.set('isCustomFlag', action.is_custom_flag);
this.set('selected', action);
return false;
},
showSubmit: function() {
if (this.get('postActionTypeId')) {
if (this.get('isCustomFlag')) {
var m = this.get('selected.message');
return m && m.length >= 10 && m.length <= 500;
} else {
return true;
}
}
return false;
}.property('isCustomFlag', 'selected.customMessageLength', 'postActionTypeId'),
submitText: function(){
var action = this.get('selected');
if (this.get('selected.is_custom_flag')) {
return Em.String.i18n("flagging.notify_action");
} else {
return Em.String.i18n("flagging.action");
}
}.property('selected'),
createFlag: function() {
var _this = this;
var action = this.get('selected');
var postAction = this.get('actionByName.' + (action.get('name_key')));
var actionType = Discourse.Site.instance().postActionTypeById(this.get('postActionTypeId'));
if (postAction) {
postAction.act({
message: action.get('message')
}).then(function() {
return $('#discourse-modal').modal('hide');
}, function(errors) {
return _this.displayErrors(errors);
});
}
return false;
}
});

View file

@ -0,0 +1,29 @@
/**
The modal for when the user has forgotten their password
@class ForgotPasswordController
@extends Discourse.Controller
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.ForgotPasswordController = Discourse.Controller.extend(Discourse.ModalFunctionality, {
// You need a value in the field to submit it.
submitDisabled: function() {
return this.blank('accountEmailOrUsername');
}.property('accountEmailOrUsername'),
submit: function() {
Discourse.ajax("/session/forgot_password", {
data: { login: this.get('accountEmailOrUsername') },
type: 'POST'
});
// don't tell people what happened, this keeps it more secure (ensure same on server)
this.flash(Em.String.i18n('forgot_password.complete'));
return false;
}
});

View file

@ -0,0 +1,85 @@
/*jshint newcap:false*/
/*global diff_match_patch:true assetPath:true*/
/**
This controller handles displaying of history
@class HistoryController
@extends Discourse.ObjectController
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.HistoryController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
diffLibraryLoaded: false,
diff: null,
init: function(){
this._super();
var historyController = this;
$LAB.script(assetPath('defer/google_diff_match_patch')).wait(function(){
historyController.set('diffLibraryLoaded', true);
});
},
loadSide: function(side) {
if (this.get("version" + side)) {
var orig = this.get('model');
var version = this.get("version" + side + ".number");
if (version === orig.get('version')) {
this.set("post" + side, orig);
} else {
var historyController = this;
Discourse.Post.loadVersion(orig.get('id'), version).then(function(post) {
historyController.set("post" + side, post);
});
}
}
},
changedLeftVersion: function() {
this.loadSide("Left");
}.observes('versionLeft'),
changedRightVersion: function() {
this.loadSide("Right");
}.observes('versionRight'),
loadedPosts: function() {
if (this.get('diffLibraryLoaded') && this.get('postLeft') && this.get('postRight')) {
var dmp = new diff_match_patch(),
before = this.get("postLeft.cooked"),
after = this.get("postRight.cooked"),
diff = dmp.diff_main(before, after);
dmp.diff_cleanupSemantic(diff);
this.set('diff', dmp.diff_prettyHtml(diff));
}
}.observes('diffLibraryLoaded', 'postLeft', 'postRight'),
refresh: function() {
this.setProperties({
loading: true,
postLeft: null,
postRight: null
});
var historyController = this;
this.get('model').loadVersions().then(function(result) {
result.each(function(item) {
item.description = "v" + item.number + " - " + Date.create(item.created_at).relative() + " - " +
Em.String.i18n("changed_by", { author: item.display_username });
});
console.log('wat');
historyController.setProperties({
loading: false,
versionLeft: result.first(),
versionRight: result.last(),
versions: result
});
});
}
});

View file

@ -0,0 +1,22 @@
/**
The modal for inviting a user to a topic
@class ImageSelectorController
@extends Discourse.Controller
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.ImageSelectorController = Discourse.Controller.extend(Discourse.ModalFunctionality, {
selectLocal: function() {
this.set('localSelected', true);
},
selectRemote: function() {
this.set('localSelected', false);
},
remoteSelected: Em.computed.not('localSelected')
});

View file

@ -0,0 +1,44 @@
/**
The modal for inviting a user to a topic
@class InviteController
@extends Discourse.Controller
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.InviteController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
disabled: function() {
if (this.get('saving')) return true;
if (this.blank('email')) return true;
if (!Discourse.Utilities.emailValid(this.get('email'))) return true;
return false;
}.property('email', 'saving'),
buttonTitle: function() {
if (this.get('saving')) return Em.String.i18n('topic.inviting');
return Em.String.i18n('topic.invite_reply.action');
}.property('saving'),
successMessage: function() {
return Em.String.i18n('topic.invite_reply.success', { email: this.get('email') });
}.property('email'),
createInvite: function() {
var inviteController = this;
this.set('saving', true);
this.set('error', false);
this.get('model').inviteUser(this.get('email')).then(function() {
// Success
inviteController.set('saving', false);
return inviteController.set('finished', true);
}, function() {
// Failure
inviteController.set('error', true);
return inviteController.set('saving', false);
});
return false;
}
});

View file

@ -0,0 +1,39 @@
/**
The modal for inviting a user to a private topic
@class InvitePrivateController
@extends Discourse.Controller
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
disabled: function() {
if (this.get('saving')) return true;
return this.blank('emailOrUsername');
}.property('emailOrUsername', 'saving'),
buttonTitle: function() {
if (this.get('saving')) return Em.String.i18n('topic.inviting');
return Em.String.i18n('topic.invite_private.action');
}.property('saving'),
invite: function() {
var invitePrivateController = this;
this.set('saving', true);
this.set('error', false);
// Invite the user to the private message
this.get('content').inviteUser(this.get('emailOrUsername')).then(function() {
// Success
invitePrivateController.set('saving', false);
invitePrivateController.set('finished', true);
}, function() {
// Failure
invitePrivateController.set('error', true);
invitePrivateController.set('saving', false);
});
return false;
}
});

View file

@ -24,11 +24,6 @@ Discourse.ListCategoriesController = Discourse.ObjectController.extend({
});
}.property('categories.@each'),
editCategory: function(category) {
this.get('controllers.modal').show(Discourse.EditCategoryView.create({ category: category }));
return false;
},
canEdit: function() {
var u = Discourse.User.current();
return u && u.admin;

View file

@ -101,11 +101,6 @@ Discourse.ListController = Discourse.Controller.extend({
});
},
createCategory: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.EditCategoryView.create()) : void 0;
},
canEditCategory: function() {
if( this.present('category') ) {
var u = Discourse.User.current();
@ -113,12 +108,7 @@ Discourse.ListController = Discourse.Controller.extend({
} else {
return false;
}
}.property('category'),
editCategory: function() {
this.get('controllers.modal').show(Discourse.EditCategoryView.create({ category: this.get('category') }));
return false;
}
}.property('category')
});

View file

@ -41,14 +41,6 @@ Discourse.ListTopicsController = Discourse.ObjectController.extend({
this.toggleProperty('rankDetailsVisible');
},
// Show rank details
showRankDetails: function(topic) {
var modalController = this.get('controllers.modal');
if (modalController) {
modalController.show(Discourse.TopicRankDetailsView.create({ topic: topic }));
}
},
createTopic: function() {
this.get('controllers.list').createTopic();
},

View file

@ -0,0 +1,156 @@
/**
This controller supports actions related to flagging
@class LoginController
@extends Discourse.Controller
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.LoginController = Discourse.Controller.extend(Discourse.ModalFunctionality, {
needs: ['modal', 'createAccount'],
authenticate: null,
loggingIn: false,
site: function() {
return Discourse.Site.instance();
}.property(),
/**
Determines whether at least one login button is enabled
**/
hasAtLeastOneLoginButton: function() {
return Discourse.SiteSettings.enable_google_logins ||
Discourse.SiteSettings.enable_facebook_logins ||
Discourse.SiteSettings.enable_cas_logins ||
Discourse.SiteSettings.enable_twitter_logins ||
Discourse.SiteSettings.enable_yahoo_logins ||
Discourse.SiteSettings.enable_github_logins ||
Discourse.SiteSettings.enable_persona_logins;
}.property(),
loginButtonText: function() {
return this.get('loggingIn') ? Em.String.i18n('login.logging_in') : Em.String.i18n('login.title');
}.property('loggingIn'),
loginDisabled: function() {
return this.get('loggingIn') || this.blank('loginName') || this.blank('loginPassword');
}.property('loginName', 'loginPassword', 'loggingIn'),
login: function() {
this.set('loggingIn', true);
var loginController = this;
Discourse.ajax("/session", {
data: { login: this.get('loginName'), password: this.get('loginPassword') },
type: 'POST'
}).then(function (result) {
// Successful login
if (result.error) {
loginController.set('loggingIn', false);
if( result.reason === 'not_activated' ) {
loginController.send('showNotActivated', {
username: loginController.get('loginName'),
sentTo: result.sent_to_email,
currentEmail: result.current_email
});
}
loginController.flash(result.error, 'error');
} else {
// Trigger the browser's password manager using the hidden static login form:
var $hidden_login_form = $('#hidden-login-form');
$hidden_login_form.find('input[name=username]').val(loginController.get('loginName'));
$hidden_login_form.find('input[name=password]').val(loginController.get('loginPassword'));
$hidden_login_form.find('input[name=redirect]').val(window.location.href);
$hidden_login_form.find('input[name=authenticity_token]').val($('meta[name=csrf-token]').attr('content'));
$hidden_login_form.submit();
}
}, function(result) {
// Failed to login
loginController.flash(Em.String.i18n('login.error'), 'error');
loginController.set('loggingIn', false);
})
return false;
},
authMessage: (function() {
if (this.blank('authenticate')) return "";
return Em.String.i18n("login." + (this.get('authenticate')) + ".message");
}).property('authenticate'),
twitterLogin: function() {
this.set('authenticate', 'twitter');
var left = this.get('lastX') - 400;
var top = this.get('lastY') - 200;
return window.open(Discourse.getURL("/auth/twitter"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
},
facebookLogin: function() {
this.set('authenticate', 'facebook');
var left = this.get('lastX') - 400;
var top = this.get('lastY') - 200;
return window.open(Discourse.getURL("/auth/facebook"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
},
casLogin: function() {
var left, top;
this.set('authenticate', 'cas');
left = this.get('lastX') - 400;
top = this.get('lastY') - 200;
return window.open("/auth/cas", "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
},
openidLogin: function(provider) {
var left = this.get('lastX') - 400;
var top = this.get('lastY') - 200;
if (provider === "yahoo") {
this.set("authenticate", 'yahoo');
return window.open(Discourse.getURL("/auth/yahoo"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
} else {
window.open(Discourse.getURL("/auth/google"), "_blank", "menubar=no,status=no,height=500,width=850,left=" + left + ",top=" + top);
return this.set("authenticate", 'google');
}
},
githubLogin: function() {
this.set('authenticate', 'github');
var left = this.get('lastX') - 400;
var top = this.get('lastY') - 200;
return window.open(Discourse.getURL("/auth/github"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
},
personaLogin: function() {
navigator.id.request();
},
authenticationComplete: function(options) {
if (options.awaiting_approval) {
this.flash(Em.String.i18n('login.awaiting_approval'), 'success');
this.set('authenticate', null);
return;
}
if (options.awaiting_activation) {
this.flash(Em.String.i18n('login.awaiting_confirmation'), 'success');
this.set('authenticate', null);
return;
}
// Reload the page if we're authenticated
if (options.authenticated) {
window.location.reload();
return;
}
var createAccountController = this.get('controllers.createAccount');
createAccountController.setProperties({
accountEmail: options.email,
accountUsername: options.username,
accountName: options.name,
authOptions: Em.Object.create(options)
})
this.send('showCreateAccount');
}
});

View file

@ -0,0 +1,56 @@
/**
Modal related to auto closing of topics
@class MergeTopicController
@extends Discourse.ObjectController
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.MergeTopicController = Discourse.ObjectController.extend(Discourse.SelectedPostsCount, Discourse.ModalFunctionality, {
needs: ['topic'],
topicController: Em.computed.alias('controllers.topic'),
selectedPosts: Em.computed.alias('topicController.selectedPosts'),
allPostsSelected: Em.computed.alias('topicController.allPostsSelected'),
buttonDisabled: function() {
if (this.get('saving')) return true;
return this.blank('selectedTopicId');
}.property('selectedTopicId', 'saving'),
buttonTitle: function() {
if (this.get('saving')) return Em.String.i18n('saving');
return Em.String.i18n('topic.merge_topic.title');
}.property('saving'),
movePostsToExistingTopic: function() {
this.set('saving', true);
var moveSelectedView = this;
var promise = null;
if (this.get('allPostsSelected')) {
promise = Discourse.Topic.mergeTopic(this.get('id'), this.get('selectedTopicId'));
} else {
var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); });
promise = Discourse.Topic.movePosts(this.get('id'), {
destination_topic_id: this.get('selectedTopicId'),
post_ids: postIds
});
}
promise.then(function(result) {
// Posts moved
$('#discourse-modal').modal('hide');
moveSelectedView.get('topicController').toggleMultiSelect();
Em.run.next(function() { Discourse.URL.routeTo(result.url); });
}, function() {
// Error moving posts
moveSelectedView.flash(Em.String.i18n('topic.merge_topic.error'));
moveSelectedView.set('saving', false);
});
return false;
}
});

View file

@ -7,9 +7,7 @@
@module Discourse
**/
Discourse.ModalController = Discourse.Controller.extend({
show: function(view) {
this.set('currentView', view);
}
});

View file

@ -0,0 +1,18 @@
/**
Modal displayed to a user when they are not active yet.
@class NotActivatedController
@extends Discourse.Controller
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.NotActivatedController = Discourse.Controller.extend(Discourse.ModalFunctionality, {
emailSent: false,
sendActivationEmail: function() {
Discourse.ajax('/users/' + this.get('username') + '/send_activation_email');
this.set('emailSent', true);
}
});

View file

@ -0,0 +1,49 @@
/**
Modal related to auto closing of topics
@class SplitTopicController
@extends Discourse.ObjectController
@namespace Discourse
@uses Discourse.ModalFunctionality
@module Discourse
**/
Discourse.SplitTopicController = Discourse.ObjectController.extend(Discourse.SelectedPostsCount, Discourse.ModalFunctionality, {
needs: ['topic'],
topicController: Em.computed.alias('controllers.topic'),
selectedPosts: Em.computed.alias('topicController.selectedPosts'),
saving: false,
buttonDisabled: function() {
if (this.get('saving')) return true;
return this.blank('topicName');
}.property('saving', 'topicName'),
buttonTitle: function() {
if (this.get('saving')) return Em.String.i18n('saving');
return Em.String.i18n('topic.split_topic.action');
}.property('saving'),
movePostsToNewTopic: function() {
this.set('saving', true);
var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); });
var moveSelectedView = this;
Discourse.Topic.movePosts(this.get('id'), {
title: this.get('topicName'),
post_ids: postIds
}).then(function(result) {
// Posts moved
$('#discourse-modal').modal('hide');
moveSelectedView.get('topicController').toggleMultiSelect();
Em.run.next(function() { Discourse.URL.routeTo(result.url); });
}, function() {
// Error moving posts
moveSelectedView.flash(Em.String.i18n('topic.split_topic.error'));
moveSelectedView.set('saving', false);
});
return false;
}
});

View file

@ -16,15 +16,6 @@ Discourse.TopicAdminMenuController = Discourse.ObjectController.extend({
hide: function() {
this.set('visible', false);
},
autoClose: function() {
var modalController = this.get('controllers.modal');
if (modalController) {
var v = Discourse.EditTopicAutoCloseView.create();
v.set('topic', this.get('content'));
modalController.show(v);
}
}
});

View file

@ -107,29 +107,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
this.toggleProperty('summaryCollapsed');
},
splitTopic: function() {
var modalController = this.get('controllers.modal');
if (!modalController) return;
modalController.show(Discourse.SplitTopicView.create({
topicController: this,
topic: this.get('content'),
selectedPosts: this.get('selectedPosts')
}));
},
mergeTopic: function() {
var modalController = this.get('controllers.modal');
if (!modalController) return;
modalController.show(Discourse.MergeTopicView.create({
topicController: this,
topic: this.get('content'),
allPostsSelected: this.get('allPostsSelected'),
selectedPosts: this.get('selectedPosts')
}));
},
deleteSelected: function() {
var topicController = this;
bootbox.confirm(Em.String.i18n("post.delete.confirm", { count: this.get('selectedPostsCount')}), function(result) {
@ -432,47 +409,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
actionType.loadUsers();
},
showPrivateInviteModal: function() {
var modal = Discourse.InvitePrivateModalView.create({
topic: this.get('content')
});
var modalController = this.get('controllers.modal');
if (modalController) {
modalController.show(modal);
}
},
showInviteModal: function() {
var modalController = this.get('controllers.modal');
if (modalController) {
modalController.show(Discourse.InviteModalView.create({
topic: this.get('content')
}));
}
},
// Clicked the flag button
showFlags: function(post) {
var modalController = this.get('controllers.modal');
if (modalController) {
modalController.show(Discourse.FlagView.create({
post: post,
controller: this
}));
}
},
showHistory: function(post) {
var modalController = this.get('controllers.modal');
if (modalController) {
modalController.show(Discourse.HistoryView.create({
originalPost: post
}));
}
},
recoverPost: function(post) {
post.set('deleted_at', null);
post.recover();

View file

@ -0,0 +1,28 @@
/**
This mixin provides functionality to modal controllers
@class Discourse.ModalFunctionality
@extends Ember.Mixin
@namespace Discourse
@module Discourse
**/
Discourse.ModalFunctionality = Em.Mixin.create({
needs: ['modal'],
/**
Flash a message at the top of the modal
@method blank
@param {String} name the name of the property we want to check
@return {Boolean}
**/
flash: function(message, messageClass) {
this.set('flashMessage', Em.Object.create({
message: message,
messageClass: messageClass
}));
}
});

View file

@ -8,10 +8,10 @@
**/
Discourse.Archetype = Discourse.Model.extend({
hasOptions: (function() {
hasOptions: function() {
if (!this.get('options')) return false;
return this.get('options').length > 0;
}).property('options.@each'),
}.property('options.@each'),
isDefault: function() {
return this.get('id') === Discourse.Site.instance().get('default_archetype');

View file

@ -0,0 +1,47 @@
/**
Application route for Discourse
@class ApplicationRoute
@extends Ember.Route
@namespace Discourse
@module Discourse
**/
Discourse.ApplicationRoute = Em.Route.extend({
events: {
showLogin: function() {
Discourse.Route.showModal(this, 'login');
},
showCreateAccount: function() {
Discourse.Route.showModal(this, 'createAccount');
},
showForgotPassword: function() {
Discourse.Route.showModal(this, 'forgotPassword');
},
showNotActivated: function(props) {
Discourse.Route.showModal(this, 'notActivated');
this.controllerFor('notActivated').setProperties(props);
},
showImageSelector: function(composerView) {
Discourse.Route.showModal(this, 'imageSelector');
this.controllerFor('imageSelector').setProperties({
localSelected: true,
composerView: composerView
});
},
editCategory: function(category) {
var router = this;
Discourse.Category.findBySlugOrId(category.get('slug')).then(function (c) {
Discourse.Route.showModal(router, 'editCategory', c);
router.controllerFor('editCategory').set('selectedTab', 'general');
})
}
}
});

View file

@ -27,6 +27,7 @@ Discourse.Route = Em.Route.extend({
var hideDropDownFunction = $('html').data('hide-dropdown');
if (hideDropDownFunction) return hideDropDownFunction();
}
});
@ -38,6 +39,23 @@ Discourse.Route.reopenClass({
if (oldBuilder) oldBuilder.call(this);
return builder.call(this);
};
},
/**
Shows a modal
@method showModal
**/
showModal: function(router, name, model) {
router.controllerFor('modal').set('modalClass', null);
router.render(name, {into: 'modal', outlet: 'modalBody'});
var controller = router.controllerFor(name);
if (controller) {
if (model) {
controller.set('model', model);
}
controller.set('flashMessage', null);
}
}
});

View file

@ -8,6 +8,15 @@
**/
Discourse.ListCategoriesRoute = Discourse.Route.extend({
events: {
createCategory: function() {
Discourse.Route.showModal(this, 'editCategory', Discourse.Category.create());
this.controllerFor('editCategory').set('selectedTab', 'general');
}
},
model: function() {
var listTopicsController = this.controllerFor('listTopics');
if (listTopicsController) listTopicsController.set('content', null);

View file

@ -8,6 +8,57 @@
**/
Discourse.TopicRoute = Discourse.Route.extend({
events: {
// Modals that can pop up within a topic
showFlags: function(post) {
Discourse.Route.showModal(this, 'flag', post);
this.controllerFor('flag').setProperties({
postActionTypeId: null
});
},
showAutoClose: function() {
Discourse.Route.showModal(this, 'editTopicAutoClose', this.modelFor('topic'));
this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal');
},
showInvite: function() {
Discourse.Route.showModal(this, 'invite', this.modelFor('topic'));
this.controllerFor('invite').setProperties({
email: null,
error: false,
saving: false,
finished: false
});
},
showPrivateInvite: function() {
Discourse.Route.showModal(this, 'invitePrivate', this.modelFor('topic'))
this.controllerFor('invitePrivate').setProperties({
email: null,
error: false,
saving: false,
finished: false
});
},
showHistory: function(post) {
Discourse.Route.showModal(this, 'history', post);
this.controllerFor('history').refresh();
this.controllerFor('modal').set('modalClass', 'history-modal')
},
mergeTopic: function() {
Discourse.Route.showModal(this, 'mergeTopic', this.modelFor('topic'));
},
splitTopic: function() {
Discourse.Route.showModal(this, 'splitTopic', this.modelFor('topic'));
}
},
model: function(params) {
var currentModel, _ref;
if (currentModel = (_ref = this.controllerFor('topic')) ? _ref.get('content') : void 0) {

View file

@ -20,7 +20,7 @@
<a href='#' {{action deleteCategory target="view"}} class='btn btn-small'>{{i18n category.delete}}</a>
{{/if}}
{{#if view.can_edit}}
<a href='#' {{action editCategory target="view"}} class='btn btn-small'>{{i18n category.edit}}</a>
<a href='#' {{action editCategory view}} class='btn btn-small'>{{i18n category.edit}}</a>
{{/if}}
<a href="/category/{{unbound view.slug}}" class='btn btn-small'>{{i18n category.view}}</a>
</footer>

View file

@ -1,36 +0,0 @@
<div class="modal-body flag-modal">
{{#if view.post.flagsAvailable}}
<form>
{{#each view.boundFlags}}
<div class='controls'>
<label class='radio'>
<input type='radio' id="radio_{{unbound name_key}}" {{action changePostActionType this target="view"}} name='post_action_type_index'> <strong>{{formattedName}}</strong>
{{#if is_custom_flag}}
{{#unless selected}}
<div class='description'>{{{description}}}</div>
{{/unless}}
{{else}}
{{#if description}}
<div class='description'>{{{description}}}</div>
{{/if}}
{{/if}}
</label>
{{#if is_custom_flag}}
{{#if selected}}
{{textarea name="message" class="flag-message" placeholder=customPlaceholder value=message}}
<div {{bindAttr class="customMessageLengthClasses"}}>{{customMessageLength}}</div>
{{/if}}
{{/if}}
</div>
{{/each}}
</form>
{{else}}
{{i18n flagging.cant}}
{{/if}}
</div>
{{#if view.showSubmit}}
<div class="modal-footer">
<button class='btn btn-primary' {{action createFlag target="view"}}>{{view.submitText}}</button>
</div>
{{/if}}

View file

@ -1,13 +1,13 @@
<ul class="nav nav-pills image-options">
<li title="{{i18n image_selector.local_title}}" {{bindAttr class="view.localSelected:active"}}>
<a href="#" {{action selectLocal target="view"}}>{{i18n image_selector.from_my_computer}}</a>
<li title="{{i18n image_selector.local_title}}" {{bindAttr class="localSelected:active"}}>
<a href="#" {{action selectLocal}}>{{i18n image_selector.from_my_computer}}</a>
</li>
<li title="{{i18n image_selector.remote_title}}" {{bindAttr class="view.remoteSelected:active"}}>
<a href="#" {{action selectRemote target="view"}}>{{i18n image_selector.from_the_web}}</a>
<li title="{{i18n image_selector.remote_title}}" {{bindAttr class="remoteSelected:active"}}>
<a href="#" {{action selectRemote}}>{{i18n image_selector.from_the_web}}</a>
</li>
</ul>
{{#if view.localSelected}}
{{#if localSelected}}
<div class='modal-body'>
<form>
<input type="file" name="file" id="filename-input" value="browse" accept="image/*"><br>
@ -15,7 +15,7 @@
</form>
</div>
<div class='modal-footer'>
<button class='btn btn-large btn-primary' {{action "upload" target="view"}}>
<button class='btn btn-large btn-primary' {{action upload target="view"}}>
<span class='add-picture'><i class='icon-picture'></i><i class='icon-plus'></i></span>
{{i18n image_selector.upload}}
</button>
@ -28,7 +28,7 @@
</form>
</div>
<div class='modal-footer'>
<button class='btn btn-large btn-primary' {{action "add" target="view"}}>
<button class='btn btn-large btn-primary' {{action add target="view"}}>
<span class='add-picture'><i class='icon-picture'></i><i class='icon-plus'></i></span>
{{i18n image_selector.add_image}}
</button>

View file

@ -9,7 +9,7 @@
{{/if}}
{{#if canEditCategory}}
<button class='btn btn-default' {{action editCategory}}>{{i18n category.edit_long}}</button>
<button class='btn btn-default' {{action editCategory category}}>{{i18n category.edit_long}}</button>
{{/if}}
{{#if canCreateCategory}}

View file

@ -1,10 +1,10 @@
<div class="modal-body">
<form>
{{autoCloseForm autoCloseDays=view.auto_close_days}}
{{autoCloseForm autoCloseDays=auto_close_days}}
</form>
</div>
<div class="modal-footer">
<button class='btn btn-primary' {{action saveAutoClose target="view"}} data-dismiss="modal">{{i18n topic.auto_close_save}}</button>
<button class='btn btn-primary' {{action saveAutoClose}} data-dismiss="modal">{{i18n topic.auto_close_save}}</button>
<button class='btn' data-dismiss="modal">{{i18n topic.auto_close_cancel}}</button>
<button class='btn pull-right' {{action removeAutoClose target="view"}} data-dismiss="modal">{{i18n topic.auto_close_remove}}</button>
<button class='btn pull-right' {{action removeAutoClose}} data-dismiss="modal">{{i18n topic.auto_close_remove}}</button>
</div>

View file

@ -1,4 +1,4 @@
{{#unless view.complete}}
{{#unless complete}}
<div class="modal-body">
<div>
<form>
@ -6,7 +6,7 @@
<tr>
<td style="width:80px"><label for='new-account-name'>{{i18n user.name.title}}</label></td>
<td style="width:496px">
{{textField value=view.accountName id="new-account-name" autofocus="autofocus"}}
{{textField value=accountName id="new-account-name" autofocus="autofocus"}}
&nbsp;{{inputTip validation=nameValidation}}
</td>
</tr>
@ -18,8 +18,8 @@
<tr>
<td><label for='new-account-email'>{{i18n user.email.title}}</label></td>
<td>
{{input value=view.accountEmail id="new-account-email"}}
&nbsp;{{inputTip validation=view.emailValidation}}
{{input value=accountEmail id="new-account-email"}}
&nbsp;{{inputTip validation=emailValidation}}
</td>
</tr>
<tr>
@ -30,8 +30,8 @@
<tr>
<td><label for='new-account-username'>{{i18n user.username.title}}</label></td>
<td>
{{input value=view.accountUsername id="new-account-username" maxlength="15"}}
&nbsp;{{inputTip validation=view.usernameValidation}}
{{input value=accountUsername id="new-account-username" maxlength="15"}}
&nbsp;{{inputTip validation=usernameValidation}}
</td>
</tr>
<tr>
@ -39,12 +39,12 @@
<td><label>{{i18n user.username.instructions}}</label></td>
</tr>
{{#if view.passwordRequired}}
{{#if passwordRequired}}
<tr>
<td><label for='new-account-password'>{{i18n user.password.title}}</label></td>
<td>
{{input type="password" value=view.accountPassword id="new-account-password"}}
&nbsp;{{inputTip validation=view.passwordValidation}}
{{input type="password" value=accountPassword id="new-account-password"}}
&nbsp;{{inputTip validation=passwordValidation}}
</td>
</tr>
{{/if}}
@ -52,8 +52,8 @@
<tr class="password-confirmation">
<td><label for='new-account-password-confirmation'>{{i18n user.password_confirmation.title}}</label></td>
<td>
{{input type="password" value=view.accountPasswordConfirm id="new-account-confirmation"}}
{{input value=view.accountChallenge id="new-account-challenge"}}
{{input type="password" value=accountPasswordConfirm id="new-account-confirmation"}}
{{input value=accountChallenge id="new-account-challenge"}}
</td>
</tr>
@ -63,6 +63,6 @@
</div>
<div class="modal-footer">
<button class='btn btn-large btn-primary' {{bindAttr disabled="view.submitDisabled"}} {{action createAccount target="view"}}>{{i18n create_account.title}}</button>
<button class='btn btn-large btn-primary' {{bindAttr disabled="submitDisabled"}} {{action createAccount}}>{{i18n create_account.title}}</button>
</div>
{{/unless}}

View file

@ -1,22 +1,20 @@
{{#with view.category}}
<div {{bindAttr class="view.loading:invisible"}}>
<div {{bindAttr class="loading:invisible"}}>
<ul class="nav nav-pills">
<li {{bindAttr class="view.generalSelected:active"}}>
<a href="#" {{action selectGeneral target="view"}}>{{i18n category.general}}</a>
<li {{bindAttr class="generalSelected:active"}}>
<a href="#" {{action selectGeneral}}>{{i18n category.general}}</a>
</li>
{{#unless isUncategorized}}
<li {{bindAttr class="view.securitySelected:active"}}>
<a href="#" {{action selectSecurity target="view"}}>{{i18n category.security}}</a>
<li {{bindAttr class="securitySelected:active"}}>
<a href="#" {{action selectSecurity}}>{{i18n category.security}}</a>
</li>
<li {{bindAttr class="view.settingsSelected:active"}}>
<a href="#" {{action selectSettings target="view"}}>{{i18n category.settings}}</a>
<li {{bindAttr class="settingsSelected:active"}}>
<a href="#" {{action selectSettings}}>{{i18n category.settings}}</a>
</li>
{{/unless}}
</ul>
<div class="modal-body">
<div {{bindAttr class=":modal-tab :general-tab view.generalSelected::invisible"}}>
<div {{bindAttr class=":modal-tab :general-tab generalSelected::invisible"}}>
<form>
<section class='field'>
<label>{{i18n category.name}}</label>
@ -34,7 +32,7 @@
{{/if}}
{{#if topic_url}}
<br/>
<button class="btn btn-small" {{action showCategoryTopic target="view"}}>{{i18n category.change_in_category_topic}}</button>
<button class="btn btn-small" {{action showCategoryTopic}}>{{i18n category.change_in_category_topic}}</button>
{{/if}}
</section>
{{/unless}}
@ -42,25 +40,25 @@
<section class='field'>
<label>{{i18n category.badge_colors}}</label>
<div class="category-color-editor">
<span class='badge-category' {{bindAttr style="view.colorStyle"}}>{{view.categoryName}}</span>
<span class='badge-category' {{bindAttr style="colorStyle"}}>{{categoryName}}</span>
<div class='input-prepend input-append' style="margin-top: 10px;">
<span class='color-title'>{{i18n category.background_color}}:</span>
<span class='add-on'>#</span>{{textField value=color placeholderKey="category.color_placeholder" maxlength="6"}}
{{colorPicker colors=view.backgroundColors usedColors=view.usedBackgroundColors value=color}}
{{colorPicker colors=backgroundColors usedColors=usedBackgroundColors value=color}}
</div>
<div class='input-prepend input-append'>
<span class='color-title'>{{i18n category.foreground_color}}:</span>
<span class='add-on'>#</span>{{textField value=text_color placeholderKey="category.color_placeholder" maxlength="6"}}
{{colorPicker colors=view.foregroundColors value=text_color}}
{{colorPicker colors=foregroundColors value=text_color}}
</div>
</div>
</section>
</form>
</div>
{{#unless isUncategorized}}
<div {{bindAttr class=":modal-tab :options-tab view.securitySelected::invisible"}}>
<div {{bindAttr class=":modal-tab :options-tab securitySelected::invisible"}}>
<section class='field'>
<label>
{{input type="checkbox" checked=secure}}
@ -73,17 +71,17 @@
{{#each groups}}
<li class="badge-group">
{{this}}
<a {{action removeGroup this target="view"}}><i class="icon icon-remove-sign"></i></a>
<a {{action removeGroup this}}><i class="icon icon-remove-sign"></i></a>
</li>
{{/each}}
</ul>
{{view Ember.Select contentBinding="availableGroups" valueBinding="view.selectedGroup"}}
<button {{action addGroup target="view"}} class="btn btn-small">{{i18n category.add_group}}</button>
{{view Ember.Select contentBinding="availableGroups" valueBinding="selectedGroup"}}
<button {{action addGroup}} class="btn btn-small">{{i18n category.add_group}}</button>
</div>
{{/if}}
</section>
</div>
<div {{bindAttr class=":modal-tab :options-tab view.settingsSelected::invisible"}}>
<div {{bindAttr class=":modal-tab :options-tab settingsSelected::invisible"}}>
<section class='field'>
{{autoCloseForm autoCloseDays=auto_close_days labelKey="category.auto_close_label"}}
</section>
@ -96,11 +94,9 @@
{{/unless}}
</div>
<div class="modal-footer">
<button class='btn btn-primary' {{bindAttr disabled="view.disabled"}} {{action saveCategory target="view"}}>{{view.buttonTitle}}</button>
{{#if view.deleteVisible}}
<button class='btn btn-danger pull-right' {{bindAttr disabled="view.deleteDisabled"}} {{action deleteCategory target="view"}}><i class="icon icon-trash"></i>{{view.deleteButtonTitle}}</button>
<button class='btn btn-primary' {{bindAttr disabled="disabled"}} {{action saveCategory}}>{{buttonTitle}}</button>
{{#if deleteVisible}}
<button class='btn btn-danger pull-right' {{bindAttr disabled="deleteDisabled"}} {{action deleteCategory}}><i class="icon icon-trash"></i>{{deleteButtonTitle}}</button>
{{/if}}
</div>
</div>
{{/with}}

View file

@ -0,0 +1,36 @@
<div class="modal-body flag-modal">
{{#if flagsAvailable}}
<form>
{{#each boundFlags}}
<div class='controls'>
<label class='radio'>
<input type='radio' id="radio_{{unbound name_key}}" {{action changePostActionType this}} name='post_action_type_index'> <strong>{{formattedName}}</strong>
{{#if is_custom_flag}}
{{#unless selected}}
<div class='description'>{{{description}}}</div>
{{/unless}}
{{else}}
{{#if description}}
<div class='description'>{{{description}}}</div>
{{/if}}
{{/if}}
</label>
{{#if is_custom_flag}}
{{#if selected}}
{{textarea name="message" class="flag-message" placeholder=customPlaceholder value=message}}
<div {{bindAttr class="customMessageLengthClasses"}}>{{customMessageLength}}</div>
{{/if}}
{{/if}}
</div>
{{/each}}
</form>
{{else}}
{{i18n flagging.cant}}
{{/if}}
</div>
{{#if showSubmit}}
<div class="modal-footer">
<button class='btn btn-primary' {{action createFlag}}>{{submitText}}</button>
</div>
{{/if}}

View file

@ -1,9 +1,9 @@
<div class="modal-body">
<form>
<label for='username-or-email'>{{i18n forgot_password.invite}}</label>
{{textField value=view.accountEmailOrUsername placeholderKey="login.email_placeholder" id="username-or-email" autocorrect="off" autocapitalize="off"}}
{{textField value=accountEmailOrUsername placeholderKey="login.email_placeholder" id="username-or-email" autocorrect="off" autocapitalize="off"}}
</form>
</div>
<div class="modal-footer">
<button class='btn btn-large btn-primary' {{bindAttr disabled="view.submitDisabled"}} {{action submit target="view"}}>{{i18n forgot_password.reset}}</button>
<button class='btn btn-large btn-primary' {{bindAttr disabled="submitDisabled"}} {{action submit}}>{{i18n forgot_password.reset}}</button>
</div>

View file

@ -1,20 +1,20 @@
<div class="modal-body">
{{#if view.loading}}
{{#if loading}}
{{i18n loading}}
{{else}}
{{#if view.versions}}
{{#if versions}}
<div class='span8'>
{{view Ember.Select
contentBinding="view.versions"
contentBinding="versions"
optionLabelPath="content.description"
optionValuePath="content.number"
selectionBinding="view.versionLeft"}}
selectionBinding="versionLeft"}}
<div class='contents'>
{{#if view.postLeft}}
{{{view.postLeft.cooked}}}
{{#if postLeft}}
{{{postLeft.cooked}}}
{{else}}
<div class='history-loading'>{{i18n loading}}</div>
{{/if}}
@ -24,14 +24,14 @@
<div class='span8 offset1'>
{{view Ember.Select
contentBinding="view.versions"
contentBinding="versions"
optionLabelPath="content.description"
optionValuePath="content.number"
selectionBinding="view.versionRight"}}
selectionBinding="versionRight"}}
<div class='contents'>
{{#if view.diff}}
{{{view.diff}}}
{{#if diff}}
{{{diff}}}
{{else}}
<div class='history-loading'>{{i18n loading}}</div>
{{/if}}

View file

@ -0,0 +1,36 @@
<ul class="nav nav-pills image-options">
<li title="{{i18n image_selector.local_title}}" {{bindAttr class="localSelected:active"}}>
<a href="#" {{action selectLocal}}>{{i18n image_selector.from_my_computer}}</a>
</li>
<li title="{{i18n image_selector.remote_title}}" {{bindAttr class="remoteSelected:active"}}>
<a href="#" {{action selectRemote}}>{{i18n image_selector.from_the_web}}</a>
</li>
</ul>
{{#if localSelected}}
<div class='modal-body'>
<form>
<input type="file" name="file" id="filename-input" value="browse" accept="image/*"><br>
<span class='description'>{{i18n image_selector.local_tip}}</span> <br>
</form>
</div>
<div class='modal-footer'>
<button class='btn btn-large btn-primary' {{action upload target="view"}}>
<span class='add-picture'><i class='icon-picture'></i><i class='icon-plus'></i></span>
{{i18n image_selector.upload}}
</button>
</div>
{{else}}
<div class='modal-body'>
<form>
<input type="text" name="text" id="fileurl-input" autofocus><br>
<span class='description'>{{i18n image_selector.remote_tip}}</span> <br>
</form>
</div>
<div class='modal-footer'>
<button class='btn btn-large btn-primary' {{action add target="view"}}>
<span class='add-picture'><i class='icon-picture'></i><i class='icon-plus'></i></span>
{{i18n image_selector.add_image}}
</button>
</div>
{{/if}}

View file

@ -1,25 +1,25 @@
<div class="modal-body">
{{#if view.error}}
{{#if error}}
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
{{i18n topic.invite_reply.error}}
</div>
{{/if}}
{{#if view.finished}}
{{{view.successMessage}}}
{{#if finished}}
{{{successMessage}}}
{{else}}
<form>
<label>{{i18n topic.invite_reply.email}}</label>
{{textField value=view.email placeholderKey="topic.invite_reply.email_placeholder"}}
{{textField value=email placeholderKey="topic.invite_reply.email_placeholder"}}
</form>
{{/if}}
</div>
<div class="modal-footer">
{{#if view.finished}}
{{#if finished}}
<button class='btn btn-primary' data-dismiss="modal">{{i18n close}}</button>
{{else}}
<button class='btn btn-primary' {{bindAttr disabled="view.disabled"}} {{action createInvite target="view"}}>{{view.buttonTitle}}</button>
<button class='btn btn-primary' {{bindAttr disabled="disabled"}} {{action createInvite}}>{{buttonTitle}}</button>
{{/if}}
</div>

View file

@ -1,25 +1,25 @@
<div class="modal-body">
{{#if view.error}}
{{#if error}}
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
{{i18n topic.invite_private.error}}
</div>
{{/if}}
{{#if view.finished}}
{{#if finished}}
{{i18n topic.invite_private.success}}
{{else}}
<form>
<label>{{i18n topic.invite_private.email_or_username}}</label>
{{textField value=view.emailOrUsername placeholderKey="topic.invite_private.email_or_username_placeholder"}}
{{textField value=emailOrUsername placeholderKey="topic.invite_private.email_or_username_placeholder"}}
</form>
{{/if}}
</div>
<div class="modal-footer">
{{#if view.finished}}
{{#if finished}}
<button class='btn btn-primary' data-dismiss="modal">{{i18n close}}</button>
{{else}}
<button class='btn btn-primary' {{bindAttr disabled="view.disabled"}} {{action invite target="view"}}>{{view.buttonTitle}}</button>
<button class='btn btn-primary' {{bindAttr disabled="disabled"}} {{action invite}}>{{buttonTitle}}</button>
{{/if}}
</div>

View file

@ -1,31 +1,31 @@
<div class="modal-body">
{{#if view.hasAtLeastOneLoginButton}}
{{#if hasAtLeastOneLoginButton}}
<div id="login-buttons">
{{#if Discourse.SiteSettings.enable_google_logins}}
<button class="btn btn-social google" title="{{i18n login.google.title}}" {{action openidLogin "google" target="view"}}>{{i18n login.google.title}}</button>
<button class="btn btn-social google" title="{{i18n login.google.title}}" {{action openidLogin "google"}}>{{i18n login.google.title}}</button>
{{/if}}
{{#if Discourse.SiteSettings.enable_facebook_logins}}
<button class="btn btn-social facebook" title="{{i18n login.facebook.title}}" {{action "facebookLogin" target="view"}}>{{i18n login.facebook.title}}</button>
<button class="btn btn-social facebook" title="{{i18n login.facebook.title}}" {{action "facebookLogin"}}>{{i18n login.facebook.title}}</button>
{{/if}}
{{#if Discourse.SiteSettings.enable_cas_logins}}
<button class="btn btn-social cas" title="{{i18n login.cas.title}}" {{action "casLogin" target="view"}}>{{i18n login.cas.title}}</button>
<button class="btn btn-social cas" title="{{i18n login.cas.title}}" {{action "casLogin"}}>{{i18n login.cas.title}}</button>
{{/if}}
{{#if Discourse.SiteSettings.enable_twitter_logins}}
<button class="btn btn-social twitter" title="{{i18n login.twitter.title}}" {{action "twitterLogin" target="view"}}>{{i18n login.twitter.title}}</button>
<button class="btn btn-social twitter" title="{{i18n login.twitter.title}}" {{action "twitterLogin"}}>{{i18n login.twitter.title}}</button>
{{/if}}
{{#if Discourse.SiteSettings.enable_yahoo_logins}}
<button class="btn btn-social yahoo" title="{{i18n login.yahoo.title}}" {{action openidLogin "yahoo" target="view"}}>{{i18n login.yahoo.title}}</button>
<button class="btn btn-social yahoo" title="{{i18n login.yahoo.title}}" {{action openidLogin "yahoo"}}>{{i18n login.yahoo.title}}</button>
{{/if}}
{{#if Discourse.SiteSettings.enable_github_logins}}
<button class="btn btn-social github" title="{{i18n login.github.title}}" {{action "githubLogin" target="view"}}>{{i18n login.github.title}}</button>
<button class="btn btn-social github" title="{{i18n login.github.title}}" {{action "githubLogin"}}>{{i18n login.github.title}}</button>
{{/if}}
{{#if Discourse.SiteSettings.enable_persona_logins}}
<button class="btn btn-social persona" title="{{i18n login.persona.title}}" {{action "personaLogin" target="view"}}>{{i18n login.persona.title}}</button>
<button class="btn btn-social persona" title="{{i18n login.persona.title}}" {{action "personaLogin"}}>{{i18n login.persona.title}}</button>
{{/if}}
</div>
{{/if}}
{{#if Discourse.SiteSettings.enable_local_logins}}
{{#if view.hasAtLeastOneLoginButton}}
{{#if hasAtLeastOneLoginButton}}
<h3 style="text-align:center; margin-bottom:10px;">{{i18n login.or}}</h3>
{{/if}}
<form id='login-form'>
@ -36,31 +36,31 @@
<label for='login-account-name'>{{i18n login.username}}&nbsp;</label>
</td>
<td>
{{textField value=view.loginName placeholderKey="login.email_placeholder" id="login-account-name" autocorrect="off" autocapitalize="off" autofocus="autofocus"}}
{{textField value=loginName placeholderKey="login.email_placeholder" id="login-account-name" autocorrect="off" autocapitalize="off" autofocus="autofocus"}}
</td>
<tr>
<td>
<label for='login-account-password'>{{i18n login.password}}&nbsp;</label>
</td>
<td>
{{textField value=view.loginPassword type="password" id="login-account-password"}} &nbsp;
<a id="forgot-password-link" {{action forgotPassword target="view"}}>{{i18n forgot_password.action}}</a>
{{textField value=loginPassword type="password" id="login-account-password"}} &nbsp;
<a id="forgot-password-link" {{action showForgotPassword}}>{{i18n forgot_password.action}}</a>
</td>
</tr>
</table>
</div>
</form>
{{/if}}
{{view.authMessage}}
<div id='login-alert' {{bindAttr class="view.alertClass"}}>{{view.alert}}</div>
{{authMessage}}
<div id='login-alert' {{bindAttr class="alertClass"}}>{{alert}}</div>
</div>
<div class="modal-footer">
{{#if view.authenticate}}
{{#if authenticate}}
{{i18n login.authenticating}}
{{/if}}
{{#if Discourse.SiteSettings.enable_local_logins}}
<button class='btn btn-large btn-primary' {{bindAttr disabled="view.loginDisabled"}} {{action login target="view"}}><i class="icon-unlock"></i>&nbsp;{{view.loginButtonText}}</button>
<button class='btn btn-large btn-primary' {{bindAttr disabled="loginDisabled"}} {{action login}}><i class="icon-unlock"></i>&nbsp;{{loginButtonText}}</button>
&nbsp;
{{i18n create_account.invite}} <a id="new-account-link" {{action newAccount target="view"}}>{{i18n create_account.action}}</a>
{{i18n create_account.invite}} <a id="new-account-link" {{action showCreateAccount}}>{{i18n create_account.action}}</a>
{{/if}}
</div>

View file

@ -1,15 +1,15 @@
<div id='move-selected' class="modal-body">
{{#if view.error}}
{{#if error}}
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
</div>
{{/if}}
<p>{{{i18n topic.merge_topic.instructions count="view.selectedPostsCount"}}}</p>
<p>{{{i18n topic.merge_topic.instructions count="selectedPostsCount"}}}</p>
{{view Discourse.ChooseTopicView selectedTopicIdBinding="view.selectedTopicId"}}
{{chooseTopic selectedTopicId=selectedTopicId}}
</div>
<div class="modal-footer">
<button class='btn btn-primary' {{bindAttr disabled="view.buttonDisabled"}} {{action movePostsToExistingTopic target="view"}}>{{view.buttonTitle}}</button>
<button class='btn btn-primary' {{bindAttr disabled="buttonDisabled"}} {{action movePostsToExistingTopic}}>{{buttonTitle}}</button>
</div>

View file

@ -0,0 +1,16 @@
<div class="modal-header">
<a class="close" data-dismiss="modal"><i class='icon-remove icon'></i></a>
<h3>{{title}}</h3>
</div>
<div id='modal-alert'></div>
{{outlet modalBody}}
{{#each errors}}
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
{{this}}
</div>
{{/each}}

View file

@ -1,8 +0,0 @@
{{#if view.errors}}
{{#each view.errors}}
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
{{this}}
</div>
{{/each}}
{{/if}}

View file

@ -1,5 +0,0 @@
<div class="modal-header">
<a class="close" data-dismiss="modal"><i class='icon-remove icon'></i></a>
<h3>{{view.title}}</h3>
</div>
<div id='modal-alert'></div>

View file

@ -1,9 +1,9 @@
<div class="modal-body">
{{#if view.emailSent}}
{{{i18n login.sent_activation_email_again currentEmail="view.currentEmail"}}}
{{#if emailSent}}
{{{i18n login.sent_activation_email_again currentEmail="currentEmail"}}}
{{else}}
{{{i18n login.not_activated sentTo="view.sentTo"}}}
<a href="#" {{action "sendActivationEmail" target="view"}}>{{i18n login.resend_activation_email}}</a>
{{{i18n login.not_activated sentTo="sentTo"}}}
<a href="#" {{action "sendActivationEmail"}}>{{i18n login.resend_activation_email}}</a>
{{/if}}
</div>
<div class="modal-footer">

View file

@ -1,19 +1,19 @@
<div id='move-selected' class="modal-body">
{{#if view.error}}
{{#if error}}
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
</div>
{{/if}}
{{{i18n topic.split_topic.instructions count="view.selectedPostsCount"}}}
{{{i18n topic.split_topic.instructions count="selectedPostsCount"}}}
<form>
<label>{{i18n topic.split_topic.topic_name}}</label>
{{textField value=view.topicName placeholderKey="composer.title_placeholder"}}
{{textField value=topicName placeholderKey="composer.title_placeholder"}}
</form>
</div>
<div class="modal-footer">
<button class='btn btn-primary' {{bindAttr disabled="view.buttonDisabled"}} {{action movePostsToNewTopic target="view"}}>{{view.buttonTitle}}</button>
<button class='btn btn-primary' {{bindAttr disabled="buttonDisabled"}} {{action movePostsToNewTopic}}>{{buttonTitle}}</button>
</div>

View file

@ -1,46 +0,0 @@
{{#with view.topic.rank_details}}
<div class="modal-body">
<!-- Note this isn't translated because it's a debugging tool for a feature
that is not complete yet. We will probably rip this out altogether -->
<table class='table'>
<tr>
<td>hot topic type</td>
<td>
{{hot_topic_type}}
</td>
</tr>
<tr>
<td>random bias</td>
<td>{{float random_bias}}</td>
</tr>
<tr>
<td>random multiplier</td>
<td>{{float random_multiplier}}</td>
</tr>
<tr>
<td>days ago bias</td>
<td>{{float days_ago_bias}}</td>
</tr>
<tr>
<td>days ago multiplier</td>
<td>{{float days_ago_multiplier}}</td>
</tr>
<tr>
<td>ranking formula</td>
<td>
<p>= (random_bias * random_multiplier) +<br/>
(days_ago_bias * days_ago_multiplier)</p>
<p>= ({{float random_bias}} * {{float random_multiplier}}) + ({{float days_ago_bias}} * {{float days_ago_multiplier}})</p>
</td>
</tr>
<tr>
<td>ranking score</td>
<td><b>{{float ranking_score}}</b></td>
</tr>
</table>
</div>
{{/with}}

View file

@ -18,7 +18,7 @@
<button {{action toggleClosed}} class='btn btn-admin'><i class='icon-unlock'></i> {{i18n topic.actions.open}}</button>
{{else}}
<button {{action toggleClosed}} class='btn btn-admin'><i class='icon-lock'></i> {{i18n topic.actions.close}}</button>
<button {{action autoClose}} class='btn btn-admin'><i class='icon-time'></i> {{i18n topic.actions.auto_close}}</button>
<button {{action showAutoClose}} class='btn btn-admin'><i class='icon-time'></i> {{i18n topic.actions.auto_close}}</button>
{{/if}}
</li>

View file

@ -18,6 +18,6 @@
</div>
{{#if can_invite_to}}
<div class='controls'>
<button class='btn' {{action showPrivateInviteModal}}>{{i18n private_message_info.invite}}</button>
<button class='btn' {{action showPrivateInvite}}>{{i18n private_message_info.invite}}</button>
</div>
{{/if}}

View file

@ -40,7 +40,7 @@ Discourse.ChooseTopicView = Discourse.View.extend({
var topicId = Em.get(topic, 'id');
this.set('selectedTopicId', topicId);
Em.run.schedule('afterRender', function () {
Em.run.next(function () {
$('#choose-topic-' + topicId).prop('checked', 'true');
});
@ -50,3 +50,4 @@ Discourse.ChooseTopicView = Discourse.View.extend({
});
Discourse.View.registerHelper('chooseTopic', Discourse.ChooseTopicView);

View file

@ -197,10 +197,7 @@ Discourse.ComposerView = Discourse.View.extend({
$uploadTarget = $('#reply-control');
this.editor.hooks.insertImageDialog = function(callback) {
callback(null);
_this.get('controller.controllers.modal').show(Discourse.ImageSelectorView.create({
composer: _this,
uploadTarget: $uploadTarget
}));
_this.get('controller').send('showImageSelector', _this);
return true;
};
@ -342,7 +339,6 @@ Discourse.ComposerView = Discourse.View.extend({
Em.run.schedule('afterRender', function() {
Discourse.Utilities.setCaretPosition(ctrl, caretPosition + text.length);
});
},
// Uses javascript to get the image sizes from the preview, if present

View file

@ -19,6 +19,8 @@ Discourse.ColorPickerView = Ember.ContainerView.extend({
var _this = this;
var isUsed, usedColors = this.get('usedColors') || [];
if (!colors) return;
colors.each(function(color) {
isUsed = usedColors.indexOf(color.toUpperCase()) >= 0;
_this.addObject(Discourse.View.create({

View file

@ -9,280 +9,18 @@
Discourse.CreateAccountView = Discourse.ModalBodyView.extend({
templateName: 'modal/create_account',
title: Em.String.i18n('create_account.title'),
uniqueUsernameValidation: null,
globalNicknameExists: false,
complete: false,
accountPasswordConfirm: 0,
accountChallenge: 0,
formSubmitted: false,
submitDisabled: (function() {
if (this.get('formSubmitted')) return true;
if (this.get('nameValidation.failed')) return true;
if (this.get('emailValidation.failed')) return true;
if (this.get('usernameValidation.failed')) return true;
if (this.get('passwordValidation.failed')) return true;
return false;
}).property('nameValidation.failed', 'emailValidation.failed', 'usernameValidation.failed', 'passwordValidation.failed', 'formSubmitted'),
passwordRequired: (function() {
return this.blank('authOptions.auth_provider');
}).property('authOptions.auth_provider'),
// Validate the name
nameValidation: (function() {
// If blank, fail without a reason
if (this.blank('accountName')) return Discourse.InputValidation.create({ failed: true });
if (this.get('accountPasswordConfirm') === 0) {
this.fetchConfirmationValue();
}
// If too short
if (this.get('accountName').length < 3) {
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.name.too_short')
});
}
// Looks good!
return Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.name.ok')
});
}).property('accountName'),
// Check the email address
emailValidation: (function() {
// If blank, fail without a reason
var email;
if (this.blank('accountEmail')) {
return Discourse.InputValidation.create({
failed: true
});
}
email = this.get("accountEmail");
if ((this.get('authOptions.email') === email) && this.get('authOptions.email_valid')) {
return Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.email.authenticated', {
provider: this.get('authOptions.auth_provider')
})
});
}
if (Discourse.Utilities.emailValid(email)) {
return Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.email.ok')
});
}
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.email.invalid')
});
}).property('accountEmail'),
usernameMatch: (function() {
if (this.usernameNeedsToBeValidatedWithEmail()) {
if (this.get('emailValidation.failed')) {
if (this.shouldCheckUsernameMatch()) {
return this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.enter_email')
}));
} else {
return this.set('uniqueUsernameValidation', Discourse.InputValidation.create({ failed: true }));
}
} else if (this.shouldCheckUsernameMatch()) {
this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.checking')
}));
return this.checkUsernameAvailability();
}
}
}).observes('accountEmail'),
basicUsernameValidation: (function() {
this.set('uniqueUsernameValidation', null);
// If blank, fail without a reason
if (this.blank('accountUsername')) {
return Discourse.InputValidation.create({
failed: true
});
}
// If too short
if (this.get('accountUsername').length < 3) {
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.too_short')
});
}
// If too long
if (this.get('accountUsername').length > 15) {
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.too_long')
});
}
this.checkUsernameAvailability();
// Let's check it out asynchronously
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.checking')
});
}).property('accountUsername'),
shouldCheckUsernameMatch: function() {
return !this.blank('accountUsername') && this.get('accountUsername').length > 2;
},
checkUsernameAvailability: Discourse.debounce(function() {
var _this = this;
if (this.shouldCheckUsernameMatch()) {
return Discourse.User.checkUsername(this.get('accountUsername'), this.get('accountEmail')).then(function(result) {
_this.set('globalNicknameExists', false);
if (result.available) {
if (result.global_match) {
_this.set('globalNicknameExists', true);
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.username.global_match')
}));
} else {
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.username.available')
}));
}
} else {
if (result.suggestion) {
if (result.global_match !== void 0 && result.global_match === false) {
_this.set('globalNicknameExists', true);
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.global_mismatch', result)
}));
} else {
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.not_available', result)
}));
}
} else if (result.errors) {
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: result.errors.join(' ')
}));
} else {
_this.set('globalNicknameExists', true);
return _this.set('uniqueUsernameValidation', Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.username.enter_email')
}));
}
}
});
}
}, 500),
// Actually wait for the async name check before we're 100% sure we're good to go
usernameValidation: (function() {
var basicValidation, uniqueUsername;
basicValidation = this.get('basicUsernameValidation');
uniqueUsername = this.get('uniqueUsernameValidation');
if (uniqueUsername) {
return uniqueUsername;
}
return basicValidation;
}).property('uniqueUsernameValidation', 'basicUsernameValidation'),
usernameNeedsToBeValidatedWithEmail: function() {
return( this.get('globalNicknameExists') || false );
},
// Validate the password
passwordValidation: (function() {
var password;
if (!this.get('passwordRequired')) {
return Discourse.InputValidation.create({
ok: true
});
}
// If blank, fail without a reason
password = this.get("accountPassword");
if (this.blank('accountPassword')) {
return Discourse.InputValidation.create({ failed: true });
}
// If too short
if (password.length < 6) {
return Discourse.InputValidation.create({
failed: true,
reason: Em.String.i18n('user.password.too_short')
});
}
// Looks good!
return Discourse.InputValidation.create({
ok: true,
reason: Em.String.i18n('user.password.ok')
});
}).property('accountPassword'),
fetchConfirmationValue: function() {
var createAccountView = this;
return Discourse.ajax('/users/hp.json').then(function (json) {
createAccountView.set('accountPasswordConfirm', json.value);
createAccountView.set('accountChallenge', json.challenge.split("").reverse().join(""));
});
},
createAccount: function() {
var challenge, email, name, password, passwordConfirm, username,
_this = this;
this.set('formSubmitted', true);
name = this.get('accountName');
email = this.get('accountEmail');
password = this.get('accountPassword');
username = this.get('accountUsername');
passwordConfirm = this.get('accountPasswordConfirm');
challenge = this.get('accountChallenge');
return Discourse.User.createAccount(name, email, password, username, passwordConfirm, challenge).then(function(result) {
if (result.success) {
_this.flash(result.message);
_this.set('complete', true);
} else {
_this.flash(result.message || Em.String.i18n('create_account.failed'), 'error');
_this.set('formSubmitted', false);
}
if (result.active) {
return window.location.reload();
}
}, function() {
_this.set('formSubmitted', false);
return _this.flash(Em.String.i18n('create_account.failed'), 'error');
});
},
didInsertElement: function(e) {
this._super();
// allows the submission the form when pressing 'ENTER' on *any* text input field
// but only when the submit button is enabled
var createAccountView = this;
var createAccountController = this.get('controller');
Em.run.schedule('afterRender', function() {
$("input[type='text'], input[type='password']").keydown(function(e) {
if (createAccountView.get('submitDisabled') === false && e.keyCode === 13) {
createAccountView.createAccount();
if (createAccountController.get('submitDisabled') === false && e.keyCode === 13) {
createAccountController.createAccount();
}
});
});

View file

@ -7,181 +7,5 @@
@module Discourse
**/
Discourse.EditCategoryView = Discourse.ModalBodyView.extend({
templateName: 'modal/edit_category',
generalSelected: Ember.computed.equal('selectedTab', 'general'),
securitySelected: Ember.computed.equal('selectedTab', 'security'),
settingsSelected: Ember.computed.equal('selectedTab', 'settings'),
foregroundColors: ['FFFFFF', '000000'],
init: function() {
this._super();
this.set('selectedTab', 'general');
},
modalClass: function() {
return "edit-category-modal " + (this.present('category.description') ? 'full' : 'small');
}.property('category.description'),
selectGeneral: function() {
this.set('selectedTab', 'general');
},
selectSecurity: function() {
this.set('selectedTab', 'security');
},
selectSettings: function() {
this.set('selectedTab', 'settings');
},
disabled: function() {
if (this.get('saving') || this.get('deleting')) return true;
if (!this.get('category.name')) return true;
if (!this.get('category.color')) return true;
return false;
}.property('category.name', 'category.color', 'deleting'),
deleteVisible: function() {
return (this.get('category.id') && this.get('category.topic_count') === 0);
}.property('category.id', 'category.topic_count'),
deleteDisabled: function() {
return (this.get('deleting') || this.get('saving') || false);
}.property('disabled', 'saving', 'deleting'),
colorStyle: function() {
return "background-color: #" + (this.get('category.color')) + "; color: #" + (this.get('category.text_color')) + ";";
}.property('category.color', 'category.text_color'),
// background colors are available as a pipe-separated string
backgroundColors: function() {
var categories = Discourse.Category.list();
return Discourse.SiteSettings.category_colors.split("|").map(function(i) { return i.toUpperCase(); }).concat(
categories.map(function(c) { return c.color.toUpperCase(); }) ).uniq();
}.property('Discourse.SiteSettings.category_colors'),
usedBackgroundColors: function() {
var categories = Discourse.Category.list();
return categories.map(function(c) {
// If editing a category, don't include its color:
return (this.get('category.id') && this.get('category.color').toUpperCase() === c.color.toUpperCase()) ? null : c.color.toUpperCase();
}, this).compact();
}.property('category.id', 'category.color'),
title: function() {
if (this.get('category.id')) return Em.String.i18n("category.edit_long");
if (this.get('category.isUncategorized')) return Em.String.i18n("category.edit_uncategorized");
return Em.String.i18n("category.create");
}.property('category.id'),
categoryName: function() {
var name = this.get('category.name') || "";
return name.trim().length > 0 ? name : Em.String.i18n("preview");
}.property('category.name'),
buttonTitle: function() {
if (this.get('saving')) return Em.String.i18n("saving");
if (this.get('category.isUncategorized')) return Em.String.i18n("save");
return (this.get('category.id') ? Em.String.i18n("category.save") : Em.String.i18n("category.create"));
}.property('saving', 'category.id'),
deleteButtonTitle: function() {
return Em.String.i18n('category.delete');
}.property(),
didInsertElement: function() {
this._super();
if (this.get('category.id')) {
this.set('loading', true);
var categoryView = this;
// We need the topic_count to be correct, so get the most up-to-date info about this category from the server.
Discourse.Category.findBySlugOrId( this.get('category.slug') || this.get('category.id') ).then( function(cat) {
categoryView.set('category', cat);
Discourse.Site.instance().updateCategory(cat);
categoryView.set('id', categoryView.get('category.slug'));
categoryView.set('loading', false);
});
} else if( this.get('category.isUncategorized') ) {
this.set('category', Discourse.Category.uncategorizedInstance());
} else {
this.set('category', Discourse.Category.create({ color: 'AB9364', text_color: 'FFFFFF', hotness: 5 }));
}
},
showCategoryTopic: function() {
$('#discourse-modal').modal('hide');
Discourse.URL.routeTo(this.get('category.topic_url'));
return false;
},
addGroup: function(){
this.get("category").addGroup(this.get("selectedGroup"));
},
removeGroup: function(group){
// OBVIOUS, Ember treats this as Ember.String, we need a real string here
group = group + "";
this.get("category").removeGroup(group);
},
saveCategory: function() {
var categoryView = this;
this.set('saving', true);
if( this.get('category.isUncategorized') ) {
$.when(
Discourse.SiteSetting.update('uncategorized_color', this.get('category.color')),
Discourse.SiteSetting.update('uncategorized_text_color', this.get('category.text_color')),
Discourse.SiteSetting.update('uncategorized_name', this.get('category.name'))
).then(function(result) {
// success
$('#discourse-modal').modal('hide');
// We can't redirect to the uncategorized category on save because the slug
// might have changed.
Discourse.URL.redirectTo("/categories");
}, function(errors) {
// errors
if(errors.length === 0) errors.push(Em.String.i18n("category.save_error"));
categoryView.displayErrors(errors);
categoryView.set('saving', false);
});
} else {
this.get('category').save().then(function(result) {
// success
$('#discourse-modal').modal('hide');
Discourse.URL.redirectTo("/category/" + Discourse.Category.slugFor(result.category));
}, function(errors) {
// errors
if(errors.length === 0) errors.push(Em.String.i18n("category.creation_error"));
categoryView.displayErrors(errors);
categoryView.set('saving', false);
});
}
},
deleteCategory: function() {
var categoryView = this;
this.set('deleting', true);
$('#discourse-modal').modal('hide');
bootbox.confirm(Em.String.i18n("category.delete_confirm"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) {
if (result) {
categoryView.get('category').destroy().then(function(){
// success
Discourse.URL.redirectTo("/categories");
}, function(jqXHR){
// error
$('#discourse-modal').modal('show');
categoryView.displayErrors([Em.String.i18n("category.delete_error")]);
categoryView.set('deleting', false);
});
} else {
$('#discourse-modal').modal('show');
categoryView.set('deleting', false);
}
});
}
templateName: 'modal/edit_category'
});

View file

@ -8,38 +8,5 @@
**/
Discourse.EditTopicAutoCloseView = Discourse.ModalBodyView.extend({
templateName: 'modal/auto_close',
title: Em.String.i18n('topic.auto_close_title'),
modalClass: 'edit-auto-close-modal',
setDays: function() {
if( this.get('topic.auto_close_at') ) {
var closeTime = Date.create( this.get('topic.auto_close_at') );
if (closeTime.isFuture()) {
this.set('auto_close_days', closeTime.daysSince());
}
}
}.observes('topic'),
saveAutoClose: function() {
this.setAutoClose( parseFloat(this.get('auto_close_days')) );
},
removeAutoClose: function() {
this.setAutoClose(null);
},
setAutoClose: function(days) {
var view = this;
Discourse.ajax({
url: "/t/" + this.get('topic.id') + "/autoclose",
type: 'PUT',
dataType: 'json',
data: { auto_close_days: days > 0 ? days : null }
}).then(function(){
view.get('topic').set('auto_close_at', Date.create(days + ' days from now').toJSON());
}, function (error) {
bootbox.alert(Em.String.i18n('generic_error'));
});
}
title: Em.String.i18n('topic.auto_close_title')
});

View file

@ -7,83 +7,19 @@
@module Discourse
**/
Discourse.FlagView = Discourse.ModalBodyView.extend({
templateName: 'flag',
templateName: 'modal/flag',
title: Em.String.i18n('flagging.title'),
// trick to bind user / post to flag
boundFlags: function(){
var _this = this;
var original = this.get('post.flagsAvailable');
if(original){
return $.map(original, function(v){
var b = Discourse.BoundPostActionType.create(v);
b.set('post', _this.get('post'));
return b;
});
}
}.property('post.flagsAvailable'),
changePostActionType: function(action) {
if (this.get('postActionTypeId') === action.id) return false;
this.get('boundFlags').each(function(f){
f.set('selected', false);
});
action.set('selected', true);
this.set('postActionTypeId', action.id);
this.set('isCustomFlag', action.is_custom_flag);
this.set('selected', action);
selectedChanged: function() {
var nameKey = this.get('controller.selected.name_key');
if (!nameKey) return;
Em.run.next(function() {
$('#radio_' + action.name_key).prop('checked', 'true');
$('#radio_' + nameKey).prop('checked', 'true');
});
return false;
},
createFlag: function() {
var _this = this;
var action = this.get('selected');
var postAction = this.get('post.actionByName.' + (action.get('name_key')));
var actionType = Discourse.Site.instance().postActionTypeById(this.get('postActionTypeId'));
if (postAction) {
postAction.act({
message: action.get('message')
}).then(function() {
return $('#discourse-modal').modal('hide');
}, function(errors) {
return _this.displayErrors(errors);
});
}
return false;
},
showSubmit: (function() {
var m;
if (this.get('postActionTypeId')) {
if (this.get('isCustomFlag')) {
m = this.get('selected.message');
return m && m.length >= 10 && m.length <= 500;
} else {
return true;
}
}
return false;
}).property('isCustomFlag', 'selected.customMessageLength', 'postActionTypeId'),
submitText: function(){
var action = this.get('selected');
if (this.get('selected.is_custom_flag')) {
return Em.String.i18n("flagging.notify_action");
} else {
return Em.String.i18n("flagging.action");
}
}.property('selected'),
}.observes('controller.selected.name_key'),
didInsertElement: function() {
this.set('postActionTypeId', null);
this._super();
// Would be nice if there were an EmberJs radio button to do this for us. Oh well, one should be coming
// in an upcoming release.

View file

@ -8,25 +8,7 @@
**/
Discourse.ForgotPasswordView = Discourse.ModalBodyView.extend({
templateName: 'modal/forgot_password',
title: Em.String.i18n('forgot_password.title'),
// You need a value in the field to submit it.
submitDisabled: (function() {
return this.blank('accountEmailOrUsername');
}).property('accountEmailOrUsername'),
submit: function() {
Discourse.ajax("/session/forgot_password", {
data: { login: this.get('accountEmailOrUsername') },
type: 'POST'
});
// don't tell people what happened, this keeps it more secure (ensure same on server)
this.flash(Em.String.i18n('forgot_password.complete'));
return false;
}
title: Em.String.i18n('forgot_password.title')
});

View file

@ -1,6 +1,3 @@
/*jshint newcap:false*/
/*global diff_match_patch:true assetPath:true*/
/**
This view handles rendering of the history of a post
@ -9,75 +6,7 @@
@namespace Discourse
@module Discourse
**/
Discourse.HistoryView = Discourse.View.extend({
templateName: 'history',
title: Em.String.i18n('history'),
modalClass: 'history-modal',
diffLibraryLoaded: false,
diff: null,
init: function(){
this._super();
var historyView = this;
$LAB.script(assetPath('defer/google_diff_match_patch')).wait(function(){
historyView.set('diffLibraryLoaded', true);
});
},
loadSide: function(side) {
if (this.get("version" + side)) {
var orig = this.get('originalPost');
var version = this.get("version" + side + ".number");
if (version === orig.get('version')) {
this.set("post" + side, orig);
} else {
var historyView = this;
Discourse.Post.loadVersion(orig.get('id'), version).then(function(post) {
historyView.set("post" + side, post);
});
}
}
},
changedLeftVersion: function() {
this.loadSide("Left");
}.observes('versionLeft'),
changedRightVersion: function() {
this.loadSide("Right");
}.observes('versionRight'),
loadedPosts: function() {
if (this.get('diffLibraryLoaded') && this.get('postLeft') && this.get('postRight')) {
var dmp = new diff_match_patch(),
before = this.get("postLeft.cooked"),
after = this.get("postRight.cooked"),
diff = dmp.diff_main(before, after);
dmp.diff_cleanupSemantic(diff);
this.set('diff', dmp.diff_prettyHtml(diff));
}
}.observes('diffLibraryLoaded', 'postLeft', 'postRight'),
didInsertElement: function() {
this.setProperties({
loading: true,
postLeft: null,
postRight: null
});
var historyView = this;
this.get('originalPost').loadVersions().then(function(result) {
result.each(function(item) {
item.description = "v" + item.number + " - " + Date.create(item.created_at).relative() + " - " +
Em.String.i18n("changed_by", { author: item.display_username });
});
historyView.setProperties({
loading: false,
versionLeft: result.first(),
versionRight: result.last(),
versions: result
});
});
}
Discourse.HistoryView = Discourse.ModalBodyView.extend({
templateName: 'modal/history',
title: Em.String.i18n('history')
});

View file

@ -2,38 +2,22 @@
This view handles the image upload interface
@class ImageSelectorView
@extends Discourse.View
@extends Discourse.ModalBodyView
@namespace Discourse
@module Discourse
**/
Discourse.ImageSelectorView = Discourse.View.extend({
templateName: 'image_selector',
Discourse.ImageSelectorView = Discourse.ModalBodyView.extend({
templateName: 'modal/image_selector',
classNames: ['image-selector'],
title: Em.String.i18n('image_selector.title'),
init: function() {
this._super();
this.set('localSelected', true);
},
selectLocal: function() {
this.set('localSelected', true);
},
selectRemote: function() {
this.set('localSelected', false);
},
remoteSelected: function() {
return !this.get('localSelected');
}.property('localSelected'),
upload: function() {
this.get('uploadTarget').fileupload('add', { fileInput: $('#filename-input') });
$('#reply-control').fileupload('add', { fileInput: $('#filename-input') });
},
add: function() {
this.get('composer').addMarkdown("![image](" + $('#fileurl-input').val() + ")");
this.get('controller.composerView').addMarkdown("![image](" + $('#fileurl-input').val() + ")");
$('#discourse-modal').modal('hide');
}
});

View file

@ -1,59 +0,0 @@
/**
A modal view for inviting a user to Discourse
@class InviteModalView
@extends Discourse.ModalBodyView
@namespace Discourse
@module Discourse
**/
Discourse.InviteModalView = Discourse.ModalBodyView.extend({
templateName: 'modal/invite',
title: Em.String.i18n('topic.invite_reply.title'),
email: null,
error: false,
saving: false,
finished: false,
disabled: (function() {
if (this.get('saving')) return true;
if (this.blank('email')) return true;
if (!Discourse.Utilities.emailValid(this.get('email'))) return true;
return false;
}).property('email', 'saving'),
buttonTitle: (function() {
if (this.get('saving')) return Em.String.i18n('topic.inviting');
return Em.String.i18n('topic.invite_reply.action');
}).property('saving'),
successMessage: (function() {
return Em.String.i18n('topic.invite_reply.success', {
email: this.get('email')
});
}).property('email'),
didInsertElement: function() {
var inviteModalView = this;
Em.run.schedule('afterRender', function() {
inviteModalView.$('input').focus();
});
},
createInvite: function() {
var _this = this;
this.set('saving', true);
this.set('error', false);
this.get('topic').inviteUser(this.get('email')).then(function() {
// Success
_this.set('saving', false);
return _this.set('finished', true);
}, function() {
// Failure
_this.set('error', true);
return _this.set('saving', false);
});
return false;
}
});

View file

@ -1,52 +0,0 @@
/**
A modal view for inviting a user to private message
@class InvitePrivateModalView
@extends Discourse.ModalBodyView
@namespace Discourse
@module Discourse
**/
Discourse.InvitePrivateModalView = Discourse.ModalBodyView.extend({
templateName: 'modal/invite_private',
title: Em.String.i18n('topic.invite_private.title'),
email: null,
error: false,
saving: false,
finished: false,
disabled: (function() {
if (this.get('saving')) return true;
return this.blank('emailOrUsername');
}).property('emailOrUsername', 'saving'),
buttonTitle: (function() {
if (this.get('saving')) return Em.String.i18n('topic.inviting');
return Em.String.i18n('topic.invite_private.action');
}).property('saving'),
didInsertElement: function() {
var invitePrivateModalView = this;
Em.run.schedule('afterRender', function() {
invitePrivateModalView.$('input').focus();
});
},
invite: function() {
var _this = this;
this.set('saving', true);
this.set('error', false);
// Invite the user to the private message
this.get('topic').inviteUser(this.get('emailOrUsername')).then(function() {
// Success
_this.set('saving', false);
_this.set('finished', true);
}, function() {
// Failure
_this.set('error', true);
_this.set('saving', false);
});
return false;
}
});

View file

@ -0,0 +1,23 @@
/**
A modal view for inviting a user to private message
@class InvitePrivateView
@extends Discourse.ModalBodyView
@namespace Discourse
@module Discourse
**/
Discourse.InvitePrivateView = Discourse.ModalBodyView.extend({
templateName: 'modal/invite_private',
title: Em.String.i18n('topic.invite_private.title'),
didInsertElement: function() {
this._super();
var invitePrivateModalView = this;
Em.run.schedule('afterRender', function() {
invitePrivateModalView.$('input').focus();
});
}
});

View file

@ -0,0 +1,24 @@
/**
A modal view for inviting a user to Discourse
@class InviteView
@extends Discourse.ModalBodyView
@namespace Discourse
@module Discourse
**/
Discourse.InviteView = Discourse.ModalBodyView.extend({
templateName: 'modal/invite',
title: Em.String.i18n('topic.invite_reply.title'),
didInsertElement: function() {
this._super();
var inviteModalView = this;
Em.run.schedule('afterRender', function() {
inviteModalView.$('input').focus();
});
}
});

View file

@ -9,175 +9,29 @@
Discourse.LoginView = Discourse.ModalBodyView.extend({
templateName: 'modal/login',
title: Em.String.i18n('login.title'),
authenticate: null,
loggingIn: false,
site: function() {
return Discourse.Site.instance();
}.property(),
showView: function(view) {
return this.get('controller').show(view);
},
newAccount: function() {
return this.showView(Discourse.CreateAccountView.create());
},
forgotPassword: function() {
return this.showView(Discourse.ForgotPasswordView.create());
},
/**
Determines whether at least one login button is enabled
**/
hasAtLeastOneLoginButton: function() {
return Discourse.SiteSettings.enable_google_logins ||
Discourse.SiteSettings.enable_facebook_logins ||
Discourse.SiteSettings.enable_cas_logins ||
Discourse.SiteSettings.enable_twitter_logins ||
Discourse.SiteSettings.enable_yahoo_logins ||
Discourse.SiteSettings.enable_github_logins ||
Discourse.SiteSettings.enable_persona_logins;
}.property(),
loginButtonText: function() {
return this.get('loggingIn') ? Em.String.i18n('login.logging_in') : Em.String.i18n('login.title');
}.property('loggingIn'),
loginDisabled: function() {
return this.get('loggingIn') || this.blank('loginName') || this.blank('loginPassword');
}.property('loginName', 'loginPassword', 'loggingIn'),
login: function() {
this.set('loggingIn', true);
var loginView = this;
Discourse.ajax("/session", {
data: { login: this.get('loginName'), password: this.get('loginPassword') },
type: 'POST'
}).then(function (result) {
// Successful login
if (result.error) {
loginView.set('loggingIn', false);
if( result.reason === 'not_activated' ) {
return loginView.showView(Discourse.NotActivatedView.create({
username: loginView.get('loginName'),
sentTo: result.sent_to_email,
currentEmail: result.current_email
}));
}
loginView.flash(result.error, 'error');
} else {
// Trigger the browser's password manager using the hidden static login form:
var $hidden_login_form = $('#hidden-login-form');
$hidden_login_form.find('input[name=username]').val(loginView.get('loginName'));
$hidden_login_form.find('input[name=password]').val(loginView.get('loginPassword'));
$hidden_login_form.find('input[name=redirect]').val(window.location.href);
$hidden_login_form.find('input[name=authenticity_token]').val($('meta[name=csrf-token]').attr('content'));
$hidden_login_form.submit();
}
}, function(result) {
// Failed to login
loginView.flash(Em.String.i18n('login.error'), 'error');
loginView.set('loggingIn', false);
})
return false;
},
authMessage: (function() {
if (this.blank('authenticate')) return "";
return Em.String.i18n("login." + (this.get('authenticate')) + ".message");
}).property('authenticate'),
twitterLogin: function() {
this.set('authenticate', 'twitter');
var left = this.get('lastX') - 400;
var top = this.get('lastY') - 200;
return window.open(Discourse.getURL("/auth/twitter"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
},
facebookLogin: function() {
this.set('authenticate', 'facebook');
var left = this.get('lastX') - 400;
var top = this.get('lastY') - 200;
return window.open(Discourse.getURL("/auth/facebook"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
},
casLogin: function() {
var left, top;
this.set('authenticate', 'cas');
left = this.get('lastX') - 400;
top = this.get('lastY') - 200;
return window.open("/auth/cas", "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
},
openidLogin: function(provider) {
var left = this.get('lastX') - 400;
var top = this.get('lastY') - 200;
if (provider === "yahoo") {
this.set("authenticate", 'yahoo');
return window.open(Discourse.getURL("/auth/yahoo"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
} else {
window.open(Discourse.getURL("/auth/google"), "_blank", "menubar=no,status=no,height=500,width=850,left=" + left + ",top=" + top);
return this.set("authenticate", 'google');
}
},
githubLogin: function() {
this.set('authenticate', 'github');
var left = this.get('lastX') - 400;
var top = this.get('lastY') - 200;
return window.open(Discourse.getURL("/auth/github"), "_blank", "menubar=no,status=no,height=400,width=800,left=" + left + ",top=" + top);
},
personaLogin: function() {
navigator.id.request();
},
authenticationComplete: function(options) {
if (options.awaiting_approval) {
this.flash(Em.String.i18n('login.awaiting_approval'), 'success');
this.set('authenticate', null);
return;
}
if (options.awaiting_activation) {
this.flash(Em.String.i18n('login.awaiting_confirmation'), 'success');
this.set('authenticate', null);
return;
}
// Reload the page if we're authenticated
if (options.authenticated) {
window.location.reload();
return;
}
return this.showView(Discourse.CreateAccountView.create({
accountEmail: options.email,
accountUsername: options.username,
accountName: options.name,
authOptions: Em.Object.create(options)
}));
},
mouseMove: function(e) {
this.set('lastX', e.screenX);
return this.set('lastY', e.screenY);
this.set('controller.lastX', e.screenX);
this.set('controller.lastY', e.screenY);
},
didInsertElement: function(e) {
this._super();
var loginController = this.get('controller');
// Get username and password from the browser's password manager,
// if it filled the hidden static login form:
this.set('loginName', $('#hidden-login-form input[name=username]').val());
this.set('loginPassword', $('#hidden-login-form input[name=password]').val());
loginController.set('loginName', $('#hidden-login-form input[name=username]').val());
loginController.set('loginPassword', $('#hidden-login-form input[name=password]').val());
var loginView = this;
Em.run.schedule('afterRender', function() {
$('#login-account-password').keydown(function(e) {
if (e.keyCode === 13) {
loginView.login();
loginController.login();
}
});
});

View file

@ -6,49 +6,9 @@
@namespace Discourse
@module Discourse
**/
Discourse.MergeTopicView = Discourse.ModalBodyView.extend(Discourse.SelectedPostsCount, {
Discourse.MergeTopicView = Discourse.ModalBodyView.extend({
templateName: 'modal/merge_topic',
title: Em.String.i18n('topic.merge_topic.title'),
buttonDisabled: function() {
if (this.get('saving')) return true;
return this.blank('selectedTopicId');
}.property('selectedTopicId', 'saving'),
buttonTitle: function() {
if (this.get('saving')) return Em.String.i18n('saving');
return Em.String.i18n('topic.merge_topic.title');
}.property('saving'),
movePostsToExistingTopic: function() {
this.set('saving', true);
var moveSelectedView = this;
var promise = null;
if (this.get('allPostsSelected')) {
promise = Discourse.Topic.mergeTopic(this.get('topic.id'), this.get('selectedTopicId'));
} else {
var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); });
promise = Discourse.Topic.movePosts(this.get('topic.id'), {
destination_topic_id: this.get('selectedTopicId'),
post_ids: postIds
});
}
promise.then(function(result) {
// Posts moved
$('#discourse-modal').modal('hide');
moveSelectedView.get('topicController').toggleMultiSelect();
Em.run.next(function() { Discourse.URL.routeTo(result.url); });
}, function() {
// Error moving posts
moveSelectedView.flash(Em.String.i18n('topic.merge_topic.error'));
moveSelectedView.set('saving', false);
});
return false;
}
title: Em.String.i18n('topic.merge_topic.title')
});

View file

@ -10,10 +10,18 @@ Discourse.ModalBodyView = Discourse.View.extend({
// Focus on first element
didInsertElement: function() {
$('#discourse-modal').modal('show');
$('#modal-alert').hide();
var modalBodyView = this;
Em.run.schedule('afterRender', function() {
modalBodyView.$('form input:first').focus();
});
var title = this.get('title');
if (title) {
this.set('controller.controllers.modal.title', title);
}
},
// Pass the errors to our errors view
@ -22,14 +30,16 @@ Discourse.ModalBodyView = Discourse.View.extend({
if (typeof callback === "function") callback();
},
// Just use jQuery to show an alert. We don't need anythign fancier for now
// like an actual ember view
flash: function(msg, flashClass) {
if (!flashClass) flashClass = "success";
flashMessageChanged: function() {
var flashMessage = this.get('controller.flashMessage');
if (flashMessage) {
var messageClass = flashMessage.get('messageClass') || 'success';
var $alert = $('#modal-alert').hide().removeClass('alert-error', 'alert-success');
$alert.addClass("alert alert-" + flashClass).html(msg);
$alert.addClass("alert alert-" + messageClass).html(flashMessage.get('message'));
$alert.fadeIn();
}
}.observes('controller.flashMessage')
});

View file

@ -6,30 +6,10 @@
@namespace Discourse
@module Discourse
**/
Discourse.ModalView = Ember.ContainerView.extend({
childViews: ['modalHeaderView', 'modalBodyView', 'modalErrorsView'],
classNames: ['modal', 'hidden'],
classNameBindings: ['controller.currentView.modalClass'],
Discourse.ModalView = Discourse.View.extend({
elementId: 'discourse-modal',
modalHeaderView: Ember.View.create({
templateName: 'modal/modal_header',
titleBinding: 'controller.currentView.title'
}),
modalBodyView: Ember.ContainerView.create({ currentViewBinding: 'controller.currentView' }),
modalErrorsView: Ember.View.create({ templateName: 'modal/modal_errors' }),
viewChanged: function() {
this.set('modalErrorsView.errors', null);
var view = this.get('controller.currentView');
var modalView = this;
if (view) {
$('#modal-alert').hide();
Em.run.schedule('afterRender', function() { modalView.$().modal('show'); });
}
}.observes('controller.currentView')
templateName: 'modal/modal',
classNameBindings: [':modal', ':hidden', 'controller.modalClass']
});

View file

@ -8,11 +8,5 @@
**/
Discourse.NotActivatedView = Discourse.ModalBodyView.extend({
templateName: 'modal/not_activated',
title: Em.String.i18n('log_in'),
emailSent: false,
sendActivationEmail: function() {
Discourse.ajax('/users/' + this.get('username') + '/send_activation_email');
this.set('emailSent', true);
}
title: Em.String.i18n('log_in')
});

View file

@ -8,40 +8,7 @@
**/
Discourse.SplitTopicView = Discourse.ModalBodyView.extend(Discourse.SelectedPostsCount, {
templateName: 'modal/split_topic',
title: Em.String.i18n('topic.split_topic.title'),
saving: false,
buttonDisabled: function() {
if (this.get('saving')) return true;
return this.blank('topicName');
}.property('saving', 'topicName'),
buttonTitle: function() {
if (this.get('saving')) return Em.String.i18n('saving');
return Em.String.i18n('topic.split_topic.action');
}.property('saving'),
movePostsToNewTopic: function() {
this.set('saving', true);
var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); });
var moveSelectedView = this;
Discourse.Topic.movePosts(this.get('topic.id'), {
title: this.get('topicName'),
post_ids: postIds
}).then(function(result) {
// Posts moved
$('#discourse-modal').modal('hide');
moveSelectedView.get('topicController').toggleMultiSelect();
Em.run.next(function() { Discourse.URL.routeTo(result.url); });
}, function() {
// Error moving posts
moveSelectedView.flash(Em.String.i18n('topic.split_topic.error'));
moveSelectedView.set('saving', false);
});
return false;
}
title: Em.String.i18n('topic.split_topic.title')
});

View file

@ -1,13 +0,0 @@
/**
A modal view for displaying the ranking details of a topic
@class TopicRankDetailsView
@extends Discourse.ModalBodyView
@namespace Discourse
@module Discourse
**/
Discourse.TopicRankDetailsView = Discourse.ModalBodyView.extend({
templateName: 'modal/topic_rank_details',
title: Em.String.i18n('rank_details.title')
});

View file

@ -113,7 +113,7 @@ Discourse.PostMenuView = Discourse.View.extend({
},
clickFlag: function() {
this.get('controller').showFlags(this.get('post'));
this.get('controller').send('showFlags', this.get('post'));
},
// Edit button

View file

@ -38,7 +38,7 @@ Discourse.TopicFooterButtonsView = Ember.ContainerView.extend({
},
click: function() {
return this.get('controller').showInviteModal();
return this.get('controller').send('showInvite');
}
}));
}
@ -170,8 +170,7 @@ Discourse.TopicFooterButtonsView = Ember.ContainerView.extend({
textKey: 'topic.login_reply',
classNames: ['btn', 'btn-primary', 'create'],
click: function() {
var _ref;
return (_ref = this.get('controller.controllers.modal')) ? _ref.show(Discourse.LoginView.create()) : void 0;
this.get('controller').send('showLogin');
}
}));
}

View file

@ -57,7 +57,7 @@ class TopicList
def has_rank_details?
# Only moderators can see rank details
return false unless @current_user.try(:moderator?)
return false unless @current_user.try(:staff?)
# Only show them on 'Hot'
return @filter == :hot