diff --git a/app/assets/javascripts/discourse/components/image-uploader.js.es6 b/app/assets/javascripts/discourse/components/image-uploader.js.es6
index 52b1112ee..4173886eb 100644
--- a/app/assets/javascripts/discourse/components/image-uploader.js.es6
+++ b/app/assets/javascripts/discourse/components/image-uploader.js.es6
@@ -13,7 +13,19 @@ export default Em.Component.extend(UploadMixin, {
this.set('imageUrl', data.result.url);
},
- deleteDone: function() {
- this.set('imageUrl', null);
+ actions: {
+ trash: function() {
+ this.set('imageUrl', null);
+
+ // Do we want to signal the delete to the server right away?
+ if (this.get('instantDelete')) {
+ Discourse.ajax(this.get('uploadUrl'), {
+ type: 'DELETE',
+ data: { image_type: this.get('type') }
+ }).then(null, function() {
+ bootbox.alert(I18n.t('generic_error'));
+ });
+ }
+ }
}
});
diff --git a/app/assets/javascripts/discourse/controllers/edit-category.js.es6 b/app/assets/javascripts/discourse/controllers/edit-category.js.es6
index eef1108be..30006d67a 100644
--- a/app/assets/javascripts/discourse/controllers/edit-category.js.es6
+++ b/app/assets/javascripts/discourse/controllers/edit-category.js.es6
@@ -9,6 +9,7 @@
**/
export default Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
foregroundColors: ['FFFFFF', '000000'],
+ categoryUploadUrl: '/category/uploads',
parentCategories: function() {
return Discourse.Category.list().filter(function (c) {
diff --git a/app/assets/javascripts/discourse/mixins/upload.js.es6 b/app/assets/javascripts/discourse/mixins/upload.js.es6
index 9560c4ff7..dc612dd89 100644
--- a/app/assets/javascripts/discourse/mixins/upload.js.es6
+++ b/app/assets/javascripts/discourse/mixins/upload.js.es6
@@ -53,18 +53,6 @@ export default Em.Mixin.create({
actions: {
selectFile: function() {
this.$('input[type=file]').click();
- },
-
- trash: function() {
- var self = this;
- Discourse.ajax(this.get('uploadUrl'), {
- type: 'DELETE',
- data: { image_type: this.get('type') }
- }).then(function() {
- self.deleteDone();
- }).catch(function() {
- bootbox.alert(I18n.t('generic_error'));
- });
}
}
});
diff --git a/app/assets/javascripts/discourse/models/category.js b/app/assets/javascripts/discourse/models/category.js
index 9687dfb85..b0382abd7 100644
--- a/app/assets/javascripts/discourse/models/category.js
+++ b/app/assets/javascripts/discourse/models/category.js
@@ -68,7 +68,9 @@ Discourse.Category = Discourse.Model.extend({
position: this.get('position'),
email_in: this.get('email_in'),
email_in_allow_strangers: this.get('email_in_allow_strangers'),
- parent_category_id: this.get('parent_category_id')
+ parent_category_id: this.get('parent_category_id'),
+ logo_url: this.get('logo_url'),
+ background_url: this.get('background_url')
},
type: this.get('id') ? 'PUT' : 'POST'
});
diff --git a/app/assets/javascripts/discourse/templates/modal/edit-category-images.js.handlebars b/app/assets/javascripts/discourse/templates/modal/edit-category-images.js.handlebars
new file mode 100644
index 000000000..73fd9cf8f
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/modal/edit-category-images.js.handlebars
@@ -0,0 +1,9 @@
+
+
+ {{image-uploader uploadUrl=categoryUploadUrl imageUrl=logo_url type="logo"}}
+
+
+
+
+ {{image-uploader uploadUrl=categoryUploadUrl imageUrl=background_url type="background"}}
+
diff --git a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars
index ff2d172c4..a81ee7b3e 100644
--- a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars
@@ -94,6 +94,7 @@
{{image-uploader uploadUrl=imageUploadUrl
imageUrl=profile_background
+ instantDelete="true"
type="profile_background"}}
diff --git a/app/assets/stylesheets/desktop/modal.scss b/app/assets/stylesheets/desktop/modal.scss
index 3ac8fc3b6..b59182870 100644
--- a/app/assets/stylesheets/desktop/modal.scss
+++ b/app/assets/stylesheets/desktop/modal.scss
@@ -186,3 +186,9 @@ animation: modal .25s;
.uploaded-avatar {
margin-top: 20px;
}
+
+.uploaded-image-preview {
+ width: 400px;
+ max-height: 150px;
+ margin-bottom: 10px;
+}
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index 088645367..a168729d9 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -26,6 +26,19 @@ class CategoriesController < ApplicationController
end
end
+ def upload
+ params.require(:image_type)
+ guardian.ensure_can_create!(Category)
+
+ file = params[:file] || params[:files].first
+ upload = Upload.create_for(current_user.id, file.tempfile, file.original_filename, File.size(file.tempfile))
+ if upload.errors.blank?
+ render json: { url: upload.url, width: upload.width, height: upload.height }
+ else
+ render status: 422, text: upload.errors.full_messages
+ end
+ end
+
def move
guardian.ensure_can_create!(Category)
@@ -105,7 +118,7 @@ class CategoriesController < ApplicationController
end
end
- params.permit(*required_param_keys, :position, :email_in, :email_in_allow_strangers, :parent_category_id, :auto_close_hours, :permissions => [*p.try(:keys)])
+ params.permit(*required_param_keys, :position, :email_in, :email_in_allow_strangers, :parent_category_id, :auto_close_hours, :logo_url, :background_url, :permissions => [*p.try(:keys)])
end
end
diff --git a/app/jobs/scheduled/clean_up_uploads.rb b/app/jobs/scheduled/clean_up_uploads.rb
index 719656077..b04fda1f8 100644
--- a/app/jobs/scheduled/clean_up_uploads.rb
+++ b/app/jobs/scheduled/clean_up_uploads.rb
@@ -6,7 +6,11 @@ module Jobs
def execute(args)
return unless SiteSetting.clean_up_uploads?
- uploads_used_as_profile_backgrounds = UserProfile.uniq.where("profile_background IS NOT NULL AND profile_background != ''").pluck(:profile_background)
+ ignore_urls = []
+ ignore_urls << UserProfile.uniq.where("profile_background IS NOT NULL AND profile_background != ''").pluck(:profile_background)
+ 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.flatten!
grace_period = [SiteSetting.clean_orphan_uploads_grace_period_hours, 1].max
@@ -14,7 +18,7 @@ module Jobs
.where("id NOT IN (SELECT upload_id from post_uploads)")
.where("id NOT IN (SELECT custom_upload_id from user_avatars)")
.where("id NOT IN (SELECT gravatar_upload_id from user_avatars)")
- .where("url NOT IN (?)", uploads_used_as_profile_backgrounds)
+ .where("url NOT IN (?)", ignore_urls)
.find_each do |upload|
upload.destroy
end
diff --git a/app/serializers/basic_category_serializer.rb b/app/serializers/basic_category_serializer.rb
index 166f8d59b..3ca2ffa13 100644
--- a/app/serializers/basic_category_serializer.rb
+++ b/app/serializers/basic_category_serializer.rb
@@ -12,7 +12,9 @@ class BasicCategorySerializer < ApplicationSerializer
:read_restricted,
:permission,
:parent_category_id,
- :notification_level
+ :notification_level,
+ :logo_url,
+ :background_url
def include_parent_category_id?
parent_category_id
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 214826e66..547fa6b7e 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1146,6 +1146,8 @@ en:
name: "Category Name"
description: "Description"
topic: "category topic"
+ logo: "Category Logo Image"
+ background_image: "Category Background Image"
badge_colors: "Badge colors"
background_color: "Background color"
foreground_color: "Foreground color"
diff --git a/config/routes.rb b/config/routes.rb
index 333cfb922..ef282eabb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -275,6 +275,7 @@ Discourse::Application.routes.draw do
resources :categories, :except => :show
get "category/:id/show" => "categories#show"
+ post "category/uploads" => "categories#upload"
post "category/:category_id/move" => "categories#move"
get "category/:category.rss" => "list#category_feed", format: :rss
get "category/:parent_category/:category.rss" => "list#category_feed", format: :rss
diff --git a/db/migrate/20140627193814_add_images_to_categories.rb b/db/migrate/20140627193814_add_images_to_categories.rb
new file mode 100644
index 000000000..58e4cce73
--- /dev/null
+++ b/db/migrate/20140627193814_add_images_to_categories.rb
@@ -0,0 +1,6 @@
+class AddImagesToCategories < ActiveRecord::Migration
+ def change
+ add_column :categories, :logo_url, :string
+ add_column :categories, :background_url, :string
+ end
+end
diff --git a/spec/controllers/categories_controller_spec.rb b/spec/controllers/categories_controller_spec.rb
index 3ccc461b8..030c7049f 100644
--- a/spec/controllers/categories_controller_spec.rb
+++ b/spec/controllers/categories_controller_spec.rb
@@ -95,6 +95,37 @@ describe CategoriesController do
end
+ describe "upload" do
+ it "requires the user to be logged in" do
+ lambda { xhr :post, :upload, image_type: 'logo'}.should raise_error(Discourse::NotLoggedIn)
+ end
+
+ describe "logged in" do
+ let!(:user) { log_in(:admin) }
+
+ let(:logo) { File.new("#{Rails.root}/spec/fixtures/images/logo.png") }
+ let(:upload) do
+ ActionDispatch::Http::UploadedFile.new({ filename: 'logo.png', tempfile: logo })
+ end
+
+ it "raises an error when you don't have permission to upload" do
+ Guardian.any_instance.expects(:can_create?).with(Category).returns(false)
+ xhr :post, :upload, image_type: 'logo', file: upload
+ response.should be_forbidden
+ end
+
+ it "requires the `image_type` param" do
+ -> { xhr :post, :upload }.should raise_error(ActionController::ParameterMissing)
+ end
+
+ it "calls Upload.create_for" do
+ Upload.expects(:create_for).returns(Upload.new)
+ xhr :post, :upload, image_type: 'logo', file: upload
+ response.should be_success
+ end
+ end
+ end
+
describe "update" do
it "requires the user to be logged in" do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 3e73192c1..4c6e9d49d 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -1272,7 +1272,7 @@ describe UsersController do
describe '.destroy_user_image' do
it 'raises an error when not logged in' do
- lambda { xhr :put, :destroy_user_image, type: 'profile_background', username: 'asdf' }.should raise_error(Discourse::NotLoggedIn)
+ lambda { xhr :delete, :destroy_user_image, type: 'profile_background', username: 'asdf' }.should raise_error(Discourse::NotLoggedIn)
end
context 'while logged in' do
@@ -1281,20 +1281,20 @@ describe UsersController do
it 'raises an error when you don\'t have permission to clear the profile background' do
Guardian.any_instance.expects(:can_edit?).with(user).returns(false)
- xhr :put, :destroy_user_image, username: user.username, image_type: 'profile_background'
+ xhr :delete, :destroy_user_image, username: user.username, image_type: 'profile_background'
response.should be_forbidden
end
it "requires the `image_type` param" do
- -> { xhr :put, :destroy_user_image, username: user.username }.should raise_error(ActionController::ParameterMissing)
+ -> { xhr :delete, :destroy_user_image, username: user.username }.should raise_error(ActionController::ParameterMissing)
end
it "only allows certain `image_types`" do
- -> { xhr :put, :destroy_user_image, username: user.username, image_type: 'wat' }.should raise_error(Discourse::InvalidParameters)
+ -> { xhr :delete, :destroy_user_image, username: user.username, image_type: 'wat' }.should raise_error(Discourse::InvalidParameters)
end
it 'can clear the profile background' do
- xhr :put, :destroy_user_image, image_type: 'profile_background', username: user.username
+ xhr :delete, :destroy_user_image, image_type: 'profile_background', username: user.username
user.reload.user_profile.profile_background.should == ""
response.should be_success
end