diff --git a/app/controllers/aspects_controller.rb b/app/controllers/aspects_controller.rb
index 95ae47f8d..101901daa 100644
--- a/app/controllers/aspects_controller.rb
+++ b/app/controllers/aspects_controller.rb
@@ -9,7 +9,7 @@ class AspectsController < ApplicationController
respond_to :json, :only => :show
def index
- @posts = current_user.visible_posts(:by_members_of => :all).paginate :page => params[:page], :per_page => 15, :order => 'created_at DESC'
+ @posts = current_user.visible_posts(:by_members_of => :all, :pending => false).paginate :page => params[:page], :per_page => 15, :order => 'created_at DESC'
@aspect = :all
if current_user.getting_started == true
diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb
index 063e98b4c..492a673c6 100644
--- a/app/controllers/photos_controller.rb
+++ b/app/controllers/photos_controller.rb
@@ -34,7 +34,6 @@ class PhotosController < ApplicationController
end
def create
-
begin
params[:photo][:user_file] = file_handler(params)
@@ -43,9 +42,9 @@ class PhotosController < ApplicationController
if @photo.save
raise 'MongoMapper failed to catch a failed save' unless @photo.id
- current_user.dispatch_post(@photo, :to => params[:photo][:to])
+ current_user.dispatch_post(@photo, :to => params[:photo][:to]) unless @photo.pending
respond_to do |format|
- format.json{render(:layout => false , :json => {"success" => true, "data" => @photo}.to_json )}
+ format.json{ render(:layout => false , :json => {"success" => true, "data" => @photo}.to_json )}
end
else
respond_with :location => photos_path, :error => message
diff --git a/app/controllers/status_messages_controller.rb b/app/controllers/status_messages_controller.rb
index d41d4da02..cf38e3087 100644
--- a/app/controllers/status_messages_controller.rb
+++ b/app/controllers/status_messages_controller.rb
@@ -9,6 +9,7 @@ class StatusMessagesController < ApplicationController
respond_to :json, :only => :show
def create
+ puts params.inspect
public_flag = params[:status_message][:public]
public_flag.to_s.match(/(true)/) ? public_flag = true : public_flag = false
params[:status_message][:public] = public_flag
diff --git a/app/models/photo.rb b/app/models/photo.rb
index 28cedf8ea..1e6ad021e 100644
--- a/app/models/photo.rb
+++ b/app/models/photo.rb
@@ -14,6 +14,7 @@ class Photo < Post
key :remote_photo_path
key :remote_photo_name
key :random_string
+
timestamps!
@@ -76,5 +77,15 @@ class Photo < Post
1.upto(len) { |i| string << chars[rand(chars.size-1)] }
return string
end
+
+ def as_json(opts={})
+ {
+ :photo => {
+ :id => self.id,
+ :url => self.url(:thumb_small)
+ }
+ }
+ end
+
end
diff --git a/app/models/post.rb b/app/models/post.rb
index 842af2b2e..cd4a666c1 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -20,6 +20,7 @@ class Post
key :diaspora_handle, String
key :user_refs, Integer, :default => 0
+ key :pending, Boolean, :default => false
many :comments, :class_name => 'Comment', :foreign_key => :post_id, :order => 'created_at ASC'
belongs_to :person, :class_name => 'Person'
@@ -33,10 +34,12 @@ class Post
after_destroy :destroy_comments
attr_accessible :user_refs
+
def self.instantiate params
new_post = self.new params.to_hash
new_post.person = params[:person]
new_post.public = params[:public]
+ new_post.pending = params[:pending]
new_post.diaspora_handle = new_post.person.diaspora_handle
new_post
end
diff --git a/app/views/photos/_new_photo.haml b/app/views/photos/_new_photo.haml
index 71636bd1e..d19f119c3 100644
--- a/app/views/photos/_new_photo.haml
+++ b/app/views/photos/_new_photo.haml
@@ -3,31 +3,24 @@
-# the COPYRIGHT file.
:javascript
- function createUploader(){
- var uploader = new qq.FileUploader({
- element: document.getElementById('file-upload'),
- params: {'photo' : { 'to' : "#{aspect_id}"}, 'set_profile_image' : "#{set_profile_image if defined?(set_profile_image)}"},
- allowedExtensions: ['jpg', 'jpeg', 'png', 'gif'],
- action: "#{photos_path}",
- template: '
' +
- '
#{t('.drop')}
' +
- '
#{t('.upload')}
' +
- '
' +
- '
',
- fileTemplate: '' +
- '' +
- '' +
- '' +
- '#{t('cancel')}' +
- '#{t('.failed')}' +
- '',
- messages: {
- typeError: "#{t('.invalid_ext')}",
- sizeError: "#{t('.size_error')}",
- emptyError: "#{t('.empty')}"
- }
- });
- }
- window.onload = createUploader;
-
-#file-upload
+ function createUploader(){
+ var uploader = new qq.FileUploaderBasic({
+ element: document.getElementById('file-upload'),
+ params: {'photo' : {'pending' : 'true', 'to' : "#{aspect_id}"}, 'set_profile_image' : "#{set_profile_image if defined?(set_profile_image)}"},
+ allowedExtensions: ['jpg', 'jpeg', 'png', 'gif'],
+ action: "#{photos_path}",
+ debug: true,
+ button: document.getElementById('file-upload'),
+ sizeLimit: 5000048,
+ onComplete: function(id, fileName, responseJSON){
+ //var obj = jQuery.parseJSON(responseJSON.data);
+ alert(responseJSON.data.photo.url);
+ var id = responseJSON.data.photo.id;
+ alert($('#new_status_message').length);
+ $('#new_status_message').append("");
+
+ }
+
+ });
+ }
+ window.onload = createUploader;
diff --git a/app/views/shared/_publisher.haml b/app/views/shared/_publisher.haml
index bdccd7ead..361010478 100644
--- a/app/views/shared/_publisher.haml
+++ b/app/views/shared/_publisher.haml
@@ -11,9 +11,9 @@
};
});
- $("#publisher textarea, #publisher input").live("focus", function(evt){
- $("#publisher .options_and_submit").fadeIn(50);
- });
+ //$("#publisher textarea, #publisher input").live("focus", function(evt){
+ // $("#publisher .options_and_submit").fadeIn(50);
+ //});
$("#publisher form").live("submit", function(evt){
$("#publisher .options_and_submit").hide();
@@ -22,15 +22,18 @@
#publisher
= owner_image_link
- = form_for StatusMessage.new, :remote => true do |status|
+ = form_for StatusMessage.new, :html => {:multipart => true,}, :remote => true do |status|
+
= status.error_messages
- %p
+ %params
= status.label :message, t('.post_a_message_to', :aspect => (aspect == :all ? "everyone" : aspect))
= status.text_area :message, :rows => 2, :value => params[:prefill]
= status.hidden_field :to, :value => (aspect == :all ? aspect : aspect.id)
.options_and_submit
+ #file-upload.button
+ drag a photo to upload
- if aspect == :all
= status.submit t('.share'), :title => t('.share_with_all'), :disable_with => t('.posting')
- else
@@ -51,9 +54,3 @@
.fancybox_content
#question_mark_pane
= render 'shared/public_explain'
-
-
- /#publisher_photo_upload
- /= t('or')
- /= render 'photos/new_photo', :aspect_id => (aspect == :all ? aspect : aspect.id)
-
diff --git a/public/javascripts/vendor/fileuploader.js b/public/javascripts/vendor/fileuploader.js
index 19b1c104c..89c09ebf5 100644
--- a/public/javascripts/vendor/fileuploader.js
+++ b/public/javascripts/vendor/fileuploader.js
@@ -2,41 +2,491 @@
* http://github.com/valums/file-uploader
*
* Multiple file upload component with progress-bar, drag-and-drop.
- * © 2010 Andrew Valums andrew(at)valums.com
+ * © 2010 Andrew Valums ( andrew(at)valums.com )
*
* Licensed under GNU GPL 2 or later, see license.txt.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
*/
-
+
+//
+// Helper functions
+//
+
var qq = qq || {};
/**
- * Class that creates our multiple file upload widget
+ * Adds all missing properties from second obj to first obj
+ */
+qq.extend = function(first, second){
+ for (var prop in second){
+ first[prop] = second[prop];
+ }
+};
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+ if (arr.indexOf) return arr.indexOf(elt, from);
+
+ from = from || 0;
+ var len = arr.length;
+
+ if (from < 0) from += len;
+
+ for (; from < len; from++){
+ if (from in arr && arr[from] === elt){
+ return from;
+ }
+ }
+ return -1;
+};
+
+qq.getUniqueId = (function(){
+ var id = 0;
+ return function(){ return id++; };
+})();
+
+//
+// Events
+
+qq.attach = function(element, type, fn){
+ if (element.addEventListener){
+ element.addEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.attachEvent('on' + type, fn);
+ }
+};
+qq.detach = function(element, type, fn){
+ if (element.removeEventListener){
+ element.removeEventListener(type, fn, false);
+ } else if (element.attachEvent){
+ element.detachEvent('on' + type, fn);
+ }
+};
+
+qq.preventDefault = function(e){
+ if (e.preventDefault){
+ e.preventDefault();
+ } else{
+ e.returnValue = false;
+ }
+};
+
+//
+// Node manipulations
+
+/**
+ * Insert node a before node b.
+ */
+qq.insertBefore = function(a, b){
+ b.parentNode.insertBefore(a, b);
+};
+qq.remove = function(element){
+ element.parentNode.removeChild(element);
+};
+
+qq.contains = function(parent, descendant){
+ // compareposition returns false in this case
+ if (parent == descendant) return true;
+
+ if (parent.contains){
+ return parent.contains(descendant);
+ } else {
+ return !!(descendant.compareDocumentPosition(parent) & 8);
+ }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+ var div = document.createElement('div');
+ return function(html){
+ div.innerHTML = html;
+ var element = div.firstChild;
+ div.removeChild(element);
+ return element;
+ };
+})();
+
+//
+// Node properties and attributes
+
+/**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+qq.css = function(element, styles){
+ if (styles.opacity != null){
+ if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+ }
+ }
+ qq.extend(element.style, styles);
+};
+qq.hasClass = function(element, name){
+ var re = new RegExp('(^| )' + name + '( |$)');
+ return re.test(element.className);
+};
+qq.addClass = function(element, name){
+ if (!qq.hasClass(element, name)){
+ element.className += ' ' + name;
+ }
+};
+qq.removeClass = function(element, name){
+ var re = new RegExp('(^| )' + name + '( |$)');
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+};
+qq.setText = function(element, text){
+ element.innerText = text;
+ element.textContent = text;
+};
+
+//
+// Selecting elements
+
+qq.children = function(element){
+ var children = [],
+ child = element.firstChild;
+
+ while (child){
+ if (child.nodeType == 1){
+ children.push(child);
+ }
+ child = child.nextSibling;
+ }
+
+ return children;
+};
+
+qq.getByClass = function(element, className){
+ if (element.querySelectorAll){
+ return element.querySelectorAll('.' + className);
+ }
+
+ var result = [];
+ var candidates = element.getElementsByTagName("*");
+ var len = candidates.length;
+
+ for (var i = 0; i < len; i++){
+ if (qq.hasClass(candidates[i], className)){
+ result.push(candidates[i]);
+ }
+ }
+ return result;
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ *
+ * how to use:
+ *
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param Object JSON-Object
+ * @param String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+ var uristrings = [],
+ prefix = '&',
+ add = function(nextObj, i){
+ var nextTemp = temp
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
+ ? temp
+ : temp+'['+i+']'
+ : i;
+ if ((nextTemp != 'undefined') && (i != 'undefined')) {
+ uristrings.push(
+ (typeof nextObj === 'object')
+ ? qq.obj2url(nextObj, nextTemp, true)
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
+ );
+ }
+ };
+
+ if (!prefixDone && temp) {
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+ uristrings.push(temp);
+ uristrings.push(qq.obj2url(obj));
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
+ // we wont use a for-in-loop on an array (performance)
+ for (var i = 0, len = obj.length; i < len; ++i){
+ add(obj[i], i);
+ }
+ } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
+ // for anything else but a scalar, we will use for-in-loop
+ for (var i in obj){
+ add(obj[i], i);
+ }
+ } else {
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+ }
+
+ return uristrings.join(prefix)
+ .replace(/^&/, '')
+ .replace(/%20/g, '+');
+};
+
+//
+//
+// Uploader Classes
+//
+//
+
+var qq = qq || {};
+
+/**
+ * Creates upload button, validates upload, but doesn't create file list or dd.
+ */
+qq.FileUploaderBasic = function(o){
+ this._options = {
+ // set to true to see the server response
+ debug: false,
+ action: '/server/upload',
+ params: {},
+ button: null,
+ multiple: true,
+ maxConnections: 3,
+ // validation
+ allowedExtensions: [],
+ sizeLimit: 0,
+ minSizeLimit: 0,
+ // events
+ // return false to cancel submit
+ onSubmit: function(id, fileName){},
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, responseJSON){},
+ onCancel: function(id, fileName){},
+ // messages
+ messages: {
+ typeError: "{file} has invalid extension. Only {extensions} are allowed.",
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+ emptyError: "{file} is empty, please select files again without it.",
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
+ },
+ showMessage: function(message){
+ alert(message);
+ }
+ };
+ qq.extend(this._options, o);
+
+ // number of files being uploaded
+ this._filesInProgress = 0;
+ this._handler = this._createUploadHandler();
+
+ if (this._options.button){
+ this._button = this._createUploadButton(this._options.button);
+ }
+
+ this._preventLeaveInProgress();
+};
+
+qq.FileUploaderBasic.prototype = {
+ setParams: function(params){
+ this._options.params = params;
+ },
+ getInProgress: function(){
+ return this._filesInProgress;
+ },
+ _createUploadButton: function(element){
+ var self = this;
+
+ return new qq.UploadButton({
+ element: element,
+ multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
+ onChange: function(input){
+ self._onInputChange(input);
+ }
+ });
+ },
+ _createUploadHandler: function(){
+ var self = this,
+ handlerClass;
+
+ if(qq.UploadHandlerXhr.isSupported()){
+ handlerClass = 'UploadHandlerXhr';
+ } else {
+ handlerClass = 'UploadHandlerForm';
+ }
+
+ var handler = new qq[handlerClass]({
+ debug: this._options.debug,
+ action: this._options.action,
+ maxConnections: this._options.maxConnections,
+ onProgress: function(id, fileName, loaded, total){
+ self._onProgress(id, fileName, loaded, total);
+ self._options.onProgress(id, fileName, loaded, total);
+ },
+ onComplete: function(id, fileName, result){
+ self._onComplete(id, fileName, result);
+ self._options.onComplete(id, fileName, result);
+ },
+ onCancel: function(id, fileName){
+ self._onCancel(id, fileName);
+ self._options.onCancel(id, fileName);
+ }
+ });
+
+ return handler;
+ },
+ _preventLeaveInProgress: function(){
+ var self = this;
+
+ qq.attach(window, 'beforeunload', function(e){
+ if (!self._filesInProgress){return;}
+
+ var e = e || window.event;
+ // for ie, ff
+ e.returnValue = self._options.messages.onLeave;
+ // for webkit
+ return self._options.messages.onLeave;
+ });
+ },
+ _onSubmit: function(id, fileName){
+ this._filesInProgress++;
+ },
+ _onProgress: function(id, fileName, loaded, total){
+ },
+ _onComplete: function(id, fileName, result){
+ this._filesInProgress--;
+ if (result.error){
+ this._options.showMessage(result.error);
+ }
+ },
+ _onCancel: function(id, fileName){
+ this._filesInProgress--;
+ },
+ _onInputChange: function(input){
+ if (this._handler instanceof qq.UploadHandlerXhr){
+ this._uploadFileList(input.files);
+ } else {
+ if (this._validateFile(input)){
+ this._uploadFile(input);
+ }
+ }
+ this._button.reset();
+ },
+ _uploadFileList: function(files){
+ for (var i=0; i this._options.sizeLimit){
+ this._error('sizeError', name);
+ return false;
+
+ } else if (size && size < this._options.minSizeLimit){
+ this._error('minSizeError', name);
+ return false;
+ }
+
+ return true;
+ },
+ _error: function(code, fileName){
+ var message = this._options.messages[code];
+ function r(name, replacement){ message = message.replace(name, replacement); }
+
+ r('{file}', this._formatFileName(fileName));
+ r('{extensions}', this._options.allowedExtensions.join(', '));
+ r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
+ r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
+
+ this._options.showMessage(message);
+ },
+ _formatFileName: function(name){
+ if (name.length > 33){
+ name = name.slice(0, 19) + '...' + name.slice(-13);
+ }
+ return name;
+ },
+ _isAllowedExtension: function(fileName){
+ var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
+ var allowed = this._options.allowedExtensions;
+
+ if (!allowed.length){return true;}
+
+ for (var i=0; i 99);
+
+ return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
+ }
+};
+
+
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FileUploaderBasic
*/
qq.FileUploader = function(o){
- this._options = {
- // container element DOM node (ex. $(selector)[0] for jQuery users)
+ // call parent constructor
+ qq.FileUploaderBasic.apply(this, arguments);
+
+ // additional options
+ qq.extend(this._options, {
element: null,
- // url of the server-side upload script, should be on the same domain
- action: '/server/upload',
- // additional data to send, name-value pairs
- params: {},
- // ex. ['jpg', 'jpeg', 'png', 'gif'] or []
- allowedExtensions: [],
- // size limit in bytes, 0 - no limit
- // this option isn't supported in all browsers
- sizeLimit: 0,
- onSubmit: function(id, fileName){},
- onComplete: function(id, fileName, responseJSON){},
-
- //
- // UI customizations
-
+ // if set, will be used instead of qq-upload-list in template
+ listElement: null,
+
template: '' +
- '
Drop photos here to upload
' +
- '
Upload a photo
' +
+ '
Drop files here to upload
' +
+ '
Upload a file
' +
'
' +
'
',
@@ -47,8 +497,8 @@ qq.FileUploader = function(o){
'' +
'Cancel' +
'Failed' +
- '',
-
+ '',
+
classes: {
// used to get elements from templates
button: 'qq-upload-button',
@@ -65,115 +515,41 @@ qq.FileUploader = function(o){
// used in css to hide progress spinner
success: 'qq-upload-success',
fail: 'qq-upload-fail'
- },
- messages: {
- //serverError: "Some files were not uploaded, please contact support and/or try again.",
- typeError: "{file} has invalid extension. Only {extensions} are allowed.",
- sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
- emptyError: "{file} is empty, please select files again without it."
- },
- showMessage: function(message){
- alert(message);
}
- };
-
+ });
+ // overwrite options with user supplied
qq.extend(this._options, o);
-
- this._element = this._options.element;
- if (this._element.nodeType != 1){
- throw new Error('element param of FileUploader should be dom node');
- }
+ this._element = this._options.element;
+ this._element.innerHTML = this._options.template;
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
- this._element.innerHTML = this._options.template;
-
- // number of files being uploaded
- this._filesInProgress = 0;
-
- // easier access
this._classes = this._options.classes;
-
- this._handler = this._createUploadHandler();
+
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
this._bindCancelEvent();
-
- var self = this;
- this._button = new qq.UploadButton({
- element: this._getElement('button'),
- multiple: qq.UploadHandlerXhr.isSupported(),
- onChange: function(input){
- self._onInputChange(input);
- }
- });
-
this._setupDragDrop();
};
-qq.FileUploader.prototype = {
- setParams: function(params){
- this._options.params = params;
- },
- /**
- * Returns true if some files are being uploaded, false otherwise
- */
- isUploading: function(){
- return !!this._filesInProgress;
- },
+// inherit from Basic Uploader
+qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
+
+qq.extend(qq.FileUploader.prototype, {
/**
* Gets one of the elements listed in this._options.classes
- *
- * First optional element is root for search,
- * this._element is default value.
- *
- * Usage
- * 1. this._getElement('button');
- * 2. this._getElement(item, 'file');
**/
- _getElement: function(parent, type){
- if (typeof parent == 'string'){
- // parent was not passed
- type = parent;
- parent = this._element;
- }
-
- var element = qq.getByClass(parent, this._options.classes[type])[0];
-
+ _find: function(parent, type){
+ var element = qq.getByClass(parent, this._options.classes[type])[0];
if (!element){
throw new Error('element not found ' + type);
}
return element;
},
- _error: function(code, fileName){
- var message = this._options.messages[code];
- message = message.replace('{file}', this._formatFileName(fileName));
- message = message.replace('{extensions}', this._options.allowedExtensions.join(', '));
- message = message.replace('{sizeLimit}', this._formatSize(this._options.sizeLimit));
- this._options.showMessage(message);
- },
- _formatFileName: function(name){
- if (name.length > 33){
- name = name.slice(0, 19) + '...' + name.slice(-13);
- }
- return name;
- },
- _isAllowedExtension: function(fileName){
- var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
- var allowed = this._options.allowedExtensions;
-
- if (!allowed.length){return true;}
-
- for (var i=0; i this._options.sizeLimit){
- this._error('sizeError',name);
- return false;
- }
-
- return true;
- },
- _addToList: function(id, fileName){
- var item = qq.toElement(this._options.fileTemplate);
- item.qqFileId = id;
-
- var fileElement = this._getElement(item, 'file');
- qq.setText(fileElement, this._formatFileName(fileName));
- this._getElement(item, 'size').style.display = 'none';
-
- this._getElement('list').appendChild(item);
-
- this._filesInProgress++;
- },
- _updateProgress: function(id, loaded, total){
var item = this._getItemByFileId(id);
- var size = this._getElement(item, 'size');
+ var size = this._find(item, 'size');
size.style.display = 'inline';
var text;
@@ -344,27 +605,39 @@ qq.FileUploader.prototype = {
text = this._formatSize(total);
}
- qq.setText(size, text);
+ qq.setText(size, text);
},
- _formatSize: function(bytes){
- var i = -1;
- do {
- bytes = bytes / 1024;
- i++;
- } while (bytes > 99);
+ _onComplete: function(id, fileName, result){
+ qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+ // mark completed
+ var item = this._getItemByFileId(id);
+ qq.remove(this._find(item, 'cancel'));
+ qq.remove(this._find(item, 'spinner'));
- return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
+ if (result.success){
+ qq.addClass(item, this._classes.success);
+ } else {
+ qq.addClass(item, this._classes.fail);
+ }
+ },
+ _addToList: function(id, fileName){
+ var item = qq.toElement(this._options.fileTemplate);
+ item.qqFileId = id;
+
+ var fileElement = this._find(item, 'file');
+ qq.setText(fileElement, this._formatFileName(fileName));
+ this._find(item, 'size').style.display = 'none';
+
+ this._listElement.appendChild(item);
},
_getItemByFileId: function(id){
- var item = this._getElement('list').firstChild;
+ var item = this._listElement.firstChild;
- // there can't be text nodes in our dynamically created list
- // because of that we can safely use nextSibling
+ // there can't be txt nodes in dynamically created list
+ // and we can use nextSibling
while (item){
- if (item.qqFileId == id){
- return item;
- }
-
+ if (item.qqFileId == id) return item;
item = item.nextSibling;
}
},
@@ -373,24 +646,23 @@ qq.FileUploader.prototype = {
**/
_bindCancelEvent: function(){
var self = this,
- list = this._getElement('list');
+ list = this._listElement;
- qq.attach(list, 'click', function(e){
+ qq.attach(list, 'click', function(e){
e = e || window.event;
var target = e.target || e.srcElement;
-
- if (qq.hasClass(target, self._classes.cancel)){
+
+ if (qq.hasClass(target, self._classes.cancel)){
qq.preventDefault(e);
-
+
var item = target.parentNode;
self._handler.cancel(item.qqFileId);
qq.remove(item);
}
});
-
}
-};
-
+});
+
qq.UploadDropZone = function(o){
this._options = {
element: null,
@@ -414,8 +686,10 @@ qq.UploadDropZone.prototype = {
if (!qq.UploadDropZone.dropOutsideDisabled ){
qq.attach(document, 'dragover', function(e){
- e.dataTransfer.dropEffect = 'none';
- e.preventDefault();
+ if (e.dataTransfer){
+ e.dataTransfer.dropEffect = 'none';
+ e.preventDefault();
+ }
});
qq.UploadDropZone.dropOutsideDisabled = true;
@@ -536,8 +810,6 @@ qq.UploadButton.prototype = {
right: 0,
top: 0,
fontFamily: 'Arial',
- // when button is big it becomes visible in IE8 on SOME PCs
- // probably related to http://social.msdn.microsoft.com/forums/en-US/iewebdevelopment/thread/29d0b0e7-4326-4b3e-823c-51420d4cf253
// 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
fontSize: '118px',
margin: 0,
@@ -577,26 +849,117 @@ qq.UploadButton.prototype = {
}
};
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+qq.UploadHandlerAbstract = function(o){
+ this._options = {
+ debug: false,
+ action: '/upload.php',
+ // maximum number of concurrent uploads
+ maxConnections: 999,
+ onProgress: function(id, fileName, loaded, total){},
+ onComplete: function(id, fileName, response){},
+ onCancel: function(id, fileName){}
+ };
+ qq.extend(this._options, o);
+
+ this._queue = [];
+ // params for files in queue
+ this._params = [];
+};
+qq.UploadHandlerAbstract.prototype = {
+ log: function(str){
+ if (this._options.debug && window.console) console.log('[uploader] ' + str);
+ },
+ /**
+ * Adds file or file input to the queue
+ * @returns id
+ **/
+ add: function(file){},
+ /**
+ * Sends the file identified by id and additional query params to the server
+ */
+ upload: function(id, params){
+ var len = this._queue.push(id);
+
+ var copy = {};
+ qq.extend(copy, params);
+ this._params[id] = copy;
+
+ // if too many active uploads, wait...
+ if (len <= this._options.maxConnections){
+ this._upload(id, this._params[id]);
+ }
+ },
+ /**
+ * Cancels file upload by id
+ */
+ cancel: function(id){
+ this._cancel(id);
+ this._dequeue(id);
+ },
+ /**
+ * Cancells all uploads
+ */
+ cancelAll: function(){
+ for (var i=0; i= max){
+ var nextId = this._queue[max-1];
+ this._upload(nextId, this._params[nextId]);
+ }
+ }
+};
+
/**
* Class for uploading files using form and iframe
+ * @inherits qq.UploadHandlerAbstract
*/
qq.UploadHandlerForm = function(o){
- this._options = {
- // URL of the server-side upload script,
- // should be on the same domain to get response
- action: '/upload',
- // fires for each file, when iframe finishes loading
- onComplete: function(id, fileName, response){}
- };
- qq.extend(this._options, o);
+ qq.UploadHandlerAbstract.apply(this, arguments);
this._inputs = {};
};
-qq.UploadHandlerForm.prototype = {
- /**
- * Adds file input to the queue
- * Returns id to use with upload, cancel
- **/
+// @inherits qq.UploadHandlerAbstract
+qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
+
+qq.extend(qq.UploadHandlerForm.prototype, {
add: function(fileInput){
fileInput.setAttribute('name', 'qqfile');
var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
@@ -610,17 +973,32 @@ qq.UploadHandlerForm.prototype = {
return id;
},
- /**
- * Sends the file identified by id and additional query params to the server
- * @param {Object} params name-value string pairs
- */
- upload: function(id, params){
+ getName: function(id){
+ // get input value and remove path to normalize
+ return this._inputs[id].value.replace(/.*(\/|\\)/, "");
+ },
+ _cancel: function(id){
+ this._options.onCancel(id, this.getName(id));
+
+ delete this._inputs[id];
+
+ var iframe = document.getElementById(id);
+ if (iframe){
+ // to cancel request set src to something else
+ // we use src="javascript:false;" because it doesn't
+ // trigger ie6 prompt on https
+ iframe.setAttribute('src', 'javascript:false;');
+
+ qq.remove(iframe);
+ }
+ },
+ _upload: function(id, params){
var input = this._inputs[id];
if (!input){
throw new Error('file with passed id was not added, or already uploaded or cancelled');
}
-
+
var fileName = this.getName(id);
var iframe = this._createIframe(id);
@@ -628,8 +1006,13 @@ qq.UploadHandlerForm.prototype = {
form.appendChild(input);
var self = this;
- this._attachLoadEvent(iframe, function(){
- self._options.onComplete(id, fileName, self._getIframeContentJSON(iframe));
+ this._attachLoadEvent(iframe, function(){
+ self.log('iframe loaded');
+
+ var response = self._getIframeContentJSON(iframe);
+
+ self._options.onComplete(id, fileName, response);
+ self._dequeue(id);
delete self._inputs[id];
// timeout added to fix busy state in FF3.6
@@ -642,26 +1025,7 @@ qq.UploadHandlerForm.prototype = {
qq.remove(form);
return id;
- },
- cancel: function(id){
- if (id in this._inputs){
- delete this._inputs[id];
- }
-
- var iframe = document.getElementById(id);
- if (iframe){
- // to cancel request set src to something else
- // we use src="javascript:false;" because it doesn't
- // trigger ie6 prompt on https
- iframe.setAttribute('src', 'javascript:false;');
-
- qq.remove(iframe);
- }
- },
- getName: function(id){
- // get input value and remove path to normalize
- return this._inputs[id].value.replace(/.*(\/|\\)/, "");
- },
+ },
_attachLoadEvent: function(iframe, callback){
qq.attach(iframe, 'load', function(){
// when we remove iframe from dom
@@ -692,12 +1056,15 @@ qq.UploadHandlerForm.prototype = {
// iframe.contentWindow.document - for IE<7
var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
response;
-
- try{
+
+ this.log("converting iframe's innerHTML to JSON");
+ this.log("innerHTML = " + doc.body.innerHTML);
+
+ try {
response = eval("(" + doc.body.innerHTML + ")");
} catch(err){
response = {};
- }
+ }
return response;
},
@@ -732,32 +1099,29 @@ qq.UploadHandlerForm.prototype = {
// Because in this case file won't be attached to request
var form = qq.toElement('');
- var queryString = '?' + qq.obj2url(params);
+ var queryString = qq.obj2url(params, this._options.action);
- form.setAttribute('action', this._options.action + queryString);
+ form.setAttribute('action', queryString);
form.setAttribute('target', iframe.name);
form.style.display = 'none';
document.body.appendChild(form);
return form;
}
-};
+});
/**
* Class for uploading files using xhr
+ * @inherits qq.UploadHandlerAbstract
*/
qq.UploadHandlerXhr = function(o){
- this._options = {
- // url of the server-side upload script,
- // should be on the same domain
- action: '/upload',
- onProgress: function(id, fileName, loaded, total){},
- onComplete: function(id, fileName, response){}
- };
- qq.extend(this._options, o);
+ qq.UploadHandlerAbstract.apply(this, arguments);
this._files = [];
this._xhrs = [];
+
+ // current loaded size in bytes for each file
+ this._loaded = [];
};
// static method
@@ -771,297 +1135,113 @@ qq.UploadHandlerXhr.isSupported = function(){
typeof (new XMLHttpRequest()).upload != "undefined" );
};
-qq.UploadHandlerXhr.prototype = {
+// @inherits qq.UploadHandlerAbstract
+qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
+
+qq.extend(qq.UploadHandlerXhr.prototype, {
/**
* Adds file to the queue
* Returns id to use with upload, cancel
**/
add: function(file){
+ if (!(file instanceof File)){
+ throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
+ }
+
return this._files.push(file) - 1;
},
+ getName: function(id){
+ var file = this._files[id];
+ // fix missing name in Safari 4
+ return file.fileName != null ? file.fileName : file.name;
+ },
+ getSize: function(id){
+ var file = this._files[id];
+ return file.fileSize != null ? file.fileSize : file.size;
+ },
+ /**
+ * Returns uploaded bytes for file identified by id
+ */
+ getLoaded: function(id){
+ return this._loaded[id] || 0;
+ },
/**
* Sends the file identified by id and additional query params to the server
* @param {Object} params name-value string pairs
*/
- upload: function(id, params){
+ _upload: function(id, params){
var file = this._files[id],
name = this.getName(id),
size = this.getSize(id);
-
- if (!file){
- throw new Error('file with passed id was not added, or already uploaded or cancelled');
- }
-
+
+ this._loaded[id] = 0;
+
var xhr = this._xhrs[id] = new XMLHttpRequest();
var self = this;
xhr.upload.onprogress = function(e){
if (e.lengthComputable){
+ self._loaded[id] = e.loaded;
self._options.onProgress(id, name, e.loaded, e.total);
}
};
- xhr.onreadystatechange = function(){
- // the request was aborted/cancelled
- if (!self._files[id]){
- return;
- }
-
+ xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
-
- self._options.onProgress(id, name, size, size);
-
- if (xhr.status == 200){
- var response;
-
- try {
- response = eval("(" + xhr.responseText + ")");
- } catch(err){
- response = {};
- }
-
- self._options.onComplete(id, name, response);
-
- } else {
- self._options.onComplete(id, name, {});
- }
-
- self._files[id] = null;
- self._xhrs[id] = null;
+ self._onComplete(id, xhr);
}
};
// build query string
- var queryString = '?qqfile=' + encodeURIComponent(name) + '&' + qq.obj2url(params);
+ params = params || {};
+ params['qqfile'] = name;
+ var queryString = qq.obj2url(params, this._options.action);
- xhr.open("POST", this._options.action + queryString, true);
+ xhr.open("POST", queryString, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.send(file);
-
},
- cancel: function(id){
+ _onComplete: function(id, xhr){
+ // the request was aborted/cancelled
+ if (!this._files[id]) return;
+
+ var name = this.getName(id);
+ var size = this.getSize(id);
+
+ this._options.onProgress(id, name, size, size);
+
+ if (xhr.status == 200){
+ this.log("xhr - server response received");
+ this.log("responseText = " + xhr.responseText);
+
+ var response;
+
+ try {
+ response = eval("(" + xhr.responseText + ")");
+ } catch(err){
+ response = {};
+ }
+
+ this._options.onComplete(id, name, response);
+
+ } else {
+ this._options.onComplete(id, name, {});
+ }
+
+ this._files[id] = null;
+ this._xhrs[id] = null;
+ this._dequeue(id);
+ },
+ _cancel: function(id){
+ this._options.onCancel(id, this.getName(id));
+
this._files[id] = null;
if (this._xhrs[id]){
this._xhrs[id].abort();
this._xhrs[id] = null;
}
- },
- getName: function(id){
- // fix missing name in Safari 4
- var file = this._files[id];
- return file.fileName != null ? file.fileName : file.name;
- },
- getSize: function(id){
- // fix missing size in Safari 4
- var file = this._files[id];
- return file.fileSize != null ? file.fileSize : file.size;
}
-};
-
-//
-// Helper functions
-//
-
-var qq = qq || {};
-
-//
-// Useful generic functions
-
-/**
- * Adds all missing properties from obj2 to obj1
- */
-qq.extend = function(obj1, obj2){
- for (var prop in obj2){
- obj1[prop] = obj2[prop];
- }
-};
-
-/**
- * @return {Number} unique id
- */
-qq.getUniqueId = (function(){
- var id = 0;
- return function(){
- return id++;
- };
-})();
-
-//
-// Events
-
-qq.attach = function(element, type, fn){
- if (element.addEventListener){
- element.addEventListener(type, fn, false);
- } else if (element.attachEvent){
- element.attachEvent('on' + type, fn);
- }
-};
-qq.detach = function(element, type, fn){
- if (element.removeEventListener){
- element.removeEventListener(type, fn, false);
- } else if (element.attachEvent){
- element.detachEvent('on' + type, fn);
- }
-};
-
-qq.preventDefault = function(e){
- if (e.preventDefault){
- e.preventDefault();
- } else{
- e.returnValue = false;
- }
-};
-//
-// Node manipulations
-
-/**
- * Insert node a before node b.
- */
-qq.insertBefore = function(a, b){
- b.parentNode.insertBefore(a, b);
-};
-qq.remove = function(element){
- element.parentNode.removeChild(element);
-};
-
-qq.contains = function(parent, descendant){
- // compareposition returns false in this case
- if (parent == descendant) return true;
-
- if (parent.contains){
- return parent.contains(descendant);
- } else {
- return !!(descendant.compareDocumentPosition(parent) & 8);
- }
-};
-
-/**
- * Creates and returns element from html string
- * Uses innerHTML to create an element
- */
-qq.toElement = (function(){
- var div = document.createElement('div');
- return function(html){
- div.innerHTML = html;
- var element = div.firstChild;
- div.removeChild(element);
- return element;
- };
-})();
-
-//
-// Node properties and attributes
-
-/**
- * Sets styles for an element.
- * Fixes opacity in IE6-8.
- */
-qq.css = function(element, styles){
- if (styles.opacity != null){
- if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
- styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
- }
- }
- qq.extend(element.style, styles);
-};
-qq.hasClass = function(element, name){
- var re = new RegExp('(^| )' + name + '( |$)');
- return re.test(element.className);
-};
-qq.addClass = function(element, name){
- if (!qq.hasClass(element, name)){
- element.className += ' ' + name;
- }
-};
-qq.removeClass = function(element, name){
- var re = new RegExp('(^| )' + name + '( |$)');
- element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
-};
-qq.setText = function(element, text){
- element.innerText = text;
- element.textContent = text;
-};
-
-//
-// Selecting elements
-
-qq.children = function(element){
- var children = [],
- child = element.firstChild;
-
- while (child){
- if (child.nodeType == 1){
- children.push(child);
- }
- child = child.nextSibling;
- }
-
- return children;
-};
-
-qq.getByClass = function(element, className){
- if (element.querySelectorAll){
- return element.querySelectorAll('.' + className);
- }
-
- var result = [];
- var candidates = element.getElementsByTagName("*");
- var len = candidates.length;
-
- for (var i = 0; i < len; i++){
- if (qq.hasClass(candidates[i], className)){
- result.push(candidates[i]);
- }
- }
- return result;
-};
-
-/**
- * obj2url() takes a json-object as argument and generates
- * a querystring. pretty much like jQuery.param()
- *
- * @param Object JSON-Object
- * @param String current querystring-part
- * @return String encoded querystring
- */
-qq.obj2url = function(obj, temp){
- var uristrings = [],
- add = function(nextObj, i){
-
- var nextTemp = temp
- ? (/\[\]$/.test(temp)) // prevent double-encoding
- ? temp
- : temp+'['+i+']'
- : i;
-
- uristrings.push(typeof nextObj === 'object'
- ? qq.obj2url(nextObj, nextTemp)
- : (Object.prototype.toString.call(nextObj) === '[object Function]')
- ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
- : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj));
- };
-
- if (Object.prototype.toString.call(obj) === '[object Array]'){
- // we wont use a for-in-loop on an array (performance)
- for (var i = 0, len = obj.length; i < len; ++i){
- add(obj[i], i);
- }
-
- } else if ((obj !== undefined) &&
- (obj !== null) &&
- (typeof obj === "object")){
-
- // for anything else but a scalar, we will use for-in-loop
- for (var i in obj){
- add(obj[i], i);
- }
- } else {
- uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
- }
-
- return uristrings.join('&').replace(/%20/g, '+');
-};
-
+});
\ No newline at end of file
diff --git a/public/stylesheets/sass/application.sass b/public/stylesheets/sass/application.sass
index e444e9b45..bbdec31e7 100644
--- a/public/stylesheets/sass/application.sass
+++ b/public/stylesheets/sass/application.sass
@@ -762,7 +762,6 @@ label
.options_and_submit
:min-height 21px
:position relative
- :display none
:padding
:top 8px
:margin