sunrise-file-upload 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +43 -0
- data/Rakefile +44 -0
- data/lib/generators/sunrise/file_upload/USAGE +7 -0
- data/lib/generators/sunrise/file_upload/install_generator.rb +26 -0
- data/lib/generators/sunrise/file_upload/templates/fileuploader-input.js +217 -0
- data/lib/sunrise-file-upload.rb +2 -0
- data/lib/sunrise/file_upload.rb +20 -0
- data/lib/sunrise/file_upload/active_record.rb +95 -0
- data/lib/sunrise/file_upload/callbacks.rb +51 -0
- data/lib/sunrise/file_upload/engine.rb +25 -0
- data/lib/sunrise/file_upload/form_builder.rb +21 -0
- data/lib/sunrise/file_upload/http.rb +81 -0
- data/lib/sunrise/file_upload/manager.rb +76 -0
- data/lib/sunrise/file_upload/request.rb +26 -0
- data/lib/sunrise/file_upload/version.rb +5 -0
- data/lib/sunrise/file_upload/view_helper.rb +58 -0
- metadata +81 -0
data/README.rdoc
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= Sunrise CMS: File uploader
|
2
|
+
|
3
|
+
== Install
|
4
|
+
|
5
|
+
rails generate sunrise:file_upload:install
|
6
|
+
|
7
|
+
== Callbacks
|
8
|
+
|
9
|
+
Sunrise::FileUpload::Manager.before_create do |env, asset|
|
10
|
+
asset.user = env['warden'].user if env['warden']
|
11
|
+
end
|
12
|
+
|
13
|
+
Sunrise::FileUpload::Manager.after_create do |env, asset|
|
14
|
+
asset.user = env['warden'].user if env['warden']
|
15
|
+
end
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
class User < ActiveRecord::Base
|
20
|
+
has_one :picture, :as => :assetable, :dependent => :destroy
|
21
|
+
|
22
|
+
fileuploads :picture
|
23
|
+
end
|
24
|
+
|
25
|
+
Find asset by foreign key or guid:
|
26
|
+
|
27
|
+
@user.fileupload_asset(:picture)
|
28
|
+
|
29
|
+
=== Views
|
30
|
+
|
31
|
+
<%= stylesheet_link_tag "fileupload/jquery.fileupload-ui.css" %>
|
32
|
+
<%= javascript_include_tag :fileupload %>
|
33
|
+
|
34
|
+
<%= form.fileupload :picture %>
|
35
|
+
<%= form.hidden_field :fileupload_guid if form.object.new_record? %>
|
36
|
+
|
37
|
+
<script id="fileupload_tmpl" type="text/x-jquery-tmpl">
|
38
|
+
<div class="fileupload-container">
|
39
|
+
<div class="fileupload-preview"><img alt="Фото" src="/images/userico.jpg"></div>
|
40
|
+
<div class="fileupload-button"><input type="image" value="Оберіть фаіл" src="/images/but_set.png"></div>
|
41
|
+
<ul class="fileupload-list" style="display:none;"></ul>
|
42
|
+
</div>
|
43
|
+
</script>
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require File.join(File.dirname(__FILE__), 'lib', 'sunrise', 'file_upload', 'version')
|
6
|
+
|
7
|
+
desc 'Default: run unit tests.'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Test the sunrise plugin.'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Generate documentation for the sunrise-file-upload plugin.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'Sunrise FileUpload'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'jeweler'
|
29
|
+
Jeweler::Tasks.new do |s|
|
30
|
+
s.name = "sunrise-file-upload"
|
31
|
+
s.version = Sunrise::FileUpload::VERSION.dup
|
32
|
+
s.summary = "Rails FileUpload"
|
33
|
+
s.description = "Sunrise is a Aimbulance CMS"
|
34
|
+
s.email = "galeta.igor@gmail.com"
|
35
|
+
s.homepage = "https://github.com/galetahub/sunrise-file-upload"
|
36
|
+
s.authors = ["Igor Galeta", "Pavlo Galeta"]
|
37
|
+
s.files = FileList["[A-Z]*", "{app,config,lib}/**/*"]
|
38
|
+
s.extra_rdoc_files = FileList["[A-Z]*"] - %w(Gemfile Rakefile)
|
39
|
+
end
|
40
|
+
|
41
|
+
Jeweler::GemcutterTasks.new
|
42
|
+
rescue LoadError
|
43
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Sunrise
|
4
|
+
module Generators
|
5
|
+
module FileUpload
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
desc "This generator downloads and installs FileUploader"
|
8
|
+
source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
9
|
+
|
10
|
+
def copy_javascripts
|
11
|
+
copy_file "fileuploader-input.js", 'public/javascripts/fileupload/fileuploader-input.js'
|
12
|
+
end
|
13
|
+
|
14
|
+
def download_stylesheet
|
15
|
+
say_status("fetching fileuploader.css", "", :green)
|
16
|
+
get "https://github.com/galetahub/file-uploader/raw/master/client/fileuploader.css", "public/javascripts/fileupload/fileuploader.css"
|
17
|
+
end
|
18
|
+
|
19
|
+
def download_fileupload
|
20
|
+
say_status("fetching fileuploader.js", "", :green)
|
21
|
+
get "https://github.com/galetahub/file-uploader/raw/master/client/fileuploader.js", "public/javascripts/fileupload/fileuploader.js"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
// Collection of all instances on page
|
2
|
+
qq.FileUploader.instances = new Object();
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Class that creates upload widget with drag-and-drop and file list
|
6
|
+
* @inherits qq.FileUploaderBasic
|
7
|
+
*/
|
8
|
+
qq.FileUploaderInput = function(o){
|
9
|
+
// call parent constructor
|
10
|
+
qq.FileUploaderBasic.apply(this, arguments);
|
11
|
+
|
12
|
+
// additional options
|
13
|
+
qq.extend(this._options, {
|
14
|
+
element: null,
|
15
|
+
// if set, will be used instead of qq-upload-list in template
|
16
|
+
listElement: null,
|
17
|
+
|
18
|
+
template_id: '#fileupload_tmpl',
|
19
|
+
|
20
|
+
// template for one item in file list
|
21
|
+
fileTemplate: '<li>' +
|
22
|
+
'<span class="fileupload-file"></span>' +
|
23
|
+
'<span class="fileupload-spinner"></span>' +
|
24
|
+
'<span class="fileupload-size"></span>' +
|
25
|
+
'<a class="fileupload-cancel" href="#">Cancel</a>' +
|
26
|
+
'<span class="fileupload-failed-text">Failed</span>' +
|
27
|
+
'</li>',
|
28
|
+
|
29
|
+
classes: {
|
30
|
+
// used to get elements from templates
|
31
|
+
button: 'fileupload-button',
|
32
|
+
drop: 'fileupload-drop-area',
|
33
|
+
dropActive: 'fileupload-drop-area-active',
|
34
|
+
list: 'fileupload-list',
|
35
|
+
preview: 'fileupload-preview',
|
36
|
+
|
37
|
+
file: 'fileupload-file',
|
38
|
+
spinner: 'fileupload-spinner',
|
39
|
+
size: 'fileupload-size',
|
40
|
+
cancel: 'fileupload-cancel',
|
41
|
+
|
42
|
+
// added to list item when upload completes
|
43
|
+
// used in css to hide progress spinner
|
44
|
+
success: 'fileupload-success',
|
45
|
+
fail: 'fileupload-fail'
|
46
|
+
}
|
47
|
+
});
|
48
|
+
// overwrite options with user supplied
|
49
|
+
qq.extend(this._options, o);
|
50
|
+
|
51
|
+
this._element = document.getElementById(this._options.element);
|
52
|
+
this._element.innerHTML = $(this._options.template_id).tmpl(this._options).html();
|
53
|
+
this._listElement = this._options.listElement || this._find(this._element, 'list');
|
54
|
+
|
55
|
+
this._classes = this._options.classes;
|
56
|
+
|
57
|
+
this._button = this._createUploadButton(this._find(this._element, 'button'));
|
58
|
+
|
59
|
+
this._bindCancelEvent();
|
60
|
+
//this._setupDragDrop();
|
61
|
+
|
62
|
+
qq.FileUploader.instances[this._element.id] = this;
|
63
|
+
};
|
64
|
+
|
65
|
+
// inherit from Basic Uploader
|
66
|
+
qq.extend(qq.FileUploaderInput.prototype, qq.FileUploaderBasic.prototype);
|
67
|
+
|
68
|
+
qq.extend(qq.FileUploaderInput.prototype, {
|
69
|
+
/**
|
70
|
+
* Gets one of the elements listed in this._options.classes
|
71
|
+
**/
|
72
|
+
_find: function(parent, type){
|
73
|
+
var element = qq.getByClass(parent, this._options.classes[type])[0];
|
74
|
+
if (!element){
|
75
|
+
throw new Error('element not found ' + type);
|
76
|
+
}
|
77
|
+
|
78
|
+
return element;
|
79
|
+
},
|
80
|
+
_setupDragDrop: function(){
|
81
|
+
var self = this,
|
82
|
+
dropArea = this._find(this._element, 'drop');
|
83
|
+
|
84
|
+
var dz = new qq.UploadDropZone({
|
85
|
+
element: dropArea,
|
86
|
+
onEnter: function(e){
|
87
|
+
qq.addClass(dropArea, self._classes.dropActive);
|
88
|
+
e.stopPropagation();
|
89
|
+
},
|
90
|
+
onLeave: function(e){
|
91
|
+
e.stopPropagation();
|
92
|
+
},
|
93
|
+
onLeaveNotDescendants: function(e){
|
94
|
+
qq.removeClass(dropArea, self._classes.dropActive);
|
95
|
+
},
|
96
|
+
onDrop: function(e){
|
97
|
+
dropArea.style.display = 'none';
|
98
|
+
qq.removeClass(dropArea, self._classes.dropActive);
|
99
|
+
self._uploadFileList(e.dataTransfer.files);
|
100
|
+
}
|
101
|
+
});
|
102
|
+
|
103
|
+
dropArea.style.display = 'none';
|
104
|
+
|
105
|
+
qq.attach(document, 'dragenter', function(e){
|
106
|
+
if (!dz._isValidFileDrag(e)) return;
|
107
|
+
|
108
|
+
dropArea.style.display = 'block';
|
109
|
+
});
|
110
|
+
qq.attach(document, 'dragleave', function(e){
|
111
|
+
if (!dz._isValidFileDrag(e)) return;
|
112
|
+
|
113
|
+
var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
|
114
|
+
// only fire when leaving document out
|
115
|
+
if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){
|
116
|
+
dropArea.style.display = 'none';
|
117
|
+
}
|
118
|
+
});
|
119
|
+
},
|
120
|
+
_onSubmit: function(id, fileName){
|
121
|
+
qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
|
122
|
+
this._addToList(id, fileName);
|
123
|
+
},
|
124
|
+
_onProgress: function(id, fileName, loaded, total){
|
125
|
+
qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
|
126
|
+
|
127
|
+
var item = this._getItemByFileId(id);
|
128
|
+
var size = this._find(item, 'size');
|
129
|
+
size.style.display = 'inline';
|
130
|
+
|
131
|
+
var text;
|
132
|
+
if (loaded != total){
|
133
|
+
text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
|
134
|
+
} else {
|
135
|
+
text = this._formatSize(total);
|
136
|
+
}
|
137
|
+
|
138
|
+
qq.setText(size, text);
|
139
|
+
},
|
140
|
+
_onComplete: function(id, fileName, result){
|
141
|
+
qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
|
142
|
+
|
143
|
+
var item = this._getItemByFileId(id);
|
144
|
+
var asset = result.asset;
|
145
|
+
|
146
|
+
// mark completed
|
147
|
+
qq.remove(this._find(item, 'cancel'));
|
148
|
+
qq.remove(this._find(item, 'spinner'));
|
149
|
+
|
150
|
+
if (asset && asset.id){
|
151
|
+
qq.addClass(item, this._classes.success);
|
152
|
+
this._updatePreview(result);
|
153
|
+
} else {
|
154
|
+
qq.addClass(item, this._classes.fail);
|
155
|
+
}
|
156
|
+
},
|
157
|
+
_addToList: function(id, fileName){
|
158
|
+
if (this._listElement) {
|
159
|
+
var item = qq.toElement(this._options.fileTemplate);
|
160
|
+
item.qqFileId = id;
|
161
|
+
|
162
|
+
var fileElement = this._find(item, 'file');
|
163
|
+
qq.setText(fileElement, this._formatFileName(fileName));
|
164
|
+
this._find(item, 'size').style.display = 'none';
|
165
|
+
|
166
|
+
this._listElement.appendChild(item);
|
167
|
+
}
|
168
|
+
},
|
169
|
+
_getItemByFileId: function(id){
|
170
|
+
var item = this._listElement.firstChild;
|
171
|
+
|
172
|
+
// there can't be txt nodes in dynamically created list
|
173
|
+
// and we can use nextSibling
|
174
|
+
while (item){
|
175
|
+
if (item.qqFileId == id) return item;
|
176
|
+
item = item.nextSibling;
|
177
|
+
}
|
178
|
+
},
|
179
|
+
/**
|
180
|
+
* delegate click event for cancel link
|
181
|
+
**/
|
182
|
+
_bindCancelEvent: function(){
|
183
|
+
var self = this,
|
184
|
+
list = this._listElement;
|
185
|
+
|
186
|
+
if (list) {
|
187
|
+
qq.attach(list, 'click', function(e){
|
188
|
+
e = e || window.event;
|
189
|
+
var target = e.target || e.srcElement;
|
190
|
+
|
191
|
+
if (qq.hasClass(target, self._classes.cancel)){
|
192
|
+
qq.preventDefault(e);
|
193
|
+
|
194
|
+
var item = target.parentNode;
|
195
|
+
self._handler.cancel(item.qqFileId);
|
196
|
+
qq.remove(item);
|
197
|
+
}
|
198
|
+
});
|
199
|
+
}
|
200
|
+
},
|
201
|
+
|
202
|
+
_updatePreview: function(result) {
|
203
|
+
var preview = this._find(this._element, 'preview'),
|
204
|
+
asset = result.asset || result;
|
205
|
+
img = null;
|
206
|
+
|
207
|
+
if (preview && asset) {
|
208
|
+
img = document.createElement('img');
|
209
|
+
img.src = asset.thumb_url;
|
210
|
+
img.alt = asset.filename;
|
211
|
+
img.setAttribute('data-url', asset.url);
|
212
|
+
|
213
|
+
preview.innerHTML = '';
|
214
|
+
preview.appendChild(img);
|
215
|
+
}
|
216
|
+
}
|
217
|
+
});
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'active_support/secure_random'
|
2
|
+
|
3
|
+
module Sunrise
|
4
|
+
module FileUpload
|
5
|
+
autoload :Http, 'sunrise/file_upload/http'
|
6
|
+
autoload :Manager, 'sunrise/file_upload/manager'
|
7
|
+
autoload :Request, 'sunrise/file_upload/request'
|
8
|
+
autoload :ActiveRecord, 'sunrise/file_upload/active_record'
|
9
|
+
autoload :Callbacks, 'sunrise/file_upload/callbacks'
|
10
|
+
|
11
|
+
autoload :ViewHelper, 'sunrise/file_upload/view_helper'
|
12
|
+
autoload :FormBuilder, 'sunrise/file_upload/form_builder'
|
13
|
+
|
14
|
+
def self.guid
|
15
|
+
ActiveSupport::SecureRandom.base64(15).tr('+/=', 'xyz').slice(0, 10)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'sunrise/file_upload/engine'
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Sunrise
|
2
|
+
module FileUpload
|
3
|
+
module ActiveRecord
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, SingletonMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module SingletonMethods
|
9
|
+
def fileuploads(*args)
|
10
|
+
options = args.extract_options!
|
11
|
+
|
12
|
+
class_attribute :fileuploads_options, :instance_writer => false
|
13
|
+
self.fileuploads_options = options
|
14
|
+
|
15
|
+
class_attribute :fileuploads_columns, :instance_writer => false
|
16
|
+
self.fileuploads_columns = args.map(&:to_sym)
|
17
|
+
|
18
|
+
unless self.is_a?(ClassMethods)
|
19
|
+
include InstanceMethods
|
20
|
+
extend ClassMethods
|
21
|
+
|
22
|
+
attr_accessible :fileupload_guid
|
23
|
+
after_save :fileuploads_update, :if => :fileupload_changed?
|
24
|
+
|
25
|
+
args.each do |asset|
|
26
|
+
accepts_nested_attributes_for asset, :allow_destroy => true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
# Update reflection klass by guid
|
34
|
+
def fileupload_update(record_id, guid, method)
|
35
|
+
klass = fileupload_klass(method)
|
36
|
+
klass.update_all(["assetable_id = ?, guid = ?", record_id, nil], ["assetable_type = ? AND guid = ?", name, guid])
|
37
|
+
end
|
38
|
+
|
39
|
+
# Find asset by guid
|
40
|
+
def fileupload_find(method, guid)
|
41
|
+
klass = fileupload_klass(method)
|
42
|
+
klass.where(:guid => guid).first
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def fileupload_klass(method)
|
48
|
+
reflections[method.to_sym].klass
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module InstanceMethods
|
53
|
+
# Generate unique key
|
54
|
+
def fileupload_guid
|
55
|
+
@fileupload_guid ||= Sunrise::FileUpload.guid
|
56
|
+
end
|
57
|
+
|
58
|
+
def fileupload_guid=(value)
|
59
|
+
@fileupload_changed = true unless value.blank?
|
60
|
+
@fileupload_guid = value.blank? ? nil : value
|
61
|
+
end
|
62
|
+
|
63
|
+
def fileupload_changed?
|
64
|
+
@fileupload_changed
|
65
|
+
end
|
66
|
+
|
67
|
+
def fileupload_multiple?(method)
|
68
|
+
association = self.class.reflect_on_association(method)
|
69
|
+
association.collection?
|
70
|
+
end
|
71
|
+
|
72
|
+
# Find or build new asset object
|
73
|
+
def fileupload_asset(method)
|
74
|
+
if fileuploads_columns.include?(method.to_sym)
|
75
|
+
asset = new_record? ? self.class.fileupload_find(method, fileupload_guid) : send(method)
|
76
|
+
asset ||= send("build_#{method}") if respond_to?("build_#{method}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def fileuploads_columns
|
81
|
+
self.class.fileuploads_columns
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def fileuploads_update
|
87
|
+
fileuploads_columns.each do |method|
|
88
|
+
self.class.fileupload_update(id, fileupload_guid, method)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Sunrise
|
3
|
+
module FileUpload
|
4
|
+
module Callbacks
|
5
|
+
# Hook to _run_callbacks asserting for conditions.
|
6
|
+
def _run_callbacks(kind, *args) #:nodoc:
|
7
|
+
options = args.last # Last callback arg MUST be a Hash
|
8
|
+
|
9
|
+
send("_#{kind}").each do |callback, conditions|
|
10
|
+
invalid = conditions.find do |key, value|
|
11
|
+
value.is_a?(Array) ? !value.include?(options[key]) : (value != options[key])
|
12
|
+
end
|
13
|
+
|
14
|
+
callback.call(*args) unless invalid
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# A callback that runs before create asset
|
19
|
+
# Example:
|
20
|
+
# Sunrise::FileUpload::Manager.before_create do |env, opts|
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
def before_create(options = {}, method = :push, &block)
|
24
|
+
raise BlockNotGiven unless block_given?
|
25
|
+
_before_create.send(method, [block, options])
|
26
|
+
end
|
27
|
+
|
28
|
+
# Provides access to the callback array for before_create
|
29
|
+
# :api: private
|
30
|
+
def _before_create
|
31
|
+
@_before_create ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
# A callback that runs after asset created
|
35
|
+
# Example:
|
36
|
+
# Sunrise::FileUpload::Manager.after_create do |env, opts|
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
def after_create(options = {}, method = :push, &block)
|
40
|
+
raise BlockNotGiven unless block_given?
|
41
|
+
_after_create.send(method, [block, options])
|
42
|
+
end
|
43
|
+
|
44
|
+
# Provides access to the callback array for after_create
|
45
|
+
# :api: private
|
46
|
+
def _after_create
|
47
|
+
@_after_create ||= []
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'sunrise-file-upload'
|
3
|
+
|
4
|
+
module Sunrise
|
5
|
+
module FileUpload
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
# Initialize Rack file upload
|
8
|
+
config.app_middleware.use Sunrise::FileUpload::Manager, :paths => "/sunrise/fileupload"
|
9
|
+
|
10
|
+
config.before_initialize do
|
11
|
+
ActiveSupport.on_load :active_record do
|
12
|
+
::ActiveRecord::Base.send :include, Sunrise::FileUpload::ActiveRecord
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveSupport.on_load :action_view do
|
16
|
+
ActionView::Base.send :include, Sunrise::FileUpload::ViewHelper
|
17
|
+
ActionView::Helpers::FormBuilder.send :include, Sunrise::FileUpload::FormBuilder
|
18
|
+
|
19
|
+
ActionView::Helpers::AssetTagHelper.register_javascript_expansion :fileupload =>
|
20
|
+
["fileupload/fileuploader.js", "fileupload/fileuploader-input.js"]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sunrise
|
2
|
+
module FileUpload
|
3
|
+
module FormBuilder
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:include, ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Example:
|
10
|
+
# <%= form_for @post do |form| %>
|
11
|
+
# ...
|
12
|
+
# <%= form.fileupload :picture %>
|
13
|
+
# <% end %>
|
14
|
+
#
|
15
|
+
def fileupload(method, options = {})
|
16
|
+
@template.fileupload_tag(@object_name, method, objectify_options(options))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'mime/types'
|
4
|
+
|
5
|
+
module Sunrise
|
6
|
+
module FileUpload
|
7
|
+
module Http
|
8
|
+
# Create file from hash
|
9
|
+
class UploadedFile
|
10
|
+
attr_accessor :original_filename, :content_type, :tempfile, :headers
|
11
|
+
|
12
|
+
def initialize(hash)
|
13
|
+
@original_filename = hash[:filename]
|
14
|
+
@content_type = hash[:type]
|
15
|
+
@headers = hash[:head]
|
16
|
+
@tempfile = hash[:tempfile]
|
17
|
+
raise(ArgumentError, ':tempfile is required') unless @tempfile
|
18
|
+
end
|
19
|
+
|
20
|
+
def open
|
21
|
+
@tempfile.open
|
22
|
+
end
|
23
|
+
|
24
|
+
def path
|
25
|
+
@tempfile.path
|
26
|
+
end
|
27
|
+
|
28
|
+
def read(*args)
|
29
|
+
@tempfile.read(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def rewind
|
33
|
+
@tempfile.rewind
|
34
|
+
end
|
35
|
+
|
36
|
+
def size
|
37
|
+
@tempfile.size
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Usage (paperclip example)
|
42
|
+
# @asset.data = QqFile.new(params[:qqfile], request)
|
43
|
+
class QqFile < ::Tempfile
|
44
|
+
|
45
|
+
def initialize(filename, request, tmpdir = Dir::tmpdir)
|
46
|
+
@original_filename = filename
|
47
|
+
@request = request
|
48
|
+
|
49
|
+
super Digest::SHA1.hexdigest(filename), tmpdir
|
50
|
+
fetch
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch
|
54
|
+
self.write @request.raw_post
|
55
|
+
self.rewind
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def original_filename
|
60
|
+
@original_filename
|
61
|
+
end
|
62
|
+
|
63
|
+
def content_type
|
64
|
+
types = MIME::Types.type_for(@request.content_type)
|
65
|
+
types.empty? ? @request.content_type : types.first.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Convert nested Hash to HashWithIndifferentAccess and replace
|
70
|
+
# file upload hash with UploadedFile objects
|
71
|
+
def self.normalize_param(*args)
|
72
|
+
value = args.first
|
73
|
+
if Hash === value && value.has_key?(:tempfile)
|
74
|
+
UploadedFile.new(value)
|
75
|
+
elsif value.is_a?(String)
|
76
|
+
QqFile.new(*args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Sunrise
|
2
|
+
module FileUpload
|
3
|
+
class Manager
|
4
|
+
extend Sunrise::FileUpload::Callbacks
|
5
|
+
|
6
|
+
def initialize(app, options = {})
|
7
|
+
@app = app
|
8
|
+
@paths = [options[:paths]].flatten
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
raw_file_post?(env) ? create(env) : @app.call(env)
|
13
|
+
end
|
14
|
+
|
15
|
+
# :api: private
|
16
|
+
def _run_callbacks(*args) #:nodoc:
|
17
|
+
self.class._run_callbacks(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def create(env)
|
23
|
+
request = Request.new(env)
|
24
|
+
params = request.params.symbolize_keys
|
25
|
+
|
26
|
+
klass = find_klass(params)
|
27
|
+
asset = find_asset(klass, params) || klass.new(params[:asset])
|
28
|
+
|
29
|
+
asset.assetable_type = params[:assetable_type]
|
30
|
+
asset.assetable_id = params[:assetable_id].blank? ? 0 : params[:assetable_id].to_i
|
31
|
+
asset.guid = params[:guid]
|
32
|
+
asset.data = Http.normalize_param(params[:qqfile], request)
|
33
|
+
|
34
|
+
_run_callbacks(:before_create, env, asset)
|
35
|
+
|
36
|
+
if asset.save
|
37
|
+
body = asset.to_json
|
38
|
+
status = 200
|
39
|
+
|
40
|
+
_run_callbacks(:after_create, env, asset)
|
41
|
+
else
|
42
|
+
body = asset.errors.to_json
|
43
|
+
status = 422
|
44
|
+
end
|
45
|
+
|
46
|
+
[status, {'Content-Type' => 'text/html', 'Content-Length' => body.size.to_s}, body]
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_klass(params)
|
50
|
+
params[:klass].blank? ? Asset : params[:klass].classify.constantize
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_asset(klass, params)
|
54
|
+
query = klass.scoped
|
55
|
+
|
56
|
+
unless params[:assetable_id].blank? || params[:assetable_type].blank?
|
57
|
+
query = query.where(:assetable_id => params[:assetable_id].to_i)
|
58
|
+
query = query.where(:assetable_type => params[:assetable_type])
|
59
|
+
else
|
60
|
+
query = query.where(:guid => params[:guid])
|
61
|
+
end
|
62
|
+
|
63
|
+
query.first
|
64
|
+
end
|
65
|
+
|
66
|
+
def raw_file_post?(env)
|
67
|
+
env['REQUEST_METHOD'] == 'POST' && upload_path?(env['PATH_INFO'])
|
68
|
+
end
|
69
|
+
|
70
|
+
def upload_path?(request_path)
|
71
|
+
return true if @paths.nil?
|
72
|
+
@paths.any? { |candidate| candidate == request_path }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Sunrise
|
5
|
+
module FileUpload
|
6
|
+
class Request < ::Rack::Request
|
7
|
+
|
8
|
+
def raw_post
|
9
|
+
unless @env.include? 'RAW_POST_DATA'
|
10
|
+
@env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
|
11
|
+
body.rewind if body.respond_to?(:rewind)
|
12
|
+
end
|
13
|
+
@env['RAW_POST_DATA']
|
14
|
+
end
|
15
|
+
|
16
|
+
def body
|
17
|
+
if raw_post = @env['RAW_POST_DATA']
|
18
|
+
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
|
19
|
+
StringIO.new(raw_post)
|
20
|
+
else
|
21
|
+
@env['rack.input']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Sunrise
|
2
|
+
module FileUpload
|
3
|
+
module ViewHelper
|
4
|
+
include ActionView::Helpers::JavaScriptHelper
|
5
|
+
|
6
|
+
def fileupload_tag(object_name, method, options = {})
|
7
|
+
object = options.delete(:object) if options.key?(:object)
|
8
|
+
object ||= @template.instance_variable_get("@#{object_name}")
|
9
|
+
|
10
|
+
value = options.delete(:value) if options.key?(:value)
|
11
|
+
value ||= object.fileupload_asset(method)
|
12
|
+
|
13
|
+
element_guid = object.fileupload_guid
|
14
|
+
element_id = dom_id(object, [method, element_guid].join('_'))
|
15
|
+
|
16
|
+
script_options = (options.delete(:script) || {}).stringify_keys
|
17
|
+
|
18
|
+
params = {
|
19
|
+
:klass => value.class.name,
|
20
|
+
:assetable_id => object.new_record? ? nil : object.id,
|
21
|
+
:assetable_type => object.class.name,
|
22
|
+
:guid => element_guid
|
23
|
+
}.merge(script_options.delete(:params) || {})
|
24
|
+
|
25
|
+
script_options['action'] ||= '/sunrise/fileupload?' + Rack::Utils.build_query(params)
|
26
|
+
script_options['allowedExtensions'] ||= ['jpg', 'jpeg', 'png', 'gif']
|
27
|
+
script_options['multiple'] ||= object.fileupload_multiple?(method)
|
28
|
+
|
29
|
+
content_tag(:div, :class => 'fileupload') do
|
30
|
+
content_tag(:div, :id => element_id) do
|
31
|
+
content_tag(:noscript) do
|
32
|
+
fields_for object do |form|
|
33
|
+
form.fields_for method, value do |f|
|
34
|
+
f.file_field :data
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end + javascript_tag( fileupload_script(element_id, value, script_options) )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def fileupload_script(element_id, value = nil, options = {})
|
45
|
+
options = { 'element' => element_id }.merge(options)
|
46
|
+
formatted_options = options.inspect.gsub('=>', ':')
|
47
|
+
js = [ "new qq.FileUploaderInput(#{formatted_options});" ]
|
48
|
+
|
49
|
+
if value && !value.new_record?
|
50
|
+
js << "qq.FileUploader.instances['#{element_id}']._updatePreview(#{value.to_json});"
|
51
|
+
end
|
52
|
+
|
53
|
+
"$(document).ready(function(){ #{js.join} });"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sunrise-file-upload
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Igor Galeta
|
14
|
+
- Pavlo Galeta
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-05-19 00:00:00 Z
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Sunrise is a Aimbulance CMS
|
23
|
+
email: galeta.igor@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.rdoc
|
30
|
+
files:
|
31
|
+
- README.rdoc
|
32
|
+
- Rakefile
|
33
|
+
- lib/generators/sunrise/file_upload/USAGE
|
34
|
+
- lib/generators/sunrise/file_upload/install_generator.rb
|
35
|
+
- lib/generators/sunrise/file_upload/templates/fileuploader-input.js
|
36
|
+
- lib/sunrise-file-upload.rb
|
37
|
+
- lib/sunrise/file_upload.rb
|
38
|
+
- lib/sunrise/file_upload/active_record.rb
|
39
|
+
- lib/sunrise/file_upload/callbacks.rb
|
40
|
+
- lib/sunrise/file_upload/engine.rb
|
41
|
+
- lib/sunrise/file_upload/form_builder.rb
|
42
|
+
- lib/sunrise/file_upload/http.rb
|
43
|
+
- lib/sunrise/file_upload/manager.rb
|
44
|
+
- lib/sunrise/file_upload/request.rb
|
45
|
+
- lib/sunrise/file_upload/version.rb
|
46
|
+
- lib/sunrise/file_upload/view_helper.rb
|
47
|
+
homepage: https://github.com/galetahub/sunrise-file-upload
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.2
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Rails FileUpload
|
80
|
+
test_files: []
|
81
|
+
|