Upload Logos Step

This commit is contained in:
Robin Ward 2016-09-08 16:58:07 -04:00
parent c94e6f1b96
commit af83c8dc14
18 changed files with 223 additions and 43 deletions

View File

@ -1,2 +1,4 @@
//= require template_include.js //= require template_include.js
//= require select2.js //= require select2.js
//= require jquery.ui.widget.js
//= require jquery.fileupload.js

View File

@ -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);
});
},
});

View File

@ -1,10 +1,13 @@
import computed from 'ember-addons/ember-computed-decorators'; import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({ 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') @computed('field.id')
inputClassName: id => `field-${Ember.String.dasherize(id)}`, fieldClass: id => `field-${Ember.String.dasherize(id)}`,
@computed('field.type', 'field.id') @computed('field.type', 'field.id')
inputComponentName(type, id) { inputComponentName(type, id) {

View File

@ -1,16 +1,17 @@
let token; let token;
export function ajax(args) { export function getToken() {
if (!token) { if (!token) {
token = $('meta[name="csrf-token"]').attr('content'); token = $('meta[name="csrf-token"]').attr('content');
} }
return token;
}
export function ajax(args) {
return new Ember.RSVP.Promise((resolve, reject) => { return new Ember.RSVP.Promise((resolve, reject) => {
args.headers = { args.headers = { 'X-CSRF-Token': getToken() };
'X-CSRF-Token': token
};
args.success = data => Ember.run(null, resolve, data); args.success = data => Ember.run(null, resolve, data);
args.error = xhr => Ember.run(null, reject, xhr); args.error = xhr => Ember.run(null, reject, xhr);
Ember.$.ajax(args); Ember.$.ajax(args);

View File

@ -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"}}

View File

@ -0,0 +1,16 @@
<label class="wizard-btn wizard-btn-upload {{if uploading 'disabled'}}">
{{#if uploading}}
{{i18n "wizard.uploading"}}
{{else}}
{{i18n "wizard.upload"}}
{{fa-icon "picture-o"}}
{{/if}}
<input disabled={{uploading}} type="file" accept="image/*" style="visibility: hidden; position: absolute;" />
</label>
{{#if field.value}}
<div class='wizard-image-preview {{fieldClass}}'>
<img src={{field.value}} class={{fieldClass}}>
</div>
{{/if}}

View File

@ -1 +1 @@
{{input value=field.value class=inputClassName placeholder=field.placeholder}} {{input value=field.value class=fieldClass placeholder=field.placeholder}}

View File

@ -1,15 +1,16 @@
<label> <label>
<span class='label-value'>{{field.label}}</span> <span class='label-value'>{{field.label}}</span>
{{#if field.description}}
<div class='field-description'>{{{field.description}}}</div>
{{/if}}
<div class='input-area'> <div class='input-area'>
{{component inputComponentName field=field step=step inputClassName=inputClassName}} {{component inputComponentName field=field step=step fieldClass=fieldClass}}
</div> </div>
{{#if field.errorDescription}} {{#if field.errorDescription}}
<div class='field-error-description'>{{field.errorDescription}}</div> <div class='field-error-description'>{{field.errorDescription}}</div>
{{/if}} {{/if}}
{{#if field.description}}
<div class='field-description'>{{field.description}}</div>
{{/if}}
</label> </label>

View File

@ -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 { .wizard-step-footer {
display: flex; display: flex;
@ -78,17 +124,10 @@ body.wizard {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
button.wizard-btn { .wizard-btn.next {
border-radius: 2px;
box-shadow: 0 1px 4px rgba(0, 0, 0, .6);
font-size: 1.0em;
background-color: #6699ff; background-color: #6699ff;
color: white; color: white;
border: 0px; box-shadow: 0 1px 4px rgba(0, 0, 0, .6);
padding: 0.5em;
outline: 0;
transition: background-color .3s;
margin-right: 0.5em;
&:hover { &:hover {
background-color: #80B3FF; background-color: #80B3FF;
@ -102,17 +141,6 @@ body.wizard {
background-color: #000167; 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; min-width: 70px;
i.fa-chevron-right { i.fa-chevron-right {
@ -121,7 +149,7 @@ body.wizard {
} }
} }
button.wizard-btn.back { .wizard-btn.back {
background-color: #fff; background-color: #fff;
color: #333; color: #333;
box-shadow: 0 1px 4px rgba(0, 0, 0, .4); box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
@ -144,6 +172,7 @@ body.wizard {
} }
button.wizard-btn.done { button.wizard-btn.done {
color: #fff;
background-color: #33B333; background-color: #33B333;
&:hover { &: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 { .wizard-field {
label .label-value { label .label-value {
font-weight: bold; font-weight: bold;
@ -177,6 +234,10 @@ body.wizard {
.field-description { .field-description {
color: #999; color: #999;
margin-top: 0.5em; margin-top: 0.5em;
a {
color: #7B68EE;
}
} }
&.text-field { &.text-field {

View File

@ -7,7 +7,7 @@ class UploadsController < ApplicationController
file = params[:file] || params[:files].try(:first) file = params[:file] || params[:files].try(:first)
url = params[:url] url = params[:url]
client_id = params[:client_id] client_id = params[:client_id]
synchronous = is_api? && params[:synchronous] synchronous = (current_user.staff? || is_api?) && params[:synchronous]
if type == "avatar" if type == "avatar"
if SiteSetting.sso_overrides_avatar || !SiteSetting.allow_uploaded_avatars if SiteSetting.sso_overrides_avatar || !SiteSetting.allow_uploaded_avatars

View File

@ -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("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) 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 = []
ids |= PostUpload.uniq.pluck(:upload_id) ids |= PostUpload.uniq.pluck(:upload_id)
ids |= User.uniq.where("uploaded_avatar_id IS NOT NULL").pluck(:uploaded_avatar_id) ids |= User.uniq.where("uploaded_avatar_id IS NOT NULL").pluck(:uploaded_avatar_id)

View File

@ -9,6 +9,7 @@
<script src="/extra-locales/wizard"></script> <script src="/extra-locales/wizard"></script>
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
<%= render partial: "layouts/head" %>
<title><%= t 'wizard.title' %></title> <title><%= t 'wizard.title' %></title>
</head> </head>

View File

@ -3231,4 +3231,6 @@ en:
back: "Back" back: "Back"
next: "Next" next: "Next"
step: "Step %{current} of %{total}" step: "Step %{current} of %{total}"
upload: "Upload"
uploading: "Uploading..."

View File

@ -3258,6 +3258,21 @@ en:
options: options:
default: "Simple" default: "Simple"
dark: "Dark" 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 <a href=\"http://en.wikipedia.org/wiki/Favicon\">favicon</a> 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: finished:
title: "Your Discourse Forum is Ready!" title: "Your Discourse Forum is Ready!"

View File

@ -59,7 +59,6 @@ class Wizard
end end
wizard.append_step('colors') do |step| wizard.append_step('colors') do |step|
theme_id = ColorScheme.where(via_wizard: true).pluck(:theme_id) theme_id = ColorScheme.where(via_wizard: true).pluck(:theme_id)
theme_id = theme_id.present? ? theme_id[0] : 'default' theme_id = theme_id.present? ? theme_id[0] : 'default'
@ -68,6 +67,13 @@ class Wizard
step.add_field(id: 'theme_preview', type: 'component') step.add_field(id: 'theme_preview', type: 'component')
end 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.append_step('finished')
wizard wizard

View File

@ -59,6 +59,13 @@ class Wizard
end end
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? def success?
@errors.blank? @errors.blank?
end end

View File

@ -60,7 +60,7 @@ describe Wizard::StepUpdater do
let!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) } let!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) }
it "updates the scheme" do it "updates the scheme" do
updater.update(color_scheme: 'dark') updater.update(theme_id: 'dark')
expect(updater.success?).to eq(true) expect(updater.success?).to eq(true)
color_scheme.reload color_scheme.reload
@ -72,7 +72,7 @@ describe Wizard::StepUpdater do
context "without an existing scheme" do context "without an existing scheme" do
it "creates the scheme" do it "creates the scheme" do
updater.update(color_scheme: 'dark') updater.update(theme_id: 'dark')
expect(updater.success?).to eq(true) expect(updater.success?).to eq(true)
color_scheme = ColorScheme.where(via_wizard: true).first color_scheme = ColorScheme.where(via_wizard: true).first
@ -83,4 +83,24 @@ describe Wizard::StepUpdater do
end end
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 end

View File

@ -23,6 +23,16 @@ describe Jobs::CleanUpUploads do
expect(Upload.count).to be(0) expect(Upload.count).to be(0)
end 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 it "does not delete profile background uploads" do
profile_background_upload = fabricate_upload profile_background_upload = fabricate_upload
UserProfile.last.update_attributes!(profile_background: profile_background_upload.url) 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 it "does not delete category logo uploads" do
category_logo_upload = fabricate_upload 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) Jobs::CleanUpUploads.new.execute(nil)
@ -55,7 +65,7 @@ describe Jobs::CleanUpUploads do
it "does not delete category background url uploads" do it "does not delete category background url uploads" do
category_background_url = fabricate_upload 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) Jobs::CleanUpUploads.new.execute(nil)
@ -65,7 +75,7 @@ describe Jobs::CleanUpUploads do
it "does not delete post uploads" do it "does not delete post uploads" do
upload = fabricate_upload upload = fabricate_upload
post = Fabricate(:post, uploads: [upload]) Fabricate(:post, uploads: [upload])
Jobs::CleanUpUploads.new.execute(nil) Jobs::CleanUpUploads.new.execute(nil)
@ -75,7 +85,7 @@ describe Jobs::CleanUpUploads do
it "does not delete user uploaded avatar" do it "does not delete user uploaded avatar" do
upload = fabricate_upload upload = fabricate_upload
user = Fabricate(:user, uploaded_avatar: upload) Fabricate(:user, uploaded_avatar: upload)
Jobs::CleanUpUploads.new.execute(nil) Jobs::CleanUpUploads.new.execute(nil)
@ -85,7 +95,7 @@ describe Jobs::CleanUpUploads do
it "does not delete user gravatar" do it "does not delete user gravatar" do
upload = fabricate_upload 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) Jobs::CleanUpUploads.new.execute(nil)