+ {{#admin-nav}}
+ {{nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}}
+ {{nav-item route='adminCustomizeCssHtml.index' label='admin.customize.css_html.title'}}
+ {{nav-item route='adminSiteText' label='admin.site_text.title'}}
+ {{nav-item route='adminCustomizeEmailTemplates' label='admin.customize.email_templates.title'}}
+ {{nav-item route='adminUserFields' label='admin.user_fields.title'}}
+ {{nav-item route='adminEmojis' label='admin.emoji.title'}}
+ {{nav-item route='adminPermalinks' label='admin.permalink.title'}}
+ {{nav-item route='adminEmbedding' label='admin.embedding.title'}}
+ {{/admin-nav}}
-
diff --git a/app/assets/javascripts/admin/templates/email.hbs b/app/assets/javascripts/admin/templates/email.hbs
index 73886537778..e14d8ba4b5e 100644
--- a/app/assets/javascripts/admin/templates/email.hbs
+++ b/app/assets/javascripts/admin/templates/email.hbs
@@ -4,6 +4,7 @@
{{nav-item route='adminEmail.sent' label='admin.email.sent'}}
{{nav-item route='adminEmail.skipped' label='admin.email.skipped'}}
{{nav-item route='adminEmail.previewDigest' label='admin.email.preview_digest'}}
+ {{nav-item route='adminCustomizeEmailTemplates' label='admin.customize.email_templates.title'}}
{{/admin-nav}}
diff --git a/app/assets/javascripts/admin/templates/site-text-edit.hbs b/app/assets/javascripts/admin/templates/site-text-edit.hbs
index 2a6693e8f3f..5ff141d0c93 100644
--- a/app/assets/javascripts/admin/templates/site-text-edit.hbs
+++ b/app/assets/javascripts/admin/templates/site-text-edit.hbs
@@ -14,13 +14,4 @@
{{ace-editor content=model.value mode="css"}}
{{/if}}
-
-
- {{#if saved}}{{i18n 'saved'}}{{/if}}
-
+{{save-controls model=model action="saveChanges" saveDisabled=saveDisabled saved=saved}}
diff --git a/app/assets/javascripts/admin/views/admin-customize.js.es6 b/app/assets/javascripts/admin/views/admin-customize.js.es6
deleted file mode 100644
index 240ed0ac73f..00000000000
--- a/app/assets/javascripts/admin/views/admin-customize.js.es6
+++ /dev/null
@@ -1,3 +0,0 @@
-export default Ember.View.extend({
- classNames: ['customize']
-});
diff --git a/app/assets/javascripts/discourse/adapters/email-template.js.es6 b/app/assets/javascripts/discourse/adapters/email-template.js.es6
new file mode 100644
index 00000000000..f57240a116f
--- /dev/null
+++ b/app/assets/javascripts/discourse/adapters/email-template.js.es6
@@ -0,0 +1,7 @@
+import RestAdapter from 'discourse/adapters/rest';
+
+export default RestAdapter.extend({
+ basePath() {
+ return "/admin/customize/";
+ }
+});
diff --git a/app/assets/javascripts/discourse/adapters/topic.js.es6 b/app/assets/javascripts/discourse/adapters/topic.js.es6
index 858eee01eb8..290dfff4b96 100644
--- a/app/assets/javascripts/discourse/adapters/topic.js.es6
+++ b/app/assets/javascripts/discourse/adapters/topic.js.es6
@@ -1,7 +1,6 @@
import RestAdapter from 'discourse/adapters/rest';
export default RestAdapter.extend({
-
find(store, type, findArgs) {
if (findArgs.similar) {
return Discourse.ajax("/topics/similar_to", { data: findArgs.similar });
diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6
index 3cf7eadff21..019a5d8a72a 100644
--- a/app/assets/javascripts/discourse/components/d-editor.js.es6
+++ b/app/assets/javascripts/discourse/components/d-editor.js.es6
@@ -215,6 +215,8 @@ export default Ember.Component.extend({
},
_updatePreview() {
+ if (this._state !== "inDOM") { return; }
+
const value = this.get('value');
const markdownOptions = this.get('markdownOptions') || {};
markdownOptions.sanitize = true;
diff --git a/app/assets/javascripts/discourse/mixins/buffered-content.js.es6 b/app/assets/javascripts/discourse/mixins/buffered-content.js.es6
index fe6cb765d74..9983d6a3753 100644
--- a/app/assets/javascripts/discourse/mixins/buffered-content.js.es6
+++ b/app/assets/javascripts/discourse/mixins/buffered-content.js.es6
@@ -1,6 +1,6 @@
/* global BufferedProxy: true */
export function bufferedProperty(property) {
- return Ember.Mixin.create({
+ const mixin = {
buffered: function() {
return Em.ObjectProxy.extend(BufferedProxy).create({
content: this.get(property)
@@ -14,7 +14,12 @@ export function bufferedProperty(property) {
commitBuffer: function() {
this.get('buffered').applyBufferedChanges();
}
- });
+ };
+
+ // It's a good idea to null out fields when declaring objects
+ mixin.property = null;
+
+ return Ember.Mixin.create(mixin);
}
export default bufferedProperty('content');
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index e361da98342..ee6ba203672 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -1208,6 +1208,16 @@ table.api-keys {
}
+.email-template {
+ input {
+ width: 100%;
+ }
+
+ label {
+ font-weight: bold;
+ }
+}
+
.row.groups {
input[type='text'] {
width: 500px;
diff --git a/app/controllers/admin/email_templates_controller.rb b/app/controllers/admin/email_templates_controller.rb
new file mode 100644
index 00000000000..27fd11d7fe8
--- /dev/null
+++ b/app/controllers/admin/email_templates_controller.rb
@@ -0,0 +1,51 @@
+class Admin::EmailTemplatesController < Admin::AdminController
+
+ def self.email_keys
+ @email_keys ||= ["invite_forum_mailer", "invite_mailer", "invite_password_instructions",
+ "new_version_mailer", "new_version_mailer_with_notes", "queued_posts_reminder",
+ "system_messages.backup_failed", "system_messages.backup_succeeded",
+ "system_messages.blocked_by_staff", "system_messages.bulk_invite_failed",
+ "system_messages.bulk_invite_succeeded", "system_messages.csv_export_failed",
+ "system_messages.csv_export_succeeded", "system_messages.download_remote_images_disabled",
+ "system_messages.email_error_notification", "system_messages.email_reject_auto_generated",
+ "system_messages.email_reject_destination", "system_messages.email_reject_empty",
+ "system_messages.email_reject_invalid_access", "system_messages.email_reject_no_account",
+ "system_messages.email_reject_parsing", "system_messages.email_reject_post_error",
+ "system_messages.email_reject_post_error_specified",
+ "system_messages.email_reject_reply_key", "system_messages.email_reject_topic_closed",
+ "system_messages.email_reject_topic_not_found", "system_messages.email_reject_trust_level",
+ "system_messages.pending_users_reminder", "system_messages.post_hidden",
+ "system_messages.restore_failed", "system_messages.restore_succeeded",
+ "system_messages.spam_post_blocked", "system_messages.too_many_spam_flags",
+ "system_messages.unblocked", "system_messages.user_automatically_blocked",
+ "system_messages.welcome_invite", "system_messages.welcome_user", "test_mailer",
+ "user_notifications.account_created", "user_notifications.admin_login",
+ "user_notifications.authorize_email", "user_notifications.forgot_password",
+ "user_notifications.set_password", "user_notifications.signup",
+ "user_notifications.signup_after_approval",
+ "user_notifications.user_invited_to_private_message_pm",
+ "user_notifications.user_invited_to_topic", "user_notifications.user_mentioned",
+ "user_notifications.user_posted", "user_notifications.user_posted_pm",
+ "user_notifications.user_quoted", "user_notifications.user_replied"]
+ end
+
+ def show
+ end
+
+ def update
+ et = params[:email_template]
+ key = params[:id]
+
+ raise Discourse::NotFound unless self.class.email_keys.include?(params[:id])
+
+ TranslationOverride.upsert!(I18n.locale, "#{key}.subject_template", et[:subject])
+ TranslationOverride.upsert!(I18n.locale, "#{key}.text_body_template", et[:body])
+
+ render_serialized(key, AdminEmailTemplateSerializer, root: 'email_template', rest_serializer: true)
+ end
+
+ def index
+ render_serialized(self.class.email_keys, AdminEmailTemplateSerializer, root: 'email_templates', rest_serializer: true)
+ end
+
+end
diff --git a/app/models/site_text.rb b/app/models/site_text.rb
index 88620766a66..82a0f37407b 100644
--- a/app/models/site_text.rb
+++ b/app/models/site_text.rb
@@ -3,8 +3,8 @@ require_dependency 'site_text_class_methods'
require_dependency 'distributed_cache'
class SiteText < ActiveRecord::Base
-
extend SiteTextClassMethods
+
self.primary_key = 'text_type'
validates_presence_of :value
diff --git a/app/serializers/admin_email_template_serializer.rb b/app/serializers/admin_email_template_serializer.rb
new file mode 100644
index 00000000000..1c8618cfec8
--- /dev/null
+++ b/app/serializers/admin_email_template_serializer.rb
@@ -0,0 +1,19 @@
+class AdminEmailTemplateSerializer < ApplicationSerializer
+ attributes :id, :title, :subject, :body
+
+ def id
+ object
+ end
+
+ def title
+ object.gsub(/.*\./, '').titleize
+ end
+
+ def subject
+ I18n.t("#{object}.subject_template")
+ end
+
+ def body
+ I18n.t("#{object}.text_body_template")
+ end
+end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 72c88369cc3..8c5efd5c318 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -2087,6 +2087,12 @@ en:
color: "Color"
opacity: "Opacity"
copy: "Copy"
+ email_templates:
+ title: "Email Templates"
+ subject: "Subject"
+ body: "Body"
+ none_selected: "Select an email template to begin editing."
+
css_html:
title: "CSS/HTML"
long_title: "CSS and HTML Customizations"
diff --git a/config/routes.rb b/config/routes.rb
index 7c02453cbd9..bf0ecb589b1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -158,6 +158,11 @@ Discourse::Application.routes.draw do
resources :site_text_types, constraints: AdminConstraint.new
resources :user_fields, constraints: AdminConstraint.new
resources :emojis, constraints: AdminConstraint.new
+
+ # They have periods in their URLs often:
+ get 'email_templates' => 'email_templates#index'
+ match 'email_templates/(:id)' => 'email_templates#show', :constraints => { :id => /[0-9a-z\_\.]+/ }, via: :get
+ match 'email_templates/(:id)' => 'email_templates#update', :constraints => { :id => /[0-9a-z\_\.]+/ }, via: :put
end
resources :embeddable_hosts, constraints: AdminConstraint.new
diff --git a/lib/i18n/backend/discourse_i18n.rb b/lib/i18n/backend/discourse_i18n.rb
index 6abe29ca2ba..a59b252b42d 100644
--- a/lib/i18n/backend/discourse_i18n.rb
+++ b/lib/i18n/backend/discourse_i18n.rb
@@ -18,17 +18,6 @@ module I18n
super
end
- def overrides_for(locale)
- @overrides ||= {}
- site_overrides = @overrides[RailsMultisite::ConnectionManagement.current_db] ||= {}
-
- return site_overrides[locale] if site_overrides[locale]
- locale_overrides = site_overrides[locale] = {}
-
-
- locale_overrides
- end
-
# force explicit loading
def load_translations(*filenames)
unless filenames.empty?
@@ -40,24 +29,6 @@ module I18n
[locale, SiteSetting.default_locale.to_sym, :en].uniq.compact
end
- def lookup(locale, key, scope = [], options = {})
-
- # Support interpolation and pluralization of overrides
- if options[:overrides]
- if options[:count]
- result = {}
- options[:overrides].each do |k, v|
- result[k.split('.').last.to_sym] = v if k != key && k.start_with?(key.to_s)
- end
- return result if result.size > 0
- end
-
- return options[:overrides][key] if options[:overrides][key]
- end
-
- super(locale, key, scope, options)
- end
-
def exists?(locale, key)
fallbacks(locale).each do |fallback|
begin
@@ -70,6 +41,25 @@ module I18n
false
end
+ protected
+
+ def lookup(locale, key, scope = [], options = {})
+ # Support interpolation and pluralization of overrides
+ if options[:overrides]
+ if options[:count]
+ result = {}
+ options[:overrides].each do |k, v|
+ result[k.split('.').last.to_sym] = v if k != key && k.start_with?(key.to_s)
+ end
+ return result if result.size > 0
+ end
+
+ return options[:overrides][key] if options[:overrides][key]
+ end
+
+ super(locale, key, scope, options)
+ end
+
end
end
end