From af83c8dc1403032f54a92c74ee095c9456059e9d Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 8 Sep 2016 16:58:07 -0400 Subject: [PATCH] Upload Logos Step --- app/assets/javascripts/wizard-vendor.js | 2 + .../components/wizard-field-image.js.es6 | 31 ++++++ .../wizard/components/wizard-field.js.es6 | 7 +- app/assets/javascripts/wizard/lib/ajax.js.es6 | 11 +- .../components/wizard-field-dropdown.hbs | 2 +- .../components/wizard-field-image.hbs | 16 +++ .../components/wizard-field-text.hbs | 2 +- .../templates/components/wizard-field.hbs | 9 +- app/assets/stylesheets/wizard.scss | 103 ++++++++++++++---- app/controllers/uploads_controller.rb | 2 +- app/jobs/scheduled/clean_up_uploads.rb | 4 + app/views/wizard/index.html.erb | 1 + config/locales/client.en.yml | 2 + config/locales/server.en.yml | 15 +++ lib/wizard.rb | 8 +- lib/wizard/step_updater.rb | 7 ++ spec/components/step_updater_spec.rb | 24 +++- spec/jobs/clean_up_uploads_spec.rb | 20 +++- 18 files changed, 223 insertions(+), 43 deletions(-) create mode 100644 app/assets/javascripts/wizard/components/wizard-field-image.js.es6 create mode 100644 app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs diff --git a/app/assets/javascripts/wizard-vendor.js b/app/assets/javascripts/wizard-vendor.js index d25fb76a0..1e8eb3fc8 100644 --- a/app/assets/javascripts/wizard-vendor.js +++ b/app/assets/javascripts/wizard-vendor.js @@ -1,2 +1,4 @@ //= require template_include.js //= require select2.js +//= require jquery.ui.widget.js +//= require jquery.fileupload.js diff --git a/app/assets/javascripts/wizard/components/wizard-field-image.js.es6 b/app/assets/javascripts/wizard/components/wizard-field-image.js.es6 new file mode 100644 index 000000000..5a2d225a5 --- /dev/null +++ b/app/assets/javascripts/wizard/components/wizard-field-image.js.es6 @@ -0,0 +1,31 @@ +import { getToken } from 'wizard/lib/ajax'; + +export default Ember.Component.extend({ + classNames: ['wizard-image-row'], + + uploading: false, + + didInsertElement() { + this._super(); + + const $upload = this.$(); + + const id = this.get('field.id'); + + $upload.fileupload({ + url: "/uploads.json", + formData: { synchronous: true, + type: `wizard_${id}`, + authenticity_token: getToken() }, + dataType: 'json', + dropZone: $upload, + }); + + $upload.on("fileuploadsubmit", () => this.set('uploading', true)); + + $upload.on('fileuploaddone', (e, response) => { + this.set('field.value', response.result.url); + this.set('uploading', false); + }); + }, +}); diff --git a/app/assets/javascripts/wizard/components/wizard-field.js.es6 b/app/assets/javascripts/wizard/components/wizard-field.js.es6 index 67b65ee47..4b09212fb 100644 --- a/app/assets/javascripts/wizard/components/wizard-field.js.es6 +++ b/app/assets/javascripts/wizard/components/wizard-field.js.es6 @@ -1,10 +1,13 @@ import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Component.extend({ - classNameBindings: [':wizard-field', ':text-field', 'field.invalid'], + classNameBindings: [':wizard-field', 'typeClass', 'field.invalid'], + + @computed('field.type') + typeClass: type => `${Ember.String.dasherize(type)}-field`, @computed('field.id') - inputClassName: id => `field-${Ember.String.dasherize(id)}`, + fieldClass: id => `field-${Ember.String.dasherize(id)}`, @computed('field.type', 'field.id') inputComponentName(type, id) { diff --git a/app/assets/javascripts/wizard/lib/ajax.js.es6 b/app/assets/javascripts/wizard/lib/ajax.js.es6 index b4b78c773..6e4c2beae 100644 --- a/app/assets/javascripts/wizard/lib/ajax.js.es6 +++ b/app/assets/javascripts/wizard/lib/ajax.js.es6 @@ -1,16 +1,17 @@ let token; -export function ajax(args) { - +export function getToken() { if (!token) { token = $('meta[name="csrf-token"]').attr('content'); } + return token; +} + +export function ajax(args) { return new Ember.RSVP.Promise((resolve, reject) => { - args.headers = { - 'X-CSRF-Token': token - }; + args.headers = { 'X-CSRF-Token': getToken() }; args.success = data => Ember.run(null, resolve, data); args.error = xhr => Ember.run(null, reject, xhr); Ember.$.ajax(args); diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs index 85dd971b0..e993ecb95 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs @@ -1 +1 @@ -{{combo-box class=inputClassName value=field.value content=field.choices nameProperty="label" width="400px"}} +{{combo-box class=fieldClass value=field.value content=field.choices nameProperty="label" width="400px"}} diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs new file mode 100644 index 000000000..6e798194b --- /dev/null +++ b/app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs @@ -0,0 +1,16 @@ + + +{{#if field.value}} +
+ +
+{{/if}} diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs index 2413f146d..2a0770e89 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs @@ -1 +1 @@ -{{input value=field.value class=inputClassName placeholder=field.placeholder}} +{{input value=field.value class=fieldClass placeholder=field.placeholder}} diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field.hbs index 973630851..21f76b269 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-field.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-field.hbs @@ -1,15 +1,16 @@ diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index cfb048e5b..024899c7a 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -71,6 +71,52 @@ body.wizard { } } + .wizard-btn { + border-radius: 2px; + font-size: 1.0em; + border: 0px; + padding: 0.5em; + outline: 0; + transition: background-color .3s; + margin-right: 0.5em; + + background-color: #fff; + color: #333; + box-shadow: 0 1px 4px rgba(0, 0, 0, .4); + + &:hover { + background-color: #eee; + } + + &:active { + background-color: #ddd; + } + + &:disabled { + background-color: #ccc; + } + + &.disabled { + background-color: #ccc; + } + + i.fa-chevron-right { + margin-left: 0.25em; + font-size: 0.8em; + } + i.fa-chevron-left { + margin-right: 0.25em; + font-size: 0.8em; + } + } + + .wizard-btn-upload { + clear: both; + display: inline-block; + .fa { + margin-left: 0.5em; + } + } .wizard-step-footer { display: flex; @@ -78,17 +124,10 @@ body.wizard { justify-content: space-between; align-items: center; - button.wizard-btn { - border-radius: 2px; - box-shadow: 0 1px 4px rgba(0, 0, 0, .6); - font-size: 1.0em; + .wizard-btn.next { background-color: #6699ff; color: white; - border: 0px; - padding: 0.5em; - outline: 0; - transition: background-color .3s; - margin-right: 0.5em; + box-shadow: 0 1px 4px rgba(0, 0, 0, .6); &:hover { background-color: #80B3FF; @@ -102,17 +141,6 @@ body.wizard { background-color: #000167; } - i.fa-chevron-right { - margin-left: 0.25em; - font-size: 0.8em; - } - i.fa-chevron-left { - margin-right: 0.25em; - font-size: 0.8em; - } - } - - button.wizard-btn.next { min-width: 70px; i.fa-chevron-right { @@ -121,7 +149,7 @@ body.wizard { } } - button.wizard-btn.back { + .wizard-btn.back { background-color: #fff; color: #333; box-shadow: 0 1px 4px rgba(0, 0, 0, .4); @@ -144,6 +172,7 @@ body.wizard { } button.wizard-btn.done { + color: #fff; background-color: #33B333; &:hover { @@ -160,6 +189,34 @@ body.wizard { } } + .wizard-image-row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .wizard-image-preview { + img.field-logo-url { + max-height: 40px; + } + img.field-logo-small-url { + max-height: 40px; + max-width: 80px; + } + img.field-favicon-url { + max-height: 16px; + max-width: 16px; + } + img.field-apple-touch-icon-url { + max-height: 40px; + max-width: 40px; + } + + padding: 0.1em; + border: 1px dotted #bbb; + } + .wizard-field { label .label-value { font-weight: bold; @@ -177,6 +234,10 @@ body.wizard { .field-description { color: #999; margin-top: 0.5em; + + a { + color: #7B68EE; + } } &.text-field { diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 95413db5a..0f6ccba28 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -7,7 +7,7 @@ class UploadsController < ApplicationController file = params[:file] || params[:files].try(:first) url = params[:url] client_id = params[:client_id] - synchronous = is_api? && params[:synchronous] + synchronous = (current_user.staff? || is_api?) && params[:synchronous] if type == "avatar" if SiteSetting.sso_overrides_avatar || !SiteSetting.allow_uploaded_avatars diff --git a/app/jobs/scheduled/clean_up_uploads.rb b/app/jobs/scheduled/clean_up_uploads.rb index b4b55ae5c..88d29c0c8 100644 --- a/app/jobs/scheduled/clean_up_uploads.rb +++ b/app/jobs/scheduled/clean_up_uploads.rb @@ -11,6 +11,10 @@ module Jobs ignore_urls |= Category.uniq.where("logo_url IS NOT NULL AND logo_url != ''").pluck(:logo_url) ignore_urls |= Category.uniq.where("background_url IS NOT NULL AND background_url != ''").pluck(:background_url) + # Any URLs in site settings are fair game + ignore_urls |= [SiteSetting.logo_url, SiteSetting.logo_small_url, SiteSetting.favicon_url, + SiteSetting.apple_touch_icon_url] + ids = [] ids |= PostUpload.uniq.pluck(:upload_id) ids |= User.uniq.where("uploaded_avatar_id IS NOT NULL").pluck(:uploaded_avatar_id) diff --git a/app/views/wizard/index.html.erb b/app/views/wizard/index.html.erb index 153026a2f..fad9c7d92 100644 --- a/app/views/wizard/index.html.erb +++ b/app/views/wizard/index.html.erb @@ -9,6 +9,7 @@ <%= csrf_meta_tags %> + <%= render partial: "layouts/head" %> <%= t 'wizard.title' %> diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 3f73411d3..41eb7ddb5 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -3231,4 +3231,6 @@ en: back: "Back" next: "Next" step: "Step %{current} of %{total}" + upload: "Upload" + uploading: "Uploading..." diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 97ec50c98..605177aeb 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -3258,6 +3258,21 @@ en: options: default: "Simple" dark: "Dark" + logos: + title: "Personalize your Forum" + fields: + logo_url: + label: "Site Logo" + description: "The logo image at the top left of your site, should be a wide rectangle shape." + logo_small_url: + label: "Small Logo" + description: "The small logo image at the top left of your site, should be a square shape, seen when scrolling down." + favicon_url: + label: "Small Icon" + description: "A favicon for your site. To work correctly over a CDN it must be a png." + apple_touch_icon_url: + label: "Large Icon" + description: "Icon used for Apple touch devices. Recommended size is 144px by 144px." finished: title: "Your Discourse Forum is Ready!" diff --git a/lib/wizard.rb b/lib/wizard.rb index ba0685aea..a58e7332b 100644 --- a/lib/wizard.rb +++ b/lib/wizard.rb @@ -59,7 +59,6 @@ class Wizard end wizard.append_step('colors') do |step| - theme_id = ColorScheme.where(via_wizard: true).pluck(:theme_id) theme_id = theme_id.present? ? theme_id[0] : 'default' @@ -68,6 +67,13 @@ class Wizard step.add_field(id: 'theme_preview', type: 'component') end + wizard.append_step('logos') do |step| + step.add_field(id: 'logo_url', type: 'image', value: SiteSetting.logo_url) + step.add_field(id: 'logo_small_url', type: 'image', value: SiteSetting.logo_small_url) + step.add_field(id: 'favicon_url', type: 'image', value: SiteSetting.favicon_url) + step.add_field(id: 'apple_touch_icon_url', type: 'image', value: SiteSetting.apple_touch_icon_url) + end + wizard.append_step('finished') wizard diff --git a/lib/wizard/step_updater.rb b/lib/wizard/step_updater.rb index fe0c0278a..5e473fe21 100644 --- a/lib/wizard/step_updater.rb +++ b/lib/wizard/step_updater.rb @@ -59,6 +59,13 @@ class Wizard end end + def update_logos(fields) + update_setting(:logo_url, fields, :logo_url) + update_setting(:logo_small_url, fields, :logo_small_url) + update_setting(:favicon_url, fields, :favicon_url) + update_setting(:apple_touch_icon_url, fields, :apple_touch_icon_url) + end + def success? @errors.blank? end diff --git a/spec/components/step_updater_spec.rb b/spec/components/step_updater_spec.rb index b61e3a288..4867ddb5a 100644 --- a/spec/components/step_updater_spec.rb +++ b/spec/components/step_updater_spec.rb @@ -60,7 +60,7 @@ describe Wizard::StepUpdater do let!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) } it "updates the scheme" do - updater.update(color_scheme: 'dark') + updater.update(theme_id: 'dark') expect(updater.success?).to eq(true) color_scheme.reload @@ -72,7 +72,7 @@ describe Wizard::StepUpdater do context "without an existing scheme" do it "creates the scheme" do - updater.update(color_scheme: 'dark') + updater.update(theme_id: 'dark') expect(updater.success?).to eq(true) color_scheme = ColorScheme.where(via_wizard: true).first @@ -83,4 +83,24 @@ describe Wizard::StepUpdater do end end + context "logos step" do + let(:updater) { Wizard::StepUpdater.new(user, 'logos') } + + it "updates the fields correctly" do + updater.update( + logo_url: '/uploads/logo.png', + logo_small_url: '/uploads/logo-small.png', + favicon_url: "/uploads/favicon.png", + apple_touch_icon_url: "/uploads/apple.png" + ) + + expect(updater).to be_success + expect(SiteSetting.logo_url).to eq('/uploads/logo.png') + expect(SiteSetting.logo_small_url).to eq('/uploads/logo-small.png') + expect(SiteSetting.favicon_url).to eq('/uploads/favicon.png') + expect(SiteSetting.apple_touch_icon_url).to eq('/uploads/apple.png') + end + end + + end diff --git a/spec/jobs/clean_up_uploads_spec.rb b/spec/jobs/clean_up_uploads_spec.rb index 9b7f22d99..4341ec728 100644 --- a/spec/jobs/clean_up_uploads_spec.rb +++ b/spec/jobs/clean_up_uploads_spec.rb @@ -23,6 +23,16 @@ describe Jobs::CleanUpUploads do expect(Upload.count).to be(0) end + it "does not clean up uploads in site settings" do + logo_upload = fabricate_upload + SiteSetting.logo_url = logo_upload.url + + Jobs::CleanUpUploads.new.execute(nil) + + expect(Upload.find_by(id: @upload.id)).to eq(nil) + expect(Upload.find_by(id: logo_upload.id)).to eq(logo_upload) + end + it "does not delete profile background uploads" do profile_background_upload = fabricate_upload UserProfile.last.update_attributes!(profile_background: profile_background_upload.url) @@ -45,7 +55,7 @@ describe Jobs::CleanUpUploads do it "does not delete category logo uploads" do category_logo_upload = fabricate_upload - category = Fabricate(:category, logo_url: category_logo_upload.url) + Fabricate(:category, logo_url: category_logo_upload.url) Jobs::CleanUpUploads.new.execute(nil) @@ -55,7 +65,7 @@ describe Jobs::CleanUpUploads do it "does not delete category background url uploads" do category_background_url = fabricate_upload - category = Fabricate(:category, background_url: category_background_url.url) + Fabricate(:category, background_url: category_background_url.url) Jobs::CleanUpUploads.new.execute(nil) @@ -65,7 +75,7 @@ describe Jobs::CleanUpUploads do it "does not delete post uploads" do upload = fabricate_upload - post = Fabricate(:post, uploads: [upload]) + Fabricate(:post, uploads: [upload]) Jobs::CleanUpUploads.new.execute(nil) @@ -75,7 +85,7 @@ describe Jobs::CleanUpUploads do it "does not delete user uploaded avatar" do upload = fabricate_upload - user = Fabricate(:user, uploaded_avatar: upload) + Fabricate(:user, uploaded_avatar: upload) Jobs::CleanUpUploads.new.execute(nil) @@ -85,7 +95,7 @@ describe Jobs::CleanUpUploads do it "does not delete user gravatar" do upload = fabricate_upload - user = Fabricate(:user, user_avatar: Fabricate(:user_avatar, gravatar_upload: upload)) + Fabricate(:user, user_avatar: Fabricate(:user_avatar, gravatar_upload: upload)) Jobs::CleanUpUploads.new.execute(nil)