支持图片上传, 增加rss订阅

This commit is contained in:
yafei Lee 2012-06-25 00:09:44 +08:00
parent edc538062b
commit de17536628
45 changed files with 905 additions and 9 deletions

2
.gitignore vendored
View File

@ -15,3 +15,5 @@
/tmp
*.swp
/public/uploads/*

View File

@ -40,3 +40,5 @@ gem 'jquery-rails'
gem "redcarpet"
gem "simple_form"
gem 'database_cleaner'
gem "mini_magick"
gem 'carrierwave-mongoid'

View File

@ -33,6 +33,12 @@ GEM
bson_ext (1.6.4)
bson (~> 1.6.4)
builder (3.0.0)
carrierwave (0.6.2)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
carrierwave-mongoid (0.2.1)
carrierwave (~> 0.6.1)
mongoid (~> 2.1)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
@ -57,6 +63,8 @@ GEM
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.19)
mini_magick (3.4)
subexec (~> 0.2.1)
mongo (1.6.2)
bson (~> 1.6.2)
mongoid (2.4.11)
@ -116,6 +124,7 @@ GEM
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
subexec (0.2.2)
thor (0.15.3)
tilt (1.3.3)
treetop (1.4.10)
@ -131,9 +140,11 @@ PLATFORMS
DEPENDENCIES
bson_ext
carrierwave-mongoid
coffee-rails (~> 3.2.1)
database_cleaner
jquery-rails
mini_magick
mongoid
rails (= 3.2.6)
redcarpet

BIN
app/assets/images/rss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -23,3 +23,14 @@ $(document).ready ->
$.post $(this).attr('url'), text: content.val(), (data)->
preview.html(data)
$('a#upload_photo').click ->
$('input[type=file]').show().focus().click().hide()
opt =
type: 'POST'
url: "/photos"
success: (data,status,xhr)->
insertAtCaret('post_content', data)
$('input[type=file]').fileUpload opt

View File

@ -12,4 +12,5 @@
//
//= require jquery
//= require jquery_ujs
//= require 'jquery.html5-fileupload'
//= require_tree .

View File

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@ -0,0 +1,34 @@
function insertAtCaret(areaId,text) {
var txtarea = document.getElementById(areaId);
var scrollPos = txtarea.scrollTop;
var strPos = 0;
var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ?
"ff" : (document.selection ? "ie" : false ) );
if (br == "ie") {
txtarea.focus();
var range = document.selection.createRange();
range.moveStart ('character', -txtarea.value.length);
strPos = range.text.length;
}
else if (br == "ff") strPos = txtarea.selectionStart;
var front = (txtarea.value).substring(0,strPos);
var back = (txtarea.value).substring(strPos,txtarea.value.length);
txtarea.value=front+text+back;
strPos = strPos + text.length;
if (br == "ie") {
txtarea.focus();
var range = document.selection.createRange();
range.moveStart ('character', -txtarea.value.length);
range.moveStart ('character', strPos);
range.moveEnd ('character', 0);
range.select();
}
else if (br == "ff") {
txtarea.selectionStart = strPos;
txtarea.selectionEnd = strPos;
txtarea.focus();
}
txtarea.scrollTop = scrollPos;
}

View File

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@ -59,3 +59,6 @@ div.preview {
display: none;
}
#upload_photo {
float: right;
}

View File

@ -2,6 +2,11 @@
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
div.blogs {
width: 700px;
float: left;
}
div.blog {
background: url('bg_fn_blog_corner.png') no-repeat;
padding: 2em 19px;
@ -20,7 +25,6 @@ div.blog {
}
strong {
font-size: 10px;
line-height: 40px;
margin-left: 1em;
display: none;
}

View File

@ -0,0 +1,3 @@
// Place all the styles related to the home controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@ -0,0 +1,3 @@
// Place all the styles related to the photos controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@ -0,0 +1,25 @@
div.slide {
display: block;
float: left;
width: 400px;
.subscribe {
margin-bottom: 19px;
padding: 9px 0 16px 9px;
border: 1px solid #CCC;
border-radius: 3px;
background-color: #E9F2F5;
clear: both;
.subscribe_descrip {
width: 290px;
display: inline-block;
line-height: 20px;
font-size: 16px;
}
a {
img {
float: right;
margin-right: 20px;
}
}
}
}

View File

@ -28,7 +28,8 @@ class Admin::PostsController < ApplicationController
def preview
text = params[:text] || ""
md = Redcarpet::Markdown.new(Redcarpet::Render::HTML, :autolink=>true)
rd = Redcarpet::Render::HTML.new(:hard_wrap=>true)
md = Redcarpet::Markdown.new(rd, :autolink=>true)
render :text => md.render(text)
end
end

View File

@ -9,6 +9,12 @@ class BlogsController < ApplicationController
end
end
def rss
@posts = Post.all.limit(10)
render :layout=>false
response.headers["Content-Type"] = "application/xml; charset=utf-8"
end
def show
@post = Post.find(params[:id])
end

View File

@ -0,0 +1,4 @@
class HomeController < ApplicationController
def index
end
end

View File

@ -0,0 +1,12 @@
class PhotosController < ApplicationController
def create
@photo = Photo.new
@photo.image = params["Filedata"]
@photo.save!
render :text=> md_url(@photo.image.url)
end
def md_url(url)
"![](#{url})"
end
end

View File

@ -0,0 +1,2 @@
module HomeHelper
end

View File

@ -0,0 +1,2 @@
module PhotosHelper
end

6
app/models/comment.rb Normal file
View File

@ -0,0 +1,6 @@
class Comment
include Mongoid::Document
include Mongoid::Timestamps
field :content, :type => String
validates :content, presence: true
end

9
app/models/photo.rb Normal file
View File

@ -0,0 +1,9 @@
class Photo
include Mongoid::Document
include Mongoid::Timestamps
field :image
attr_accessible :image
mount_uploader :image, PhotoUploader
end

View File

@ -14,4 +14,10 @@ class Post
validates :title, :presence=>true, :uniqueness=> true
validates :content, :presence=>true, :length => { :minimum=> 30 }
validates :type, :presence=>true, :inclusion => { :in => [ TECH, LIFE, CREATOR ] }
def content_html
rd = Redcarpet::Render::HTML.new(:hard_wrap=>true)
md = Redcarpet::Markdown.new(rd, :autolink=>true)
md.render(self.content)
end
end

View File

@ -0,0 +1,55 @@
# encoding: utf-8
class PhotoUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
include CarrierWave::MiniMagick
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
# include Sprockets::Helpers::RailsHelper
# include Sprockets::Helpers::IsolatedHelper
# Choose what kind of storage to use for this uploader:
storage :file
# storage :fog
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Provide a default URL as a default if there hasn't been a file uploaded:
# def default_url
# # For Rails 3.1+ asset pipeline compatibility:
# # asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
#
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
# end
# Process files as they are uploaded:
process :resize_to_limit => [680,nil]
#
# def scale(width, height)
# # do something
# end
# Create different versions of your uploaded files:
# version :thumb do
# process :scale => [50, 50]
# end
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_white_list
%w(jpg jpeg gif png)
end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename
# "something.jpg" if original_filename
# end
end

View File

@ -4,6 +4,8 @@
<li class="active" id="content">content</li>
<li id="preview" url='<%= preview_admin_posts_path %>'>preview</li>
</ul>
<%= link_to t(:upload_photo), "#", :id=>'upload_photo' %>
<input type="file" style="display: none;" />
<%= f.input :content, :as=> :text, :label=>false %>
<div class="preview"></div>
<%= f.input :type, :as=>:select, :collection=> [ Post::TECH, Post::LIFE, Post::CREATOR ], :include_blank=>false %>

View File

@ -1,5 +1,5 @@
<div class="blog">
<h2><%= link_to (post.title + "<strong> >></strong>").html_safe, blog_path(post) %></h2>
<div class="content"><%= post.content %></div>
<div class="content"><%= post.content_html.html_safe %></div>
</div>
<div class="bottom"></div>

View File

@ -1 +1,3 @@
<%= render :partial=> "post", :collection=> @posts %>
<div class="blogs">
<%= render :partial=> "post", :collection=> @posts %>
</div>

View File

@ -0,0 +1,20 @@
xml.instruct!
xml.rss "version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/" do
xml.channel do
xml.title "windy's blog"
xml.link url_for :only_path => false, :controller => 'blogs'
xml.description "windy's blogs here"
@posts.each do |post|
xml.item do
xml.title post.title
xml.link blog_url(post)
xml.description post.content
xml.guid blog_url(post)
end
end
end
end

View File

@ -1,4 +1 @@
<div class="blog">
<h2><%= @post.title %></h2>
<div class="content"><%= @post.content %></div>
</div>
<%= render :partial=> "post", :locals=> { :post=> @post } %>

View File

@ -0,0 +1,12 @@
<div class="slide">
<div class="subscribe">
<div class="subscribe_descrip">
如果你想关注我的每一篇文章,推荐进行订阅, 将右面的链接复制到你的阅读器里吧.
</div>
<%= link_to(image_tag('rss.png'), rss_blogs_path) %>
</div>
<div class="broadcast"></div>
<div class="comment"></div>
<div class="recommend"></div>
</div>

View File

@ -0,0 +1,4 @@
<h1>关于我</h1>
<p>
为什么会有这里?
</p>

View File

@ -19,10 +19,12 @@
<li> <a id="creator" href="creator">创业<strong>Creator</strong></a> </li>
</ul>
<strong class="descrip">WinDy的个人中心 - 记录人生经历 - 技术 生活 And 创业</strong>
<a id="about" href="/about">关于我</a>
</div>
</div>
<div class="container">
<%= yield %>
<%= render "common/slide" %>
</div>
</body>

View File

@ -0,0 +1,2 @@
<h1>Photos#create</h1>
<p>Find me in app/views/photos/create.html.erb</p>

View File

@ -0,0 +1,42 @@
require 'rexml/parsers/pullparser'
class String
def truncate_html(len = 30, at_end = nil)
p = REXML::Parsers::PullParser.new(self)
tags = []
new_len = len
results = ''
while p.has_next? && new_len > 0
p_e = p.pull
case p_e.event_type
when :start_element
tags.push p_e[0]
results << "<#{tags.last}#{attrs_to_s(p_e[1])}>"
when :end_element
results << "</#{tags.pop}>"
when :text
results << p_e[0][0..new_len]
new_len -= p_e[0].length
else
results << "<!-- #{p_e.inspect} -->"
end
end
if at_end
results << "..."
end
tags.reverse.each do |tag|
results << "</#{tag}>"
end
results
end
private
def attrs_to_s(attrs)
if attrs.empty?
''
else
' ' + attrs.to_a.map { |attr| %{#{attr[0]}="#{attr[1]}"} }.join(' ')
end
end
end

View File

@ -3,3 +3,4 @@
en:
hello: "Hello world"
upload_photo: "上传图片"

View File

@ -1,6 +1,14 @@
WBlog::Application.routes.draw do
root :to => 'blogs#index'
resources :blogs, :only=>[:index, :show]
resources :blogs, :only=>[:index, :show] do
collection do
get :rss
end
end
# photos
resources :photos, :only=>[:create]
namespace :admin do
resources :posts do
collection do
@ -8,6 +16,7 @@ WBlog::Application.routes.draw do
end
end
end
match '/about' => 'home#index'
match '/admin' => 'admin/posts#new'
match '/:type' => 'blogs#index'
end

View File

@ -0,0 +1,12 @@
require 'spec_helper'
describe HomeController do
describe "GET 'index'" do
it "returns http success" do
get 'index'
response.should be_success
end
end
end

View File

@ -0,0 +1,12 @@
require 'spec_helper'
describe PhotosController do
describe "GET 'create'" do
it "returns http success" do
get 'create'
response.should be_success
end
end
end

View File

@ -0,0 +1,15 @@
require 'spec_helper'
# Specs in this file have access to a helper object that includes
# the HomeHelper. For example:
#
# describe HomeHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# helper.concat_strings("this","that").should == "this that"
# end
# end
# end
describe HomeHelper do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,15 @@
require 'spec_helper'
# Specs in this file have access to a helper object that includes
# the PhotosHelper. For example:
#
# describe PhotosHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# helper.concat_strings("this","that").should == "this that"
# end
# end
# end
describe PhotosHelper do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,10 @@
require 'spec_helper'
describe Comment do
it "comment should not blank" do
a = Comment.new
a.save.should == false
a = Comment.new(content: '11')
a.save.should == true
end
end

View File

@ -0,0 +1,8 @@
require 'spec_helper'
describe Photo do
it "photo with picture will be ok" do
a = Photo.new
a.save
end
end

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe "home/index.html.erb" do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe "photos/create.html.erb" do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,513 @@
/*
* jQuery HTML5 File Upload
*
* Author: timdream at gmail.com
* Web: http://timc.idv.tw/html5-file-upload/
*
* Ajax File Upload that use real xhr,
* built with getAsBinary, sendAsBinary, FormData, FileReader, ArrayBuffer, BlobBuilder and etc.
* works in Firefox 3, Chrome 5, Safari 5 and higher
*
* Image resizing and uploading currently works in Fx 3 and up, and Chrome 9 (dev) and up only.
* Extra settings will allow current Webkit users to upload the original image
* or send the resized image in base64 form.
*
* Usage:
* $.fileUploadSupported // a boolean value indicates if the browser is supported.
* $.imageUploadSupported // a boolean value indicates if the browser could resize image and upload in binary form.
* $.fileUploadAsBase64Supported // a boolean value indicate if the browser upload files in based64.
* $.imageUploadAsBase64Supported // a boolean value indicate if the browser could resize image and upload in based64.
* $('input[type=file]').fileUpload(ajaxSettings); //Make a input[type=file] select-and-send file upload widget
* $('#any-element').fileUpload(ajaxSettings); //Make a element receive dropped file
* //TBD $('form#fileupload').fileUpload(ajaxSettings); //Send a ajax form with file
* //TBD $('canvas').fileUpload(ajaxSettings); //Upload given canvas as if it's an png image.
*
* ajaxSettings is the object contains $.ajax settings that will be passed to.
* Available extended settings are:
* fileType:
* regexp check against filename extension; You should always checked it again on server-side.
* e.g. /^(gif|jpe?g|png|tiff?)$/i for images
* fileMaxSize:
* Maxium file size allowed in bytes. Use scientific notation for converience.
* e.g. 1E4 for 1KB, 1E8 for 1MB, 1E9 for 10MB.
* If you really care the difference between 1024 and 1000, use Math.pow(2, 10)
* fileError(info, textStatus, textDescription):
* callback function when there is any error preventing file upload to start,
* $.ajax and ajax events won't be called when error.
* Use $.noop to overwrite default alert function.
* imageMaxWidth, imageMaxHeight:
* Use any of the two settings to enable client-size image resizing.
* Image will be resized to fit into given rectangle.
* File size and type limit checking will be ignored.
* allowUploadOriginalImage:
* Set to true if you accept original image to be uploaded as a fallback
* when image resizing functionality is not availible (such as Webkit browsers).
* File size and type limit will be enforced.
* allowDataInBase64:
* Alternatively, you may wish to resize the image anyway and send the data
* in base64. The data will be 133% larger and you will need to process it further with
* server-side script.
* This setting might work with browsers which could read file but cannot send it in original
* binary (no known browser are designed this way though)
* forceResize:
* Set to true will cause the image being re-sampled even if the resized image
* has the same demension as the original one.
* imageType:
* Acceptable values are: 'jpeg', 'png', or 'auto'.
*
* TBD:
* ability to change settings after binding (you can unbind and bind again as a workaround)
* multipole file handling
* form intergation
*
*/
(function($) {
// Don't do logging if window.log function does not exist.
var log = window.log || $.noop;
// jQuery.ajax config
var config = {
fileError: function (info, textStatus, textDescription) {
window.alert(textDescription);
}
};
// Feature detection
// Read as binary string: FileReader API || Gecko-specific function (Fx3)
var canReadAsBinaryString = (window.FileReader || window.File.prototype.getAsBinary);
// Read file using FormData interface
var canReadFormData = !!(window.FormData);
// Read file into data: URL: FileReader API || Gecko-specific function (Fx3)
var canReadAsBase64 = (window.FileReader || window.File.prototype.getAsDataURL);
var canResizeImageToBase64 = !!(document.createElement('canvas').toDataURL);
var canResizeImageToBinaryString = canResizeImageToBase64 && window.atob;
var canResizeImageToFile = !!(document.createElement('canvas').mozGetAsFile);
// Send file in multipart/form-data with binary xhr (Gecko-specific function)
// || xhr.send(blob) that sends blob made with ArrayBuffer.
var canSendBinaryString = (
(window.XMLHttpRequest && window.XMLHttpRequest.prototype.sendAsBinary)
|| (window.ArrayBuffer && window.BlobBuilder)
);
// Send file as in FormData object
var canSendFormData = !!(window.FormData);
// Send image base64 data by extracting data: URL
var canSendImageInBase64 = !!(document.createElement('canvas').toDataURL);
var isSupported = (
(canReadAsBinaryString && canSendBinaryString)
|| (canReadFormData && canSendFormData)
);
var isImageSupported = (
canReadAsBase64 && (
(canResizeImageToBinaryString && canSendBinaryString)
|| (canResizeImageToFile && canSendFormData)
)
);
var isSupportedInBase64 = canReadAsBase64;
var isImageSupportedInBase64 = canReadAsBase64 && canResizeImageToBase64;
var dataURLtoBase64 = function (dataurl) {
return dataurl.substring(dataurl.indexOf(',')+1, dataurl.length);
}
// Step 1: check file info and attempt to read the file
// paramaters: Ajax settings, File object
var handleFile = function (settings, file) {
var info = {
// properties of standard File object || Gecko 1.9 properties
type: file.type || '', // MIME type
size: file.size || file.fileSize,
name: file.name || file.fileName
};
settings.resizeImage = !!(settings.imageMaxWidth || settings.imageMaxHeight);
if (settings.resizeImage && !isImageSupported && settings.allowUploadOriginalImage) {
log('WARN: Fall back to upload original un-resized image.');
settings.resizeImage = false;
}
if (settings.resizeImage) {
settings.imageMaxWidth = settings.imageMaxWidth || Infinity;
settings.imageMaxHeight = settings.imageMaxHeight || Infinity;
}
if (!settings.resizeImage) {
if (settings.fileType && settings.fileType.test) {
// Not using MIME types
if (!settings.fileType.test(info.name.substr(info.name.lastIndexOf('.')+1))) {
log('ERROR: Invalid Filetype.');
settings.fileError.call(this, info, 'INVALID_FILETYPE', 'Invalid filetype.');
return;
}
}
if (settings.fileMaxSize && file.size > settings.fileMaxSize) {
log('ERROR: File exceeds size limit.');
settings.fileError.call(this, info, 'FILE_EXCEEDS_SIZE_LIMIT', 'File exceeds size limit.');
return;
}
}
if (!settings.resizeImage && canReadFormData) {
log('INFO: Bypass file reading, insert file object into FormData object directly.');
handleForm(settings, 'file', file, info);
} else if (window.FileReader) {
log('INFO: Using FileReader to do asynchronously file reading.');
var reader = new FileReader();
reader.onerror = function (ev) {
if (ev.target.error) {
switch (ev.target.error) {
case 8:
log('ERROR: File not found.');
settings.fileError.call(this, info, 'FILE_NOT_FOUND', 'File not found.');
break;
case 24:
log('ERROR: File not readable.');
settings.fileError.call(this, info, 'IO_ERROR', 'File not readable.');
break;
case 18:
log('ERROR: File cannot be access due to security constrant.');
settings.fileError.call(this, info, 'SECURITY_ERROR', 'File cannot be access due to security constrant.');
break;
case 20: //User Abort
break;
}
}
}
if (!settings.resizeImage) {
if (canSendBinaryString) {
reader.onloadend = function (ev) {
var bin = ev.target.result;
handleForm(settings, 'bin', bin, info);
};
reader.readAsBinaryString(file);
} else if (settings.allowDataInBase64) {
reader.onloadend = function (ev) {
handleForm(
settings,
'base64',
dataURLtoBase64(ev.target.result),
info
);
};
reader.readAsDataURL(file);
} else {
log('ERROR: No available method to extract file; allowDataInBase64 not set.');
settings.fileError.call(this, info, 'NO_BIN_SUPPORT_AND_BASE64_NOT_SET', 'No available method to extract file; allowDataInBase64 not set.');
}
} else {
reader.onloadend = function (ev) {
var dataurl = ev.target.result;
handleImage(settings, dataurl, info);
};
reader.readAsDataURL(file);
}
} else if (window.File.prototype.getAsBinary) {
log('WARN: FileReader does not exist, UI will be blocked when reading big file.');
if (!settings.resizeImage) {
try {
var bin = file.getAsBinary();
} catch (e) {
log('ERROR: File not readable.');
settings.fileError.call(this, info, 'IO_ERROR', 'File not readable.');
return;
}
handleForm(settings, 'bin', bin, info);
} else {
try {
var bin = file.getAsDataURL();
} catch (e) {
log('ERROR: File not readable.');
settings.fileError.call(this, info, 'IO_ERROR', 'File not readable.');
return;
}
handleImage(settings, dataurl, info);
}
} else {
log('ERROR: No available method to extract file; this browser is not supported.');
settings.fileError.call(this, info, 'NOT_SUPPORT', 'ERROR: No available method to extract file; this browser is not supported.');
}
};
// step 1.5: inject file into <img>, paste the pixels into <canvas>,
// read the final image
var handleImage = function (settings, dataurl, info) {
var img = new Image();
img.onerror = function () {
log('ERROR: <img> failed to load, file is not a supported image format.');
settings.fileError.call(this, info, 'FILE_NOT_IMAGE', 'File is not a supported image format.');
};
img.onload = function () {
var ratio = Math.max(
img.width/settings.imageMaxWidth,
img.height/settings.imageMaxHeight,
1
);
var d = {
w: Math.floor(Math.max(img.width/ratio, 1)),
h: Math.floor(Math.max(img.height/ratio, 1))
}
log(
'INFO: Original image size: ' + img.width.toString(10) + 'x' + img.height.toString(10)
+ ', resized image size: ' + d.w + 'x' + d.h + '.'
);
if (!settings.forceResize && img.width === d.w && img.height === d.h) {
log('INFO: Image demension is the same, send the original file.');
if (canResizeImageToBinaryString) {
handleForm(
settings,
'bin',
window.atob(dataURLtoBase64(dataurl)),
info
);
} else if (settings.allowDataInBase64) {
handleForm(
settings,
'base64',
dataURLtoBase64(dataurl),
info
);
} else {
log('ERROR: No available method to send the original file; allowDataInBase64 not set.');
settings.fileError.call(this, info, 'NO_BIN_SUPPORT_AND_BASE64_NOT_SET', 'No available method to extract file; allowDataInBase64 not set.');
}
return;
}
var canvas = document.createElement('canvas');
canvas.setAttribute('width', d.w);
canvas.setAttribute('height', d.h);
canvas.getContext('2d').drawImage(
img,
0,
0,
img.width,
img.height,
0,
0,
d.w,
d.h
);
if (!settings.imageType || settings.imageType === 'auto') {
if (info.type === 'image/jpeg') settings.imageType = 'jpeg';
else settings.imageType = 'png';
}
var ninfo = {
type: 'image/' + settings.imageType,
name: info.name.substr(0, info.name.indexOf('.')) + '.resized.' + settings.imageType
};
if (canResizeImageToFile && canSendFormData) {
// Gecko 2 (Fx4) non-standard function
var nfile = canvas.mozGetAsFile(
ninfo.name,
'image/' + settings.imageType
);
ninfo.size = file.size || file.fileSize;
handleForm(
settings,
'file',
nfile,
ninfo
);
} else if (canResizeImageToBinaryString && canSendBinaryString) {
// Read the image as DataURL, convert it back to binary string.
var bin = window.atob(dataURLtoBase64(canvas.toDataURL('image/' + settings.imageType)));
ninfo.size = bin.length;
handleForm(
settings,
'bin',
bin,
ninfo
);
} else if (settings.allowDataInBase64 && canResizeImageToBase64 && canSendImageInBase64) {
handleForm(
settings,
'base64',
dataURLtoBase64(canvas.toDataURL('image/' + settings.imageType)),
ninfo
);
} else {
log('ERROR: No available method to extract image; allowDataInBase64 not set.');
settings.fileError.call(this, info, 'NO_BIN_SUPPORT_AND_BASE64_NOT_SET', 'No available method to extract file; allowDataInBase64 not set.');
}
}
img.src = dataurl;
}
// Step 2: construct form data and send the file
// paramaters: Ajax settings, File object, binary string of file || null, file info assoc array
var handleForm = function (settings, type, data, info) {
if (canSendFormData && type === 'file') {
// FormData API saves the day
log('INFO: Using FormData to construct form.');
var formdata = new FormData();
formdata.append('Filedata', data);
// Prevent jQuery form convert FormData object into string.
settings.processData = false;
// Prevent jQuery from overwrite automatically generated xhr content-Type header
// by unsetting the default contentType and inject data only right before xhr.send()
settings.contentType = null;
settings.__beforeSend = settings.beforeSend;
settings.beforeSend = function (xhr, s) {
s.data = formdata;
if (s.__beforeSend) return s.__beforeSend.call(this, xhr, s);
}
//settings.data = formdata;
} else if (canSendBinaryString && type === 'bin') {
log('INFO: Concat our own multipart/form-data data string.');
// A placeholder MIME type
if (!info.type) info.type = 'application/octet-stream';
if (/[^\x20-\x7E]/.test(info.name)) {
log('INFO: Filename contains non-ASCII code, do UTF8-binary string conversion.');
info.name_bin = unescape(encodeURIComponent(info.name));
}
//filtered out non-ASCII chars in filenames
// info.name = info.name.replace(/[^\x20-\x7E]/g, '_');
// multipart/form-data boundary
var bd = 'xhrupload-' + parseInt(Math.random()*(2 << 16));
settings.contentType = 'multipart/form-data; boundary=' + bd;
var formdata = '--' + bd + '\n' // RFC 1867 Format, simulate form file upload
+ 'content-disposition: form-data; name="Filedata";'
+ ' filename="' + (info.name_bin || info.name) + '"\n'
+ 'Content-Type: ' + info.type + '\n\n'
+ data + '\n\n'
+ '--' + bd + '--';
if (window.XMLHttpRequest.prototype.sendAsBinary) {
// Use xhr.sendAsBinary that takes binary string
log('INFO: Pass binary string to xhr.');
settings.data = formdata;
} else {
// make a blob
log('INFO: Convert binary string into Blob.');
var buf = new ArrayBuffer(formdata.length);
var view = new Uint8Array(buf);
$.each(
formdata,
function (i, o) {
view[i] = o.charCodeAt(0);
}
);
var bb = new BlobBuilder();
bb.append(buf);
var blob = bb.getBlob();
settings.processData = false;
settings.__beforeSend = settings.beforeSend;
settings.beforeSend = function (xhr, s) {
s.data = blob;
if (s.__beforeSend) return s.__beforeSend.call(this, xhr, s);
};
}
} else if (settings.allowDataInBase64 && type === 'base64') {
log('INFO: Concat our own multipart/form-data data string; send the file in base64 because binary xhr is not supported.');
// A placeholder MIME type
if (!info.type) info.type = 'application/octet-stream';
// multipart/form-data boundary
var bd = 'xhrupload-' + parseInt(Math.random()*(2 << 16));
settings.contentType = 'multipart/form-data; boundary=' + bd;
settings.data = '--' + bd + '\n' // RFC 1867 Format, simulate form file upload
+ 'content-disposition: form-data; name="Filedata";'
+ ' filename="' + encodeURIComponent(info.name) + '.base64"\n'
+ 'Content-Transfer-Encoding: base64\n' // Vaild MIME header, but won't work with PHP file upload handling.
+ 'Content-Type: ' + info.type + '\n\n'
+ data + '\n\n'
+ '--' + bd + '--';
} else {
log('ERROR: Data is not given in processable form.');
settings.fileError.call(this, info, 'INTERNAL_ERROR', 'Data is not given in processable form.');
return;
}
xhrupload(settings);
};
// Step 3: start sending out file
var xhrupload = function (settings) {
log('INFO: Sending file.');
if (typeof settings.data === 'string' && canSendBinaryString) {
log('INFO: Using xhr.sendAsBinary.');
settings.___beforeSend = settings.beforeSend;
settings.beforeSend = function (xhr, s) {
xhr.send = xhr.sendAsBinary;
if (s.___beforeSend) return s.___beforeSend.call(this, xhr, s);
}
}
$.ajax(settings);
};
$.fn.fileUpload = function(settings) {
this.each(function(i, el) {
if ($(el).is('input[type=file]')) {
log('INFO: binding onchange event to a input[type=file].');
$(el).bind(
'change',
function () {
if (!this.files.length) {
log('ERROR: no file selected.');
return;
} else if (this.files.length > 1) {
log('WARN: Multiple file upload not implemented yet, only first file will be uploaded.');
}
handleFile($.extend({}, config, settings), this.files[0]);
if (this.form.length === 1) {
this.form.reset();
} else {
log('WARN: Unable to reset file selection, upload won\'t be triggered again if user selects the same file.');
}
return;
}
);
}
if ($(el).is('form')) {
log('ERROR: <form> not implemented yet.');
} else {
log('INFO: binding ondrop event.');
$(el).bind(
'dragover', // dragover behavior should be blocked for drop to invoke.
function(ev) {
return false;
}
).bind(
'drop',
function (ev) {
if (!ev.originalEvent.dataTransfer.files) {
log('ERROR: No FileList object present; user might had dropped text.');
return false;
}
if (!ev.originalEvent.dataTransfer.files.length) {
log('ERROR: User had dropped a virual file (e.g. "My Computer")');
return false;
}
if (!ev.originalEvent.dataTransfer.files.length > 1) {
log('WARN: Multiple file upload not implemented yet, only first file will be uploaded.');
}
handleFile($.extend({}, config, settings), ev.originalEvent.dataTransfer.files[0]);
return false;
}
);
}
});
return this;
};
$.fileUploadSupported = isSupported;
$.imageUploadSupported = isImageSupported;
$.fileUploadAsBase64Supported = isSupportedInBase64;
$.imageUploadAsBase64Supported = isImageSupportedInBase64;
})(jQuery);