Skip to content

Commit

Permalink
Merged ajax_upload branch (#3957).
Browse files Browse the repository at this point in the history
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@10977 e93f8b46-1217-0410-a6f0-8f06a7374b81
  • Loading branch information
jplang committed Dec 10, 2012
1 parent 2304f5d commit ef25210
Show file tree
Hide file tree
Showing 22 changed files with 442 additions and 83 deletions.
10 changes: 10 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,16 @@ def find_issues
render_404
end

def find_attachments
if (attachments = params[:attachments]).present?
att = attachments.values.collect do |attachment|
Attachment.find_by_token( attachment[:token] ) if attachment[:token].present?
end
att.compact!
end
@attachments = att || []
end

# make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project
def check_project_privacy
Expand Down
32 changes: 21 additions & 11 deletions app/controllers/attachments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,25 +85,35 @@ def upload
@attachment = Attachment.new(:file => request.raw_post)
@attachment.author = User.current
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
saved = @attachment.save

if @attachment.save
respond_to do |format|
format.api { render :action => 'upload', :status => :created }
end
else
respond_to do |format|
format.api { render_validation_errors(@attachment) }
end
respond_to do |format|
format.js
format.api {
if saved
render :action => 'upload', :status => :created
else
render_validation_errors(@attachment)
end
}
end
end

def destroy
if @attachment.container.respond_to?(:init_journal)
@attachment.container.init_journal(User.current)
end
# Make sure association callbacks are called
@attachment.container.attachments.delete(@attachment)
redirect_to_referer_or project_path(@project)
if @attachment.container
# Make sure association callbacks are called
@attachment.container.attachments.delete(@attachment)
else
@attachment.destroy
end

respond_to do |format|
format.html { redirect_to_referer_or project_path(@project) }
format.js
end
end

private
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/messages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class MessagesController < ApplicationController
menu_item :boards
default_search_scope :messages
before_filter :find_board, :only => [:new, :preview]
before_filter :find_attachments, :only => [:preview]
before_filter :find_message, :except => [:new, :preview]
before_filter :authorize, :except => [:preview, :edit, :destroy]

Expand Down Expand Up @@ -117,7 +118,6 @@ def quote

def preview
message = @board.messages.find_by_id(params[:id])
@attachements = message.attachments if message
@text = (params[:message] || params[:reply])[:content]
@previewed = message
render :partial => 'common/preview'
Expand Down
4 changes: 1 addition & 3 deletions app/controllers/previews_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class PreviewsController < ApplicationController
before_filter :find_project
before_filter :find_project, :find_attachments

def issue
@issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
if @issue
@attachements = @issue.attachments
@description = params[:issue] && params[:issue][:description]
if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
@description = nil
Expand All @@ -37,7 +36,6 @@ def issue
def news
if params[:id].present? && news = News.visible.find_by_id(params[:id])
@previewed = news
@attachments = news.attachments
end
@text = (params[:news] ? params[:news][:description] : nil)
render :partial => 'common/preview'
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/wiki_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class WikiController < ApplicationController
before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
accept_api_auth :index, :show, :update, :destroy
before_filter :find_attachments, :only => [:preview]

helper :attachments
include AttachmentsHelper
Expand Down Expand Up @@ -293,7 +294,7 @@ def preview
# page is nil when previewing a new page
return render_403 unless page.nil? || editable?(page)
if page
@attachements = page.attachments
@attachments += page.attachments
@previewed = page.content
end
@text = params[:content][:text]
Expand Down
5 changes: 3 additions & 2 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -597,8 +597,9 @@ def parse_non_pre_blocks(text, obj, macros)

def parse_inline_attachments(text, project, obj, attr, only_path, options)
# when using an image link, try to use an attachment, if possible
if options[:attachments] || (obj && obj.respond_to?(:attachments))
attachments = options[:attachments] || obj.attachments
if options[:attachments].present? || (obj && obj.respond_to?(:attachments))
attachments = options[:attachments] || []
attachments += obj.attachments if obj
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
filename, ext, alt, alttext = $1.downcase, $2, $3, $4
# search for the picture in attachments
Expand Down
12 changes: 10 additions & 2 deletions app/models/attachment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,19 @@ def project
end

def visible?(user=User.current)
container && container.attachments_visible?(user)
if container_id
container && container.attachments_visible?(user)
else
author == user
end
end

def deletable?(user=User.current)
container && container.attachments_deletable?(user)
if container_id
container && container.attachments_deletable?(user)
else
author == user
end
end

def image?
Expand Down
34 changes: 22 additions & 12 deletions app/views/attachments/_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
<span id="attachments_fields">
<% if defined?(container) && container && container.saved_attachments %>
<% container.saved_attachments.each_with_index do |attachment, i| %>
<span class="icon icon-attachment" style="display:block; line-height:1.5em;">
<%= h(attachment.filename) %> (<%= number_to_human_size(attachment.filesize) %>)
<%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.id}.#{attachment.digest}" %>
<span id="attachments_p<%= i %>">
<%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename') +
text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') +
link_to('&nbsp;'.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %>
<%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %>
</span>
<% end %>
<% end %>
<span id="attachments_fields">
<span>
<%= file_field_tag 'attachments[1][file]', :id => nil, :class => 'file',
:onchange => "checkFileSize(this, #{Setting.attachment_max_size.to_i.kilobytes}, '#{escape_javascript(l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)))}');" -%>
<%= text_field_tag 'attachments[1][description]', '', :id => nil, :class => 'description', :maxlength => 255, :placeholder => l(:label_optional_description) %>
<%= link_to_function(image_tag('delete.png'), 'removeFileField(this)', :title => (l(:button_delete))) %>
</span>
</span>
<span class="add_attachment"><%= link_to l(:label_add_another_file), '#', :onclick => 'addFileField(); return false;', :class => 'add_attachment' %>
(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</span>
<span class="add_attachment">
<%= file_field_tag 'attachments_files',
:id => nil,
:multiple => true,
:onchange => 'addInputFiles(this);',
:data => {
:max_file_size => Setting.attachment_max_size.to_i.kilobytes,
:max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)),
:max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i,
:upload_path => uploads_path(:format => 'js'),
:description_placeholder => l(:label_optional_description)
} %>
(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
</span>

<%= javascript_include_tag 'attachments' %>
1 change: 1 addition & 0 deletions app/views/attachments/destroy.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$('#attachments_<%= j params[:attachment_id] %>').remove();
9 changes: 9 additions & 0 deletions app/views/attachments/upload.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var fileSpan = $('#attachments_<%= j params[:attachment_id] %>');
$('<input>', { type: 'hidden', name: 'attachments[<%= j params[:attachment_id] %>][token]' } ).val('<%= j @attachment.token %>').appendTo(fileSpan);
fileSpan.find('a.remove-upload')
.attr({
"data-remote": true,
"data-method": 'delete',
href: '<%= j attachment_path(@attachment, :attachment_id => params[:attachment_id], :format => 'js') %>'
})
.off('click');
2 changes: 1 addition & 1 deletion app/views/common/_preview.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<fieldset class="preview"><legend><%= l(:label_preview) %></legend>
<%= textilizable @text, :attachments => @attachements, :object => @previewed %>
<%= textilizable @text, :attachments => @attachments, :object => @previewed %>
</fieldset>
4 changes: 2 additions & 2 deletions app/views/previews/issue.html.erb
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<% if @notes %>
<fieldset class="preview"><legend><%= l(:field_notes) %></legend>
<%= textilizable @notes, :attachments => @attachements, :object => @issue %>
<%= textilizable @notes, :attachments => @attachments, :object => @issue %>
</fieldset>
<% end %>

<% if @description %>
<fieldset class="preview"><legend><%= l(:field_description) %></legend>
<%= textilizable @description, :attachments => @attachements, :object => @issue %>
<%= textilizable @description, :attachments => @attachments, :object => @issue %>
</fieldset>
<% end %>
3 changes: 3 additions & 0 deletions config/configuration.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ default:
#
rmagick_font_path:

# Maximum number of simultaneous AJAX uploads
#max_concurrent_ajax_uploads: 2

# specific configuration options for production environment
# that overrides the default ones
production:
Expand Down
3 changes: 2 additions & 1 deletion lib/redmine/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ module Configuration

# Configuration default values
@defaults = {
'email_delivery' => nil
'email_delivery' => nil,
'max_concurrent_ajax_uploads' => 2
}

@config = nil
Expand Down
Binary file added public/images/hourglass.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 7 additions & 36 deletions public/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,40 +290,6 @@ function submit_query_form(id) {
$('#'+id).submit();
}

var fileFieldCount = 1;
function addFileField() {
var fields = $('#attachments_fields');
if (fields.children().length >= 10) return false;
fileFieldCount++;
var s = fields.children('span').first().clone();
s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val('');
s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val('');
fields.append(s);
}

function removeFileField(el) {
var fields = $('#attachments_fields');
var s = $(el).parents('span').first();
if (fields.children().length > 1) {
s.remove();
} else {
s.children('input.file').val('');
s.children('input.description').val('');
}
}

function checkFileSize(el, maxSize, message) {
var files = el.files;
if (files) {
for (var i=0; i<files.length; i++) {
if (files[i].size > maxSize) {
alert(message);
el.value = "";
}
}
}
}

function showTab(name) {
$('div#content .tab-content').hide();
$('div.tabs a').removeClass('selected');
Expand Down Expand Up @@ -579,8 +545,8 @@ function warnLeavingUnsaved(message) {
};

$(document).ready(function(){
$('#ajax-indicator').bind('ajaxSend', function(){
if ($('.ajax-loading').length == 0) {
$('#ajax-indicator').bind('ajaxSend', function(event, xhr, settings){
if ($('.ajax-loading').length == 0 && settings.contentType != 'application/octet-stream') {
$('#ajax-indicator').show();
}
});
Expand All @@ -607,5 +573,10 @@ function addFormObserversForDoubleSubmit() {
});
}

function blockEventPropagation(event) {
event.stopPropagation();
event.preventDefault();
}

$(document).ready(hideOnLoad);
$(document).ready(addFormObserversForDoubleSubmit);
Loading

0 comments on commit ef25210

Please sign in to comment.