From a4a14e6d5ab5358e70fd30c0baec66a3aa10a80a Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Thu, 6 Aug 2015 16:46:49 -0400 Subject: [PATCH 01/71] add back the warning based on Sidekiq queue size, but only when the queue size is 100k or more --- app/models/admin_dashboard_data.rb | 7 ++++++- config/locales/server.en.yml | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index a1dfa5f78a..abe94003dd 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -37,7 +37,7 @@ class AdminDashboardData ruby_version_check, host_names_check, gc_checks, - sidekiq_check, + sidekiq_check || queue_size_check, ram_check, google_oauth2_config_check, facebook_config_check, @@ -109,6 +109,11 @@ class AdminDashboardData I18n.t('dashboard.sidekiq_warning') if Jobs.queued > 0 and (last_job_performed_at.nil? or last_job_performed_at < 2.minutes.ago) end + def queue_size_check + queue_size = Jobs.queued + I18n.t('dashboard.queue_size_warning', queue_size: queue_size) unless queue_size < 100_000 + end + def ram_check I18n.t('dashboard.memory_warning') if MemInfo.new.mem_total and MemInfo.new.mem_total < 1_000_000 end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index cb285896d9..63034613e8 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -703,6 +703,7 @@ en: host_names_warning: "Your config/database.yml file is using the default localhost hostname. Update it to use your site's hostname." gc_warning: 'Your server is using default ruby garbage collection parameters, which will not give you the best performance. Read this topic on performance tuning: Tuning Ruby and Rails for Discourse.' sidekiq_warning: 'Sidekiq is not running. Many tasks, like sending emails, are executed asynchronously by sidekiq. Please ensure at least one sidekiq process is running. Learn about Sidekiq here.' + queue_size_warning: 'The number of queued jobs is %{queue_size}, which is high. This could indicate a problem with the Sidekiq process(es), or you may need to add more Sidekiq workers.' memory_warning: 'Your server is running with less than 1 GB of total memory. At least 1 GB of memory is recommended.' google_oauth2_config_warning: 'The server is configured to allow signup and log in with Google OAuth2 (enable_google_oauth2_logins), but the client id and client secret values are not set. Go to the Site Settings and update the settings. See this guide to learn more.' facebook_config_warning: 'The server is configured to allow signup and log in with Facebook (enable_facebook_logins), but the app id and app secret values are not set. Go to the Site Settings and update the settings. See this guide to learn more.' From fd63d89753a9e2842c4f8ad1a8c40006fa9d59f2 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 7 Aug 2015 11:41:48 +1000 Subject: [PATCH 02/71] FEATURE: simpler definition of enum types --- app/models/category_style_setting.rb | 19 ------------------- config/site_settings.yml | 7 ++++++- lib/site_setting_extension.rb | 22 +++++++++++++++++++--- 3 files changed, 25 insertions(+), 23 deletions(-) delete mode 100644 app/models/category_style_setting.rb diff --git a/app/models/category_style_setting.rb b/app/models/category_style_setting.rb deleted file mode 100644 index e62720ebc0..0000000000 --- a/app/models/category_style_setting.rb +++ /dev/null @@ -1,19 +0,0 @@ -# TODO all enums should probably move out of models -# TODO we should be able to do this kind of stuff without a backing class -require_dependency 'enum_site_setting' - -class CategoryStyleSetting < EnumSiteSetting - - VALUES = ["bar", "box", "bullet"] - - def self.valid_value?(val) - VALUES.include?(val) - end - - def self.values - VALUES.map do |l| - {name: l, value: l} - end - end - -end diff --git a/config/site_settings.yml b/config/site_settings.yml index 42401150f9..2bf7eb4369 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -12,6 +12,7 @@ # type: email - Must be a valid email address. # type: username - Must match the username of an existing user. # type: list - A list of values, chosen from a set of valid values defined in the choices option. +# type: enum - A single value, chosen from a set of valid values in the choices option. # # A type:list setting with the word 'colors' in its name will make color values have a bold line of the corresponding color # @@ -147,7 +148,11 @@ basic: category_style: client: true default: 'bullet' - enum: 'CategoryStyleSetting' + type: enum + choices: + - bar + - box + - bullet preview: 'Category' enable_mobile_theme: client: true diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index facb22bb4b..228a7e6f39 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -3,6 +3,11 @@ require_dependency 'site_settings/db_provider' module SiteSettingExtension + # For plugins, so they can tell if a feature is supported + def supported_types + [:email, :username, :list, :enum] + end + # part 1 of refactor, centralizing the dependency here def provider=(val) @provider = val @@ -171,7 +176,13 @@ module SiteSettingExtension category: categories[s], preview: previews[s] } - opts.merge!({valid_values: enum_class(s).values, translate_names: enum_class(s).translate_names?}) if type == :enum + + if type == :enum && enum_class(s) + opts.merge!({valid_values: enum_class(s).values, translate_names: enum_class(s).translate_names?}) + elsif type == :enum + opts.merge!({valid_values: choices[s].map{|c| {name: c, value: c}}, translate_names: false}) + end + opts[:choices] = choices[s] if choices.has_key? s opts end @@ -278,7 +289,11 @@ module SiteSettingExtension end if type == types[:enum] - raise Discourse::InvalidParameters.new(:value) unless enum_class(name).valid_value?(val) + if enum_class(name) + raise Discourse::InvalidParameters.new(:value) unless enum_class(name).valid_value?(val) + else + raise Discourse::InvalidParameters.new(:value) unless choices[name].include?(val) + end end if v = validators[name] @@ -408,7 +423,8 @@ module SiteSettingExtension 'username' => UsernameSettingValidator, types[:fixnum] => IntegerSettingValidator, types[:string] => StringSettingValidator, - 'list' => StringSettingValidator + 'list' => StringSettingValidator, + 'enum' => StringSettingValidator } @validator_mapping[type_name] end From 415a3ff0de2457dd00bda8d50e8cf46bb7585c04 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 7 Aug 2015 16:52:21 +1000 Subject: [PATCH 03/71] FIX: replace all occurances in preview not only first --- app/assets/javascripts/admin/components/site-setting.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/components/site-setting.js.es6 b/app/assets/javascripts/admin/components/site-setting.js.es6 index c7375af4fc..d56d1f2e8d 100644 --- a/app/assets/javascripts/admin/components/site-setting.js.es6 +++ b/app/assets/javascripts/admin/components/site-setting.js.es6 @@ -14,7 +14,7 @@ export default Ember.Component.extend(BufferedContent, ScrollTop, { const preview = this.get('setting.preview'); if (preview) { return new Handlebars.SafeString("
" + - preview.replace("{{value}}", this.get('buffered.value')) + + preview.replace(/\{\{value\}\}/g, this.get('buffered.value')) + "
"); } }.property('buffered.value'), From 1a6cbd37a5daaabc41c9bebf518322d2f29f8fef Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 7 Aug 2015 19:12:47 +1000 Subject: [PATCH 04/71] missing outlet for tags on mobile --- .../discourse/templates/mobile/list/topic_list_item.raw.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs index f52e48ba43..972acfa16a 100644 --- a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs @@ -14,6 +14,7 @@ {{category-link content.category}} {{/unless}} + {{plugin-outlet "topic-list-tags"}}
{{raw "list/posts-count-column" topic=content tagName="div"}} From 9156d6cd9d9f6f0c94f1a350cc50559b6f6216df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 7 Aug 2015 16:30:30 +0200 Subject: [PATCH 05/71] FIX: only migrate SiteText to SiteCustomization if there are any --- ...migrate_site_text_to_site_customization.rb | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/db/migrate/20150112172259_migrate_site_text_to_site_customization.rb b/db/migrate/20150112172259_migrate_site_text_to_site_customization.rb index 569f4b98b2..4e3b18fe73 100644 --- a/db/migrate/20150112172259_migrate_site_text_to_site_customization.rb +++ b/db/migrate/20150112172259_migrate_site_text_to_site_customization.rb @@ -2,18 +2,25 @@ class MigrateSiteTextToSiteCustomization < ActiveRecord::Migration def up execute <<-SQL - INSERT INTO site_customizations - (name, user_id, enabled, key, created_at, updated_at, head_tag, body_tag) - VALUES ( - 'Migrated from Site Text', - -1, - 't', - '#{SecureRandom.uuid}', - now(), - now(), - (SELECT value FROM site_texts WHERE text_type = 'head' LIMIT 1), - (SELECT value FROM site_texts WHERE text_type = 'bottom' LIMIT 1) - ) + DO + $do$ + BEGIN + IF EXISTS(SELECT 1 FROM site_texts WHERE (text_type = 'head' OR text_type = 'bottom')) THEN + INSERT INTO site_customizations (name, user_id, enabled, key, created_at, updated_at, head_tag, body_tag) + VALUES + ( + 'Migrated from Site Text', + -1, + 't', + '#{SecureRandom.uuid}', + now(), + now(), + (SELECT value FROM site_texts WHERE text_type = 'head' LIMIT 1), + (SELECT value FROM site_texts WHERE text_type = 'bottom' LIMIT 1) + ); + END IF; + END + $do$ SQL end From 15418f3d44df7c248ebdb1239092e0aa676b3947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 7 Aug 2015 17:34:58 +0200 Subject: [PATCH 06/71] FEATURE: new 'backup_frequency' site setting --- .../controllers/admin-backups-index.js.es6 | 10 +++++----- .../admin/controllers/admin-backups.js.es6 | 4 ++-- .../admin/routes/admin-backups.js.es6 | 10 +++++----- .../javascripts/admin/templates/backups.hbs | 4 ++-- .../admin/templates/backups_index.hbs | 10 +++++----- .../admin/views/admin-backups-logs.js.es6 | 20 +++++++++---------- ...reate_daily_backup.rb => create_backup.rb} | 4 ++-- app/jobs/scheduled/schedule_backup.rb | 10 ++++++++-- app/models/site_setting.rb | 4 ++++ config/locales/server.en.yml | 2 +- config/site_settings.yml | 12 ++++------- spec/jobs/create_daily_backup_spec.rb | 18 ----------------- 12 files changed, 48 insertions(+), 60 deletions(-) rename app/jobs/regular/{create_daily_backup.rb => create_backup.rb} (71%) delete mode 100644 spec/jobs/create_daily_backup_spec.rb diff --git a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 index 8eceb0bb7d..6a44057121 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 @@ -1,20 +1,20 @@ export default Ember.ArrayController.extend({ needs: ["adminBackups"], status: Em.computed.alias("controllers.adminBackups"), - isOperationRunning: Em.computed.alias("status.isOperationRunning"), - restoreDisabled: Em.computed.alias("status.restoreDisabled"), + isOperationRunning: Em.computed.alias("status.model.isOperationRunning"), + restoreDisabled: Em.computed.alias("status.model.restoreDisabled"), uploadLabel: function() { return I18n.t("admin.backups.upload.label"); }.property(), restoreTitle: function() { - if (!this.get('status.allowRestore')) { + if (!this.get('status.model.allowRestore')) { return "admin.backups.operations.restore.is_disabled"; - } else if (this.get("status.isOperationRunning")) { + } else if (this.get("status.model.isOperationRunning")) { return "admin.backups.operations.is_running"; } else { return "admin.backups.operations.restore.title"; } - }.property("status.{allowRestore,isOperationRunning}"), + }.property("status.model.{allowRestore,isOperationRunning}"), actions: { diff --git a/app/assets/javascripts/admin/controllers/admin-backups.js.es6 b/app/assets/javascripts/admin/controllers/admin-backups.js.es6 index 64bb7ad105..287ac92e46 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups.js.es6 @@ -1,5 +1,5 @@ export default Ember.ObjectController.extend({ - noOperationIsRunning: Em.computed.not("isOperationRunning"), - rollbackEnabled: Em.computed.and("canRollback", "restoreEnabled", "noOperationIsRunning"), + noOperationIsRunning: Em.computed.not("model.isOperationRunning"), + rollbackEnabled: Em.computed.and("model.canRollback", "model.restoreEnabled", "noOperationIsRunning"), rollbackDisabled: Em.computed.not("rollbackEnabled") }); diff --git a/app/assets/javascripts/admin/routes/admin-backups.js.es6 b/app/assets/javascripts/admin/routes/admin-backups.js.es6 index 981dc5394b..a48d16f16e 100644 --- a/app/assets/javascripts/admin/routes/admin-backups.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-backups.js.es6 @@ -10,14 +10,14 @@ export default Discourse.Route.extend({ _processLogMessage(log) { if (log.message === "[STARTED]") { - this.controllerFor("adminBackups").set("isOperationRunning", true); + this.controllerFor("adminBackups").set("model.isOperationRunning", true); this.controllerFor("adminBackupsLogs").clear(); } else if (log.message === "[FAILED]") { - this.controllerFor("adminBackups").set("isOperationRunning", false); + this.controllerFor("adminBackups").set("model.isOperationRunning", false); bootbox.alert(I18n.t("admin.backups.operations.failed", { operation: log.operation })); } else if (log.message === "[SUCCESS]") { Discourse.User.currentProp("hideReadOnlyAlert", false); - this.controllerFor("adminBackups").set("isOperationRunning", false); + this.controllerFor("adminBackups").set("model.isOperationRunning", false); if (log.operation === "restore") { // redirect to homepage when the restore is done (session might be lost) window.location.pathname = Discourse.getURL("/"); @@ -30,7 +30,7 @@ export default Discourse.Route.extend({ model() { return PreloadStore.getAndRemove("operations_status", function() { return Discourse.ajax("/admin/backups/status.json"); - }).then(function (status) { + }).then(status => { return Discourse.BackupStatus.create({ isOperationRunning: status.is_operation_running, canRollback: status.can_rollback, @@ -99,7 +99,7 @@ export default Discourse.Route.extend({ function(confirmed) { if (confirmed) { Discourse.Backup.cancel().then(function() { - self.controllerFor("adminBackups").set("isOperationRunning", false); + self.modelFor("adminBackups").set("isOperationRunning", false); }); } } diff --git a/app/assets/javascripts/admin/templates/backups.hbs b/app/assets/javascripts/admin/templates/backups.hbs index d274285b2b..7562754c08 100644 --- a/app/assets/javascripts/admin/templates/backups.hbs +++ b/app/assets/javascripts/admin/templates/backups.hbs @@ -6,7 +6,7 @@
- {{#if canRollback}} + {{#if model.canRollback}} {{d-button action="rollback" class="btn-rollback" label="admin.backups.operations.rollback.label" @@ -14,7 +14,7 @@ icon="ambulance" disabled=rollbackDisabled}} {{/if}} - {{#if isOperationRunning}} + {{#if model.isOperationRunning}} {{d-button action="cancelOperation" class="btn-danger" title="admin.backups.operations.cancel.title" diff --git a/app/assets/javascripts/admin/templates/backups_index.hbs b/app/assets/javascripts/admin/templates/backups_index.hbs index c6a6598be6..0b66fcaf73 100644 --- a/app/assets/javascripts/admin/templates/backups_index.hbs +++ b/app/assets/javascripts/admin/templates/backups_index.hbs @@ -6,9 +6,9 @@
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadLabel title="admin.backups.upload.title"}} {{#if site.isReadOnly}} - {{d-button icon="eye" action="toggleReadOnlyMode" disabled=isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}} + {{d-button icon="eye" action="toggleReadOnlyMode" disabled=model.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}} {{else}} - {{d-button icon="eye" action="toggleReadOnlyMode" disabled=isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}} + {{d-button icon="eye" action="toggleReadOnlyMode" disabled=model.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}} {{/if}}
@@ -20,12 +20,12 @@
{{fa-icon "download"}}{{i18n 'admin.backups.operations.download.label'}} - {{#if isOperationRunning}} + {{#if model.isOperationRunning}} {{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" disabled="true" title="admin.backups.operations.is_running"}} - {{d-button icon="play" action="startRestore" actionParam=backup disabled=restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}} + {{d-button icon="play" action="startRestore" actionParam=backup disabled=model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}} {{else}} {{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" title="admin.backups.operations.destroy.title"}} - {{d-button icon="play" action="startRestore" actionParam=backup disabled=restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}} + {{d-button icon="play" action="startRestore" actionParam=backup disabled=model.restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}} {{/if}}
diff --git a/app/assets/javascripts/admin/views/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/views/admin-backups-logs.js.es6 index a7c2af453b..d4f03cd138 100644 --- a/app/assets/javascripts/admin/views/admin-backups-logs.js.es6 +++ b/app/assets/javascripts/admin/views/admin-backups-logs.js.es6 @@ -5,20 +5,20 @@ export default Discourse.View.extend({ _initialize: function() { this._reset(); }.on("init"), - _reset: function() { + _reset() { this.setProperties({ formattedLogs: "", index: 0 }); }, _updateFormattedLogs: Discourse.debounce(function() { - var logs = this.get("controller.model"); + const logs = this.get("controller.model"); if (logs.length === 0) { this._reset(); // reset the cached logs whenever the model is reset } else { // do the log formatting only once for HELLish performance - var formattedLogs = this.get("formattedLogs"); - for (var i = this.get("index"), length = logs.length; i < length; i++) { - var date = logs[i].get("timestamp"), - message = Handlebars.Utils.escapeExpression(logs[i].get("message")); + let formattedLogs = this.get("formattedLogs"); + for (let i = this.get("index"), length = logs.length; i < length; i++) { + const date = logs[i].get("timestamp"), + message = Handlebars.Utils.escapeExpression(logs[i].get("message")); formattedLogs += "[" + date + "] " + message + "\n"; } // update the formatted logs & cache index @@ -28,8 +28,8 @@ export default Discourse.View.extend({ } }, 150).observes("controller.model.@each"), - render: function(buffer) { - var formattedLogs = this.get("formattedLogs"); + render(buffer) { + const formattedLogs = this.get("formattedLogs"); if (formattedLogs && formattedLogs.length > 0) { buffer.push("
");
       buffer.push(formattedLogs);
@@ -38,13 +38,13 @@ export default Discourse.View.extend({
       buffer.push("

" + I18n.t("admin.backups.logs.none") + "

"); } // add a loading indicator - if (this.get("controller.status.isOperationRunning")) { + if (this.get("controller.status.model.isOperationRunning")) { buffer.push(renderSpinner('small')); } }, _forceScrollToBottom: function() { - var $div = this.$()[0]; + const $div = this.$()[0]; $div.scrollTop = $div.scrollHeight; }.on("didInsertElement") diff --git a/app/jobs/regular/create_daily_backup.rb b/app/jobs/regular/create_backup.rb similarity index 71% rename from app/jobs/regular/create_daily_backup.rb rename to app/jobs/regular/create_backup.rb index 40024fc76d..d455e6ab8c 100644 --- a/app/jobs/regular/create_daily_backup.rb +++ b/app/jobs/regular/create_backup.rb @@ -1,11 +1,11 @@ require "backup_restore/backup_restore" module Jobs - class CreateDailyBackup < Jobs::Base + class CreateBackup < Jobs::Base sidekiq_options retry: false def execute(args) - return unless SiteSetting.backup_daily? + return unless SiteSetting.backups_enabled? BackupRestore.backup!(Discourse.system_user.id, publish_to_message_bus: false) end end diff --git a/app/jobs/scheduled/schedule_backup.rb b/app/jobs/scheduled/schedule_backup.rb index 7408641e54..fa1584b6a0 100644 --- a/app/jobs/scheduled/schedule_backup.rb +++ b/app/jobs/scheduled/schedule_backup.rb @@ -5,8 +5,14 @@ module Jobs sidekiq_options retry: false def execute(args) - return unless SiteSetting.backup_daily? - Jobs.enqueue_in(rand(10.minutes), :create_daily_backup) + return unless SiteSetting.backups_enabled? + + if latest_backup = Backup.all[0] + date = Date.parse(latest_backup.filename[/\d{4}-\d{2}-\d{2}/]) + return if date + SiteSetting.backup_frequency.days > Time.now + end + + Jobs.enqueue_in(rand(10.minutes), :create_backup) end end end diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index 02dad62554..56c1cb5f95 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -111,6 +111,10 @@ class SiteSetting < ActiveRecord::Base false end + def self.backups_enabled? + SiteSetting.backup_frequency > 0 + end + end # == Schema Information diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 63034613e8..7d10fb4eaf 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -938,7 +938,7 @@ en: allow_restore: "Allow restore, which can replace ALL site data! Leave false unless you plan to restore a backup" maximum_backups: "The maximum amount of backups to keep on disk. Older backups are automatically deleted" - backup_daily: "Automatically create a site backup once a day." + backup_frequency: "How frequently we create a site backup, in days." enable_s3_backups: "Upload backups to S3 when complete. IMPORTANT: requires valid S3 credentials entered in Files settings." s3_backup_bucket: "The remote bucket to hold backups. WARNING: Make sure it is a private bucket." diff --git a/config/site_settings.yml b/config/site_settings.yml index 2bf7eb4369..e000630c93 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -43,7 +43,6 @@ required: client: true default: '/images/d-logo-sketch-small.png' digest_logo_url: - client: false default: '' mobile_logo_url: client: true @@ -70,7 +69,6 @@ basic: default: 5 min: 0 limit_suggested_to_category: - client: false default: false default_external_links_in_new_tab: false track_external_right_clicks: @@ -789,19 +787,17 @@ legal: backups: allow_restore: - client: false default: false maximum_backups: client: true default: 7 - backup_daily: - client: false - default: false + backup_frequency: + min: 0 + max: 7 + default: 1 enable_s3_backups: - client: false default: false s3_backup_bucket: - client: false default: '' regex: "^[^A-Z_.]+$" # can't use '.' when using HTTPS diff --git a/spec/jobs/create_daily_backup_spec.rb b/spec/jobs/create_daily_backup_spec.rb deleted file mode 100644 index 0430f367d0..0000000000 --- a/spec/jobs/create_daily_backup_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -require_dependency 'jobs/regular/create_daily_backup' - -describe Jobs::CreateDailyBackup do - it "does nothing when daily backups are disabled" do - SiteSetting.stubs(:backup_daily?).returns(false) - BackupRestore.expects(:backup!).never - Jobs::CreateDailyBackup.new.execute({}) - end - - it "calls `backup!` when the daily backups are enabled" do - SiteSetting.stubs(:backup_daily?).returns(true) - BackupRestore.expects(:backup!).with(Discourse.system_user.id, { publish_to_message_bus: false }).once - Jobs::CreateDailyBackup.new.execute({}) - end -end - From 1aa075f70bed81b419e1a209ce74dd2ce7205cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 7 Aug 2015 18:06:55 +0200 Subject: [PATCH 07/71] FIX: hide custom top/footer in admin section --- app/assets/javascripts/admin/routes/admin.js.es6 | 16 ++++++++++++++++ .../javascripts/admin/routes/admin_route.js | 5 ----- .../discourse/controllers/application.js.es6 | 1 + .../discourse/templates/application.hbs | 4 +++- 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 app/assets/javascripts/admin/routes/admin.js.es6 delete mode 100644 app/assets/javascripts/admin/routes/admin_route.js diff --git a/app/assets/javascripts/admin/routes/admin.js.es6 b/app/assets/javascripts/admin/routes/admin.js.es6 new file mode 100644 index 0000000000..c1b80b2df5 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin.js.es6 @@ -0,0 +1,16 @@ +export default Discourse.Route.extend({ + titleToken() { + return I18n.t('admin_title'); + }, + + activate() { + this.controllerFor("application").setProperties({ + showTop: false, + showFooter: false, + }); + }, + + deactivate() { + this.controllerFor("application").set("showTop", true); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin_route.js b/app/assets/javascripts/admin/routes/admin_route.js deleted file mode 100644 index 07fc597f03..0000000000 --- a/app/assets/javascripts/admin/routes/admin_route.js +++ /dev/null @@ -1,5 +0,0 @@ -Discourse.AdminRoute = Discourse.Route.extend({ - titleToken: function() { - return I18n.t('admin_title'); - } -}); diff --git a/app/assets/javascripts/discourse/controllers/application.js.es6 b/app/assets/javascripts/discourse/controllers/application.js.es6 index 6017af3214..2f0b4cec5f 100644 --- a/app/assets/javascripts/discourse/controllers/application.js.es6 +++ b/app/assets/javascripts/discourse/controllers/application.js.es6 @@ -1,4 +1,5 @@ export default Ember.Controller.extend({ + showTop: true, showFooter: false, styleCategory: null, diff --git a/app/assets/javascripts/discourse/templates/application.hbs b/app/assets/javascripts/discourse/templates/application.hbs index 7eb87f79e3..9d92d2063f 100644 --- a/app/assets/javascripts/discourse/templates/application.hbs +++ b/app/assets/javascripts/discourse/templates/application.hbs @@ -2,7 +2,9 @@
- {{custom-html "top"}} + {{#if showTop}} + {{custom-html "top"}} + {{/if}} {{global-notice}}
{{outlet}} From 485d9a5a5256846474f3a559f8c82b073cb9f095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 7 Aug 2015 19:31:15 +0200 Subject: [PATCH 08/71] Log an error when we can't reach an image to get its dimensions --- lib/cooked_post_processor.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 233f299d11..262b4f0a90 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -107,13 +107,18 @@ class CookedPostProcessor end def get_size(url) + return @size_cache[url] if @size_cache.has_key?(url) + absolute_url = url absolute_url = Discourse.base_url_no_prefix + absolute_url if absolute_url =~ /^\/[^\/]/ # FastImage fails when there's no scheme absolute_url = SiteSetting.scheme + ":" + absolute_url if absolute_url.start_with?("//") + return unless is_valid_image_url?(absolute_url) + # we can *always* crawl our own images return unless SiteSetting.crawl_images? || Discourse.store.has_been_uploaded?(url) + @size_cache[url] ||= FastImage.size(absolute_url) rescue Zlib::BufError # FastImage.size raises BufError for some gifs end @@ -131,6 +136,12 @@ class CookedPostProcessor width, height = img["width"].to_i, img["height"].to_i original_width, original_height = get_size(src) + # can't reach the image... + if original_width.nil? || original_height.nil? + Rails.logger.error "Can't reach '#{src}' to get its dimension." + return + end + return if original_width.to_i <= width && original_height.to_i <= height return if original_width.to_i <= SiteSetting.max_image_width && original_height.to_i <= SiteSetting.max_image_height From 0300da59e28d54447278439c0107f892f059020b Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Fri, 7 Aug 2015 22:55:49 +0530 Subject: [PATCH 09/71] UX: simplify composer title --- app/assets/javascripts/discourse/models/composer.js.es6 | 6 ++++-- config/locales/client.en.yml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index edde5df3c6..18378f9aee 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -101,12 +101,13 @@ const Composer = RestModel.extend({ actionTitle: function() { const topic = this.get('topic'); - let postLink, topicLink; + let postLink, topicLink, usernameLink; if (topic) { const postNumber = this.get('post.post_number'); postLink = "" + I18n.t("post.post_number", { number: postNumber }) + ""; topicLink = " " + (Handlebars.Utils.escapeExpression(topic.get('title'))) + ""; + usernameLink = "" + this.get('post.username') + ""; } let postDescription; @@ -116,7 +117,8 @@ const Composer = RestModel.extend({ postDescription = I18n.t('post.' + this.get('action'), { link: postLink, replyAvatar: Discourse.Utilities.tinyAvatar(post.get('avatar_template')), - username: this.get('post.username') + username: this.get('post.username'), + usernameLink }); if (!Discourse.Mobile.mobileView) { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 844729482e..a08fb767b3 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1251,8 +1251,8 @@ en: other: "You have selected {{count}} posts." post: - reply: "Replying to {{link}} {{replyAvatar}} {{username}}" - reply_topic: "Reply to {{link}}" + reply: " {{replyAvatar}} {{usernameLink}}" + reply_topic: " {{link}}" quote_reply: "quote reply" edit: "Editing {{link}} {{replyAvatar}} {{username}}" edit_reason: "Reason: " From 262e94c48ea0472ef3a513ca8208050501ded084 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 7 Aug 2015 12:43:29 -0700 Subject: [PATCH 10/71] make all tags grey in .reply-to area --- app/assets/stylesheets/desktop/compose.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss index 9efb687221..b09b0840d4 100644 --- a/app/assets/stylesheets/desktop/compose.scss +++ b/app/assets/stylesheets/desktop/compose.scss @@ -336,7 +336,7 @@ .reply-to { margin-bottom: 10px; - .reply-to-glyph { + i { color: scale-color($primary, $lightness: 50%); } } From 3427049ce76275ab1a45985c581e6169afed4eba Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Fri, 7 Aug 2015 12:49:36 -0700 Subject: [PATCH 11/71] UX: simpler styling on post reply button --- app/assets/stylesheets/desktop/topic-post.scss | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index 873ed56a2e..fe3f9d7143 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -123,19 +123,8 @@ nav.post-controls { button.create { margin-right: 0; - color: $tertiary; - background: dark-light-diff($tertiary, $secondary, 85%, -65%); - padding: 8px 20px; + color: scale-color($primary, $lightness: 20%); margin-left: 10px; - - &:hover { color: $secondary; - background: dark-light-diff($tertiary, $secondary, 0%, -20%) - } - - &:active { - background: dark-light-diff($tertiary, $secondary, 50%, -35%); - box-shadow: inset 0 1px 3px rgba(0,0,0, .3); - } } .create i { From d76fafc8ec35e5243985876fbd8ae3fa083dc196 Mon Sep 17 00:00:00 2001 From: Grzegorz Tanczyk Date: Sat, 8 Aug 2015 00:00:10 +0200 Subject: [PATCH 12/71] Adding two plugin outlets for notifications and their dropdowns extensibility --- app/assets/javascripts/discourse/templates/header.hbs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index 3a8bd239ed..42b2323f6a 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -12,6 +12,9 @@ {{/unless}} {{#if view.renderDropdowns}} + + {{plugin-outlet "header-before-dropdowns"}} + {{render "search"}} {{render "notifications" notifications}} From 3eeb765f00dceb1422c3d32d4a40faa8eed14003 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 10 Aug 2015 11:41:11 +1000 Subject: [PATCH 13/71] fix deprecation --- .../javascripts/discourse/templates/mobile/discovery/topics.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs index 82f0e59d79..7a02b2a778 100644 --- a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs @@ -38,7 +38,7 @@

{{footerMessage}} - {{#if can_create_topic}}{{i18n 'topic.suggest_create_topic'}}{{/if}} + {{#if model.can_create_topic}}{{i18n 'topic.suggest_create_topic'}}{{/if}}

{{else}} {{#if top}} From 2052ceca9567f29ae4ec460cbff2df787e782eea Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 10 Aug 2015 13:19:01 +1000 Subject: [PATCH 14/71] FIX: stop screen from jittering when mobile has images This fix makes sure we apply reasonable settings to all images while they are loading, it stops mobile from dancing around on topics that have images We no longer use height: auto on mobile! --- .../ensure-max-image-dimensions.js.es6 | 22 +++++++++++++++ .../discourse/views/composer.js.es6 | 16 ----------- .../javascripts/discourse/views/post.js.es6 | 27 +++++++++++++++++++ 3 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 app/assets/javascripts/discourse/initializers/ensure-max-image-dimensions.js.es6 diff --git a/app/assets/javascripts/discourse/initializers/ensure-max-image-dimensions.js.es6 b/app/assets/javascripts/discourse/initializers/ensure-max-image-dimensions.js.es6 new file mode 100644 index 0000000000..d4ed00aed2 --- /dev/null +++ b/app/assets/javascripts/discourse/initializers/ensure-max-image-dimensions.js.es6 @@ -0,0 +1,22 @@ +export default { + name: 'ensure-image-dimensions', + initialize: function() { + if (!window) { return; } + + // This enforces maximum dimensions of images based on site settings + // for mobile we use the window width as a safeguard + // This rule should never really be at play unless for some reason images do not have dimensions + + var width = Discourse.SiteSettings.max_image_width; + var height = Discourse.SiteSettings.max_image_height; + + if (Discourse.Mobile.mobileView) { + width = $(window).width() - 20; + } + + const style = 'max-width:' + width + 'px;' + + 'max-height:' + height + 'px;'; + + $('').appendTo('head'); + } +}; diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6 index 6c3047ba8f..865daa886e 100644 --- a/app/assets/javascripts/discourse/views/composer.js.es6 +++ b/app/assets/javascripts/discourse/views/composer.js.es6 @@ -130,7 +130,6 @@ const ComposerView = Discourse.View.extend(Ember.Evented, { onDrag(sizePx) { self.movePanels.apply(self, [sizePx]); } }); afterTransition($replyControl, resizer); - this.ensureMaximumDimensionForImagesInPreview(); this.set('controller.view', this); positioningWorkaround(this.$()); @@ -140,21 +139,6 @@ const ComposerView = Discourse.View.extend(Ember.Evented, { this.set('controller.view', null); }.on('willDestroyElement'), - ensureMaximumDimensionForImagesInPreview() { - // This enforce maximum dimensions of images in the preview according - // to the current site settings. - // For interactivity, we immediately insert the locally cooked version - // of the post into the stream when the user hits reply. We therefore also - // need to enforce these rules on the .cooked version. - // Meanwhile, the server is busy post-processing the post and generating thumbnails. - const style = Discourse.Mobile.mobileView ? - 'max-width: 100%; height: auto;' : - 'max-width:' + Discourse.SiteSettings.max_image_width + 'px;' + - 'max-height:' + Discourse.SiteSettings.max_image_height + 'px;'; - - $('').appendTo('head'); - }, - click() { this.get('controller').send('openIfDraft'); }, diff --git a/app/assets/javascripts/discourse/views/post.js.es6 b/app/assets/javascripts/discourse/views/post.js.es6 index 12891e215c..38fb86f0f4 100644 --- a/app/assets/javascripts/discourse/views/post.js.es6 +++ b/app/assets/javascripts/discourse/views/post.js.es6 @@ -282,6 +282,33 @@ const PostView = Discourse.GroupedView.extend(Ember.Evented, { this._applySearchHighlight(); }.on('didInsertElement'), + _fixImageSizes: function(){ + var maxWidth; + this.$('img:not(.avatar)').each(function(idx,img){ + + // deferring work only for posts with images + // we got to use screen here, cause nothing is rendered yet. + // long term we may want to allow for weird margins that are enforced, instead of hardcoding at 70/20 + maxWidth = maxWidth || $(window).width() - (Discourse.Mobile.mobileView ? 20 : 70); + if (Discourse.SiteSettings.max_image_width < maxWidth) { + maxWidth = Discourse.SiteSettings.max_image_width; + } + + var aspect = img.height / img.width; + if (img.width > maxWidth) { + img.width = maxWidth; + img.height = parseInt(maxWidth * aspect,10); + } + + // very unlikely but lets fix this too + if (img.height > Discourse.SiteSettings.max_image_height) { + img.height = Discourse.SiteSettings.max_image_height; + img.width = parseInt(maxWidth / aspect,10); + } + + }); + }.on('willInsertElement'), + _applySearchHighlight: function() { const highlight = this.get('controller.searchHighlight'); const cooked = this.$('.cooked'); From 066824e54578ff80c45d78858bfb35de540e1eb0 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 10 Aug 2015 10:25:27 +0530 Subject: [PATCH 15/71] FIX: invite accepted notification should link to invited user profile --- .../components/notification-item.js.es6 | 4 +-- .../discourse/controllers/invite.js.es6 | 25 ++++++++++--------- .../discourse/templates/modal/invite.hbs | 8 +++--- .../discourse/templates/user-invited-show.hbs | 6 ++--- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/discourse/components/notification-item.js.es6 b/app/assets/javascripts/discourse/components/notification-item.js.es6 index d4692458cb..f0add4c2f3 100644 --- a/app/assets/javascripts/discourse/components/notification-item.js.es6 +++ b/app/assets/javascripts/discourse/components/notification-item.js.es6 @@ -30,9 +30,9 @@ export default Ember.Component.extend({ } if (it.get('notification_type') === INVITED_TYPE) { - return Discourse.getURL('/my/invited'); + return Discourse.getURL('/users/' + it.get('data.display_username')); } - }.property("notification.data.{badge_id,badge_name}", "model.slug", "model.topic_id", "model.post_number"), + }.property("notification.data.{badge_id,badge_name,display_username}", "model.slug", "model.topic_id", "model.post_number"), description: function() { const badgeName = this.get("notification.data.badge_name"); diff --git a/app/assets/javascripts/discourse/controllers/invite.js.es6 b/app/assets/javascripts/discourse/controllers/invite.js.es6 index 4529672277..048f984658 100644 --- a/app/assets/javascripts/discourse/controllers/invite.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invite.js.es6 @@ -14,7 +14,7 @@ export default ObjectController.extend(Presence, ModalFunctionality, { }.property(), disabled: function() { - if (this.get('saving')) return true; + if (this.get('model.saving')) return true; if (this.blank('emailOrUsername')) return true; const emailOrUsername = this.get('emailOrUsername').trim(); // when inviting to forum, email must be valid @@ -22,14 +22,14 @@ export default ObjectController.extend(Presence, ModalFunctionality, { // normal users (not admin) can't invite users to private topic via email if (!this.get('isAdmin') && this.get('isPrivateTopic') && Discourse.Utilities.emailValid(emailOrUsername)) return true; // when invting to private topic via email, group name must be specified - if (this.get('isPrivateTopic') && this.blank('groupNames') && Discourse.Utilities.emailValid(emailOrUsername)) return true; + if (this.get('isPrivateTopic') && this.blank('model.groupNames') && Discourse.Utilities.emailValid(emailOrUsername)) return true; if (this.get('model.details.can_invite_to')) return false; return false; - }.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'groupNames', 'saving'), + }.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'model.groupNames', 'model.saving'), buttonTitle: function() { - return this.get('saving') ? I18n.t('topic.inviting') : I18n.t('topic.invite_reply.action'); - }.property('saving'), + return this.get('model.saving') ? I18n.t('topic.inviting') : I18n.t('topic.invite_reply.action'); + }.property('model.saving'), // We are inviting to a topic if the model isn't the current user. // The current user would mean we are inviting to the forum in general. @@ -117,8 +117,8 @@ export default ObjectController.extend(Presence, ModalFunctionality, { // Reset the modal to allow a new user to be invited. reset() { - this.setProperties({ - emailOrUsername: null, + this.set('emailOrUsername', null); + this.get('model').setProperties({ groupNames: null, error: false, saving: false, @@ -131,13 +131,14 @@ export default ObjectController.extend(Presence, ModalFunctionality, { createInvite() { if (this.get('disabled')) { return; } - const groupNames = this.get('groupNames'), - userInvitedController = this.get('controllers.user-invited-show'); + const groupNames = this.get('model.groupNames'), + userInvitedController = this.get('controllers.user-invited-show'), + model = this.get('model'); - this.setProperties({ saving: true, error: false }); + model.setProperties({ saving: true, error: false }); return this.get('model').createInvite(this.get('emailOrUsername').trim(), groupNames).then(result => { - this.setProperties({ saving: false, finished: true }); + model.setProperties({ saving: false, finished: true }); if (!this.get('invitingToTopic')) { Discourse.Invite.findInvitedBy(this.currentUser, userInvitedController.get('filter')).then(invite_model => { userInvitedController.set('model', invite_model); @@ -146,7 +147,7 @@ export default ObjectController.extend(Presence, ModalFunctionality, { } else if (this.get('isMessage') && result && result.user) { this.get('model.details.allowed_users').pushObject(result.user); } - }).catch(() => this.setProperties({ saving: false, error: true })); + }).catch(() => model.setProperties({ saving: false, error: true })); } } diff --git a/app/assets/javascripts/discourse/templates/modal/invite.hbs b/app/assets/javascripts/discourse/templates/modal/invite.hbs index 5984aa86ab..cba7a89b4e 100644 --- a/app/assets/javascripts/discourse/templates/modal/invite.hbs +++ b/app/assets/javascripts/discourse/templates/modal/invite.hbs @@ -1,11 +1,11 @@ - diff --git a/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 b/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 index 72bf3aee6e..d7a980a0b4 100644 --- a/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 +++ b/app/assets/javascripts/discourse/controllers/upload-customization.js.es6 @@ -15,7 +15,6 @@ export default Ember.Controller.extend(ModalFunctionality, { actions: { createCustomization: function() { - const self = this; const object = JSON.parse(this.get('customizationFile')).site_customization; // Slight fixup before creating object diff --git a/app/assets/stylesheets/embed.css.scss b/app/assets/stylesheets/embed.css.scss index 8ada6da381..cc4865d7a5 100644 --- a/app/assets/stylesheets/embed.css.scss +++ b/app/assets/stylesheets/embed.css.scss @@ -5,7 +5,7 @@ @import "./common/foundation/colors"; @import "./common/base/onebox"; -article.post { +article { border-bottom: 1px solid #ddd; &.deleted { @@ -54,26 +54,6 @@ article.post { } } - h3.username { - font-size: 0.929em; - margin: 0 0 10px 0; - - a { - color: #5c5c5c - } - a.staff { - background-color: #ffffc2; - } - - a.new-user { - color: scale-color($primary, $lightness: 70%); - } - - span.title { - font-weight: normal; - color: #999; - } - } .cooked { padding: 10px 0 20px 0; @@ -93,17 +73,32 @@ article.post { } } +.username { + font-size: 0.929em; + margin: 0 0 10px 0; + + a { + color: #5c5c5c + } + a.staff { + background-color: #ffffc2; + } + + a.new-user { + color: scale-color($primary, $lightness: 70%); + } + + span.title { + font-weight: normal; + color: #999; + } +} + img.emoji { width: 20px; height: 20px; } -.post-replies { - background-color: #eee; - padding: 5px; - display: inline-block; -} - .in-reply-to { font-size: 0.929em; text-align: center; @@ -118,7 +113,7 @@ img.emoji { color: #999; } -header.embedded { +header { padding: 10px 10px 20px 10px; font-size: 1.286em; border-bottom: 1px solid #ddd; @@ -132,7 +127,7 @@ footer { margin-top: 10px; } - a[href].button { + .button { margin: 10px 0 0 10px; } } @@ -142,7 +137,7 @@ footer { max-height: 30px; } -a[href].button { +.button { background-color: #eee; padding: 5px; display: inline-block; diff --git a/app/controllers/admin/site_customizations_controller.rb b/app/controllers/admin/site_customizations_controller.rb index 0308b85b96..afd3c162e5 100644 --- a/app/controllers/admin/site_customizations_controller.rb +++ b/app/controllers/admin/site_customizations_controller.rb @@ -78,7 +78,7 @@ class Admin::SiteCustomizationsController < Admin::AdminController :mobile_stylesheet, :mobile_header, :mobile_top, :mobile_footer, :head_tag, :body_tag, :position, :enabled, :key, - :stylesheet_baked) + :stylesheet_baked, :embedded_css) end def log_site_customization_change(old_record, new_params) diff --git a/app/controllers/site_customizations_controller.rb b/app/controllers/site_customizations_controller.rb index e457d7d785..34a314720f 100644 --- a/app/controllers/site_customizations_controller.rb +++ b/app/controllers/site_customizations_controller.rb @@ -2,7 +2,6 @@ class SiteCustomizationsController < ApplicationController skip_before_filter :preload_json, :check_xhr, :redirect_to_login_if_required def show - no_cookies cache_time = request.env["HTTP_IF_MODIFIED_SINCE"] @@ -30,7 +29,7 @@ class SiteCustomizationsController < ApplicationController response.headers["Last-Modified"] = stylesheet_time.httpdate expires_in 1.year, public: true - render text: SiteCustomization.stylesheet_contents(params[:key], params[:target] == "mobile" ? :mobile : :desktop), + render text: SiteCustomization.stylesheet_contents(params[:key], params[:target]), content_type: "text/css" end end diff --git a/app/models/site_customization.rb b/app/models/site_customization.rb index 790f15ca41..4b316a0671 100644 --- a/app/models/site_customization.rb +++ b/app/models/site_customization.rb @@ -6,6 +6,10 @@ class SiteCustomization < ActiveRecord::Base ENABLED_KEY = '7e202ef2-56d7-47d5-98d8-a9c8d15e57dd' @cache = DistributedCache.new('site_customization') + def self.css_fields + %w(stylesheet mobile_stylesheet embedded_css) + end + before_create do self.enabled ||= false self.key ||= SecureRandom.uuid @@ -20,7 +24,7 @@ class SiteCustomization < ActiveRecord::Base end before_save do - ['stylesheet', 'mobile_stylesheet'].each do |stylesheet_attr| + SiteCustomization.css_fields.each do |stylesheet_attr| if self.send("#{stylesheet_attr}_changed?") begin self.send("#{stylesheet_attr}_baked=", compile_stylesheet(self.send(stylesheet_attr))) @@ -31,9 +35,16 @@ class SiteCustomization < ActiveRecord::Base end end + def any_stylesheet_changed? + SiteCustomization.css_fields.each do |fieldname| + return true if self.send("#{fieldname}_changed?") + end + false + end + after_save do remove_from_cache! - if stylesheet_changed? || mobile_stylesheet_changed? + if any_stylesheet_changed? MessageBus.publish "/file-change/#{key}", SecureRandom.hex MessageBus.publish "/file-change/#{SiteCustomization::ENABLED_KEY}", SecureRandom.hex end @@ -50,10 +61,24 @@ class SiteCustomization < ActiveRecord::Base ENABLED_KEY.dup << RailsMultisite::ConnectionManagement.current_db end + def self.field_for_target(target=nil) + target ||= :desktop + + case target.to_sym + when :mobile then :mobile_stylesheet + when :desktop then :stylesheet + when :embedded then :embedded_css + end + end + + def self.baked_for_target(target=nil) + "#{field_for_target(target)}_baked".to_sym + end + def self.enabled_stylesheet_contents(target=:desktop) @cache["enabled_stylesheet_#{target}"] ||= where(enabled: true) .order(:name) - .pluck(target == :desktop ? :stylesheet_baked : :mobile_stylesheet_baked) + .pluck(baked_for_target(target)) .compact .join("\n") end @@ -63,7 +88,7 @@ class SiteCustomization < ActiveRecord::Base enabled_stylesheet_contents(target) else where(key: key) - .pluck(target == :mobile ? :mobile_stylesheet_baked : :stylesheet_baked) + .pluck(baked_for_target(target)) .first end end @@ -127,7 +152,7 @@ class SiteCustomization < ActiveRecord::Base end def stylesheet_link_tag(target=:desktop) - content = target == :mobile ? mobile_stylesheet : stylesheet + content = self.send(SiteCustomization.field_for_target(target)) SiteCustomization.stylesheet_link_tag(key, target, content) end diff --git a/app/serializers/site_customization_serializer.rb b/app/serializers/site_customization_serializer.rb index 1c8ff8f9da..6a3e70ff21 100644 --- a/app/serializers/site_customization_serializer.rb +++ b/app/serializers/site_customization_serializer.rb @@ -3,5 +3,5 @@ class SiteCustomizationSerializer < ApplicationSerializer attributes :id, :name, :key, :enabled, :created_at, :updated_at, :stylesheet, :header, :footer, :top, :mobile_stylesheet, :mobile_header, :mobile_footer, :mobile_top, - :head_tag, :body_tag + :head_tag, :body_tag, :embedded_css end diff --git a/app/views/embed/comments.html.erb b/app/views/embed/comments.html.erb index f381e965a1..0608da9d64 100644 --- a/app/views/embed/comments.html.erb +++ b/app/views/embed/comments.html.erb @@ -29,9 +29,9 @@ <%- if post.reply_count > 0 %> <%- if post.reply_count == 1 %> - <%= link_to I18n.t('embed.replies', count: post.reply_count), post.url, 'data-link-to-post' => post.replies.first.id.to_s, :class => 'post-replies' %> + <%= link_to I18n.t('embed.replies', count: post.reply_count), post.url, 'data-link-to-post' => post.replies.first.id.to_s, :class => 'post-replies button' %> <% else %> - <%= link_to I18n.t('embed.replies', count: post.reply_count), post.url, class: 'post-replies', target: "_blank" %> + <%= link_to I18n.t('embed.replies', count: post.reply_count), post.url, class: 'post-replies button', target: "_blank" %> <%- end %> <%- end %>
diff --git a/app/views/layouts/embed.html.erb b/app/views/layouts/embed.html.erb index b9d40e1a7d..3466bd1b52 100644 --- a/app/views/layouts/embed.html.erb +++ b/app/views/layouts/embed.html.erb @@ -2,6 +2,9 @@ <%= stylesheet_link_tag 'embed' %> + <%- unless customization_disabled? %> + <%= SiteCustomization.custom_stylesheet(session[:preview_style], :embedded) %> + <%- end %> <%= javascript_include_tag 'break_string' %>