Merge pull request #4849 from discourse/prefs

User preferences in tabs
This commit is contained in:
Neil Lalonde
2017-05-03 16:53:26 -04:00
committed by GitHub
35 changed files with 1085 additions and 695 deletions
@@ -1,230 +1,3 @@
import { setting } from 'discourse/lib/computed';
import CanCheckEmails from 'discourse/mixins/can-check-emails';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
import { cook } from 'discourse/lib/text';
import { NotificationLevels } from 'discourse/lib/notification-levels';
import { listThemes, selectDefaultTheme, previewTheme } from 'discourse/lib/theme-selector';
export default Ember.Controller.extend(CanCheckEmails, {
userSelectableThemes: function(){
return listThemes(this.site);
}.property(),
@observes("selectedTheme")
themeKeyChanged() {
let key = this.get("selectedTheme");
previewTheme(key);
},
@computed("model.watchedCategories", "model.trackedCategories", "model.mutedCategories")
selectedCategories(watched, tracked, muted) {
return [].concat(watched, tracked, muted);
},
// By default we haven't saved anything
saved: false,
newNameInput: null,
@computed("model.user_fields.@each.value")
userFields() {
let siteUserFields = this.site.get('user_fields');
if (!Ember.isEmpty(siteUserFields)) {
const userFields = this.get('model.user_fields');
// Staff can edit fields that are not `editable`
if (!this.get('currentUser.staff')) {
siteUserFields = siteUserFields.filterBy('editable', true);
}
return siteUserFields.sortBy('position').map(function(field) {
const value = userFields ? userFields[field.get('id').toString()] : null;
return Ember.Object.create({ value, field });
});
}
},
cannotDeleteAccount: Em.computed.not('currentUser.can_delete_account'),
deleteDisabled: Em.computed.or('model.isSaving', 'deleting', 'cannotDeleteAccount'),
canEditName: setting('enable_names'),
@computed()
nameInstructions() {
return I18n.t(this.siteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions');
},
@computed("model.has_title_badges")
canSelectTitle(hasTitleBadges) {
return this.siteSettings.enable_badges && hasTitleBadges;
},
@computed("model.can_change_bio")
canChangeBio(canChangeBio)
{
return canChangeBio;
},
@computed()
canChangePassword() {
return !this.siteSettings.enable_sso && this.siteSettings.enable_local_logins;
},
@computed()
availableLocales() {
return this.siteSettings.available_locales.split('|').map(s => ({ name: s, value: s }));
},
@computed()
frequencyEstimate() {
var estimate = this.get('model.mailing_list_posts_per_day');
if (!estimate || estimate < 2) {
return I18n.t('user.mailing_list_mode.few_per_day');
} else {
return I18n.t('user.mailing_list_mode.many_per_day', { dailyEmailEstimate: estimate });
}
},
@computed()
mailingListModeOptions() {
return [
{name: I18n.t('user.mailing_list_mode.daily'), value: 0},
{name: this.get('frequencyEstimate'), value: 1},
{name: I18n.t('user.mailing_list_mode.individual_no_echo'), value: 2}
];
},
previousRepliesOptions: [
{name: I18n.t('user.email_previous_replies.always'), value: 0},
{name: I18n.t('user.email_previous_replies.unless_emailed'), value: 1},
{name: I18n.t('user.email_previous_replies.never'), value: 2}
],
digestFrequencies: [{ name: I18n.t('user.email_digests.every_30_minutes'), value: 30 },
{ name: I18n.t('user.email_digests.every_hour'), value: 60 },
{ name: I18n.t('user.email_digests.daily'), value: 1440 },
{ name: I18n.t('user.email_digests.every_three_days'), value: 4320 },
{ name: I18n.t('user.email_digests.weekly'), value: 10080 },
{ name: I18n.t('user.email_digests.every_two_weeks'), value: 20160 }],
likeNotificationFrequencies: [{ name: I18n.t('user.like_notification_frequency.always'), value: 0 },
{ name: I18n.t('user.like_notification_frequency.first_time_and_daily'), value: 1 },
{ name: I18n.t('user.like_notification_frequency.first_time'), value: 2 },
{ name: I18n.t('user.like_notification_frequency.never'), value: 3 }],
autoTrackDurations: [{ name: I18n.t('user.auto_track_options.never'), value: -1 },
{ name: I18n.t('user.auto_track_options.immediately'), value: 0 },
{ name: I18n.t('user.auto_track_options.after_30_seconds'), value: 30000 },
{ name: I18n.t('user.auto_track_options.after_1_minute'), value: 60000 },
{ name: I18n.t('user.auto_track_options.after_2_minutes'), value: 120000 },
{ name: I18n.t('user.auto_track_options.after_3_minutes'), value: 180000 },
{ name: I18n.t('user.auto_track_options.after_4_minutes'), value: 240000 },
{ name: I18n.t('user.auto_track_options.after_5_minutes'), value: 300000 },
{ name: I18n.t('user.auto_track_options.after_10_minutes'), value: 600000 }],
notificationLevelsForReplying: [{ name: I18n.t('topic.notifications.watching.title'), value: NotificationLevels.WATCHING },
{ name: I18n.t('topic.notifications.tracking.title'), value: NotificationLevels.TRACKING },
{ name: I18n.t('topic.notifications.regular.title'), value: NotificationLevels.REGULAR }],
considerNewTopicOptions: [{ name: I18n.t('user.new_topic_duration.not_viewed'), value: -1 },
{ name: I18n.t('user.new_topic_duration.after_1_day'), value: 60 * 24 },
{ name: I18n.t('user.new_topic_duration.after_2_days'), value: 60 * 48 },
{ name: I18n.t('user.new_topic_duration.after_1_week'), value: 7 * 60 * 24 },
{ name: I18n.t('user.new_topic_duration.after_2_weeks'), value: 2 * 7 * 60 * 24 },
{ name: I18n.t('user.new_topic_duration.last_here'), value: -2 }],
@computed("model.isSaving")
saveButtonText(isSaving) {
return isSaving ? I18n.t('saving') : I18n.t('save');
},
reset() {
this.setProperties({
passwordProgress: null
});
},
passwordProgress: null,
actions: {
save() {
this.set('saved', false);
const model = this.get('model');
const userFields = this.get('userFields');
// Update the user fields
if (!Ember.isEmpty(userFields)) {
const modelFields = model.get('user_fields');
if (!Ember.isEmpty(modelFields)) {
userFields.forEach(function(uf) {
modelFields[uf.get('field.id').toString()] = uf.get('value');
});
}
}
// Cook the bio for preview
model.set('name', this.get('newNameInput'));
return model.save().then(() => {
if (Discourse.User.currentProp('id') === model.get('id')) {
Discourse.User.currentProp('name', model.get('name'));
}
model.set('bio_cooked', cook(model.get('bio_raw')));
selectDefaultTheme(this.get('selectedTheme'));
this.set('saved', true);
}).catch(popupAjaxError);
},
changePassword() {
if (!this.get('passwordProgress')) {
this.set('passwordProgress', I18n.t("user.change_password.in_progress"));
return this.get('model').changePassword().then(() => {
// password changed
this.setProperties({
changePasswordProgress: false,
passwordProgress: I18n.t("user.change_password.success")
});
}).catch(() => {
// password failed to change
this.setProperties({
changePasswordProgress: false,
passwordProgress: I18n.t("user.change_password.error")
});
});
}
},
delete() {
this.set('deleting', true);
const self = this,
message = I18n.t('user.delete_account_confirm'),
model = this.get('model'),
buttons = [
{ label: I18n.t("cancel"),
class: "cancel-inline",
link: true,
callback: () => { this.set('deleting', false); }
},
{ label: '<i class="fa fa-exclamation-triangle"></i> ' + I18n.t("user.delete_account"),
class: "btn btn-danger",
callback() {
model.delete().then(function() {
bootbox.alert(I18n.t('user.deleted_yourself'), function() {
window.location.pathname = Discourse.getURL('/');
});
}, function() {
bootbox.alert(I18n.t('user.delete_yourself_not_allowed'));
self.set('deleting', false);
});
}
}
];
bootbox.dialog(message, buttons, {"classes": "delete-account"});
}
}
export default Ember.Controller.extend({
application: Ember.inject.controller()
});
@@ -0,0 +1,71 @@
import CanCheckEmails from 'discourse/mixins/can-check-emails';
import { default as computed } from "ember-addons/ember-computed-decorators";
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
export default Ember.Controller.extend(CanCheckEmails, PreferencesTabController, {
passwordProgress: null,
cannotDeleteAccount: Em.computed.not('currentUser.can_delete_account'),
deleteDisabled: Em.computed.or('model.isSaving', 'deleting', 'cannotDeleteAccount'),
reset() {
this.setProperties({
passwordProgress: null
});
},
@computed()
canChangePassword() {
return !this.siteSettings.enable_sso && this.siteSettings.enable_local_logins;
},
actions: {
changePassword() {
if (!this.get('passwordProgress')) {
this.set('passwordProgress', I18n.t("user.change_password.in_progress"));
return this.get('model').changePassword().then(() => {
// password changed
this.setProperties({
changePasswordProgress: false,
passwordProgress: I18n.t("user.change_password.success")
});
}).catch(() => {
// password failed to change
this.setProperties({
changePasswordProgress: false,
passwordProgress: I18n.t("user.change_password.error")
});
});
}
},
delete() {
this.set('deleting', true);
const self = this,
message = I18n.t('user.delete_account_confirm'),
model = this.get('model'),
buttons = [
{ label: I18n.t("cancel"),
class: "cancel-inline",
link: true,
callback: () => { this.set('deleting', false); }
},
{ label: '<i class="fa fa-exclamation-triangle"></i> ' + I18n.t("user.delete_account"),
class: "btn btn-danger",
callback() {
model.delete().then(function() {
bootbox.alert(I18n.t('user.deleted_yourself'), function() {
window.location.pathname = Discourse.getURL('/');
});
}, function() {
bootbox.alert(I18n.t('user.delete_yourself_not_allowed'));
self.set('deleting', false);
});
}
}
];
bootbox.dialog(message, buttons, {"classes": "delete-account"});
}
}
});
@@ -0,0 +1,20 @@
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Controller.extend(PreferencesTabController, {
saveAttrNames: [
'muted_category_ids',
'watched_category_ids',
'tracked_category_ids',
'watched_first_post_category_ids'
],
actions: {
save() {
this.set('saved', false);
return this.get('model').save(this.get('saveAttrNames')).then(() => {
this.set('saved', true);
}).catch(popupAjaxError);
}
}
});
@@ -0,0 +1,62 @@
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { default as computed } from "ember-addons/ember-computed-decorators";
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Controller.extend(PreferencesTabController, {
saveAttrNames: [
'email_always',
'mailing_list_mode',
'mailing_list_mode_frequency',
'email_digests',
'email_direct',
'email_in_reply_to',
'email_private_messages',
'email_previous_replies',
'digest_after_minutes',
'include_tl0_in_digests'
],
@computed()
frequencyEstimate() {
var estimate = this.get('model.mailing_list_posts_per_day');
if (!estimate || estimate < 2) {
return I18n.t('user.mailing_list_mode.few_per_day');
} else {
return I18n.t('user.mailing_list_mode.many_per_day', { dailyEmailEstimate: estimate });
}
},
@computed()
mailingListModeOptions() {
return [
{name: I18n.t('user.mailing_list_mode.daily'), value: 0},
{name: this.get('frequencyEstimate'), value: 1},
{name: I18n.t('user.mailing_list_mode.individual_no_echo'), value: 2}
];
},
previousRepliesOptions: [
{name: I18n.t('user.email_previous_replies.always'), value: 0},
{name: I18n.t('user.email_previous_replies.unless_emailed'), value: 1},
{name: I18n.t('user.email_previous_replies.never'), value: 2}
],
digestFrequencies: [
{ name: I18n.t('user.email_digests.every_30_minutes'), value: 30 },
{ name: I18n.t('user.email_digests.every_hour'), value: 60 },
{ name: I18n.t('user.email_digests.daily'), value: 1440 },
{ name: I18n.t('user.email_digests.every_three_days'), value: 4320 },
{ name: I18n.t('user.email_digests.weekly'), value: 10080 },
{ name: I18n.t('user.email_digests.every_two_weeks'), value: 20160 }
],
actions: {
save() {
this.set('saved', false);
return this.get('model').save(this.get('saveAttrNames')).then(() => {
this.set('saved', true);
}).catch(popupAjaxError);
}
}
});
@@ -0,0 +1,45 @@
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
import { listThemes, previewTheme } from 'discourse/lib/theme-selector';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { selectDefaultTheme } from 'discourse/lib/theme-selector';
export default Ember.Controller.extend(PreferencesTabController, {
saveAttrNames: [
'locale',
'external_links_in_new_tab',
'dynamic_favicon',
'enable_quoting',
'disable_jump_reply',
'automatically_unpin_topics'
],
preferencesController: Ember.inject.controller('preferences'),
@computed()
availableLocales() {
return this.siteSettings.available_locales.split('|').map(s => ({ name: s, value: s }));
},
userSelectableThemes: function(){
return listThemes(this.site);
}.property(),
@observes("selectedTheme")
themeKeyChanged() {
let key = this.get("selectedTheme");
this.get('preferencesController').set('selectedTheme', key);
previewTheme(key);
},
actions: {
save() {
this.set('saved', false);
return this.get('model').save(this.get('saveAttrNames')).then(() => {
this.set('saved', true);
selectDefaultTheme(this.get('selectedTheme'));
}).catch(popupAjaxError);
}
}
});
@@ -0,0 +1,56 @@
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { default as computed } from "ember-addons/ember-computed-decorators";
import { NotificationLevels } from 'discourse/lib/notification-levels';
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Controller.extend(PreferencesTabController, {
saveAttrNames:[
'muted_usernames',
'new_topic_duration_minutes',
'auto_track_topics_after_msecs',
'notification_level_when_replying',
'like_notification_frequency'
],
@computed("model.watchedCategories", "model.trackedCategories", "model.mutedCategories")
selectedCategories(watched, tracked, muted) {
return [].concat(watched, tracked, muted);
},
likeNotificationFrequencies: [{ name: I18n.t('user.like_notification_frequency.always'), value: 0 },
{ name: I18n.t('user.like_notification_frequency.first_time_and_daily'), value: 1 },
{ name: I18n.t('user.like_notification_frequency.first_time'), value: 2 },
{ name: I18n.t('user.like_notification_frequency.never'), value: 3 }],
autoTrackDurations: [{ name: I18n.t('user.auto_track_options.never'), value: -1 },
{ name: I18n.t('user.auto_track_options.immediately'), value: 0 },
{ name: I18n.t('user.auto_track_options.after_30_seconds'), value: 30000 },
{ name: I18n.t('user.auto_track_options.after_1_minute'), value: 60000 },
{ name: I18n.t('user.auto_track_options.after_2_minutes'), value: 120000 },
{ name: I18n.t('user.auto_track_options.after_3_minutes'), value: 180000 },
{ name: I18n.t('user.auto_track_options.after_4_minutes'), value: 240000 },
{ name: I18n.t('user.auto_track_options.after_5_minutes'), value: 300000 },
{ name: I18n.t('user.auto_track_options.after_10_minutes'), value: 600000 }],
notificationLevelsForReplying: [{ name: I18n.t('topic.notifications.watching.title'), value: NotificationLevels.WATCHING },
{ name: I18n.t('topic.notifications.tracking.title'), value: NotificationLevels.TRACKING },
{ name: I18n.t('topic.notifications.regular.title'), value: NotificationLevels.REGULAR }],
considerNewTopicOptions: [{ name: I18n.t('user.new_topic_duration.not_viewed'), value: -1 },
{ name: I18n.t('user.new_topic_duration.after_1_day'), value: 60 * 24 },
{ name: I18n.t('user.new_topic_duration.after_2_days'), value: 60 * 48 },
{ name: I18n.t('user.new_topic_duration.after_1_week'), value: 7 * 60 * 24 },
{ name: I18n.t('user.new_topic_duration.after_2_weeks'), value: 2 * 7 * 60 * 24 },
{ name: I18n.t('user.new_topic_duration.last_here'), value: -2 }],
actions: {
save() {
this.set('saved', false);
return this.get('model').save(this.get('saveAttrNames')).then(() => {
this.set('saved', true);
}).catch(popupAjaxError);
}
}
});
@@ -0,0 +1,84 @@
import { default as computed } from "ember-addons/ember-computed-decorators";
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { setting } from 'discourse/lib/computed';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { cook } from 'discourse/lib/text';
export default Ember.Controller.extend(PreferencesTabController, {
saveAttrNames: [
'name',
'bio_raw',
'website',
'location',
'custom_fields',
'user_fields',
'profile_background',
'card_background',
'date_of_birth'
],
canEditName: setting('enable_names'),
newNameInput: null,
@computed()
nameInstructions() {
return I18n.t(this.siteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions');
},
@computed("model.has_title_badges")
canSelectTitle(hasTitleBadges) {
return this.siteSettings.enable_badges && hasTitleBadges;
},
@computed("model.user_fields.@each.value")
userFields() {
let siteUserFields = this.site.get('user_fields');
if (!Ember.isEmpty(siteUserFields)) {
const userFields = this.get('model.user_fields');
// Staff can edit fields that are not `editable`
if (!this.get('currentUser.staff')) {
siteUserFields = siteUserFields.filterBy('editable', true);
}
return siteUserFields.sortBy('position').map(function(field) {
const value = userFields ? userFields[field.get('id').toString()] : null;
return Ember.Object.create({ value, field });
});
}
},
@computed("model.can_change_bio")
canChangeBio(canChangeBio)
{
return canChangeBio;
},
actions: {
save() {
this.set('saved', false);
const model = this.get('model'),
userFields = this.get('userFields');
model.set('name', this.get('newNameInput'));
// Update the user fields
if (!Ember.isEmpty(userFields)) {
const modelFields = model.get('user_fields');
if (!Ember.isEmpty(modelFields)) {
userFields.forEach(function(uf) {
modelFields[uf.get('field.id').toString()] = uf.get('value');
});
}
}
return model.save(this.get('saveAttrNames')).then(() => {
model.set('bio_cooked', cook(model.get('bio_raw')));
this.set('saved', true);
}).catch(popupAjaxError);
}
}
});
@@ -0,0 +1,21 @@
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Controller.extend(PreferencesTabController, {
saveAttrNames: [
'muted_tags',
'tracked_tags',
'watched_tags',
'watching_first_post_tags'
],
actions: {
save() {
this.set('saved', false);
return this.get('model').save(this.get('saveAttrNames')).then(() => {
this.set('saved', true);
}).catch(popupAjaxError);
}
}
});
@@ -0,0 +1,10 @@
import { default as computed } from "ember-addons/ember-computed-decorators";
export default Ember.Mixin.create({
saved: false,
@computed("model.isSaving")
saveButtonText(isSaving) {
return isSaving ? I18n.t('saving') : I18n.t('save');
}
});
@@ -201,8 +201,9 @@ const User = RestModel.extend({
return Discourse.User.create(this.getProperties(Object.keys(this)));
},
save() {
const data = this.getProperties(
save(fields) {
let userFields = [
'bio_raw',
'website',
'location',
@@ -217,43 +218,55 @@ const User = RestModel.extend({
'tracked_tags',
'watched_tags',
'watching_first_post_tags',
'date_of_birth');
'date_of_birth'
];
['email_always',
'mailing_list_mode',
'mailing_list_mode_frequency',
'external_links_in_new_tab',
'email_digests',
'email_direct',
'email_in_reply_to',
'email_private_messages',
'email_previous_replies',
'dynamic_favicon',
'enable_quoting',
'disable_jump_reply',
'automatically_unpin_topics',
'digest_after_minutes',
'new_topic_duration_minutes',
'auto_track_topics_after_msecs',
'notification_level_when_replying',
'like_notification_frequency',
'include_tl0_in_digests'
].forEach(s => {
const data = this.getProperties(fields ? _.intersection(userFields, fields) : userFields);
let userOptionFields = [
'email_always',
'mailing_list_mode',
'mailing_list_mode_frequency',
'external_links_in_new_tab',
'email_digests',
'email_direct',
'email_in_reply_to',
'email_private_messages',
'email_previous_replies',
'dynamic_favicon',
'enable_quoting',
'disable_jump_reply',
'automatically_unpin_topics',
'digest_after_minutes',
'new_topic_duration_minutes',
'auto_track_topics_after_msecs',
'notification_level_when_replying',
'like_notification_frequency',
'include_tl0_in_digests'
];
if (fields) {
userOptionFields = _.intersection(userOptionFields, fields);
}
userOptionFields.forEach(s => {
data[s] = this.get(`user_option.${s}`);
});
var updatedState = {};
['muted','watched','tracked','watched_first_post'].forEach(s => {
let prop = s === "watched_first_post" ? "watchedFirstPostCategories" : s + "Categories";
let cats = this.get(prop);
if (cats) {
let cat_ids = cats.map(c => c.get('id'));
updatedState[s + '_category_ids'] = cat_ids;
if (fields === undefined || fields.includes(s + '_category_ids')) {
let prop = s === "watched_first_post" ? "watchedFirstPostCategories" : s + "Categories";
let cats = this.get(prop);
if (cats) {
let cat_ids = cats.map(c => c.get('id'));
updatedState[s + '_category_ids'] = cat_ids;
// HACK: denote lack of categories
if (cats.length === 0) { cat_ids = [-1]; }
data[s + '_category_ids'] = cat_ids;
// HACK: denote lack of categories
if (cats.length === 0) { cat_ids = [-1]; }
data[s + '_category_ids'] = cat_ids;
}
}
});
@@ -97,6 +97,15 @@ export default function() {
});
this.route('preferences', { resetNamespace: true }, function() {
this.route('account');
this.route('profile');
this.route('emails');
this.route('notifications');
this.route('categories');
this.route('tags');
this.route('interface');
this.route('apps');
this.route('username');
this.route('email');
this.route('about', { path: '/about-me' });
@@ -0,0 +1,8 @@
import RestrictedUserRoute from "discourse/routes/restricted-user";
export default RestrictedUserRoute.extend({
setupController(controller, user) {
controller.reset();
controller.setProperties({ model: user });
}
});
@@ -1,7 +1,7 @@
import RestrictedUserRoute from "discourse/routes/restricted-user";
export default RestrictedUserRoute.extend({
renderTemplate: function() {
this.render('preferences', { into: 'user', controller: 'preferences' });
redirect() {
this.transitionTo('preferences.account');
}
});
@@ -0,0 +1,11 @@
import RestrictedUserRoute from "discourse/routes/restricted-user";
import { currentThemeKey } from 'discourse/lib/theme-selector';
export default RestrictedUserRoute.extend({
setupController(controller, user) {
controller.setProperties({
model: user,
selectedTheme: $.cookie('theme_key') || currentThemeKey()
});
}
});
@@ -0,0 +1,10 @@
import RestrictedUserRoute from "discourse/routes/restricted-user";
export default RestrictedUserRoute.extend({
setupController(controller, user) {
controller.setProperties({
model: user,
newNameInput: user.get('name')
});
}
});
@@ -1,7 +1,6 @@
import RestrictedUserRoute from "discourse/routes/restricted-user";
import showModal from 'discourse/lib/show-modal';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { currentThemeKey } from 'discourse/lib/theme-selector';
export default RestrictedUserRoute.extend({
model() {
@@ -9,11 +8,8 @@ export default RestrictedUserRoute.extend({
},
setupController(controller, user) {
controller.reset();
controller.setProperties({
model: user,
newNameInput: user.get('name'),
selectedTheme: $.cookie('theme_key') || currentThemeKey()
model: user
});
},
@@ -1,6 +1,6 @@
<label>{{{field.name}}}</label>
<label class="control-label">{{{field.name}}}</label>
<div class='controls'>
{{input value=value maxlength=site.user_field_max_length}}
{{#if field.required}}<span class='required'>*</span>{{/if}}
<p>{{{field.description}}}</p>
<div class="instructions">{{{field.description}}}</div>
</div>
@@ -1,383 +1,25 @@
{{#d-section pageClass="user-preferences" class="user-content user-preferences"}}
{{#d-section pageClass="user-preferences" class="user-navigation"}}
{{#mobile-nav class='preferences-nav' desktopClass='preferences-list action-list nav-stacked' currentPath=application.currentPath}}
<li class='no-glyph nav-account'>{{#link-to 'preferences.account'}}{{i18n 'user.preferences_nav.account'}}{{/link-to}}</li>
<li class='no-glyph nav-profile'>{{#link-to 'preferences.profile'}}{{i18n 'user.preferences_nav.profile'}}{{/link-to}}</li>
<li class='no-glyph nav-emails'>{{#link-to 'preferences.emails'}}{{i18n 'user.preferences_nav.emails'}}{{/link-to}}</li>
<li class='no-glyph nav-notifications'>{{#link-to 'preferences.notifications'}}{{i18n 'user.preferences_nav.notifications'}}{{/link-to}}</li>
<li class='no-glyph indent nav-categories'>{{#link-to 'preferences.categories'}}{{i18n 'user.preferences_nav.categories'}}{{/link-to}}</li>
{{#if siteSettings.tagging_enabled}}
<li class='no-glyph indent nav-tags'>{{#link-to 'preferences.tags'}}{{i18n 'user.preferences_nav.tags'}}{{/link-to}}</li>
{{/if}}
<li class='no-glyph nav-interface'>{{#link-to 'preferences.interface'}}{{i18n 'user.preferences_nav.interface'}}{{/link-to}}</li>
{{#if model.userApiKeys}}
<li class='no-glyph nav-apps'>{{#link-to 'preferences.apps'}}{{i18n 'user.preferences_nav.apps'}}{{/link-to}}</li>
{{/if}}
{{plugin-outlet name="user-preferences-nav"}}
{{/mobile-nav}}
{{/d-section}}
<section class='user-right user-preferences'>
{{plugin-outlet name="above-user-preferences"}}
<form class="form-horizontal">
<div class="control-group save-button" id='save-button-top'>
<div class="controls">
{{partial 'user/preferences/save-button'}}
</div>
</div>
<div class="control-group pref-username">
<label class="control-label">{{i18n 'user.username.title'}}</label>
<div class="controls">
<span class='static'>{{model.username}}</span>
{{#if model.can_edit_username}}
{{#link-to "preferences.username" class="btn btn-small pad-left no-text"}}<i class="fa fa-pencil"></i>{{/link-to}}
{{/if}}
</div>
<div class='instructions'>
{{{i18n 'user.username.short_instructions' username=model.username}}}
</div>
</div>
{{#if canEditName}}
<div class="control-group pref-name">
<label class="control-label">{{i18n 'user.name.title'}}</label>
<div class="controls">
{{#if model.can_edit_name}}
{{text-field value=newNameInput classNames="input-xxlarge"}}
{{else}}
<span class='static'>{{model.name}}</span>
{{/if}}
</div>
<div class='instructions'>
{{nameInstructions}}
</div>
</div>
{{/if}}
{{#if canSelectTitle}}
<div class="control-group pref-title">
<label class="control-label">{{i18n 'user.title.title'}}</label>
<div class="controls">
<span class="static">{{model.title}}</span>
{{#link-to "preferences.badgeTitle" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
</div>
</div>
{{/if}}
{{#if canCheckEmails}}
<div class="control-group pref-email">
<label class="control-label">{{i18n 'user.email.title'}}</label>
{{#if model.email}}
<div class="controls">
<span class='static'>{{model.email}}</span>
{{#if model.can_edit_email}}
{{#link-to "preferences.email" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
{{/if}}
</div>
<div class='instructions'>
{{i18n 'user.email.instructions'}}
</div>
{{else}}
<div class="controls">
{{d-button action="checkEmail" actionParam=model title="admin.users.check_email.title" icon="envelope-o" label="admin.users.check_email.text"}}
</div>
{{/if}}
</div>
{{/if}}
{{#if canChangePassword}}
<div class="control-group pref-password">
<label class="control-label">{{i18n 'user.password.title'}}</label>
<div class="controls">
<a href {{action "changePassword"}} class='btn'>
{{fa-icon "envelope"}}
{{#if model.no_password}}
{{i18n 'user.change_password.set_password'}}
{{else}}
{{i18n 'user.change_password.action'}}
{{/if}}
</a>
{{passwordProgress}}
</div>
</div>
{{/if}}
<div class="control-group pref-avatar">
<label class="control-label">{{i18n 'user.avatar.title'}}</label>
<div class="controls">
{{! we want the "huge" version even though we're downsizing it to "large" in CSS }}
{{bound-avatar model "huge"}}
{{#unless siteSettings.sso_overrides_avatar}}
{{d-button action="showAvatarSelector" class="pad-left" icon="pencil"}}
{{/unless}}
</div>
</div>
{{#if siteSettings.allow_profile_backgrounds}}
<div class="control-group pref-profile-bg">
<label class="control-label">{{i18n 'user.change_profile_background.title'}}</label>
<div class="controls">
{{image-uploader imageUrl=model.profile_background type="profile_background"}}
</div>
<div class='instructions'>
{{i18n 'user.change_profile_background.instructions'}}
</div>
</div>
<div class="control-group pref-profile-bg">
<label class="control-label">{{i18n 'user.change_card_background.title'}}</label>
<div class="controls">
{{image-uploader imageUrl=model.card_background type="card_background"}}
</div>
<div class='instructions'>
{{i18n 'user.change_card_background.instructions'}}
</div>
</div>
{{/if}}
{{#if siteSettings.allow_user_locale}}
<div class="control-group pref-locale">
<label class="control-label">{{i18n 'user.locale.title'}}</label>
<div class="controls">
{{combo-box valueAttribute="value" content=availableLocales value=model.locale none="user.locale.default"}}
</div>
<div class='instructions'>
{{i18n 'user.locale.instructions'}}
</div>
</div>
{{/if}}
{{#if canChangeBio}}
<div class="control-group pref-bio">
<label class="control-label">{{i18n 'user.bio'}}</label>
<div class="controls bio-composer">
{{d-editor value=model.bio_raw}}
</div>
</div>
{{/if}}
{{#each userFields as |uf|}}
{{user-field field=uf.field value=uf.value}}
{{/each}}
<div class='clearfix'></div>
<div class="control-group pref-location">
<label class="control-label">{{i18n 'user.location'}}</label>
<div class="controls">
{{input type="text" value=model.location class="input-xxlarge" id='edit-location'}}
</div>
</div>
<div class="control-group pref-website">
<label class="control-label">{{i18n 'user.website'}}</label>
<div class="controls">
{{input type="text" value=model.website class="input-xxlarge"}}
</div>
</div>
<div class="control-group pref-card-badge">
<label class="control-label">{{i18n 'user.card_badge.title'}}</label>
<div class="controls">
{{#if model.card_image_badge}}
{{icon-or-image model.card_image_badge}}
{{/if}}
{{#link-to "preferences.card-badge" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
</div>
</div>
<div class="control-group pref-email-settings">
<label class="control-label">{{i18n 'user.email_settings'}}</label>
<div class='controls controls-dropdown'>
<label>{{i18n 'user.email_previous_replies.title'}}</label>
{{combo-box valueAttribute="value" content=previousRepliesOptions value=model.user_option.email_previous_replies}}
</div>
{{preference-checkbox labelKey="user.email_in_reply_to" checked=model.user_option.email_in_reply_to}}
{{preference-checkbox labelKey="user.email_private_messages" checked=model.user_option.email_private_messages}}
{{preference-checkbox labelKey="user.email_direct" checked=model.user_option.email_direct}}
{{preference-checkbox labelKey="user.email_always" checked=model.user_option.email_always}}
{{#unless model.user_option.email_always}}
<div class='instructions'>
{{#if siteSettings.email_time_window_mins}}
{{i18n 'user.email.frequency' count=siteSettings.email_time_window_mins}}
{{else}}
{{i18n 'user.email.frequency_immediately'}}
{{/if}}
</div>
{{/unless}}
</div>
{{#unless siteSettings.disable_digest_emails}}
<div class='control-group pref-activity-summary'>
<label class="control-label">{{i18n 'user.email_activity_summary'}}</label>
{{preference-checkbox labelKey="user.email_digests.title" disabled=model.user_option.mailing_list_mode checked=model.user_option.email_digests}}
{{#if model.user_option.email_digests}}
<div class='controls controls-dropdown'>
{{combo-box valueAttribute="value" content=digestFrequencies value=model.user_option.digest_after_minutes}}
</div>
{{preference-checkbox labelKey="user.include_tl0_in_digests" disabled=model.user_option.mailing_list_mode checked=model.user_option.include_tl0_in_digests}}
{{/if}}
</div>
{{/unless}}
{{#unless siteSettings.disable_mailing_list_mode}}
<div class='control-group pref-mailing-list-mode'>
<label class="control-label">{{i18n 'user.mailing_list_mode.label'}}</label>
{{preference-checkbox labelKey="user.mailing_list_mode.enabled" checked=model.user_option.mailing_list_mode}}
<div class='instructions'>{{{i18n 'user.mailing_list_mode.instructions'}}}</div>
{{#if model.user_option.mailing_list_mode}}
<div class='controls controls-dropdown'>
{{combo-box valueAttribute="value" content=mailingListModeOptions value=model.user_option.mailing_list_mode_frequency}}
</div>
{{/if}}
</div>
{{/unless}}
<div class="control-group notifications">
<label class="control-label">{{i18n 'user.desktop_notifications.label'}}</label>
{{desktop-notification-config}}
<div class="instructions">{{i18n 'user.desktop_notifications.each_browser_note'}}</div>
</div>
<div class="control-group other">
<label class="control-label">{{i18n 'user.other_settings'}}</label>
<div class="controls controls-dropdown">
<label>{{i18n 'user.new_topic_duration.label'}}</label>
{{combo-box valueAttribute="value" content=considerNewTopicOptions value=model.user_option.new_topic_duration_minutes}}
</div>
<div class="controls controls-dropdown">
<label>{{i18n 'user.auto_track_topics'}}</label>
{{combo-box valueAttribute="value" content=autoTrackDurations value=model.user_option.auto_track_topics_after_msecs}}
</div>
<div class="controls controls-dropdown">
<label>{{i18n 'user.notification_level_when_replying'}}</label>
{{combo-box valueAttribute="value" content=notificationLevelsForReplying value=model.user_option.notification_level_when_replying}}
</div>
<div class="controls controls-dropdown">
<label>{{i18n 'user.like_notification_frequency.title'}}</label>
{{combo-box valueAttribute="value" content=likeNotificationFrequencies value=model.user_option.like_notification_frequency}}
</div>
{{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.user_option.external_links_in_new_tab}}
{{preference-checkbox labelKey="user.enable_quoting" checked=model.user_option.enable_quoting}}
{{preference-checkbox labelKey="user.dynamic_favicon" checked=model.user_option.dynamic_favicon}}
{{preference-checkbox labelKey="user.disable_jump_reply" checked=model.user_option.disable_jump_reply}}
{{plugin-outlet name="user-custom-preferences" args=(hash model=model)}}
</div>
<div class="control-group category">
<label class="control-label">{{i18n 'user.categories_settings'}}</label>
<div class="controls category-controls">
<label><span class="icon fa fa-exclamation-circle watching"></span> {{i18n 'user.watched_categories'}}</label>
{{category-selector categories=model.watchedCategories blacklist=selectedCategories}}
</div>
<div class="instructions">{{i18n 'user.watched_categories_instructions'}}</div>
<div class="controls category-controls">
<a href="{{unbound model.watchingTopicsPath}}">{{i18n 'user.watched_topics_link'}}</a>
</div>
<div class="controls category-controls">
<label><span class="icon fa fa-circle tracking"></span> {{i18n 'user.tracked_categories'}}</label>
{{category-selector categories=model.trackedCategories blacklist=selectedCategories}}
</div>
<div class="instructions">{{i18n 'user.tracked_categories_instructions'}}</div>
<div class="controls category-controls">
<a href="{{unbound model.trackingTopicsPath}}">{{i18n 'user.tracked_topics_link'}}</a>
</div>
<div class="controls category-controls">
<label><span class="icon fa fa-dot-circle-o watching-first-post"></span> {{i18n 'user.watched_first_post_categories'}}</label>
{{category-selector categories=model.watchedFirstPostCategories}}
</div>
<div class="instructions">{{i18n 'user.watched_first_post_categories_instructions'}}</div>
<div class="controls category-controls">
<label><span class="icon fa fa-times-circle muted"></span> {{i18n 'user.muted_categories'}}</label>
{{category-selector categories=model.mutedCategories blacklist=selectedCategories}}
</div>
<div class="instructions">{{i18n 'user.muted_categories_instructions'}}</div>
<div class="controls category-controls">
<a href="{{unbound model.mutedTopicsPath}}">{{i18n 'user.muted_topics_link'}}</a>
</div>
</div>
{{#if siteSettings.tagging_enabled}}
<div class="control-group tags">
<label class="control-label">{{i18n 'user.tag_settings'}}</label>
<div class="controls tag-controls">
<label><span class="icon fa fa-exclamation-circle watching"></span> {{i18n 'user.watched_tags'}}</label>
{{tag-chooser tags=model.watched_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}}
</div>
<div class="instructions">{{i18n 'user.watched_tags_instructions'}}</div>
<div class="controls tag-controls">
<label><span class="icon fa fa-circle tracking"></span> {{i18n 'user.tracked_tags'}}</label>
{{tag-chooser tags=model.tracked_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}}
</div>
<div class="instructions">{{i18n 'user.tracked_tags_instructions'}}</div>
<div class="controls tag-controls">
<label><span class="icon fa fa-dot-circle-o watching-first-post"></span> {{i18n 'user.watched_first_post_tags'}}</label>
{{tag-chooser tags=model.watching_first_post_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}}
</div>
<div class="instructions">{{i18n 'user.watched_first_post_tags_instructions'}}</div>
<div class="controls tag-controls">
<label><span class="icon fa fa-times-circle muted"></span> {{i18n 'user.muted_tags'}}</label>
{{tag-chooser tags=model.muted_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}}
</div>
<div class="instructions">{{i18n 'user.muted_tags_instructions'}}</div>
</div>
{{/if}}
<div class="control-group muting">
<label class="control-label">{{i18n 'user.users'}}</label>
<div class="controls category-controls">
<label><span class="icon fa fa-times-circle muted"></span> {{i18n 'user.muted_users'}}</label>
{{user-selector excludeCurrentUser=true usernames=model.muted_usernames class="user-selector"}}
</div>
<div class="instructions">{{i18n 'user.muted_users_instructions'}}</div>
</div>
{{#if siteSettings.automatically_unpin_topics}}
<div class="control-group topics">
<label class="control-label">{{i18n 'categories.topics'}}</label>
{{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.user_option.automatically_unpin_topics}}
</div>
{{/if}}
{{#if model.userApiKeys}}
<div class="control-group apps">
<label class="control-label">{{i18n 'user.apps'}}</label>
<div class="controls">
{{#each model.userApiKeys as |key|}}
<div>
<span>{{key.application_name}}</span>
{{#if key.revoked}}
{{d-button action="undoRevokeApiKey" actionParam=key class="btn" label="user.undo_revoke_access"}}
{{else}}
{{d-button action="revokeApiKey" actionParam=key class="btn" label="user.revoke_access"}}
{{/if}}
<p>
<ul>
{{#each key.scopes as |scope|}}
<li>{{scope}}</li>
{{/each}}
</ul>
</p>
<p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p>
</div>
{{/each}}
</div>
</div>
{{/if}}
{{#if userSelectableThemes}}
<div class="control-group theme">
<label class="control-label">{{i18n 'user.theme'}}</label>
<div class="controls">
{{combo-box content=userSelectableThemes value=selectedTheme}}
</div>
</div>
{{/if}}
{{plugin-outlet name="user-custom-controls" args=(hash model=model)}}
<div class="control-group save-button">
<div class="controls">
{{partial 'user/preferences/save-button'}}
</div>
</div>
{{#if model.canDeleteAccount}}
<div class="control-group delete-account">
<hr/>
<div class="controls">
{{d-button action="delete" disabled=deleteDisabled class="btn-danger" icon="trash-o" label="user.delete_account"}}
</div>
</div>
{{/if}}
<form class="form-vertical">
{{outlet}}
</form>
{{/d-section}}
</section>
@@ -0,0 +1,63 @@
<div class="control-group pref-username">
<label class="control-label">{{i18n 'user.username.title'}}</label>
<div class="controls">
<span class='static'>{{model.username}}</span>
{{#if model.can_edit_username}}
{{#link-to "preferences.username" class="btn btn-small pad-left no-text"}}<i class="fa fa-pencil"></i>{{/link-to}}
{{/if}}
</div>
<div class='instructions'>
{{{i18n 'user.username.short_instructions' username=model.username}}}
</div>
</div>
{{#if canCheckEmails}}
<div class="control-group pref-email">
<label class="control-label">{{i18n 'user.email.title'}}</label>
{{#if model.email}}
<div class="controls">
<span class='static'>{{model.email}}</span>
{{#if model.can_edit_email}}
{{#link-to "preferences.email" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
{{/if}}
</div>
<div class='instructions'>
{{i18n 'user.email.instructions'}}
</div>
{{else}}
<div class="controls">
{{d-button action="checkEmail" actionParam=model title="admin.users.check_email.title" icon="envelope-o" label="admin.users.check_email.text"}}
</div>
{{/if}}
</div>
{{/if}}
{{#if canChangePassword}}
<div class="control-group pref-password">
<label class="control-label">{{i18n 'user.password.title'}}</label>
<div class="controls">
<a href {{action "changePassword"}} class='btn'>
{{fa-icon "envelope"}}
{{#if model.no_password}}
{{i18n 'user.change_password.set_password'}}
{{else}}
{{i18n 'user.change_password.action'}}
{{/if}}
</a>
{{passwordProgress}}
</div>
</div>
{{/if}}
{{plugin-outlet name="user-preferences-account"}}
<br/>
{{#if model.canDeleteAccount}}
<div class="control-group delete-account">
<br/>
<div class="controls">
{{d-button action="delete" disabled=deleteDisabled class="btn-danger" icon="trash-o" label="user.delete_account"}}
</div>
</div>
{{/if}}
@@ -0,0 +1,27 @@
{{#if model.userApiKeys}}
<div class="control-group apps">
<label class="control-label">{{i18n 'user.apps'}}</label>
<div class="controls">
{{#each model.userApiKeys as |key|}}
<div>
<span>{{key.application_name}}</span>
{{#if key.revoked}}
{{d-button action="undoRevokeApiKey" actionParam=key class="btn" label="user.undo_revoke_access"}}
{{else}}
{{d-button action="revokeApiKey" actionParam=key class="btn" label="user.revoke_access"}}
{{/if}}
<p>
<ul>
{{#each key.scopes as |scope|}}
<li>{{scope}}</li>
{{/each}}
</ul>
</p>
<p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p>
</div>
{{/each}}
</div>
</div>
{{/if}}
{{plugin-outlet name="user-preferences-apps"}}
@@ -0,0 +1,50 @@
<div class="control-group category-notifications">
<label class="control-label">{{i18n 'user.categories_settings'}}</label>
<div class="controls category-controls">
<label><span class="icon fa fa-exclamation-circle watching"></span> {{i18n 'user.watched_categories'}}</label>
{{category-selector categories=model.watchedCategories blacklist=selectedCategories}}
</div>
<div class="instructions">{{i18n 'user.watched_categories_instructions'}}</div>
<div class="controls">
<a href="{{unbound model.watchingTopicsPath}}">{{i18n 'user.watched_topics_link'}}</a>
</div>
<div class="controls category-controls">
<label><span class="icon fa fa-circle tracking"></span> {{i18n 'user.tracked_categories'}}</label>
{{category-selector categories=model.trackedCategories blacklist=selectedCategories}}
</div>
<div class="instructions">{{i18n 'user.tracked_categories_instructions'}}</div>
<div class="controls">
<a href="{{unbound model.trackingTopicsPath}}">{{i18n 'user.tracked_topics_link'}}</a>
</div>
<div class="controls category-controls">
<label><span class="icon fa fa-dot-circle-o watching-first-post"></span> {{i18n 'user.watched_first_post_categories'}}</label>
{{category-selector categories=model.watchedFirstPostCategories}}
</div>
<div class="instructions">{{i18n 'user.watched_first_post_categories_instructions'}}</div>
<div class="controls category-controls">
<label><span class="icon fa fa-times-circle muted"></span> {{i18n 'user.muted_categories'}}</label>
{{category-selector categories=model.mutedCategories blacklist=selectedCategories}}
</div>
<div class="instructions">{{i18n 'user.muted_categories_instructions'}}</div>
<div class="controls">
<a href="{{unbound model.mutedTopicsPath}}">{{i18n 'user.muted_topics_link'}}</a>
</div>
</div>
{{plugin-outlet name="user-preferences-categories"}}
<br/>
{{plugin-outlet name="user-custom-controls" args=(hash model=model)}}
<div class="control-group save-button">
<div class="controls">
{{partial 'user/preferences/save-button'}}
</div>
</div>
@@ -0,0 +1,58 @@
<div class="control-group pref-email-settings">
<label class="control-label">{{i18n 'user.email_settings'}}</label>
<div class='controls controls-dropdown'>
<label>{{i18n 'user.email_previous_replies.title'}}</label>
{{combo-box valueAttribute="value" content=previousRepliesOptions value=model.user_option.email_previous_replies}}
</div>
{{preference-checkbox labelKey="user.email_in_reply_to" checked=model.user_option.email_in_reply_to}}
{{preference-checkbox labelKey="user.email_private_messages" checked=model.user_option.email_private_messages}}
{{preference-checkbox labelKey="user.email_direct" checked=model.user_option.email_direct}}
{{preference-checkbox labelKey="user.email_always" checked=model.user_option.email_always}}
{{#unless model.user_option.email_always}}
<div class='instructions'>
{{#if siteSettings.email_time_window_mins}}
{{i18n 'user.email.frequency' count=siteSettings.email_time_window_mins}}
{{else}}
{{i18n 'user.email.frequency_immediately'}}
{{/if}}
</div>
{{/unless}}
</div>
{{#unless siteSettings.disable_digest_emails}}
<div class='control-group pref-activity-summary'>
<label class="control-label">{{i18n 'user.email_activity_summary'}}</label>
{{preference-checkbox labelKey="user.email_digests.title" disabled=model.user_option.mailing_list_mode checked=model.user_option.email_digests}}
{{#if model.user_option.email_digests}}
<div class='controls controls-dropdown'>
{{combo-box valueAttribute="value" content=digestFrequencies value=model.user_option.digest_after_minutes}}
</div>
{{preference-checkbox labelKey="user.include_tl0_in_digests" disabled=model.user_option.mailing_list_mode checked=model.user_option.include_tl0_in_digests}}
{{/if}}
</div>
{{/unless}}
{{#unless siteSettings.disable_mailing_list_mode}}
<div class='control-group pref-mailing-list-mode'>
<label class="control-label">{{i18n 'user.mailing_list_mode.label'}}</label>
{{preference-checkbox labelKey="user.mailing_list_mode.enabled" checked=model.user_option.mailing_list_mode}}
<div class='instructions'>{{{i18n 'user.mailing_list_mode.instructions'}}}</div>
{{#if model.user_option.mailing_list_mode}}
<div class='controls controls-dropdown'>
{{combo-box valueAttribute="value" content=mailingListModeOptions value=model.user_option.mailing_list_mode_frequency}}
</div>
{{/if}}
</div>
{{/unless}}
{{plugin-outlet name="user-preferences-emails"}}
<br/>
{{plugin-outlet name="user-custom-controls" args=(hash model=model)}}
<div class="control-group save-button">
<div class="controls">
{{partial 'user/preferences/save-button'}}
</div>
</div>
@@ -0,0 +1,49 @@
{{#if userSelectableThemes}}
<div class="control-group theme">
<label class="control-label">{{i18n 'user.theme'}}</label>
<div class="controls">
{{combo-box content=userSelectableThemes value=selectedTheme}}
</div>
</div>
{{/if}}
{{#if siteSettings.allow_user_locale}}
<div class="control-group pref-locale">
<label class="control-label">{{i18n 'user.locale.title'}}</label>
<div class="controls">
{{combo-box valueAttribute="value" content=availableLocales value=model.locale none="user.locale.default"}}
</div>
<div class='instructions'>
{{i18n 'user.locale.instructions'}}
</div>
</div>
{{/if}}
<div class="control-group other">
<label class="control-label">{{i18n 'user.other_settings'}}</label>
{{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.user_option.external_links_in_new_tab}}
{{preference-checkbox labelKey="user.enable_quoting" checked=model.user_option.enable_quoting}}
{{preference-checkbox labelKey="user.dynamic_favicon" checked=model.user_option.dynamic_favicon}}
{{preference-checkbox labelKey="user.disable_jump_reply" checked=model.user_option.disable_jump_reply}}
</div>
{{#if siteSettings.automatically_unpin_topics}}
<div class="control-group topics">
<label class="control-label">{{i18n 'categories.topics'}}</label>
{{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.user_option.automatically_unpin_topics}}
</div>
{{/if}}
{{plugin-outlet name="user-preferences-interface"}}
<br/>
{{plugin-outlet name="user-custom-controls" args=(hash model=model)}}
<div class="control-group save-button">
<div class="controls">
{{partial 'user/preferences/save-button'}}
</div>
</div>
@@ -0,0 +1,48 @@
<div class="control-group notifications">
<div class="controls controls-dropdown">
<label>{{i18n 'user.new_topic_duration.label'}}</label>
{{combo-box valueAttribute="value" content=considerNewTopicOptions value=model.user_option.new_topic_duration_minutes}}
</div>
<div class="controls controls-dropdown">
<label>{{i18n 'user.auto_track_topics'}}</label>
{{combo-box valueAttribute="value" content=autoTrackDurations value=model.user_option.auto_track_topics_after_msecs}}
</div>
<div class="controls controls-dropdown">
<label>{{i18n 'user.notification_level_when_replying'}}</label>
{{combo-box valueAttribute="value" content=notificationLevelsForReplying value=model.user_option.notification_level_when_replying}}
</div>
<div class="controls controls-dropdown">
<label>{{i18n 'user.like_notification_frequency.title'}}</label>
{{combo-box valueAttribute="value" content=likeNotificationFrequencies value=model.user_option.like_notification_frequency}}
</div>
</div>
<div class="control-group desktop-notifications">
<label class="control-label">{{i18n 'user.desktop_notifications.label'}}</label>
{{desktop-notification-config}}
<div class="instructions">{{i18n 'user.desktop_notifications.each_browser_note'}}</div>
</div>
<div class="control-group muting">
<label class="control-label">{{i18n 'user.users'}}</label>
<div class="controls category-controls">
<label><span class="icon fa fa-times-circle muted"></span> {{i18n 'user.muted_users'}}</label>
{{user-selector excludeCurrentUser=true usernames=model.muted_usernames class="user-selector"}}
</div>
<div class="instructions">{{i18n 'user.muted_users_instructions'}}</div>
</div>
{{plugin-outlet name="user-preferences-notifications"}}
<br/>
{{plugin-outlet name="user-custom-controls" args=(hash model=model)}}
<div class="control-group save-button">
<div class="controls">
{{partial 'user/preferences/save-button'}}
</div>
</div>
@@ -0,0 +1,112 @@
{{#if canEditName}}
<div class="control-group pref-name">
<label class="control-label">{{i18n 'user.name.title'}}</label>
<div class="controls">
{{#if model.can_edit_name}}
{{text-field value=newNameInput classNames="input-xxlarge"}}
{{else}}
<span class='static'>{{model.name}}</span>
{{/if}}
</div>
<div class='instructions'>
{{nameInstructions}}
</div>
</div>
{{/if}}
{{#if canSelectTitle}}
<div class="control-group pref-title">
<label class="control-label">{{i18n 'user.title.title'}}</label>
<div class="controls">
<span class="static">{{model.title}}</span>
{{#link-to "preferences.badgeTitle" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
</div>
</div>
{{/if}}
<div class="control-group pref-avatar">
<label class="control-label">{{i18n 'user.avatar.title'}}</label>
<div class="controls">
{{! we want the "huge" version even though we're downsizing it to "large" in CSS }}
{{bound-avatar model "huge"}}
{{#unless siteSettings.sso_overrides_avatar}}
{{d-button action="showAvatarSelector" class="pad-left" icon="pencil"}}
{{/unless}}
</div>
</div>
{{#if siteSettings.allow_profile_backgrounds}}
<div class="control-group pref-profile-bg">
<label class="control-label">{{i18n 'user.change_profile_background.title'}}</label>
<div class="controls">
{{image-uploader imageUrl=model.profile_background type="profile_background"}}
</div>
<div class='instructions'>
{{i18n 'user.change_profile_background.instructions'}}
</div>
</div>
<div class="control-group pref-profile-bg">
<label class="control-label">{{i18n 'user.change_card_background.title'}}</label>
<div class="controls">
{{image-uploader imageUrl=model.card_background type="card_background"}}
</div>
<div class='instructions'>
{{i18n 'user.change_card_background.instructions'}}
</div>
</div>
{{/if}}
{{#if canChangeBio}}
<div class="control-group pref-bio">
<label class="control-label">{{i18n 'user.bio'}}</label>
<div class="controls bio-composer">
{{d-editor value=model.bio_raw}}
</div>
</div>
{{/if}}
<div class="control-group pref-location">
<label class="control-label">{{i18n 'user.location'}}</label>
<div class="controls">
{{input type="text" value=model.location class="input-xxlarge" id='edit-location'}}
</div>
</div>
<div class="control-group pref-website">
<label class="control-label">{{i18n 'user.website'}}</label>
<div class="controls">
{{input type="text" value=model.website class="input-xxlarge"}}
</div>
</div>
{{#each userFields as |uf|}}
<div class="control-group">
{{user-field field=uf.field value=uf.value}}
</div>
{{/each}}
<div class='clearfix'></div>
<div class="control-group pref-card-badge">
<label class="control-label">{{i18n 'user.card_badge.title'}}</label>
<div class="controls">
{{#if model.card_image_badge}}
{{icon-or-image model.card_image_badge}}
{{/if}}
{{#link-to "preferences.card-badge" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
</div>
</div>
{{plugin-outlet name="user-preferences-profile"}}
{{plugin-outlet name="user-custom-preferences" args=(hash model=model)}}
<br/>
{{plugin-outlet name="user-custom-controls" args=(hash model=model)}}
<div class="control-group save-button">
<div class="controls">
{{partial 'user/preferences/save-button'}}
</div>
</div>
@@ -0,0 +1,43 @@
{{#if siteSettings.tagging_enabled}}
<div class="control-group tag-notifications">
<label class="control-label">{{i18n 'user.tag_settings'}}</label>
<div class="controls tag-controls">
<label><span class="icon fa fa-exclamation-circle watching"></span> {{i18n 'user.watched_tags'}}</label>
{{tag-chooser tags=model.watched_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}}
</div>
<div class="instructions">{{i18n 'user.watched_tags_instructions'}}</div>
<div class="controls tag-controls">
<label><span class="icon fa fa-circle tracking"></span> {{i18n 'user.tracked_tags'}}</label>
{{tag-chooser tags=model.tracked_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}}
</div>
<div class="instructions">{{i18n 'user.tracked_tags_instructions'}}</div>
<div class="controls tag-controls">
<label><span class="icon fa fa-dot-circle-o watching-first-post"></span> {{i18n 'user.watched_first_post_tags'}}</label>
{{tag-chooser tags=model.watching_first_post_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}}
</div>
<div class="instructions">{{i18n 'user.watched_first_post_tags_instructions'}}</div>
<div class="controls tag-controls">
<label><span class="icon fa fa-times-circle muted"></span> {{i18n 'user.muted_tags'}}</label>
{{tag-chooser tags=model.muted_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}}
</div>
<div class="instructions">{{i18n 'user.muted_tags_instructions'}}</div>
</div>
{{plugin-outlet name="user-preferences-tags"}}
<br/>
{{plugin-outlet name="user-custom-controls" args=(hash model=model)}}
<div class="control-group save-button">
<div class="controls">
{{partial 'user/preferences/save-button'}}
</div>
</div>
{{/if}}