diff --git a/assets/javascripts/plupload/i18n/fi.js b/assets/javascripts/plupload/i18n/fi.js new file mode 100644 index 00000000..12a639ef --- /dev/null +++ b/assets/javascripts/plupload/i18n/fi.js @@ -0,0 +1,33 @@ +// .fi file like language pack +plupload.addI18n({ + 'Select files' : 'Valitse tiedostoja', + 'Add files to the upload queue and click the start button.' : 'Lisää tiedostoja latausjonoon ja klikkaa aloita-nappia.', + 'Filename' : 'Tiedostonimi', + 'Status' : 'Tila', + 'Size' : 'Koko', + 'Add files' : 'Lisää tiedostoja', + 'Stop current upload' : 'Pysäytä nykyinen lataus', + 'Start uploading queue' : 'Aloita jonon lataus', + 'Drag files here.' : 'Raahaa tiedostot tänne.', + 'Start upload' : 'Aloita lataus', + 'Uploaded %d/%d files': 'Ladattu %d/%d tiedostoa', + 'Stop upload': 'Pysäytä lataus', + 'Start upload': 'Aloita lataus', + '%d files queued': '%d tiedostoa jonossa', + 'File: %s': 'Tiedosto: %s', + 'Close': 'Sulje', + 'Using runtime: ': 'Käytetään ajonaikaista: ', + 'File: %f, size: %s, max file size: %m': 'Tiedosto: %f, koko: %s, maksimi tiedostokoko: %m', + 'Upload element accepts only %d file(s) at a time. Extra files were stripped.': 'Latauselementti sallii ladata vain %d tiedosto(a) kerrallaan. Ylimääräiset tiedostot ohitettiin.', + 'Upload URL might be wrong or doesn\'t exist': 'Lataus URL saattaa olla väärin tai ei ole olemassa', + 'Error: File too large: ': 'Virhe: Tiedosto liian suuri: ', + 'Error: Invalid file extension: ': 'Virhe: Kelpaamaton tiedostopääte: ', + 'File extension error.': 'Tiedostopäätevirhe.', + 'File size error.': 'Tiedostokokovirhe.', + 'File count error.': 'Tiedostolaskentavirhe.', + 'Init error.': 'Init virhe.', + 'HTTP Error.': 'HTTP virhe.', + 'Security error.': 'Tietoturvavirhe.', + 'Generic error.': 'Yleinen virhe.', + 'IO error.': 'I/O virhe.' +}); \ No newline at end of file diff --git a/assets/javascripts/plupload/i18n/ja.js b/assets/javascripts/plupload/i18n/ja.js index c70773cf..02c85aee 100644 --- a/assets/javascripts/plupload/i18n/ja.js +++ b/assets/javascripts/plupload/i18n/ja.js @@ -1,25 +1,37 @@ -// Japanese -plupload.addI18n({ - 'Select files' : 'ファイルの選択:', - 'Add files to the upload queue and click the start button.' : 'アップロードキューにファイルを追加しスタートボタンをクリックしてください。', - 'Filename' : 'ファイル名', - 'Status' : '進行状況', - 'Size' : 'サイズ', - 'Add files' : 'ファイルの追加', - 'Stop current upload' : '現在のアップロードを中止', - 'Start uploading queue' : 'キューのアップロードを開始', - 'Uploaded %d/%d files': '%d/%d 個のファイルをアップロードしました', - 'N/A' : 'N/A', - 'Drag files here.' : 'ここにファイルをドラッグしてください。', - 'File extension error.': 'ファイル拡張子エラーです。', - 'File size error.': 'ファイルサイズエラーです。', - 'Init error.': '初期化エラーです。', - 'HTTP Error.': 'HTTP エラーです。', - 'Security error.': 'セキュリティエラーです。', - 'Generic error.': '原因不明のエラーです。', - 'IO error.': 'I/O エラーです。', - 'Stop Upload': 'アップロードの中止', - 'Add Files': 'ファイルの追加', - 'Start Upload': 'アップロードの開始', - '%d files queued': '%d 個のファイルがキューにあります。' -}); \ No newline at end of file +// Japanese +plupload.addI18n({ + 'Select files' : 'ファイル選択', + 'Add files to the upload queue and click the start button.' : 'ファイルをアップロードキューに追加してスタートボタンをクリックしてください', + 'Filename' : 'ファイル名', + 'Status' : 'ステータス', + 'Size' : 'サイズ', + 'Add Files' : 'ファイルを追加', + 'Stop Upload' : 'アップロード停止', + 'Start Upload' : 'アップロード', + 'Add files' : 'ファイルを追加', + 'Add files.' : 'ファイルを追加', + 'Stop current upload' : '現在のアップロードを停止', + 'Start uploading queue' : 'アップロード', + 'Stop upload' : 'アップロード停止', + 'Start upload' : 'アップロード', + 'Uploaded %d/%d files': 'アップロード中 %d/%d ファイル', + 'N/A' : 'N/A', + 'Drag files here.' : 'ここにファイルをドラッグ', + 'File extension error.': 'ファイル拡張子エラー', + 'File size error.': 'ファイルサイズエラー', + 'File count error.': 'ファイル数エラー', + 'Init error.': 'イニシャライズエラー', + 'HTTP Error.': 'HTTP エラー', + 'Security error.': 'セキュリティエラー', + 'Generic error.': 'エラー', + 'IO error.': 'IO エラー', + 'File: %s': 'ファイル: %s', + 'Close': '閉じる', + '%d files queued': '%d ファイルが追加されました', + 'Using runtime: ': 'モード: ', + 'File: %f, size: %s, max file size: %m': 'ファイル: %f, サイズ: %s, 最大ファイルサイズ: %m', + 'Upload element accepts only %d file(s) at a time. Extra files were stripped.': 'アップロード可能なファイル数は %d です。余分なファイルは削除されました', + 'Upload URL might be wrong or doesn\'t exist': 'アップロード先の URL が存在しません', + 'Error: File too large: ': 'エラー: サイズが大きすぎます: ', + 'Error: Invalid file extension: ': 'エラー: 拡張子が許可されていません: ' +}); diff --git a/assets/javascripts/plupload/i18n/lv.js b/assets/javascripts/plupload/i18n/lv.js index 21eda086..2a040452 100644 --- a/assets/javascripts/plupload/i18n/lv.js +++ b/assets/javascripts/plupload/i18n/lv.js @@ -20,7 +20,7 @@ plupload.addI18n({ 'File: %f, size: %s, max file size: %m': 'Fails: %f, izmērs: %s, maksimālais faila izmērs: %m', 'Upload element accepts only %d file(s) at a time. Extra files were stripped.': 'Iespējams ielādēt tikai %d failus vienā reizē. Atlikušie faili netika pievienoti', 'Upload URL might be wrong or doesn\'t exist': 'Augšupielādes URL varētu būt nepareizs vai neeksistē', - 'Error: File to large: ': 'Kļūda: Fails pārāk liels: ', + 'Error: File too large: ': 'Kļūda: Fails pārāk liels: ', 'Error: Invalid file extension: ': 'Kļūda: Nekorekts faila paplašinājums:', 'File extension error.': 'Faila paplašinājuma kļūda.', 'File size error.': 'Faila izmēra kļūda.', diff --git a/assets/javascripts/plupload/i18n/pt-br.js b/assets/javascripts/plupload/i18n/pt-br.js index bd696d3f..9f34a64a 100644 --- a/assets/javascripts/plupload/i18n/pt-br.js +++ b/assets/javascripts/plupload/i18n/pt-br.js @@ -30,6 +30,6 @@ plupload.addI18n({ 'File: %f, size: %s, max file size: %m': 'Arquivo: %f, tamanho: %s, máximo: %m', 'Upload element accepts only %d file(s) at a time. Extra files were stripped.': 'Só são aceitos %d arquivos por vez. O que passou disso foi descartado.', 'Upload URL might be wrong or doesn\'t exist': 'URL de envio está errada ou não existe', - 'Error: File to large: ': 'Erro: Arquivo muito grande: ', + 'Error: File too large: ': 'Erro: Arquivo muito grande: ', 'Error: Invalid file extension: ': 'Erro: Tipo de arquivo não permitido: ' }); diff --git a/assets/javascripts/plupload/jquery.ui.plupload/jquery.ui.plupload.js b/assets/javascripts/plupload/jquery.ui.plupload/jquery.ui.plupload.js index 60802d81..680ffec8 100644 --- a/assets/javascripts/plupload/jquery.ui.plupload/jquery.ui.plupload.js +++ b/assets/javascripts/plupload/jquery.ui.plupload/jquery.ui.plupload.js @@ -37,7 +37,7 @@ function renderUI(obj) { '
' + '
' + '' + - + '
' + '' + '' + @@ -133,7 +133,11 @@ $.widget("ui.plupload", { this.container = $('.plupload_container', this.element).attr('id', id + '_container'); // list of files, may become sortable - this.filelist = $('.plupload_filelist_content', this.container).attr('id', id + '_filelist'); + this.filelist = $('.plupload_filelist_content', this.container) + .attr({ + id: id + '_filelist', + unselectable: 'on' + }); // buttons this.browse_button = $('.plupload_add', this.container).attr('id', id + '_browse'); @@ -217,12 +221,12 @@ $.widget("ui.plupload", { // check if file count doesn't exceed the limit if (self.options.max_file_count) { - uploader.bind('FilesAdded', function(up, files) { - var length = files.length, removed = []; - length += up.files.length; + uploader.bind('FilesAdded', function(up, selectedFiles) { + var removed = [], selectedCount = selectedFiles.length; + var extraCount = up.files.length + selectedCount - self.options.max_file_count; - if (length > self.options.max_file_count) { - removed = files.splice(self.options.max_file_count - up.files.length); + if (extraCount > 0) { + removed = selectedFiles.splice(selectedCount - extraCount, extraCount); up.trigger('Error', { code : self.FILE_COUNT_ERROR, @@ -336,7 +340,7 @@ $.widget("ui.plupload", { message += "
" + details + ""; } - self._notify('error', message); + self.notify('error', message); self._trigger('error', null, { up: up, file: file, error: message } ); } }); @@ -651,24 +655,12 @@ $.widget("ui.plupload", { return el.clone(true).find('td:not(.plupload_file_name)').remove().end().css('width', '100%'); }, - start: function(e, ui) { - idxStart = $('tr', this).index(ui.item); - }, - stop: function(e, ui) { - var i, length, idx, files = [], idxStop = $('tr', this).index(ui.item); - - for (i = 0, length = self.uploader.files.length; i < length; i++) { - - if (i === idxStop) { - idx = idxStart; - } else if (i === idxStart) { - idx = idxStop; - } else { - idx = i; - } - files[files.length] = self.uploader.files[idx]; - } + var i, length, idx, files = []; + + $.each($(this).sortable('toArray'), function(i, id) { + files[files.length] = self.uploader.getFile(id); + }); files.unshift(files.length); files.unshift(0); @@ -679,7 +671,7 @@ $.widget("ui.plupload", { }); }, - _notify: function(type, message) { + notify: function(type, message) { var popup = $( '
' + '' + @@ -696,8 +688,9 @@ $.widget("ui.plupload", { .click(function() { popup.remove(); }) - .end() - .appendTo('.plupload_header_content', this.container); + .end(); + + $('.plupload_header_content', this.container).append(popup); }, diff --git a/assets/javascripts/plupload/plupload.browserplus.js b/assets/javascripts/plupload/plupload.browserplus.js deleted file mode 100644 index d1be1b7a..00000000 --- a/assets/javascripts/plupload/plupload.browserplus.js +++ /dev/null @@ -1,337 +0,0 @@ -/** - * plupload.browserplus.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under GPL License. - * - * License: http://www.plupload.com/license - * Contributing: http://www.plupload.com/contributing - */ - -// JSLint defined globals -/*global plupload:false, BrowserPlus:false, window:false */ - -(function(plupload) { - /** - * Yahoo BrowserPlus implementation. This runtime supports these features: dragdrop, jpgresize, pngresize. - * - * @static - * @class plupload.runtimes.BrowserPlus - * @extends plupload.Runtime - */ - plupload.runtimes.BrowserPlus = plupload.addRuntime("browserplus", { - /** - * Returns a list of supported features for the runtime. - * - * @return {Object} Name/value object with supported features. - */ - getFeatures : function() { - return { - dragdrop : true, - jpgresize : true, - pngresize : true, - chunks : true, - progress: true, - multipart: true - }; - }, - - /** - * Initializes the browserplus runtime. - * - * @method init - * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized. - * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true. - */ - init : function(uploader, callback) { - var browserPlus = window.BrowserPlus, browserPlusFiles = {}, settings = uploader.settings, resize = settings.resize; - - function addSelectedFiles(native_files) { - var files, i, selectedFiles = [], file, id; - - // Add the native files and setup plupload files - for (i = 0; i < native_files.length; i++) { - file = native_files[i]; - id = plupload.guid(); - browserPlusFiles[id] = file; - - selectedFiles.push(new plupload.File(id, file.name, file.size)); - } - - // Any files selected fire event - if (i) { - uploader.trigger("FilesAdded", selectedFiles); - } - } - - // Setup event listeners if browserplus was initialized - function setup() { - // Add drop handler - uploader.bind("PostInit", function() { - var dropTargetElm, dropElmId = settings.drop_element, - dropTargetId = uploader.id + '_droptarget', - dropElm = document.getElementById(dropElmId), - lastState; - - // Enable/disable drop support for the drop target - // this is needed to resolve IE bubbeling issues and make it possible to drag/drop - // files into gears runtimes on the same page - function addDropHandler(id, end_callback) { - // Add drop target and listener - browserPlus.DragAndDrop.AddDropTarget({id : id}, function(res) { - browserPlus.DragAndDrop.AttachCallbacks({ - id : id, - hover : function(res) { - if (!res && end_callback) { - end_callback(); - } - }, - drop : function(res) { - if (end_callback) { - end_callback(); - } - - addSelectedFiles(res); - } - }, function() { - }); - }); - } - - function hide() { - document.getElementById(dropTargetId).style.top = '-1000px'; - } - - if (dropElm) { - // Since IE has issues with bubbeling when it comes to the drop of files - // we need to do this hack where we show a drop target div element while dropping - if (document.attachEvent && (/MSIE/gi).test(navigator.userAgent)) { - // Create drop target - dropTargetElm = document.createElement('div'); - dropTargetElm.setAttribute('id', dropTargetId); - plupload.extend(dropTargetElm.style, { - position : 'absolute', - top : '-1000px', - background : 'red', - filter : 'alpha(opacity=0)', - opacity : 0 - }); - - document.body.appendChild(dropTargetElm); - - plupload.addEvent(dropElm, 'dragenter', function(e) { - var dropElm, dropElmPos; - - dropElm = document.getElementById(dropElmId); - dropElmPos = plupload.getPos(dropElm); - - plupload.extend(document.getElementById(dropTargetId).style, { - top : dropElmPos.y + 'px', - left : dropElmPos.x + 'px', - width : dropElm.offsetWidth + 'px', - height : dropElm.offsetHeight + 'px' - }); - }); - - addDropHandler(dropTargetId, hide); - } else { - addDropHandler(dropElmId); - } - } - - plupload.addEvent(document.getElementById(settings.browse_button), 'click', function(e) { - var mimeTypes = [], i, a, filters = settings.filters, ext; - - e.preventDefault(); - - // Convert extensions to mimetypes - for (i = 0; i < filters.length; i++) { - ext = filters[i].extensions.split(','); - - for (a = 0; a < ext.length; a++) { - mimeTypes.push(plupload.mimeTypes[ext[a]]); - } - } - - browserPlus.FileBrowse.OpenBrowseDialog({ - mimeTypes : mimeTypes - }, function(res) { - if (res.success) { - addSelectedFiles(res.value); - } - }); - }); - - // Prevent IE leaks - dropElm = dropTargetElm = null; - }); - - uploader.bind("UploadFile", function(up, file) { - var nativeFile = browserPlusFiles[file.id], reqParams = {}, - chunkSize = up.settings.chunk_size, loadProgress, chunkStack = []; - - function uploadFile(chunk, chunks) { - var chunkFile; - - // Stop upload if file is maked as failed - if (file.status == plupload.FAILED) { - return; - } - - reqParams.name = file.target_name || file.name; - - // Only send chunk parameters if chunk size is defined - if (chunkSize) { - reqParams.chunk = "" + chunk; - reqParams.chunks = "" + chunks; - } - - chunkFile = chunkStack.shift(); - - browserPlus.Uploader.upload({ - url : up.settings.url, - files : {file : chunkFile}, - cookies : document.cookies, - postvars : plupload.extend(reqParams, up.settings.multipart_params), - progressCallback : function(res) { - var i, loaded = 0; - - // since more than 1 chunk can be sent at a time, keep track of how many bytes - // of each chunk was sent - loadProgress[chunk] = parseInt(res.filePercent * chunkFile.size / 100, 10); - for (i = 0; i < loadProgress.length; i++) { - loaded += loadProgress[i]; - } - - file.loaded = loaded; - up.trigger('UploadProgress', file); - } - }, function(res) { - var httpStatus, chunkArgs; - - if (res.success) { - httpStatus = res.value.statusCode; - - if (chunkSize) { - up.trigger('ChunkUploaded', file, { - chunk : chunk, - chunks : chunks, - response : res.value.body, - status : httpStatus - }); - } - - if (chunkStack.length > 0) { - // More chunks to be uploaded - uploadFile(++chunk, chunks); - } else { - file.status = plupload.DONE; - - up.trigger('FileUploaded', file, { - response : res.value.body, - status : httpStatus - }); - - // Is error status - if (httpStatus >= 400) { - up.trigger('Error', { - code : plupload.HTTP_ERROR, - message : plupload.translate('HTTP Error.'), - file : file, - status : httpStatus - }); - } - } - } else { - up.trigger('Error', { - code : plupload.GENERIC_ERROR, - message : plupload.translate('Generic Error.'), - file : file, - details : res.error - }); - } - }); - } - - function chunkAndUploadFile(native_file) { - file.size = native_file.size; - if (chunkSize) { - browserPlus.FileAccess.chunk({file : native_file, chunkSize : chunkSize}, function(cr) { - if (cr.success) { - var chunks = cr.value, len = chunks.length; - - loadProgress = Array(len); - - for (var i = 0; i < len; i++) { - loadProgress[i] = 0; - chunkStack.push(chunks[i]); - } - - uploadFile(0, len); - } - }); - } else { - loadProgress = Array(1); - chunkStack.push(native_file); - uploadFile(0, 1); - } - } - - // Resize image if it's a supported format and resize is enabled - if (resize && /\.(png|jpg|jpeg)$/i.test(file.name)) { - BrowserPlus.ImageAlter.transform({ - file : nativeFile, - quality : resize.quality || 90, - actions : [{ - scale : { - maxwidth : resize.width, - maxheight : resize.height - } - }] - }, function(res) { - if (res.success) { - chunkAndUploadFile(res.value.file); - } - }); - } else { - chunkAndUploadFile(nativeFile); - } - }); - - callback({success : true}); - } - - // Check for browserplus object - if (browserPlus) { - browserPlus.init(function(res) { - var services = [ - {service: "Uploader", version: "3"}, - {service: "DragAndDrop", version: "1"}, - {service: "FileBrowse", version: "1"}, - {service: "FileAccess", version: "2"} - ]; - - if (resize) { - services.push({service : 'ImageAlter', version : "4"}); - } - - if (res.success) { - browserPlus.require({ - services : services - }, function(sres) { - if (sres.success) { - setup(); - } else { - callback(); - } - }); - } else { - callback(); - } - }); - } else { - callback(); - } - } - }); -})(plupload); diff --git a/assets/javascripts/plupload/plupload.flash.js b/assets/javascripts/plupload/plupload.flash.js index 28941d97..a5d08b10 100644 --- a/assets/javascripts/plupload/plupload.flash.js +++ b/assets/javascripts/plupload/plupload.flash.js @@ -42,7 +42,7 @@ * @param {Object} obj Parameters to be passed with event. */ trigger : function(id, name, obj) { - + // Detach the call so that error handling in the browser is presented correctly setTimeout(function() { var uploader = uploadInstances[id], i, args; @@ -76,7 +76,8 @@ maxHeight: 8091, chunks: true, progress: true, - multipart: true + multipart: true, + multi_selection: true }; }, @@ -88,12 +89,7 @@ * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true. */ init : function(uploader, callback) { - var browseButton, flashContainer, flashVars, waitCount = 0, container = document.body; - - if (bowser.opera) { - callback({success : false}); - return; - } + var browseButton, flashContainer, waitCount = 0, container = document.body; if (getFlashVersion() < 10) { callback({success : false}); @@ -129,15 +125,33 @@ } container.appendChild(flashContainer); + + // insert flash object + (function() { + var html, el; + + html = '' + - '' + - '' + - '' + - ''; + html += 'width="100%" height="100%" style="outline:0">' + + '' + + '' + + '' + + '' + + ''; + + if (plupload.ua.ie) { + el = document.createElement('div'); + flashContainer.appendChild(el); + el.outerHTML = html; + el = null; // just in case + } else { + flashContainer.innerHTML = html; + } + }()); function getFlashObj() { return document.getElementById(uploader.id + '_flash'); @@ -334,6 +348,17 @@ plupload.removeClass(browseButton, activeClass); } }); + + + uploader.bind('Flash:ExifData', function(up, obj) { + uploader.trigger('ExifData', uploader.getFile(lookup[obj.id]), obj.data); + }); + + + uploader.bind('Flash:GpsData', function(up, obj) { + uploader.trigger('GpsData', uploader.getFile(lookup[obj.id]), obj.data); + }); + uploader.bind("QueueChanged", function(up) { uploader.refresh(); diff --git a/assets/javascripts/plupload/plupload.flash.swf b/assets/javascripts/plupload/plupload.flash.swf index 7a1a9370..36460568 100644 Binary files a/assets/javascripts/plupload/plupload.flash.swf and b/assets/javascripts/plupload/plupload.flash.swf differ diff --git a/assets/javascripts/plupload/plupload.gears.js b/assets/javascripts/plupload/plupload.gears.js deleted file mode 100644 index a2738538..00000000 --- a/assets/javascripts/plupload/plupload.gears.js +++ /dev/null @@ -1,420 +0,0 @@ -/** - * plupload.gears.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under GPL License. - * - * License: http://www.plupload.com/license - * Contributing: http://www.plupload.com/contributing - */ - -// JSLint defined globals -/*global window:false, document:false, plupload:false, google:false, GearsFactory:false, ActiveXObject:false */ - -// Copyright 2007, Google Inc. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// 3. Neither the name of Google Inc. nor the names of its contributors may be -// used to endorse or promote products derived from this software without -// specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Sets up google.gears.*, which is *the only* supported way to access Gears. -// -// Circumvent this file at your own risk! -// -// In the future, Gears may automatically define google.gears.* without this -// file. Gears may use these objects to transparently fix bugs and compatibility -// issues. Applications that use the code below will continue to work seamlessly -// when that happens. - -(function() { - // We are already defined. Hooray! - if (window.google && google.gears) { - return; - } - - var factory = null; - - // Firefox - if (typeof GearsFactory != 'undefined') { - factory = new GearsFactory(); - } else { - // IE - try { - factory = new ActiveXObject('Gears.Factory'); - // privateSetGlobalObject is only required and supported on WinCE. - if (factory.getBuildInfo().indexOf('ie_mobile') != -1) { - factory.privateSetGlobalObject(this); - } - } catch (e) { - // Safari - if ((typeof navigator.mimeTypes != 'undefined') && navigator.mimeTypes["application/x-googlegears"]) { - factory = document.createElement("object"); - factory.style.display = "none"; - factory.width = 0; - factory.height = 0; - factory.type = "application/x-googlegears"; - document.documentElement.appendChild(factory); - } - } - } - - // *Do not* define any objects if Gears is not installed. This mimics the - // behavior of Gears defining the objects in the future. - if (!factory) { - return; - } - - // Now set up the objects, being careful not to overwrite anything. - // - // Note: In Internet Explorer for Windows Mobile, you can't add properties to - // the window object. However, global objects are automatically added as - // properties of the window object in all browsers. - if (!window.google) { - window.google = {}; - } - - if (!google.gears) { - google.gears = {factory: factory}; - } -})(); - -(function(window, document, plupload, undef) { - var blobs = {}; - - function scaleImage(image_blob, resize, mime) { - var percentage, canvas, context, scale; - - // Setup canvas and scale - canvas = google.gears.factory.create('beta.canvas'); - try { - canvas.decode(image_blob); - - if (!resize['width']) { - resize['width'] = canvas.width; - } - - if (!resize['height']) { - resize['height'] = canvas.height; - } - - scale = Math.min(width / canvas.width, height / canvas.height); - - if (scale < 1 || (scale === 1 && mime === 'image/jpeg')) { - canvas.resize(Math.round(canvas.width * scale), Math.round(canvas.height * scale)); - - if (resize['quality']) { - return canvas.encode(mime, {quality : resize.quality / 100}); - } - - return canvas.encode(mime); - } - } catch (e) { - // Ignore for example when a user uploads a file that can't be decoded - } - - return image_blob; - } - - /** - * Gears implementation. This runtime supports these features: dragdrop, jpgresize, pngresize, chunks. - * - * @static - * @class plupload.runtimes.Gears - * @extends plupload.Runtime - */ - plupload.runtimes.Gears = plupload.addRuntime("gears", { - /** - * Returns a list of supported features for the runtime. - * - * @return {Object} Name/value object with supported features. - */ - getFeatures : function() { - return { - dragdrop: true, - jpgresize: true, - pngresize: true, - chunks: true, - progress: true, - multipart: true - }; - }, - - /** - * Initializes the upload runtime. - * - * @method init - * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized. - * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true. - */ - init : function(uploader, callback) { - var desktop; - - // Check for gears support - if (!window.google || !google.gears) { - return callback({success : false}); - } - - try { - desktop = google.gears.factory.create('beta.desktop'); - } catch (e) { - // Might fail on the latest Gecko build for some odd reason - return callback({success : false}); - } - - function addSelectedFiles(selected_files) { - var file, i, files = [], id; - - // Add the selected files to the file queue - for (i = 0; i < selected_files.length; i++) { - file = selected_files[i]; - - // Store away gears blob internally - id = plupload.guid(); - blobs[id] = file.blob; - - files.push(new plupload.File(id, file.name, file.blob.length)); - } - - // Fire FilesAdded event - uploader.trigger("FilesAdded", files); - } - - // Add drop handler - uploader.bind("PostInit", function() { - var settings = uploader.settings, dropElm = document.getElementById(settings.drop_element); - - if (dropElm) { - // Block browser default drag over - plupload.addEvent(dropElm, 'dragover', function(e) { - desktop.setDropEffect(e, 'copy'); - e.preventDefault(); - }, uploader.id); - - // Attach drop handler and grab files from Gears - plupload.addEvent(dropElm, 'drop', function(e) { - var dragData = desktop.getDragData(e, 'application/x-gears-files'); - - if (dragData) { - addSelectedFiles(dragData.files); - } - - e.preventDefault(); - }, uploader.id); - - // Prevent IE leak - dropElm = 0; - } - - // Add browse button - plupload.addEvent(document.getElementById(settings.browse_button), 'click', function(e) { - var filters = [], i, a, ext; - - e.preventDefault(); - - for (i = 0; i < settings.filters.length; i++) { - ext = settings.filters[i].extensions.split(','); - - for (a = 0; a < ext.length; a++) { - filters.push('.' + ext[a]); - } - } - - desktop.openFiles(addSelectedFiles, {singleFile : !settings.multi_selection, filter : filters}); - }, uploader.id); - }); - - uploader.bind("UploadFile", function(up, file) { - var chunk = 0, chunks, chunkSize, loaded = 0, resize = up.settings.resize, chunking; - - // If file is png or jpeg and resize is configured then resize it - if (resize && /\.(png|jpg|jpeg)$/i.test(file.name)) { - blobs[file.id] = scaleImage(blobs[file.id], resize, /\.png$/i.test(file.name) ? 'image/png' : 'image/jpeg'); - } - - file.size = blobs[file.id].length; - - chunkSize = up.settings.chunk_size; - chunking = chunkSize > 0; - chunks = Math.ceil(file.size / chunkSize); - - // If chunking is disabled then upload the whole file in one huge chunk - if (!chunking) { - chunkSize = file.size; - chunks = 1; - } - - function uploadNextChunk() { - var req, curChunkSize, multipart = up.settings.multipart, multipartLength = 0, reqArgs = {name : file.target_name || file.name}, url = up.settings.url; - - // Sends the binary blob multipart encoded or raw depending on config - function sendBinaryBlob(blob) { - var builder, boundary = '----pluploadboundary' + plupload.guid(), dashdash = '--', crlf = '\r\n', multipartBlob, mimeType; - - // Build multipart request - if (multipart) { - req.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); - builder = google.gears.factory.create('beta.blobbuilder'); - - // Append mutlipart parameters - plupload.each(plupload.extend(reqArgs, up.settings.multipart_params), function(value, name) { - builder.append( - dashdash + boundary + crlf + - 'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf - ); - - builder.append(value + crlf); - }); - - mimeType = plupload.mimeTypes[file.name.replace(/^.+\.([^.]+)/, '$1').toLowerCase()] || 'application/octet-stream'; - - // Add file header - builder.append( - dashdash + boundary + crlf + - 'Content-Disposition: form-data; name="' + up.settings.file_data_name + '"; filename="' + file.name + '"' + crlf + - 'Content-Type: ' + mimeType + crlf + crlf - ); - - // Add file data - builder.append(blob); - - // Add footer - builder.append(crlf + dashdash + boundary + dashdash + crlf); - multipartBlob = builder.getAsBlob(); - multipartLength = multipartBlob.length - blob.length; - blob = multipartBlob; - } - - // Send blob or multipart blob depending on config - req.send(blob); - } - - // File upload finished - if (file.status == plupload.DONE || file.status == plupload.FAILED || up.state == plupload.STOPPED) { - return; - } - - // Only add chunking args if needed - if (chunking) { - reqArgs.chunk = chunk; - reqArgs.chunks = chunks; - } - - // Setup current chunk size - curChunkSize = Math.min(chunkSize, file.size - (chunk * chunkSize)); - - if (!multipart) { - url = plupload.buildUrl(up.settings.url, reqArgs); - } - - req = google.gears.factory.create('beta.httprequest'); - req.open('POST', url); - - // Add disposition and type if multipart is disabled - if (!multipart) { - req.setRequestHeader('Content-Disposition', 'attachment; filename="' + file.name + '"'); - req.setRequestHeader('Content-Type', 'application/octet-stream'); - } - - // Set custom headers - plupload.each(up.settings.headers, function(value, name) { - req.setRequestHeader(name, value); - }); - - req.upload.onprogress = function(progress) { - file.loaded = loaded + progress.loaded - multipartLength; - up.trigger('UploadProgress', file); - }; - - req.onreadystatechange = function() { - var chunkArgs; - - if (req.readyState == 4) { - if (req.status == 200) { - chunkArgs = { - chunk : chunk, - chunks : chunks, - response : req.responseText, - status : req.status - }; - - up.trigger('ChunkUploaded', file, chunkArgs); - - // Stop upload - if (chunkArgs.cancelled) { - file.status = plupload.FAILED; - return; - } - - loaded += curChunkSize; - - if (++chunk >= chunks) { - file.status = plupload.DONE; - up.trigger('FileUploaded', file, { - response : req.responseText, - status : req.status - }); - } else { - uploadNextChunk(); - } - } else { - up.trigger('Error', { - code : plupload.HTTP_ERROR, - message : plupload.translate('HTTP Error.'), - file : file, - chunk : chunk, - chunks : chunks, - status : req.status - }); - } - } - }; - - if (chunk < chunks) { - sendBinaryBlob(blobs[file.id].slice(chunk * chunkSize, curChunkSize)); - } - } - - // Start uploading chunks - uploadNextChunk(); - }); - - uploader.bind("Destroy", function(up) { - var name, element, - elements = { - browseButton: up.settings.browse_button, - dropElm: up.settings.drop_element - }; - - // Unbind event handlers - for (name in elements) { - element = document.getElementById(elements[name]); - if (element) { - plupload.removeAllEvents(element, up.id); - } - } - }); - - - callback({success : true}); - } - }); -})(window, document, plupload); diff --git a/assets/javascripts/plupload/plupload.html4.js b/assets/javascripts/plupload/plupload.html4.js index 7c470b85..087002c1 100644 --- a/assets/javascripts/plupload/plupload.html4.js +++ b/assets/javascripts/plupload/plupload.html4.js @@ -30,15 +30,13 @@ * * @return {Object} Name/value object with supported features. */ - getFeatures : function() { + getFeatures : function() { // Only multipart feature return { multipart: true, - /* WebKit let you trigger file dialog programmatically while FF and Opera - do not, so we - sniff for it here... probably not that good idea, but impossibillity of controlling cursor style - on top of add files button obviously feels even worse */ - canOpenDialog: navigator.userAgent.indexOf('WebKit') !== -1 + // WebKit and Gecko 2+ can trigger file dialog progrmmatically + triggerDialog: (plupload.ua.gecko && window.FormData || plupload.ua.webkit) }; }, @@ -106,7 +104,7 @@ browseButton = getById(up.settings.browse_button); // Route click event to input element programmatically, if possible - if (up.features.canOpenDialog && browseButton) { + if (up.features.triggerDialog && browseButton) { plupload.addEvent(getById(up.settings.browse_button), 'click', function(e) { input.click(); e.preventDefault(); @@ -118,7 +116,7 @@ width : '100%', height : '100%', opacity : 0, - fontSize: '99px' // force input element to be bigger then needed to occupy whole space + fontSize: '999px' // force input element to be bigger then needed to occupy whole space }); plupload.extend(form.style, { @@ -153,7 +151,7 @@ files.push(new plupload.File(currentFileId, name)); // Clean-up events - they won't be needed anymore - if (!up.features.canOpenDialog) { + if (!up.features.triggerDialog) { plupload.removeAllEvents(form, up.id); } else { plupload.removeEvent(browseButton, 'click', up.id); @@ -209,8 +207,8 @@ } // Get result - result = el.documentElement.innerText || el.documentElement.textContent; - + result = el.body.innerHTML; + // Assume no error if (result) { currentFile.status = plupload.DONE; @@ -287,14 +285,16 @@ if (up.state == plupload.STOPPED) { window.setTimeout(function() { plupload.removeEvent(iframe, 'load', up.id); - iframe.parentNode.removeChild(iframe); + if (iframe.parentNode) { // #382 + iframe.parentNode.removeChild(iframe); + } }, 0); } }); // Refresh button, will reposition the input form up.bind("Refresh", function(up) { - var browseButton, topElement, hoverClass, activeClass, browsePos, browseSize, inputContainer, inputFile, pzIndex; + var browseButton, topElement, hoverClass, activeClass, browsePos, browseSize, inputContainer, inputFile, zIndex; browseButton = getById(up.settings.browse_button); if (browseButton) { @@ -312,25 +312,25 @@ // for IE and WebKit place input element underneath the browse button and route onclick event // TODO: revise when browser support for this feature will change - if (up.features.canOpenDialog) { - pzIndex = parseInt(browseButton.parentNode.style.zIndex, 10); - - if (isNaN(pzIndex)) { - pzIndex = 0; - } - - plupload.extend(browseButton.style, { - zIndex : pzIndex - }); - + if (up.features.triggerDialog) { if (plupload.getStyle(browseButton, 'position') === 'static') { plupload.extend(browseButton.style, { position : 'relative' }); } + + zIndex = parseInt(browseButton.style.zIndex, 10); + + if (isNaN(zIndex)) { + zIndex = 0; + } + + plupload.extend(browseButton.style, { + zIndex : zIndex + }); plupload.extend(inputContainer.style, { - zIndex : pzIndex - 1 + zIndex : zIndex - 1 }); } @@ -340,7 +340,7 @@ TODO: needs to be revised as things will change */ hoverClass = up.settings.browse_button_hover; activeClass = up.settings.browse_button_active; - topElement = up.features.canOpenDialog ? browseButton : inputContainer; + topElement = up.features.triggerDialog ? browseButton : inputContainer; if (hoverClass) { plupload.addEvent(topElement, 'mouseover', function() { diff --git a/assets/javascripts/plupload/plupload.html5.js b/assets/javascripts/plupload/plupload.html5.js index 35fe451b..3e5ecfc5 100644 --- a/assets/javascripts/plupload/plupload.html5.js +++ b/assets/javascripts/plupload/plupload.html5.js @@ -12,27 +12,8 @@ /*global plupload:false, File:false, window:false, atob:false, FormData:false, FileReader:false, ArrayBuffer:false, Uint8Array:false, BlobBuilder:false, unescape:false */ (function(window, document, plupload, undef) { - var fakeSafariDragDrop; - - if ((typeof File !== 'undefined') && !File.prototype.slice) { - if (File.prototype.webkitSlice) File.prototype.slice = File.prototype.webkitSlice; - if (File.prototype.mozSlice) File.prototype.slice = File.prototype.mozSlice; - } - - /* Introduce sendAsBinary for latest WebKits having support for BlobBuilder and typed arrays: - credits: http://javascript0.org/wiki/Portable_sendAsBinary, - more info: http://code.google.com/p/chromium/issues/detail?id=35705 - */ - if (window.Uint8Array && window.ArrayBuffer && !XMLHttpRequest.prototype.sendAsBinary) { - XMLHttpRequest.prototype.sendAsBinary = function(datastr) { - var ui8a = new Uint8Array(datastr.length); - for (var i = 0; i < datastr.length; i++) { - ui8a[i] = (datastr.charCodeAt(i) & 0xff); - } - this.send(ui8a.buffer); - }; - } - + var html5files = {}, // queue of original File objects + fakeSafariDragDrop; function readFileAsDataURL(file, callback) { var reader; @@ -64,10 +45,11 @@ } } - function scaleImage(image_file, resize, mime, callback) { - var canvas, context, img, scale; - - readFileAsDataURL(image_file, function(data) { + function scaleImage(file, resize, mime, callback) { + var canvas, context, img, scale, + up = this; + + readFileAsDataURL(html5files[file.id], function(data) { // Setup canvas and context canvas = document.createElement("canvas"); canvas.style.display = 'none'; @@ -112,9 +94,18 @@ // Set new width and height exifParser.setExif('PixelXDimension', width); exifParser.setExif('PixelYDimension', height); - + // Update EXIF header jpegHeaders.set('exif', exifParser.getBinary()); + + // trigger Exif events only if someone listens to them + if (up.hasEventListener('ExifData')) { + up.trigger('ExifData', file, exifParser.EXIF()); + } + + if (up.hasEventListener('GpsData')) { + up.trigger('GpsData', file, exifParser.GPS()); + } } } @@ -134,8 +125,8 @@ data = data.substring(data.indexOf('base64,') + 7); data = atob(data); - // Restore JPEG headers - if (jpegHeaders['headers'] && jpegHeaders['headers'].length) { + // Restore JPEG headers if applicable + if (jpegHeaders && jpegHeaders['headers'] && jpegHeaders['headers'].length) { data = jpegHeaders.restore(data); jpegHeaders.purge(); // free memory } @@ -167,11 +158,11 @@ * @return {Object} Name/value object with supported features. */ getFeatures : function() { - var xhr, hasXhrSupport, hasProgress, dataAccessSupport, sliceSupport, win = window; + var xhr, hasXhrSupport, hasProgress, canSendBinary, dataAccessSupport, sliceSupport; hasXhrSupport = hasProgress = dataAccessSupport = sliceSupport = false; - if (win.XMLHttpRequest) { + if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); hasProgress = !!xhr.upload; hasXhrSupport = !!(xhr.sendAsBinary || xhr.upload); @@ -179,28 +170,35 @@ // Check for support for various features if (hasXhrSupport) { + canSendBinary = !!(xhr.sendAsBinary || (window.Uint8Array && window.ArrayBuffer)); + // Set dataAccessSupport only for Gecko since BlobBuilder and XHR doesn't handle binary data correctly - dataAccessSupport = !!(File && (File.prototype.getAsDataURL || win.FileReader) && xhr.sendAsBinary); - sliceSupport = !!(File && File.prototype.slice); + dataAccessSupport = !!(File && (File.prototype.getAsDataURL || window.FileReader) && canSendBinary); + sliceSupport = !!(File && (File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice)); } - // Sniff for Safari and fake drag/drop - fakeSafariDragDrop = navigator.userAgent.indexOf('Safari') > 0 && navigator.vendor.indexOf('Apple') !== -1; + // sniff out Safari for Windows and fake drag/drop + fakeSafariDragDrop = plupload.ua.safari && plupload.ua.windows; return { - // Detect drag/drop file support by sniffing, will try to find a better way html5: hasXhrSupport, // This is a special one that we check inside the init call - dragdrop: win.mozInnerScreenX !== undef || sliceSupport || fakeSafariDragDrop, + dragdrop: (function() { + // this comes directly from Modernizr: http://www.modernizr.com/ + var div = document.createElement('div'); + return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); + }()), jpgresize: dataAccessSupport, pngresize: dataAccessSupport, - multipart: dataAccessSupport || !!win.FileReader || !!win.FormData, + multipart: dataAccessSupport || !!window.FileReader || !!window.FormData, + canSendBinary: canSendBinary, + // gecko 2/5/6 can't send blob with FormData: https://bugzilla.mozilla.org/show_bug.cgi?id=649150 + cantSendBlobInFormData: !!(plupload.ua.gecko && window.FormData && window.FileReader && !FileReader.prototype.readAsArrayBuffer), progress: hasProgress, - chunks: sliceSupport || dataAccessSupport, - - /* WebKit let you trigger file dialog programmatically while FF and Opera - do not, so we - sniff for it here... probably not that good idea, but impossibillity of controlling cursor style - on top of add files button obviously feels even worse */ - canOpenDialog: navigator.userAgent.indexOf('WebKit') !== -1 + chunks: sliceSupport, + // Safari on Windows has problems when selecting multiple files + multi_selection: !(plupload.ua.safari && plupload.ua.windows), + // WebKit and Gecko 2+ can trigger file dialog progrmmatically + triggerDialog: (plupload.ua.gecko && window.FormData || plupload.ua.webkit) }; }, @@ -212,7 +210,7 @@ * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true. */ init : function(uploader, callback) { - var html5files = {}, features; + var features; function addSelectedFiles(native_files) { var file, i, files = [], id, fileNames = {}; @@ -220,7 +218,7 @@ // Add the selected files to the file queue for (i = 0; i < native_files.length; i++) { file = native_files[i]; - + // Safari on Windows will add first file from dragged set multiple times // @see: https://bugs.webkit.org/show_bug.cgi?id=37957 if (fileNames[file.name]) { @@ -233,7 +231,7 @@ html5files[id] = file; // Expose id, name and size - files.push(new plupload.File(id, file.fileName, file.fileSize || file.size)); // File.fileSize depricated + files.push(new plupload.File(id, file.fileName || file.name, file.fileSize || file.size)); // fileName / fileSize depricated } // Trigger FilesAdded event if we added any @@ -265,7 +263,6 @@ zIndex : 99999, opacity : uploader.settings.shim_bgcolor ? '' : 0 // Force transparent if bgcolor is undefined }); - inputContainer.className = 'plupload html5'; if (uploader.settings.container) { @@ -300,29 +297,44 @@ // Insert the input inside the input container - inputContainer.innerHTML = ''; - + inputContainer.innerHTML = ''; + + inputContainer.scrollTop = 100; inputFile = document.getElementById(uploader.id + '_html5'); + + if (up.features.triggerDialog) { + plupload.extend(inputFile.style, { + position: 'absolute', + width: '100%', + height: '100%' + }); + } else { + // shows arrow cursor instead of the text one, bit more logical + plupload.extend(inputFile.style, { + cssFloat: 'right', + styleFloat: 'right' + }); + } + inputFile.onchange = function() { // Add the selected files from file input addSelectedFiles(this.files); - + // Clearing the value enables the user to select the same file again if they want to this.value = ''; }; /* Since we have to place input[type=file] on top of the browse_button for some browsers (FF, Opera), browse_button loses interactivity, here we try to neutralize this issue highlighting browse_button - with a special class + with a special classes TODO: needs to be revised as things will change */ browseButton = document.getElementById(up.settings.browse_button); if (browseButton) { var hoverClass = up.settings.browse_button_hover, activeClass = up.settings.browse_button_active, - topElement = up.features.canOpenDialog ? browseButton : inputContainer; + topElement = up.features.triggerDialog ? browseButton : inputContainer; if (hoverClass) { plupload.addEvent(topElement, 'mouseover', function() { @@ -343,7 +355,7 @@ } // Route click event to the input[type=file] element for supporting browsers - if (up.features.canOpenDialog) { + if (up.features.triggerDialog) { plupload.addEvent(browseButton, 'click', function(e) { document.getElementById(up.id + '_html5').click(); e.preventDefault(); @@ -374,7 +386,7 @@ plupload.addEvent(dropInputElm, 'change', function() { // Add the selected files from file input addSelectedFiles(this.files); - + // Remove input element plupload.removeEvent(dropInputElm, 'change', uploader.id); dropInputElm.parentNode.removeChild(dropInputElm); @@ -426,7 +438,7 @@ }); uploader.bind("Refresh", function(up) { - var browseButton, browsePos, browseSize, inputContainer, pzIndex; + var browseButton, browsePos, browseSize, inputContainer, zIndex; browseButton = document.getElementById(uploader.settings.browse_button); if (browseButton) { @@ -443,25 +455,24 @@ // for WebKit place input element underneath the browse button and route onclick event // TODO: revise when browser support for this feature will change - if (uploader.features.canOpenDialog) { - pzIndex = parseInt(browseButton.parentNode.style.zIndex, 10); - - if (isNaN(pzIndex)) { - pzIndex = 0; - } - - plupload.extend(browseButton.style, { - zIndex : pzIndex - }); - + if (uploader.features.triggerDialog) { if (plupload.getStyle(browseButton, 'position') === 'static') { plupload.extend(browseButton.style, { position : 'relative' }); } + + zIndex = parseInt(plupload.getStyle(browseButton, 'z-index'), 10); + if (isNaN(zIndex)) { + zIndex = 0; + } + + plupload.extend(browseButton.style, { + zIndex : zIndex + }); plupload.extend(inputContainer.style, { - zIndex : pzIndex - 1 + zIndex : zIndex - 1 }); } } @@ -469,14 +480,201 @@ uploader.bind("UploadFile", function(up, file) { var settings = up.settings, nativeFile, resize; + + function w3cBlobSlice(blob, start, end) { + var blobSlice; + + if (File.prototype.slice) { + try { + blob.slice(); // depricated version will throw WRONG_ARGUMENTS_ERR exception + return blob.slice(start, end); + } catch (e) { + // depricated slice method + return blob.slice(start, end - start); + } + // slice method got prefixed: https://bugzilla.mozilla.org/show_bug.cgi?id=649672 + } else if (blobSlice = File.prototype.webkitSlice || File.prototype.mozSlice) { + return blobSlice.call(blob, start, end); + } else { + return null; // or throw some exception + } + } function sendBinaryBlob(blob) { - var chunk = 0, loaded = 0; + var chunk = 0, loaded = 0, + fr = ("FileReader" in window) ? new FileReader : null; + function uploadNextChunk() { - var chunkBlob = blob, xhr, upload, chunks, args, multipartDeltaSize = 0, - boundary = '----pluploadboundary' + plupload.guid(), chunkSize, curChunkSize, formData, - dashdash = '--', crlf = '\r\n', multipartBlob = '', mimeType, url = up.settings.url; + var chunkBlob, br, chunks, args, chunkSize, curChunkSize, mimeType, url = up.settings.url; + + + function prepareAndSend(bin) { + var multipartDeltaSize = 0, + xhr = new XMLHttpRequest, + upload = xhr.upload, + boundary = '----pluploadboundary' + plupload.guid(), formData, dashdash = '--', crlf = '\r\n', multipartBlob = '' + + // Do we have upload progress support + if (upload) { + upload.onprogress = function(e) { + file.loaded = Math.min(file.size, loaded + e.loaded - multipartDeltaSize); // Loaded can be larger than file size due to multipart encoding + up.trigger('UploadProgress', file); + }; + } + + xhr.onreadystatechange = function() { + var httpStatus, chunkArgs; + + if (xhr.readyState == 4) { + // Getting the HTTP status might fail on some Gecko versions + try { + httpStatus = xhr.status; + } catch (ex) { + httpStatus = 0; + } + + // Is error status + if (httpStatus >= 400) { + up.trigger('Error', { + code : plupload.HTTP_ERROR, + message : plupload.translate('HTTP Error.'), + file : file, + status : httpStatus + }); + } else { + // Handle chunk response + if (chunks) { + chunkArgs = { + chunk : chunk, + chunks : chunks, + response : xhr.responseText, + status : httpStatus + }; + + up.trigger('ChunkUploaded', file, chunkArgs); + loaded += curChunkSize; + + // Stop upload + if (chunkArgs.cancelled) { + file.status = plupload.FAILED; + return; + } + + file.loaded = Math.min(file.size, (chunk + 1) * chunkSize); + } else { + file.loaded = file.size; + } + + up.trigger('UploadProgress', file); + + bin = chunkBlob = formData = multipartBlob = null; // Free memory + + // Check if file is uploaded + if (!chunks || ++chunk >= chunks) { + file.status = plupload.DONE; + + up.trigger('FileUploaded', file, { + response : xhr.responseText, + status : httpStatus + }); + } else { + // Still chunks left + uploadNextChunk(); + } + } + + xhr = null; + + } + }; + + + // Build multipart request + if (up.settings.multipart && features.multipart) { + + args.name = file.target_name || file.name; + + xhr.open("post", url, true); + + // Set custom headers + plupload.each(up.settings.headers, function(value, name) { + xhr.setRequestHeader(name, value); + }); + + + // if has FormData support like Chrome 6+, Safari 5+, Firefox 4, use it + if (typeof(bin) !== 'string' && !!window.FormData) { + formData = new FormData(); + + // Add multipart params + plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) { + formData.append(name, value); + }); + + // Add file and send it + formData.append(up.settings.file_data_name, bin); + xhr.send(formData); + + return; + } // if no FormData we can still try to send it directly as last resort (see below) + + + if (typeof(bin) === 'string') { + // Trying to send the whole thing as binary... + + // multipart request + xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); + + // append multipart parameters + plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) { + multipartBlob += dashdash + boundary + crlf + + 'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf; + + multipartBlob += unescape(encodeURIComponent(value)) + crlf; + }); + + mimeType = plupload.mimeTypes[file.name.replace(/^.+\.([^.]+)/, '$1').toLowerCase()] || 'application/octet-stream'; + + // Build RFC2388 blob + multipartBlob += dashdash + boundary + crlf + + 'Content-Disposition: form-data; name="' + up.settings.file_data_name + '"; filename="' + unescape(encodeURIComponent(file.name)) + '"' + crlf + + 'Content-Type: ' + mimeType + crlf + crlf + + bin + crlf + + dashdash + boundary + dashdash + crlf; + + multipartDeltaSize = multipartBlob.length - bin.length; + bin = multipartBlob; + + + if (xhr.sendAsBinary) { // Gecko + xhr.sendAsBinary(bin); + } else if (features.canSendBinary) { // WebKit with typed arrays support + var ui8a = new Uint8Array(bin.length); + for (var i = 0; i < bin.length; i++) { + ui8a[i] = (bin.charCodeAt(i) & 0xff); + } + xhr.send(ui8a.buffer); + } + return; // will return from here only if shouldn't send binary + } + } + + // if no multipart, or last resort, send as binary stream + url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params)); + + xhr.open("post", url, true); + + xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header + + // Set custom headers + plupload.each(up.settings.headers, function(value, name) { + xhr.setRequestHeader(name, value); + }); + + xhr.send(bin); + } // prepareAndSend + // File upload finished if (file.status == plupload.DONE || file.status == plupload.FAILED || up.state == plupload.STOPPED) { @@ -487,7 +685,7 @@ args = {name : file.target_name || file.name}; // Only add chunking args if needed - if (settings.chunk_size && features.chunks) { + if (settings.chunk_size && file.size > settings.chunk_size && (features.chunks || typeof(blob) == 'string')) { // blob will be of type string if it was loaded in memory chunkSize = settings.chunk_size; chunks = Math.ceil(file.size / chunkSize); curChunkSize = Math.min(chunkSize, file.size - (chunk * chunkSize)); @@ -498,7 +696,7 @@ chunkBlob = blob.substring(chunk * chunkSize, chunk * chunkSize + curChunkSize); } else { // Slice the chunk - chunkBlob = blob.slice(chunk * chunkSize, curChunkSize); + chunkBlob = w3cBlobSlice(blob, chunk * chunkSize, chunk * chunkSize + curChunkSize); } // Setup query string arguments @@ -506,148 +704,19 @@ args.chunks = chunks; } else { curChunkSize = file.size; + chunkBlob = blob; } - - // Setup XHR object - xhr = new XMLHttpRequest(); - upload = xhr.upload; - - // Do we have upload progress support - if (upload) { - upload.onprogress = function(e) { - file.loaded = Math.min(file.size, loaded + e.loaded - multipartDeltaSize); // Loaded can be larger than file size due to multipart encoding - up.trigger('UploadProgress', file); - }; - } - - // Add name, chunk and chunks to query string on direct streaming - if (!up.settings.multipart || !features.multipart) { - url = plupload.buildUrl(up.settings.url, args); - } else { - args.name = file.target_name || file.name; - } - - xhr.open("post", url, true); - - xhr.onreadystatechange = function() { - var httpStatus, chunkArgs; - - if (xhr.readyState == 4) { - // Getting the HTTP status might fail on some Gecko versions - try { - httpStatus = xhr.status; - } catch (ex) { - httpStatus = 0; - } - - // Is error status - if (httpStatus >= 400) { - up.trigger('Error', { - code : plupload.HTTP_ERROR, - message : plupload.translate('HTTP Error.'), - file : file, - status : httpStatus - }); - } else { - // Handle chunk response - if (chunks) { - chunkArgs = { - chunk : chunk, - chunks : chunks, - response : xhr.responseText, - status : httpStatus - }; - - up.trigger('ChunkUploaded', file, chunkArgs); - loaded += curChunkSize; - - // Stop upload - if (chunkArgs.cancelled) { - file.status = plupload.FAILED; - return; - } - - file.loaded = Math.min(file.size, (chunk + 1) * chunkSize); - } else { - file.loaded = file.size; - } - - up.trigger('UploadProgress', file); - - // Check if file is uploaded - if (!chunks || ++chunk >= chunks) { - file.status = plupload.DONE; - up.trigger('FileUploaded', file, { - response : xhr.responseText, - status : httpStatus - }); - - nativeFile = blob = html5files[file.id] = null; // Free memory - } else { - // Still chunks left - uploadNextChunk(); - } - } - - xhr = chunkBlob = formData = multipartBlob = null; // Free memory + + // workaround Gecko 2,5,6 FormData+Blob bug: https://bugzilla.mozilla.org/show_bug.cgi?id=649150 + if (typeof(chunkBlob) !== 'string' && fr && features.cantSendBlobInFormData && features.chunks && up.settings.chunk_size) {// Gecko 2,5,6 + fr.onload = function() { + prepareAndSend(fr.result); } - }; - - // Set custom headers - plupload.each(up.settings.headers, function(value, name) { - xhr.setRequestHeader(name, value); - }); - - // Build multipart request - if (up.settings.multipart && features.multipart) { - // Has FormData support like Chrome 6+, Safari 5+, Firefox 4 - if (!xhr.sendAsBinary) { - formData = new FormData(); - - // Add multipart params - plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) { - formData.append(name, value); - }); - - // Add file and send it - formData.append(up.settings.file_data_name, chunkBlob); - xhr.send(formData); - - return; - } - - // Gecko multipart request - xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); - - // Append multipart parameters - plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) { - multipartBlob += dashdash + boundary + crlf + - 'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf; - - multipartBlob += unescape(encodeURIComponent(value)) + crlf; - }); - - mimeType = plupload.mimeTypes[file.name.replace(/^.+\.([^.]+)/, '$1').toLowerCase()] || 'application/octet-stream'; - - // Build RFC2388 blob - multipartBlob += dashdash + boundary + crlf + - 'Content-Disposition: form-data; name="' + up.settings.file_data_name + '"; filename="' + unescape(encodeURIComponent(file.name)) + '"' + crlf + - 'Content-Type: ' + mimeType + crlf + crlf + - chunkBlob + crlf + - dashdash + boundary + dashdash + crlf; - - multipartDeltaSize = multipartBlob.length - chunkBlob.length; - chunkBlob = multipartBlob; + fr.readAsBinaryString(chunkBlob); } else { - // Binary stream header - xhr.setRequestHeader('Content-Type', 'application/octet-stream'); - } - - if (xhr.sendAsBinary) { - xhr.sendAsBinary(chunkBlob); // Gecko - } else { - xhr.send(chunkBlob); // WebKit + prepareAndSend(chunkBlob); } + } // Start uploading chunks @@ -655,26 +724,24 @@ } nativeFile = html5files[file.id]; - resize = up.settings.resize; - - if (features.jpgresize) { - // Resize image if it's a supported format and resize is enabled - if (resize && /\.(png|jpg|jpeg)$/i.test(file.name)) { - scaleImage(nativeFile, resize, /\.png$/i.test(file.name) ? 'image/png' : 'image/jpeg', function(res) { - // If it was scaled send the scaled image if it failed then - // send the raw image and let the server do the scaling - if (res.success) { - file.size = res.data.length; - sendBinaryBlob(res.data); - } else { - readFileAsBinary(nativeFile, sendBinaryBlob); - } - }); - } else { - readFileAsBinary(nativeFile, sendBinaryBlob); - } + + // Resize image if it's a supported format and resize is enabled + if (features.jpgresize && up.settings.resize && /\.(png|jpg|jpeg)$/i.test(file.name)) { + scaleImage.call(up, file, up.settings.resize, /\.png$/i.test(file.name) ? 'image/png' : 'image/jpeg', function(res) { + // If it was scaled send the scaled image if it failed then + // send the raw image and let the server do the scaling + if (res.success) { + file.size = res.data.length; + sendBinaryBlob(res.data); + } else { + sendBinaryBlob(nativeFile); + } + }); + // if there's no way to slice file without preloading it in memory, preload it + } else if (!features.chunks && features.jpgresize) { + readFileAsBinary(nativeFile, sendBinaryBlob); } else { - sendBinaryBlob(nativeFile); // this works on older WebKits, but fails on fresh ones + sendBinaryBlob(nativeFile); } }); @@ -874,10 +941,19 @@ read.init(data); // Check if data is jpeg - if (read.SHORT(0) !== 0xFFD8) { + var jpegHeaders = new JPEG_Headers(data); + + if (!jpegHeaders['headers']) { return false; } + // Delete any existing headers that need to be replaced + for (var i = jpegHeaders['headers'].length; i > 0; i--) { + var hdr = jpegHeaders['headers'][i - 1]; + read.SEGMENT(hdr.start, hdr.length, '') + } + jpegHeaders.purge(); + idx = read.SHORT(2) == 0xFFE0 ? 4 + read.SHORT(4) : 2; for (var i = 0, max = headers.length; i < max; i++) { @@ -1276,8 +1352,7 @@ } return false; }, - - + EXIF: function() { var Exif; @@ -1285,12 +1360,14 @@ Exif = extractTags(offsets.exifIFD, tags.exif); // Fix formatting of some tags - Exif.ExifVersion = String.fromCharCode( - Exif.ExifVersion[0], - Exif.ExifVersion[1], - Exif.ExifVersion[2], - Exif.ExifVersion[3] - ); + if (Exif.ExifVersion) { + Exif.ExifVersion = String.fromCharCode( + Exif.ExifVersion[0], + Exif.ExifVersion[1], + Exif.ExifVersion[2], + Exif.ExifVersion[3] + ); + } return Exif; }, @@ -1299,7 +1376,11 @@ var GPS; GPS = extractTags(offsets.gpsIFD, tags.gps); - GPS.GPSVersionID = GPS.GPSVersionID.join('.'); + + // iOS devices (and probably some others) do not put in GPSVersionID tag (why?..) + if (GPS.GPSVersionID) { + GPS.GPSVersionID = GPS.GPSVersionID.join('.'); + } return GPS; }, diff --git a/assets/javascripts/plupload/plupload.js b/assets/javascripts/plupload/plupload.js index 93f71da6..2e903916 100644 --- a/assets/javascripts/plupload/plupload.js +++ b/assets/javascripts/plupload/plupload.js @@ -58,6 +58,7 @@ "image/bmp,bmp," + "image/gif,gif," + "image/jpeg,jpeg jpg jpe," + + "image/photoshop,psd," + "image/png,png," + "image/svg+xml,svg svgz," + "image/tiff,tiff tif," + @@ -68,7 +69,11 @@ "video/mp4,mp4," + "video/x-m4v,m4v," + "video/x-flv,flv," + + "video/x-ms-wmv,wmv," + + "video/avi,avi," + + "video/webm,webm," + "video/vnd.rn-realvideo,rv," + + "text/csv,csv," + "text/plain,asc txt text diff log," + "application/octet-stream,exe" ); @@ -183,7 +188,7 @@ INIT_ERROR : -500, /** - * File size error. If the user selects a file that is to large it will be blocked and an error of this type will be triggered. + * File size error. If the user selects a file that is too large it will be blocked and an error of this type will be triggered. * * @property FILE_SIZE_ERROR * @final @@ -231,6 +236,26 @@ * @final */ mimeTypes : mimes, + + /** + * In some cases sniffing is the only way around :( + */ + ua: (function() { + var nav = navigator, userAgent = nav.userAgent, vendor = nav.vendor, webkit, opera, safari; + + webkit = /WebKit/.test(userAgent); + safari = webkit && vendor.indexOf('Apple') !== -1; + opera = window.opera && window.opera.buildNumber; + + return { + windows: navigator.platform.indexOf('Win') !== -1, + ie: !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName), + webkit: webkit, + gecko: !webkit && /Gecko/.test(userAgent), + safari: safari, + opera: !!opera + }; + }()), /** * Extends the specified object with another object. @@ -491,7 +516,7 @@ var mul; if (typeof(size) == 'string') { - size = /^([0-9]+)([mgk]+)$/.exec(size.toLowerCase().replace(/[^0-9mkg]/g, '')); + size = /^([0-9]+)([mgk]?)$/.exec(size.toLowerCase().replace(/[^0-9mkg]/g, '')); mul = size[2]; size = +size[1]; @@ -657,7 +682,11 @@ } // Add event listener - if (obj.attachEvent) { + if (obj.addEventListener) { + func = callback; + + obj.addEventListener(name, func, false); + } else if (obj.attachEvent) { func = function() { var evt = window.event; @@ -672,12 +701,7 @@ callback(evt); }; obj.attachEvent('on' + name, func); - - } else if (obj.addEventListener) { - func = callback; - - obj.addEventListener(name, func, false); - } + } // Log event handler to objects internal Plupload registry if (obj[uid] === undef) { @@ -786,7 +810,7 @@ plupload.each(eventhash[obj[uid]], function(events, name) { plupload.removeEvent(obj, name, key); }); - } + } }; @@ -848,8 +872,9 @@ if (!file && files[i].status == plupload.QUEUED) { file = files[i]; file.status = plupload.UPLOADING; - this.trigger("BeforeUpload", file); - this.trigger("UploadFile", file); + if (this.trigger("BeforeUpload", file)) { + this.trigger("UploadFile", file); + } } else { count++; } @@ -857,8 +882,8 @@ // All files are DONE or FAILED if (count == files.length) { - this.trigger("UploadComplete", files); this.stop(); + this.trigger("UploadComplete", files); } } } @@ -1302,6 +1327,16 @@ return true; }, + + /** + * Check whether uploader has any listeners to the specified event. + * + * @method hasEventListener + * @param {String} name Event name to check for. + */ + hasEventListener : function(name) { + return !!events[name.toLowerCase()]; + }, /** * Adds an event listener by name. diff --git a/assets/javascripts/plupload/plupload.silverlight.js b/assets/javascripts/plupload/plupload.silverlight.js index 34d8aa17..6d285f7b 100644 --- a/assets/javascripts/plupload/plupload.silverlight.js +++ b/assets/javascripts/plupload/plupload.silverlight.js @@ -17,6 +17,11 @@ function jsonSerialize(obj) { var value, type = typeof obj, isArray, i, key; + // Treat undefined as null + if (obj === undef || obj === null) { + return 'null'; + } + // Encode strings if (type === 'string') { value = '\bb\tt\nn\ff\rr\""\'\'\\\\'; @@ -66,11 +71,6 @@ return value; } - // Treat undefined as null - if (obj === undef) { - return 'null'; - } - // Convert all other types to string return '' + obj; } @@ -167,7 +167,8 @@ pngresize: true, chunks: true, progress: true, - multipart: true + multipart: true, + multi_selection: true }; }, diff --git a/assets/javascripts/plupload/plupload.silverlight.xap b/assets/javascripts/plupload/plupload.silverlight.xap index c92dbf0b..1a5cbb56 100644 Binary files a/assets/javascripts/plupload/plupload.silverlight.xap and b/assets/javascripts/plupload/plupload.silverlight.xap differ