card 1.16.3 → 1.16.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/card.gemspec +1 -1
- data/db/migrate_core_cards/20150724123438_update_file_and_image_cards.rb +1 -1
- data/db/migrate_core_cards/20150824135418_update_file_history.rb +20 -0
- data/db/migrate_core_cards/20150903130006_attachment_upload_cards.rb +13 -0
- data/db/seed/new/card_actions.yml +16 -0
- data/db/seed/new/card_acts.yml +1 -1
- data/db/seed/new/card_changes.yml +56 -80
- data/db/seed/new/card_references.yml +282 -58
- data/db/seed/new/cards.yml +1348 -1312
- data/db/seed/test/fixtures/card_actions.yml +884 -868
- data/db/seed/test/fixtures/card_acts.yml +250 -250
- data/db/seed/test/fixtures/card_changes.yml +1935 -1959
- data/db/seed/test/fixtures/card_references.yml +1024 -800
- data/db/seed/test/fixtures/cards.yml +2402 -2366
- data/db/version_core_cards.txt +1 -1
- data/lib/card.rb +3 -1
- data/lib/card/cache.rb +5 -5
- data/lib/card/chunk.rb +2 -0
- data/lib/card/env.rb +1 -1
- data/lib/card/query/card_clause.rb +59 -58
- data/lib/card/set.rb +7 -0
- data/lib/card/success.rb +143 -0
- data/mod/01_core/chunk/query_reference.rb +16 -7
- data/mod/01_core/chunk/reference.rb +3 -3
- data/mod/01_core/set/all/collection.rb +1 -1
- data/mod/01_core/set/all/name.rb +2 -1
- data/mod/01_core/set/all/phases.rb +12 -2
- data/mod/01_core/set/all/type.rb +5 -5
- data/mod/01_history/set/all/actions.rb +6 -2
- data/mod/01_history/set/all/content_history.rb +17 -2
- data/mod/01_history/set/all/history.rb +1 -1
- data/mod/02_basic_types/set/all/file.rb +0 -31
- data/mod/03_machines/lib/javascript/jquery.fileupload.js +539 -182
- data/mod/03_machines/lib/javascript/wagn.js.coffee +3 -0
- data/mod/03_machines/lib/javascript/wagn_mod.js.coffee +20 -18
- data/mod/03_machines/lib/stylesheets/style_cards.scss +28 -1
- data/mod/05_email/set/all/notify.rb +1 -1
- data/mod/05_standard/file/favicon/image-icon.png +0 -0
- data/mod/05_standard/file/favicon/image-large.png +0 -0
- data/mod/05_standard/file/favicon/image-medium.png +0 -0
- data/mod/05_standard/file/favicon/image-original.png +0 -0
- data/mod/05_standard/file/favicon/image-small.png +0 -0
- data/mod/05_standard/lib/carrier_wave/cardmount.rb +25 -6
- data/mod/05_standard/lib/file_uploader.rb +27 -11
- data/mod/05_standard/lib/image_uploader.rb +7 -4
- data/mod/05_standard/set/abstract/attachment.rb +132 -14
- data/mod/05_standard/set/right/account.rb +2 -2
- data/mod/05_standard/set/self/signin.rb +0 -1
- data/mod/05_standard/set/type/file.rb +48 -19
- data/mod/05_standard/set/type/image.rb +9 -12
- data/mod/05_standard/spec/chunk/include_spec.rb +13 -12
- data/mod/05_standard/spec/chunk/query_reference_spec.rb +50 -0
- data/mod/05_standard/spec/set/right/account_spec.rb +24 -25
- data/mod/05_standard/spec/set/type/file_spec.rb +1 -1
- data/spec/lib/card/reference_spec.rb +14 -0
- data/spec/lib/card/success_spec.rb +142 -0
- data/tmpsets/set/mod001-01_core/all/collection.rb +1 -1
- data/tmpsets/set/mod001-01_core/all/name.rb +2 -1
- data/tmpsets/set/mod001-01_core/all/phases.rb +12 -2
- data/tmpsets/set/mod001-01_core/all/type.rb +5 -5
- data/tmpsets/set/mod002-01_history/all/actions.rb +6 -2
- data/tmpsets/set/mod002-01_history/all/content_history.rb +17 -2
- data/tmpsets/set/mod002-01_history/all/history.rb +1 -1
- data/tmpsets/set/mod003-02_basic_types/all/file.rb +0 -24
- data/tmpsets/set/mod003-02_basic_types/all/rss.rb +8 -5
- data/tmpsets/set/mod003-02_basic_types/type/pointer.rb +2 -2
- data/tmpsets/set/mod005-04_settings/right/structure.rb +7 -2
- data/tmpsets/set/mod006-05_email/all/notify.rb +1 -1
- data/tmpsets/set/mod007-05_standard/abstract/attachment.rb +132 -14
- data/tmpsets/set/mod007-05_standard/all/links.rb +8 -0
- data/tmpsets/set/mod007-05_standard/all/rich_html/header.rb +5 -7
- data/tmpsets/set/mod007-05_standard/right/account.rb +2 -2
- data/tmpsets/set/mod007-05_standard/self/signin.rb +0 -1
- data/tmpsets/set/mod007-05_standard/type/file.rb +49 -20
- data/tmpsets/set/mod007-05_standard/type/image.rb +9 -12
- data/tmpsets/set/mod007-05_standard/type/search_type.rb +40 -22
- data/tmpsets/set/mod008-06_bootstrap/self/bootswatch_shared.rb +1 -1
- metadata +10 -4
data/lib/card/set.rb
CHANGED
@@ -444,6 +444,13 @@ EOF
|
|
444
444
|
Card.set_specific_attributes ||= []
|
445
445
|
Card.set_specific_attributes += args.map(&:to_s)
|
446
446
|
end
|
447
|
+
|
448
|
+
def attachment name, args
|
449
|
+
include Abstract::Attachment
|
450
|
+
set_specific_attributes name, :load_from_mod, "remote_#{name}_url".to_sym,
|
451
|
+
uploader_class = args[:uploader] || FileUploader
|
452
|
+
mount_uploader name, uploader_class
|
453
|
+
end
|
447
454
|
end
|
448
455
|
end
|
449
456
|
|
data/lib/card/success.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
class Card
|
2
|
+
class Success
|
3
|
+
include Card::Format::Location
|
4
|
+
include Card::HtmlFormat::Location
|
5
|
+
|
6
|
+
attr_accessor :params, :redirect, :id, :name, :card, :name_context
|
7
|
+
|
8
|
+
def initialize name_context=nil, success_params=nil
|
9
|
+
@name_context = name_context
|
10
|
+
@new_args = {}
|
11
|
+
@params = OpenStruct.new
|
12
|
+
case success_params
|
13
|
+
when Hash
|
14
|
+
apply(success_params)
|
15
|
+
when /^REDIRECT:\s*(.+)/
|
16
|
+
@redirect=true
|
17
|
+
self.target = $1
|
18
|
+
when nil ; self.name = '_self'
|
19
|
+
else ; self.target = success_params
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def << value
|
25
|
+
case value
|
26
|
+
when Hash ; apply value
|
27
|
+
else ; self.target = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def hard_redirect?
|
32
|
+
@redirect == true
|
33
|
+
end
|
34
|
+
|
35
|
+
# reset card object and override params with success params
|
36
|
+
def soft_redirect?
|
37
|
+
@redirect == :soft
|
38
|
+
end
|
39
|
+
|
40
|
+
def mark= value
|
41
|
+
case value
|
42
|
+
when Integer ; @id = value
|
43
|
+
when String ; @name = value
|
44
|
+
when Card ; @card = value
|
45
|
+
else ; self.target = value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def id= id
|
50
|
+
self.mark = id # for backwards compatibility use mark here: id was often used for the card name
|
51
|
+
end
|
52
|
+
|
53
|
+
def type= type
|
54
|
+
@new_args[:type] = type
|
55
|
+
end
|
56
|
+
|
57
|
+
def content= content
|
58
|
+
@new_args[:content] = content
|
59
|
+
end
|
60
|
+
|
61
|
+
def target= value
|
62
|
+
@id = @name = @card = nil
|
63
|
+
@target =
|
64
|
+
case value
|
65
|
+
when '*previous', :previous ; :previous
|
66
|
+
when /^(http|\/)/ ; value
|
67
|
+
when /^TEXT:\s*(.+)/ ; $1
|
68
|
+
when '' ; ''
|
69
|
+
else ; self.mark = value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def apply args
|
74
|
+
args.each_pair do |key, value|
|
75
|
+
self[key] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def card name_context=@name_context
|
80
|
+
if @card
|
81
|
+
@card
|
82
|
+
elsif @id
|
83
|
+
Card.find @id
|
84
|
+
elsif @name
|
85
|
+
Card.fetch @name.to_name.to_absolute(name_context), :new=>@new_args
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def target name_context=@name_context
|
90
|
+
card(name_context) || ( @target == :previous ? previous_location : @target ) || Card.fetch(name_context)
|
91
|
+
end
|
92
|
+
|
93
|
+
def []= key, value
|
94
|
+
if respond_to? "#{key}="
|
95
|
+
send "#{key}=", value
|
96
|
+
elsif key.to_sym == :soft_redirect
|
97
|
+
@redirect = :soft
|
98
|
+
else
|
99
|
+
@params.send "#{key}=", value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def [] key
|
104
|
+
if respond_to? key.to_sym
|
105
|
+
send key.to_sym
|
106
|
+
elsif key.to_sym == :soft_redirect
|
107
|
+
@redirect == :soft
|
108
|
+
else
|
109
|
+
@params.send key.to_sym
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def params
|
114
|
+
@params.marshal_dump
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_url name_context=@name_context
|
118
|
+
case (target = target(name_context))
|
119
|
+
when Card
|
120
|
+
page_path target.cardname, params
|
121
|
+
else
|
122
|
+
target
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def method_missing method, *args
|
127
|
+
case method
|
128
|
+
when /^(\w+)=$/
|
129
|
+
self[$1.to_sym] = args[0]
|
130
|
+
when /^(\w+)$/
|
131
|
+
self[$1.to_sym]
|
132
|
+
else
|
133
|
+
super
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def session
|
138
|
+
Card::Env.session
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
end
|
@@ -10,11 +10,12 @@ module Card::Chunk
|
|
10
10
|
# 3a) {"plus_right":["Alfred"]}
|
11
11
|
# but not in
|
12
12
|
# 2b) "content":"foo", "Alfred":"bar"
|
13
|
-
# 3b) {"name":["Alfred", "Toni"]}
|
13
|
+
# 3b) {"name":["Alfred", "Toni"]} ("Alfred" is an operator here)
|
14
14
|
# It's not possible to distinguish between 2a) and 2b) or 3a) and 3b) with a simple regex
|
15
15
|
# hence we use a too general regex and check for query keywords after the match
|
16
16
|
# which of course means that we don't find references with query keywords as name
|
17
17
|
class QueryReference < Reference
|
18
|
+
|
18
19
|
QUERY_KEYWORDS = ::Set.new(
|
19
20
|
(
|
20
21
|
Card::Query::MODIFIERS.keys +
|
@@ -26,15 +27,23 @@ module Card::Chunk
|
|
26
27
|
)
|
27
28
|
word = /\s*([^"]+)\s*/
|
28
29
|
|
29
|
-
# we check for colon, comma or square bracket before a quote
|
30
|
-
# OPTIMIZE: instead of comma or square bracket check for operator followed by comma or "plus_right"|"plus_left"|"plus" followed by square bracket
|
31
30
|
Card::Chunk.register_class self, {
|
32
|
-
:prefix_re => '(?<=[:,\\[])\\s*"', # we
|
33
|
-
|
34
|
-
|
31
|
+
:prefix_re => '(?<=[:,\\[])\\s*"', # we check for colon, comma or square bracket before a quote
|
32
|
+
# we have to use a lookbehind, otherwise
|
33
|
+
# if the colon matches it would be
|
34
|
+
# identified mistakenly as an URI chunk
|
35
35
|
:full_re => /"([^"]+)"/,
|
36
36
|
:idx_char => '"'
|
37
37
|
}
|
38
|
+
# OPTIMIZE: instead of comma or square bracket check for operator followed by comma or "plus_right"|"plus_left"|"plus" followed by square bracket
|
39
|
+
# something like
|
40
|
+
# prefix_patterns = [
|
41
|
+
# "\"\\s*(?:#{Card::Query::OPERATORS.keys.join('|')})\"\\s*,",
|
42
|
+
# "\"\\s*(?:#{Card::Query::CardClause::PLUS_ATTRIBUTES}.keys.join('|')})\\s*:\\s*\\[\\s*",
|
43
|
+
# "\"\\s*(?:#{(QUERY_KEYWORDS - Card::Query::CardClause::PLUS_ATTRIBUTES).join('|')})\"\\s*:",
|
44
|
+
# ]
|
45
|
+
# :prefix_re => '(?<=#{prefix_patterns.join('|')})\\s*"'
|
46
|
+
# But: What do we do with the "in" operator? After the first value there is no prefix which we can use to detect the following values as QueryReference chunks
|
38
47
|
|
39
48
|
class << self
|
40
49
|
def full_match content, prefix
|
@@ -59,7 +68,7 @@ module Card::Chunk
|
|
59
68
|
|
60
69
|
def replace_reference old_name, new_name
|
61
70
|
replace_name_reference old_name, new_name
|
62
|
-
@text = "\"#{
|
71
|
+
@text = "\"#{@name.to_s}\""
|
63
72
|
end
|
64
73
|
end
|
65
74
|
end
|
@@ -5,12 +5,12 @@ module Card::Chunk
|
|
5
5
|
|
6
6
|
def referee_name
|
7
7
|
return if name.nil?
|
8
|
-
|
8
|
+
|
9
9
|
@referee_name ||= begin
|
10
10
|
rendered_name = render_obj( name )
|
11
11
|
ref_card = case rendered_name
|
12
12
|
when /^\~(\d+)$/ # get by id
|
13
|
-
Card.fetch $1.to_i
|
13
|
+
Card.fetch $1.to_i
|
14
14
|
when /^\:(\w+)$/ # get by codename
|
15
15
|
Card.fetch $1.to_sym
|
16
16
|
end
|
@@ -39,7 +39,7 @@ module Card::Chunk
|
|
39
39
|
@name = name.to_name.replace_part( old_name, new_name )
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
def render_obj raw
|
44
44
|
if format && Card::Content===raw
|
45
45
|
format.card.references_expired = nil # don't love this; this is to keep from running update_references again
|
data/mod/01_core/set/all/name.rb
CHANGED
@@ -233,7 +233,7 @@ end
|
|
233
233
|
|
234
234
|
|
235
235
|
event :cascade_name_changes, :after=>:store, :on=>:update, :changed=>:name do
|
236
|
-
Rails.logger.
|
236
|
+
#Rails.logger.info "------------------- #{name_was} CASCADE #{self.name} -------------------------------------"
|
237
237
|
|
238
238
|
self.update_referencers = false if self.update_referencers == 'false' #handle strings from cgi
|
239
239
|
Card::Reference.update_on_rename self, name, self.update_referencers
|
@@ -245,6 +245,7 @@ event :cascade_name_changes, :after=>:store, :on=>:update, :changed=>:name do
|
|
245
245
|
|
246
246
|
deps.each do |dep|
|
247
247
|
# here we specifically want NOT to invoke recursive cascades on these cards, have to go this low level to avoid callbacks.
|
248
|
+
Rails.logger.info "cascading name: #{dep.name}"
|
248
249
|
Card.expire dep.name #old name
|
249
250
|
newname = dep.cardname.replace_part name_was, name
|
250
251
|
Card.where( :id=> dep.id ).update_all :name => newname.to_s, :key => newname.key
|
@@ -11,7 +11,7 @@ def abort status, msg='action canceled'
|
|
11
11
|
if status == :failure && errors.empty?
|
12
12
|
errors.add :abort, msg
|
13
13
|
elsif Hash === status and status[:success]
|
14
|
-
|
14
|
+
success << status[:success]
|
15
15
|
status = :success
|
16
16
|
end
|
17
17
|
raise Card::Abort.new( status, msg)
|
@@ -49,11 +49,18 @@ end
|
|
49
49
|
# perhaps above should be in separate module?
|
50
50
|
#~~~~~~
|
51
51
|
|
52
|
-
def
|
52
|
+
def prepare
|
53
53
|
@action = identify_action
|
54
54
|
# the following should really happen when type, name etc are changed
|
55
55
|
reset_patterns
|
56
56
|
include_set_modules
|
57
|
+
run_callbacks :prepare
|
58
|
+
rescue =>e
|
59
|
+
rescue_event e
|
60
|
+
end
|
61
|
+
|
62
|
+
def approve
|
63
|
+
@action ||= identify_action
|
57
64
|
run_callbacks :approve
|
58
65
|
expire_pieces if errors.any?
|
59
66
|
errors.empty?
|
@@ -178,4 +185,7 @@ event :store_subcards, :after=>:store do
|
|
178
185
|
end
|
179
186
|
end
|
180
187
|
|
188
|
+
def success
|
189
|
+
Env[:success] ||= Card::Success.new(cardname)
|
190
|
+
end
|
181
191
|
|
data/mod/01_core/set/all/type.rb
CHANGED
@@ -28,7 +28,7 @@ end
|
|
28
28
|
|
29
29
|
def get_type_id args={}
|
30
30
|
return if args[:type_id] # type_id was set explicitly. no need to set again.
|
31
|
-
|
31
|
+
|
32
32
|
type_id = case
|
33
33
|
when args[:type_code]
|
34
34
|
if code=args[:type_code]
|
@@ -60,11 +60,11 @@ event :validate_type_change, :before=>:approve, :on=>:update, :changed=>:type_id
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
event :validate_type, :before=>:approve, :changed=>:type_id do
|
63
|
+
event :validate_type, :before=>:approve, :changed=>:type_id do
|
64
64
|
if !type_name
|
65
65
|
errors.add :type, "No such type"
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
if rt = structure and rt.assigns_type? and type_id!=rt.type_id
|
69
69
|
errors.add :type, "can't be changed because #{name} is hard templated to #{rt.type_name}"
|
70
70
|
end
|
@@ -72,7 +72,7 @@ end
|
|
72
72
|
|
73
73
|
event :reset_type_specific_fields, :after=>:store do
|
74
74
|
Auth.as_bot do
|
75
|
-
Card.search :left=>{ :
|
75
|
+
Card.search :left=>{ :left_id=>type_id }, :right=>{:codename=>'type_plus_right'} do |set_card|
|
76
76
|
set_card.reset_set_patterns
|
77
77
|
end
|
78
78
|
end
|
@@ -82,4 +82,4 @@ end
|
|
82
82
|
# Card["#{lef}"]
|
83
83
|
# set_card.reset_set_patterns
|
84
84
|
# end
|
85
|
-
|
85
|
+
|
@@ -1,13 +1,17 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
3
|
def select_action_by_params params
|
4
|
-
action = find_action_by_params(params)
|
4
|
+
if (action = find_action_by_params(params))
|
5
|
+
run_callbacks :select_action do
|
6
|
+
self.selected_action_id = action.id
|
7
|
+
end
|
8
|
+
end
|
5
9
|
end
|
6
10
|
|
7
11
|
def find_action_by_params args
|
8
12
|
if args[:rev]
|
9
13
|
nth_action args[:rev]
|
10
|
-
elsif args[:rev_id] =~ /^\d+$/
|
14
|
+
elsif Integer === args[:rev_id] || args[:rev_id] =~ /^\d+$/
|
11
15
|
if action = Action.fetch(args[:rev_id]) and action.card_id == id
|
12
16
|
action
|
13
17
|
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
def content
|
5
5
|
if @selected_action_id
|
6
6
|
@selected_content ||= begin
|
7
|
-
(change = last_change_on( :db_content, :not_after=> @selected_action_id ) and change.value) || db_content
|
7
|
+
(change = last_change_on( :db_content, :not_after=> @selected_action_id, :including_drafts=>true ) and change.value) || db_content
|
8
8
|
end
|
9
9
|
else
|
10
10
|
super
|
@@ -24,7 +24,10 @@ def save_content_draft content
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def last_change_on(field, opts={})
|
27
|
-
where_sql = 'card_actions.card_id = :card_id AND field = :field
|
27
|
+
where_sql = 'card_actions.card_id = :card_id AND field = :field '
|
28
|
+
if !opts[:including_drafts]
|
29
|
+
where_sql += 'AND (draft is not true) '
|
30
|
+
end
|
28
31
|
where_sql += if opts[:before]
|
29
32
|
'AND card_action_id < :action_id'
|
30
33
|
elsif opts[:not_after]
|
@@ -54,6 +57,18 @@ def selected_action
|
|
54
57
|
selected_action_id and Action.fetch(selected_action_id)
|
55
58
|
end
|
56
59
|
|
60
|
+
def with_selected_action_id action_id
|
61
|
+
current_action_id = @selected_action_id
|
62
|
+
run_callbacks :select_action do
|
63
|
+
self.selected_action_id = action_id
|
64
|
+
end
|
65
|
+
result = yield
|
66
|
+
run_callbacks :select_action do
|
67
|
+
self.selected_action_id = current_action_id
|
68
|
+
end
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
57
72
|
def selected_content_action_id
|
58
73
|
@selected_action_id ||
|
59
74
|
(@current_action && (new_card? || @current_action.new_content? || db_content_changed?) && @current_action.id) ||
|
@@ -5,7 +5,7 @@ def history?
|
|
5
5
|
end
|
6
6
|
|
7
7
|
# must be called on all actions and before :set_name, :process_subcards and :validate_delete_children
|
8
|
-
event :assign_act, :before=>:
|
8
|
+
event :assign_act, :before=>:prepare, :when=>proc {|c| c.history?} do
|
9
9
|
@current_act = (@supercard && @supercard.current_act) || Card::Act.create(:ip_address=>Env.ip)
|
10
10
|
end
|
11
11
|
|
@@ -1,34 +1,3 @@
|
|
1
|
-
# FIXME: these methods should move to type/file.rb but some machine stuff is failing if it's not in a "all" set
|
2
|
-
def store_dir
|
3
|
-
if (mod = mod_file?)
|
4
|
-
"#{ Cardio.gem_root}/mod/#{mod}/file/#{codename}"
|
5
|
-
elsif id
|
6
|
-
"#{ Card.paths['files'].existent.first }/#{id}"
|
7
|
-
else
|
8
|
-
tmp_store_dir
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def tmp_store_dir
|
13
|
-
"#{ Card.paths['files'].existent.first }/#{key}"
|
14
|
-
end
|
15
|
-
|
16
|
-
def mod_file?
|
17
|
-
# when db_content was changed assume that it's no longer a mod file
|
18
|
-
if !db_content_changed? && content.present?
|
19
|
-
case content
|
20
|
-
when /^:[^\/]+\/([^.]+)/ ; $1 # current mod_file format
|
21
|
-
when /^\~/ ; false # current id file format
|
22
|
-
else
|
23
|
-
if lines = content.split("\n") and lines.size == 4 # old format, still used in card_changes.
|
24
|
-
lines.last
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
1
|
format :file do
|
33
2
|
|
34
3
|
view :core do |args|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
* jQuery File Upload Plugin 5.
|
2
|
+
* jQuery File Upload Plugin 5.42.3
|
3
3
|
* https://github.com/blueimp/jQuery-File-Upload
|
4
4
|
*
|
5
5
|
* Copyright 2010, Sebastian Tschan
|
@@ -9,8 +9,8 @@
|
|
9
9
|
* http://www.opensource.org/licenses/MIT
|
10
10
|
*/
|
11
11
|
|
12
|
-
/*
|
13
|
-
/*global define, window, document,
|
12
|
+
/* jshint nomen:false */
|
13
|
+
/* global define, require, window, document, location, Blob, FormData */
|
14
14
|
|
15
15
|
(function (factory) {
|
16
16
|
'use strict';
|
@@ -20,6 +20,12 @@
|
|
20
20
|
'jquery',
|
21
21
|
'jquery.ui.widget'
|
22
22
|
], factory);
|
23
|
+
} else if (typeof exports === 'object') {
|
24
|
+
// Node/CommonJS:
|
25
|
+
factory(
|
26
|
+
require('jquery'),
|
27
|
+
require('./vendor/jquery.ui.widget')
|
28
|
+
);
|
23
29
|
} else {
|
24
30
|
// Browser globals:
|
25
31
|
factory(window.jQuery);
|
@@ -27,12 +33,49 @@
|
|
27
33
|
}(function ($) {
|
28
34
|
'use strict';
|
29
35
|
|
36
|
+
// Detect file input support, based on
|
37
|
+
// http://viljamis.com/blog/2012/file-upload-support-on-mobile/
|
38
|
+
$.support.fileInput = !(new RegExp(
|
39
|
+
// Handle devices which give false positives for the feature detection:
|
40
|
+
'(Android (1\\.[0156]|2\\.[01]))' +
|
41
|
+
'|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
|
42
|
+
'|(w(eb)?OSBrowser)|(webOS)' +
|
43
|
+
'|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
|
44
|
+
).test(window.navigator.userAgent) ||
|
45
|
+
// Feature detection for all other devices:
|
46
|
+
$('<input type="file">').prop('disabled'));
|
47
|
+
|
30
48
|
// The FileReader API is not actually used, but works as feature detection,
|
31
|
-
// as
|
32
|
-
// but not non-multipart XHR file uploads
|
33
|
-
|
49
|
+
// as some Safari versions (5?) support XHR file uploads via the FormData API,
|
50
|
+
// but not non-multipart XHR file uploads.
|
51
|
+
// window.XMLHttpRequestUpload is not available on IE10, so we check for
|
52
|
+
// window.ProgressEvent instead to detect XHR2 file upload capability:
|
53
|
+
$.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
|
34
54
|
$.support.xhrFormDataFileUpload = !!window.FormData;
|
35
55
|
|
56
|
+
// Detect support for Blob slicing (required for chunked uploads):
|
57
|
+
$.support.blobSlice = window.Blob && (Blob.prototype.slice ||
|
58
|
+
Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
|
59
|
+
|
60
|
+
// Helper function to create drag handlers for dragover/dragenter/dragleave:
|
61
|
+
function getDragHandler(type) {
|
62
|
+
var isDragOver = type === 'dragover';
|
63
|
+
return function (e) {
|
64
|
+
e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
|
65
|
+
var dataTransfer = e.dataTransfer;
|
66
|
+
if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
|
67
|
+
this._trigger(
|
68
|
+
type,
|
69
|
+
$.Event(type, {delegatedEvent: e})
|
70
|
+
) !== false) {
|
71
|
+
e.preventDefault();
|
72
|
+
if (isDragOver) {
|
73
|
+
dataTransfer.dropEffect = 'copy';
|
74
|
+
}
|
75
|
+
}
|
76
|
+
};
|
77
|
+
}
|
78
|
+
|
36
79
|
// The fileupload widget listens for change events on file input fields defined
|
37
80
|
// via fileInput setting and paste or drop events of the given dropZone.
|
38
81
|
// In addition to the default jQuery Widget methods, the fileupload widget
|
@@ -47,9 +90,9 @@
|
|
47
90
|
// The drop target element(s), by the default the complete document.
|
48
91
|
// Set to null to disable drag & drop support:
|
49
92
|
dropZone: $(document),
|
50
|
-
// The paste target element(s), by the default
|
51
|
-
// Set to
|
52
|
-
pasteZone:
|
93
|
+
// The paste target element(s), by the default undefined.
|
94
|
+
// Set to a DOM node or jQuery object to enable file pasting:
|
95
|
+
pasteZone: undefined,
|
53
96
|
// The file input field(s), that are listened to for change events.
|
54
97
|
// If undefined, it is set to the file input fields inside
|
55
98
|
// of the widget element on plugin initialization.
|
@@ -72,6 +115,14 @@
|
|
72
115
|
// To limit the number of files uploaded with one XHR request,
|
73
116
|
// set the following option to an integer greater than 0:
|
74
117
|
limitMultiFileUploads: undefined,
|
118
|
+
// The following option limits the number of files uploaded with one
|
119
|
+
// XHR request to keep the request size under or equal to the defined
|
120
|
+
// limit in bytes:
|
121
|
+
limitMultiFileUploadSize: undefined,
|
122
|
+
// Multipart file uploads add a number of bytes to each uploaded file,
|
123
|
+
// therefore the following option adds an overhead for each file used
|
124
|
+
// in the limitMultiFileUploadSize configuration:
|
125
|
+
limitMultiFileUploadSizeOverhead: 512,
|
75
126
|
// Set the following option to true to issue all file upload requests
|
76
127
|
// in a sequential order:
|
77
128
|
sequentialUploads: false,
|
@@ -112,6 +163,25 @@
|
|
112
163
|
progressInterval: 100,
|
113
164
|
// Interval in milliseconds to calculate progress bitrate:
|
114
165
|
bitrateInterval: 500,
|
166
|
+
// By default, uploads are started automatically when adding files:
|
167
|
+
autoUpload: true,
|
168
|
+
|
169
|
+
// Error and info messages:
|
170
|
+
messages: {
|
171
|
+
uploadedBytes: 'Uploaded bytes exceed file size'
|
172
|
+
},
|
173
|
+
|
174
|
+
// Translation function, gets the message key to be translated
|
175
|
+
// and an object with context specific data as arguments:
|
176
|
+
i18n: function (message, context) {
|
177
|
+
message = this.messages[message] || message.toString();
|
178
|
+
if (context) {
|
179
|
+
$.each(context, function (key, value) {
|
180
|
+
message = message.replace('{' + key + '}', value);
|
181
|
+
});
|
182
|
+
}
|
183
|
+
return message;
|
184
|
+
},
|
115
185
|
|
116
186
|
// Additional form data to be sent along with the file uploads can be set
|
117
187
|
// using this option, which accepts an array of objects with name and
|
@@ -125,57 +195,95 @@
|
|
125
195
|
// The add callback is invoked as soon as files are added to the fileupload
|
126
196
|
// widget (via file input selection, drag & drop, paste or add API call).
|
127
197
|
// If the singleFileUploads option is enabled, this callback will be
|
128
|
-
// called once for each file in the selection for XHR file
|
198
|
+
// called once for each file in the selection for XHR file uploads, else
|
129
199
|
// once for each file selection.
|
200
|
+
//
|
130
201
|
// The upload starts when the submit method is invoked on the data parameter.
|
131
202
|
// The data object contains a files property holding the added files
|
132
|
-
// and allows to override plugin options as well as define ajax settings.
|
203
|
+
// and allows you to override plugin options as well as define ajax settings.
|
204
|
+
//
|
133
205
|
// Listeners for this callback can also be bound the following way:
|
134
206
|
// .bind('fileuploadadd', func);
|
207
|
+
//
|
135
208
|
// data.submit() returns a Promise object and allows to attach additional
|
136
209
|
// handlers using jQuery's Deferred callbacks:
|
137
210
|
// data.submit().done(func).fail(func).always(func);
|
138
211
|
add: function (e, data) {
|
139
|
-
|
212
|
+
if (e.isDefaultPrevented()) {
|
213
|
+
return false;
|
214
|
+
}
|
215
|
+
if (data.autoUpload || (data.autoUpload !== false &&
|
216
|
+
$(this).fileupload('option', 'autoUpload'))) {
|
217
|
+
data.process().done(function () {
|
218
|
+
data.submit();
|
219
|
+
});
|
220
|
+
}
|
140
221
|
},
|
141
222
|
|
142
223
|
// Other callbacks:
|
224
|
+
|
143
225
|
// Callback for the submit event of each file upload:
|
144
226
|
// submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
|
227
|
+
|
145
228
|
// Callback for the start of each file upload request:
|
146
229
|
// send: function (e, data) {}, // .bind('fileuploadsend', func);
|
230
|
+
|
147
231
|
// Callback for successful uploads:
|
148
232
|
// done: function (e, data) {}, // .bind('fileuploaddone', func);
|
233
|
+
|
149
234
|
// Callback for failed (abort or error) uploads:
|
150
235
|
// fail: function (e, data) {}, // .bind('fileuploadfail', func);
|
236
|
+
|
151
237
|
// Callback for completed (success, abort or error) requests:
|
152
238
|
// always: function (e, data) {}, // .bind('fileuploadalways', func);
|
239
|
+
|
153
240
|
// Callback for upload progress events:
|
154
241
|
// progress: function (e, data) {}, // .bind('fileuploadprogress', func);
|
242
|
+
|
155
243
|
// Callback for global upload progress events:
|
156
244
|
// progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
|
245
|
+
|
157
246
|
// Callback for uploads start, equivalent to the global ajaxStart event:
|
158
247
|
// start: function (e) {}, // .bind('fileuploadstart', func);
|
248
|
+
|
159
249
|
// Callback for uploads stop, equivalent to the global ajaxStop event:
|
160
250
|
// stop: function (e) {}, // .bind('fileuploadstop', func);
|
251
|
+
|
161
252
|
// Callback for change events of the fileInput(s):
|
162
253
|
// change: function (e, data) {}, // .bind('fileuploadchange', func);
|
254
|
+
|
163
255
|
// Callback for paste events to the pasteZone(s):
|
164
256
|
// paste: function (e, data) {}, // .bind('fileuploadpaste', func);
|
257
|
+
|
165
258
|
// Callback for drop events of the dropZone(s):
|
166
259
|
// drop: function (e, data) {}, // .bind('fileuploaddrop', func);
|
260
|
+
|
167
261
|
// Callback for dragover events of the dropZone(s):
|
168
262
|
// dragover: function (e) {}, // .bind('fileuploaddragover', func);
|
169
263
|
|
264
|
+
// Callback for the start of each chunk upload request:
|
265
|
+
// chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
|
266
|
+
|
267
|
+
// Callback for successful chunk uploads:
|
268
|
+
// chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
|
269
|
+
|
270
|
+
// Callback for failed (abort or error) chunk uploads:
|
271
|
+
// chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
|
272
|
+
|
273
|
+
// Callback for completed (success, abort or error) chunk upload requests:
|
274
|
+
// chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
|
275
|
+
|
170
276
|
// The plugin options are used as settings object for the ajax calls.
|
171
277
|
// The following are jQuery ajax settings required for the file uploads:
|
172
278
|
processData: false,
|
173
279
|
contentType: false,
|
174
|
-
cache: false
|
280
|
+
cache: false,
|
281
|
+
timeout: 0
|
175
282
|
},
|
176
283
|
|
177
|
-
// A list of options that require
|
178
|
-
|
284
|
+
// A list of options that require reinitializing event listeners and/or
|
285
|
+
// special initialization code:
|
286
|
+
_specialOptions: [
|
179
287
|
'fileInput',
|
180
288
|
'dropZone',
|
181
289
|
'pasteZone',
|
@@ -183,8 +291,13 @@
|
|
183
291
|
'forceIframeTransport'
|
184
292
|
],
|
185
293
|
|
294
|
+
_blobSlice: $.support.blobSlice && function () {
|
295
|
+
var slice = this.slice || this.webkitSlice || this.mozSlice;
|
296
|
+
return slice.apply(this, arguments);
|
297
|
+
},
|
298
|
+
|
186
299
|
_BitrateTimer: function () {
|
187
|
-
this.timestamp =
|
300
|
+
this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
|
188
301
|
this.loaded = 0;
|
189
302
|
this.bitrate = 0;
|
190
303
|
this.getBitrate = function (now, loaded, interval) {
|
@@ -206,13 +319,13 @@
|
|
206
319
|
|
207
320
|
_getFormData: function (options) {
|
208
321
|
var formData;
|
209
|
-
if (
|
322
|
+
if ($.type(options.formData) === 'function') {
|
210
323
|
return options.formData(options.form);
|
211
324
|
}
|
212
325
|
if ($.isArray(options.formData)) {
|
213
326
|
return options.formData;
|
214
327
|
}
|
215
|
-
if (options.formData) {
|
328
|
+
if ($.type(options.formData) === 'object') {
|
216
329
|
formData = [];
|
217
330
|
$.each(options.formData, function (name, value) {
|
218
331
|
formData.push({name: name, value: value});
|
@@ -230,10 +343,35 @@
|
|
230
343
|
return total;
|
231
344
|
},
|
232
345
|
|
346
|
+
_initProgressObject: function (obj) {
|
347
|
+
var progress = {
|
348
|
+
loaded: 0,
|
349
|
+
total: 0,
|
350
|
+
bitrate: 0
|
351
|
+
};
|
352
|
+
if (obj._progress) {
|
353
|
+
$.extend(obj._progress, progress);
|
354
|
+
} else {
|
355
|
+
obj._progress = progress;
|
356
|
+
}
|
357
|
+
},
|
358
|
+
|
359
|
+
_initResponseObject: function (obj) {
|
360
|
+
var prop;
|
361
|
+
if (obj._response) {
|
362
|
+
for (prop in obj._response) {
|
363
|
+
if (obj._response.hasOwnProperty(prop)) {
|
364
|
+
delete obj._response[prop];
|
365
|
+
}
|
366
|
+
}
|
367
|
+
} else {
|
368
|
+
obj._response = {};
|
369
|
+
}
|
370
|
+
},
|
371
|
+
|
233
372
|
_onProgress: function (e, data) {
|
234
373
|
if (e.lengthComputable) {
|
235
|
-
var now =
|
236
|
-
total,
|
374
|
+
var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
|
237
375
|
loaded;
|
238
376
|
if (data._time && data.progressInterval &&
|
239
377
|
(now - data._time < data.progressInterval) &&
|
@@ -241,16 +379,19 @@
|
|
241
379
|
return;
|
242
380
|
}
|
243
381
|
data._time = now;
|
244
|
-
|
245
|
-
|
246
|
-
e.loaded / e.total * (data.chunkSize || total),
|
247
|
-
10
|
382
|
+
loaded = Math.floor(
|
383
|
+
e.loaded / e.total * (data.chunkSize || data._progress.total)
|
248
384
|
) + (data.uploadedBytes || 0);
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
385
|
+
// Add the difference from the previously loaded state
|
386
|
+
// to the global loaded counter:
|
387
|
+
this._progress.loaded += (loaded - data._progress.loaded);
|
388
|
+
this._progress.bitrate = this._bitrateTimer.getBitrate(
|
389
|
+
now,
|
390
|
+
this._progress.loaded,
|
391
|
+
data.bitrateInterval
|
392
|
+
);
|
393
|
+
data._progress.loaded = data.loaded = loaded;
|
394
|
+
data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
|
254
395
|
now,
|
255
396
|
loaded,
|
256
397
|
data.bitrateInterval
|
@@ -258,19 +399,18 @@
|
|
258
399
|
// Trigger a custom progress event with a total data property set
|
259
400
|
// to the file size(s) of the current upload and a loaded data
|
260
401
|
// property calculated accordingly:
|
261
|
-
this._trigger(
|
402
|
+
this._trigger(
|
403
|
+
'progress',
|
404
|
+
$.Event('progress', {delegatedEvent: e}),
|
405
|
+
data
|
406
|
+
);
|
262
407
|
// Trigger a global progress event for all current file uploads,
|
263
408
|
// including ajax calls queued for sequential file uploads:
|
264
|
-
this._trigger(
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
now,
|
270
|
-
this._loaded,
|
271
|
-
data.bitrateInterval
|
272
|
-
)
|
273
|
-
});
|
409
|
+
this._trigger(
|
410
|
+
'progressall',
|
411
|
+
$.Event('progressall', {delegatedEvent: e}),
|
412
|
+
this._progress
|
413
|
+
);
|
274
414
|
}
|
275
415
|
},
|
276
416
|
|
@@ -294,20 +434,29 @@
|
|
294
434
|
}
|
295
435
|
},
|
296
436
|
|
437
|
+
_isInstanceOf: function (type, obj) {
|
438
|
+
// Cross-frame instanceof check
|
439
|
+
return Object.prototype.toString.call(obj) === '[object ' + type + ']';
|
440
|
+
},
|
441
|
+
|
297
442
|
_initXHRData: function (options) {
|
298
|
-
var
|
443
|
+
var that = this,
|
444
|
+
formData,
|
299
445
|
file = options.files[0],
|
300
446
|
// Ignore non-multipart setting if not supported:
|
301
447
|
multipart = options.multipart || !$.support.xhrFileUpload,
|
302
|
-
paramName = options.paramName
|
303
|
-
|
448
|
+
paramName = $.type(options.paramName) === 'array' ?
|
449
|
+
options.paramName[0] : options.paramName;
|
450
|
+
options.headers = $.extend({}, options.headers);
|
304
451
|
if (options.contentRange) {
|
305
452
|
options.headers['Content-Range'] = options.contentRange;
|
306
453
|
}
|
307
|
-
if (!multipart) {
|
454
|
+
if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
|
308
455
|
options.headers['Content-Disposition'] = 'attachment; filename="' +
|
309
456
|
encodeURI(file.name) + '"';
|
310
|
-
|
457
|
+
}
|
458
|
+
if (!multipart) {
|
459
|
+
options.contentType = file.type || 'application/octet-stream';
|
311
460
|
options.data = options.blob || file;
|
312
461
|
} else if ($.support.xhrFormDataFileUpload) {
|
313
462
|
if (options.postMessage) {
|
@@ -324,13 +473,14 @@
|
|
324
473
|
} else {
|
325
474
|
$.each(options.files, function (index, file) {
|
326
475
|
formData.push({
|
327
|
-
name: options.paramName
|
476
|
+
name: ($.type(options.paramName) === 'array' &&
|
477
|
+
options.paramName[index]) || paramName,
|
328
478
|
value: file
|
329
479
|
});
|
330
480
|
});
|
331
481
|
}
|
332
482
|
} else {
|
333
|
-
if (options.formData
|
483
|
+
if (that._isInstanceOf('FormData', options.formData)) {
|
334
484
|
formData = options.formData;
|
335
485
|
} else {
|
336
486
|
formData = new FormData();
|
@@ -339,21 +489,18 @@
|
|
339
489
|
});
|
340
490
|
}
|
341
491
|
if (options.blob) {
|
342
|
-
options.headers['Content-Disposition'] = 'attachment; filename="' +
|
343
|
-
encodeURI(file.name) + '"';
|
344
492
|
formData.append(paramName, options.blob, file.name);
|
345
493
|
} else {
|
346
494
|
$.each(options.files, function (index, file) {
|
347
|
-
// Files are also Blob instances, but some browsers
|
348
|
-
// (Firefox 3.6) support the File API but not Blobs.
|
349
495
|
// This check allows the tests to run with
|
350
496
|
// dummy objects:
|
351
|
-
if ((
|
352
|
-
(
|
497
|
+
if (that._isInstanceOf('File', file) ||
|
498
|
+
that._isInstanceOf('Blob', file)) {
|
353
499
|
formData.append(
|
354
|
-
options.paramName
|
500
|
+
($.type(options.paramName) === 'array' &&
|
501
|
+
options.paramName[index]) || paramName,
|
355
502
|
file,
|
356
|
-
file.name
|
503
|
+
file.uploadName || file.name
|
357
504
|
);
|
358
505
|
}
|
359
506
|
});
|
@@ -366,13 +513,13 @@
|
|
366
513
|
},
|
367
514
|
|
368
515
|
_initIframeSettings: function (options) {
|
516
|
+
var targetHost = $('<a></a>').prop('href', options.url).prop('host');
|
369
517
|
// Setting the dataType to iframe enables the iframe transport:
|
370
518
|
options.dataType = 'iframe ' + (options.dataType || '');
|
371
519
|
// The iframe transport accepts a serialized array as form data:
|
372
520
|
options.formData = this._getFormData(options);
|
373
521
|
// Add redirect url to form data on cross-domain uploads:
|
374
|
-
if (options.redirect &&
|
375
|
-
.prop('host') !== location.host) {
|
522
|
+
if (options.redirect && targetHost && targetHost !== location.host) {
|
376
523
|
options.formData.push({
|
377
524
|
name: options.redirectParamName || 'redirect',
|
378
525
|
value: options.redirect
|
@@ -394,7 +541,7 @@
|
|
394
541
|
options.dataType = 'postmessage ' + (options.dataType || '');
|
395
542
|
}
|
396
543
|
} else {
|
397
|
-
this._initIframeSettings(options
|
544
|
+
this._initIframeSettings(options);
|
398
545
|
}
|
399
546
|
},
|
400
547
|
|
@@ -437,8 +584,10 @@
|
|
437
584
|
options.url = options.form.prop('action') || location.href;
|
438
585
|
}
|
439
586
|
// The HTTP request method must be "POST" or "PUT":
|
440
|
-
options.type = (options.type ||
|
441
|
-
.
|
587
|
+
options.type = (options.type ||
|
588
|
+
($.type(options.form.prop('method')) === 'string' &&
|
589
|
+
options.form.prop('method')) || ''
|
590
|
+
).toUpperCase();
|
442
591
|
if (options.type !== 'POST' && options.type !== 'PUT' &&
|
443
592
|
options.type !== 'PATCH') {
|
444
593
|
options.type = 'POST';
|
@@ -455,6 +604,21 @@
|
|
455
604
|
return options;
|
456
605
|
},
|
457
606
|
|
607
|
+
// jQuery 1.6 doesn't provide .state(),
|
608
|
+
// while jQuery 1.8+ removed .isRejected() and .isResolved():
|
609
|
+
_getDeferredState: function (deferred) {
|
610
|
+
if (deferred.state) {
|
611
|
+
return deferred.state();
|
612
|
+
}
|
613
|
+
if (deferred.isResolved()) {
|
614
|
+
return 'resolved';
|
615
|
+
}
|
616
|
+
if (deferred.isRejected()) {
|
617
|
+
return 'rejected';
|
618
|
+
}
|
619
|
+
return 'pending';
|
620
|
+
},
|
621
|
+
|
458
622
|
// Maps jqXHR callbacks to the equivalent
|
459
623
|
// methods of the given Promise object:
|
460
624
|
_enhancePromise: function (promise) {
|
@@ -479,6 +643,66 @@
|
|
479
643
|
return this._enhancePromise(promise);
|
480
644
|
},
|
481
645
|
|
646
|
+
// Adds convenience methods to the data callback argument:
|
647
|
+
_addConvenienceMethods: function (e, data) {
|
648
|
+
var that = this,
|
649
|
+
getPromise = function (args) {
|
650
|
+
return $.Deferred().resolveWith(that, args).promise();
|
651
|
+
};
|
652
|
+
data.process = function (resolveFunc, rejectFunc) {
|
653
|
+
if (resolveFunc || rejectFunc) {
|
654
|
+
data._processQueue = this._processQueue =
|
655
|
+
(this._processQueue || getPromise([this])).pipe(
|
656
|
+
function () {
|
657
|
+
if (data.errorThrown) {
|
658
|
+
return $.Deferred()
|
659
|
+
.rejectWith(that, [data]).promise();
|
660
|
+
}
|
661
|
+
return getPromise(arguments);
|
662
|
+
}
|
663
|
+
).pipe(resolveFunc, rejectFunc);
|
664
|
+
}
|
665
|
+
return this._processQueue || getPromise([this]);
|
666
|
+
};
|
667
|
+
data.submit = function () {
|
668
|
+
if (this.state() !== 'pending') {
|
669
|
+
data.jqXHR = this.jqXHR =
|
670
|
+
(that._trigger(
|
671
|
+
'submit',
|
672
|
+
$.Event('submit', {delegatedEvent: e}),
|
673
|
+
this
|
674
|
+
) !== false) && that._onSend(e, this);
|
675
|
+
}
|
676
|
+
return this.jqXHR || that._getXHRPromise();
|
677
|
+
};
|
678
|
+
data.abort = function () {
|
679
|
+
if (this.jqXHR) {
|
680
|
+
return this.jqXHR.abort();
|
681
|
+
}
|
682
|
+
this.errorThrown = 'abort';
|
683
|
+
that._trigger('fail', null, this);
|
684
|
+
return that._getXHRPromise(false);
|
685
|
+
};
|
686
|
+
data.state = function () {
|
687
|
+
if (this.jqXHR) {
|
688
|
+
return that._getDeferredState(this.jqXHR);
|
689
|
+
}
|
690
|
+
if (this._processQueue) {
|
691
|
+
return that._getDeferredState(this._processQueue);
|
692
|
+
}
|
693
|
+
};
|
694
|
+
data.processing = function () {
|
695
|
+
return !this.jqXHR && this._processQueue && that
|
696
|
+
._getDeferredState(this._processQueue) === 'pending';
|
697
|
+
};
|
698
|
+
data.progress = function () {
|
699
|
+
return this._progress;
|
700
|
+
};
|
701
|
+
data.response = function () {
|
702
|
+
return this._response;
|
703
|
+
};
|
704
|
+
},
|
705
|
+
|
482
706
|
// Parses the Range header from the server response
|
483
707
|
// and returns the uploaded bytes:
|
484
708
|
_getUploadedBytes: function (jqXHR) {
|
@@ -495,12 +719,13 @@
|
|
495
719
|
// should be uploaded in chunks, but does not invoke any
|
496
720
|
// upload requests:
|
497
721
|
_chunkedUpload: function (options, testOnly) {
|
722
|
+
options.uploadedBytes = options.uploadedBytes || 0;
|
498
723
|
var that = this,
|
499
724
|
file = options.files[0],
|
500
725
|
fs = file.size,
|
501
|
-
ub = options.uploadedBytes
|
726
|
+
ub = options.uploadedBytes,
|
502
727
|
mcs = options.maxChunkSize || fs,
|
503
|
-
slice =
|
728
|
+
slice = this._blobSlice,
|
504
729
|
dfd = $.Deferred(),
|
505
730
|
promise = dfd.promise(),
|
506
731
|
jqXHR,
|
@@ -513,7 +738,7 @@
|
|
513
738
|
return true;
|
514
739
|
}
|
515
740
|
if (ub >= fs) {
|
516
|
-
file.error = '
|
741
|
+
file.error = options.i18n('uploadedBytes');
|
517
742
|
return this._getXHRPromise(
|
518
743
|
false,
|
519
744
|
options.context,
|
@@ -521,9 +746,10 @@
|
|
521
746
|
);
|
522
747
|
}
|
523
748
|
// The chunk upload method:
|
524
|
-
upload = function (
|
749
|
+
upload = function () {
|
525
750
|
// Clone the options object for each chunk upload:
|
526
|
-
var o = $.extend({}, options)
|
751
|
+
var o = $.extend({}, options),
|
752
|
+
currentLoaded = o._progress.loaded;
|
527
753
|
o.blob = slice.call(
|
528
754
|
file,
|
529
755
|
ub,
|
@@ -540,13 +766,15 @@
|
|
540
766
|
that._initXHRData(o);
|
541
767
|
// Add progress listeners for this chunk upload:
|
542
768
|
that._initProgressListener(o);
|
543
|
-
jqXHR = (
|
769
|
+
jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
|
770
|
+
that._getXHRPromise(false, o.context))
|
544
771
|
.done(function (result, textStatus, jqXHR) {
|
545
772
|
ub = that._getUploadedBytes(jqXHR) ||
|
546
773
|
(ub + o.chunkSize);
|
547
|
-
// Create a progress event if
|
548
|
-
//
|
549
|
-
|
774
|
+
// Create a progress event if no final progress event
|
775
|
+
// with loaded equaling total has been triggered
|
776
|
+
// for this chunk:
|
777
|
+
if (currentLoaded + o.chunkSize - o._progress.loaded) {
|
550
778
|
that._onProgress($.Event('progress', {
|
551
779
|
lengthComputable: true,
|
552
780
|
loaded: ub - o.uploadedBytes,
|
@@ -554,6 +782,11 @@
|
|
554
782
|
}), o);
|
555
783
|
}
|
556
784
|
options.uploadedBytes = o.uploadedBytes = ub;
|
785
|
+
o.result = result;
|
786
|
+
o.textStatus = textStatus;
|
787
|
+
o.jqXHR = jqXHR;
|
788
|
+
that._trigger('chunkdone', null, o);
|
789
|
+
that._trigger('chunkalways', null, o);
|
557
790
|
if (ub < fs) {
|
558
791
|
// File upload not yet complete,
|
559
792
|
// continue with the next chunk:
|
@@ -566,6 +799,11 @@
|
|
566
799
|
}
|
567
800
|
})
|
568
801
|
.fail(function (jqXHR, textStatus, errorThrown) {
|
802
|
+
o.jqXHR = jqXHR;
|
803
|
+
o.textStatus = textStatus;
|
804
|
+
o.errorThrown = errorThrown;
|
805
|
+
that._trigger('chunkfail', null, o);
|
806
|
+
that._trigger('chunkalways', null, o);
|
569
807
|
dfd.rejectWith(
|
570
808
|
o.context,
|
571
809
|
[jqXHR, textStatus, errorThrown]
|
@@ -588,63 +826,66 @@
|
|
588
826
|
this._trigger('start');
|
589
827
|
// Set timer for global bitrate progress calculation:
|
590
828
|
this._bitrateTimer = new this._BitrateTimer();
|
829
|
+
// Reset the global progress values:
|
830
|
+
this._progress.loaded = this._progress.total = 0;
|
831
|
+
this._progress.bitrate = 0;
|
591
832
|
}
|
833
|
+
// Make sure the container objects for the .response() and
|
834
|
+
// .progress() methods on the data object are available
|
835
|
+
// and reset to their initial state:
|
836
|
+
this._initResponseObject(data);
|
837
|
+
this._initProgressObject(data);
|
838
|
+
data._progress.loaded = data.loaded = data.uploadedBytes || 0;
|
839
|
+
data._progress.total = data.total = this._getTotal(data.files) || 1;
|
840
|
+
data._progress.bitrate = data.bitrate = 0;
|
592
841
|
this._active += 1;
|
593
842
|
// Initialize the global progress values:
|
594
|
-
this.
|
595
|
-
this.
|
843
|
+
this._progress.loaded += data.loaded;
|
844
|
+
this._progress.total += data.total;
|
596
845
|
},
|
597
846
|
|
598
847
|
_onDone: function (result, textStatus, jqXHR, options) {
|
599
|
-
|
600
|
-
|
848
|
+
var total = options._progress.total,
|
849
|
+
response = options._response;
|
850
|
+
if (options._progress.loaded < total) {
|
851
|
+
// Create a progress event if no final progress event
|
852
|
+
// with loaded equaling total has been triggered:
|
601
853
|
this._onProgress($.Event('progress', {
|
602
854
|
lengthComputable: true,
|
603
|
-
loaded:
|
604
|
-
total:
|
855
|
+
loaded: total,
|
856
|
+
total: total
|
605
857
|
}), options);
|
606
858
|
}
|
607
|
-
options.result = result;
|
608
|
-
options.textStatus = textStatus;
|
609
|
-
options.jqXHR = jqXHR;
|
859
|
+
response.result = options.result = result;
|
860
|
+
response.textStatus = options.textStatus = textStatus;
|
861
|
+
response.jqXHR = options.jqXHR = jqXHR;
|
610
862
|
this._trigger('done', null, options);
|
611
863
|
},
|
612
864
|
|
613
865
|
_onFail: function (jqXHR, textStatus, errorThrown, options) {
|
614
|
-
|
615
|
-
options.textStatus = textStatus;
|
616
|
-
options.errorThrown = errorThrown;
|
617
|
-
this._trigger('fail', null, options);
|
866
|
+
var response = options._response;
|
618
867
|
if (options.recalculateProgress) {
|
619
868
|
// Remove the failed (error or abort) file upload from
|
620
869
|
// the global progress calculation:
|
621
|
-
this.
|
622
|
-
this.
|
870
|
+
this._progress.loaded -= options._progress.loaded;
|
871
|
+
this._progress.total -= options._progress.total;
|
623
872
|
}
|
873
|
+
response.jqXHR = options.jqXHR = jqXHR;
|
874
|
+
response.textStatus = options.textStatus = textStatus;
|
875
|
+
response.errorThrown = options.errorThrown = errorThrown;
|
876
|
+
this._trigger('fail', null, options);
|
624
877
|
},
|
625
878
|
|
626
879
|
_onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
|
627
|
-
|
628
|
-
options
|
629
|
-
if (jqXHRorError && jqXHRorError.always) {
|
630
|
-
options.jqXHR = jqXHRorError;
|
631
|
-
options.result = jqXHRorResult;
|
632
|
-
} else {
|
633
|
-
options.jqXHR = jqXHRorResult;
|
634
|
-
options.errorThrown = jqXHRorError;
|
635
|
-
}
|
880
|
+
// jqXHRorResult, textStatus and jqXHRorError are added to the
|
881
|
+
// options object via done and fail callbacks
|
636
882
|
this._trigger('always', null, options);
|
637
|
-
if (this._active === 0) {
|
638
|
-
// The stop callback is triggered when all uploads have
|
639
|
-
// been completed, equivalent to the global ajaxStop event:
|
640
|
-
this._trigger('stop');
|
641
|
-
// Reset the global progress values:
|
642
|
-
this._loaded = this._total = 0;
|
643
|
-
this._bitrateTimer = null;
|
644
|
-
}
|
645
883
|
},
|
646
884
|
|
647
885
|
_onSend: function (e, data) {
|
886
|
+
if (!data.submit) {
|
887
|
+
this._addConvenienceMethods(e, data);
|
888
|
+
}
|
648
889
|
var that = this,
|
649
890
|
jqXHR,
|
650
891
|
aborted,
|
@@ -656,7 +897,11 @@
|
|
656
897
|
// Set timer for bitrate progress calculation:
|
657
898
|
options._bitrateTimer = new that._BitrateTimer();
|
658
899
|
jqXHR = jqXHR || (
|
659
|
-
((aborted || that._trigger(
|
900
|
+
((aborted || that._trigger(
|
901
|
+
'send',
|
902
|
+
$.Event('send', {delegatedEvent: e}),
|
903
|
+
options
|
904
|
+
) === false) &&
|
660
905
|
that._getXHRPromise(false, options.context, aborted)) ||
|
661
906
|
that._chunkedUpload(options) || $.ajax(options)
|
662
907
|
).done(function (result, textStatus, jqXHR) {
|
@@ -664,32 +909,32 @@
|
|
664
909
|
}).fail(function (jqXHR, textStatus, errorThrown) {
|
665
910
|
that._onFail(jqXHR, textStatus, errorThrown, options);
|
666
911
|
}).always(function (jqXHRorResult, textStatus, jqXHRorError) {
|
667
|
-
that._sending -= 1;
|
668
912
|
that._onAlways(
|
669
913
|
jqXHRorResult,
|
670
914
|
textStatus,
|
671
915
|
jqXHRorError,
|
672
916
|
options
|
673
917
|
);
|
918
|
+
that._sending -= 1;
|
919
|
+
that._active -= 1;
|
674
920
|
if (options.limitConcurrentUploads &&
|
675
921
|
options.limitConcurrentUploads > that._sending) {
|
676
922
|
// Start the next queued upload,
|
677
923
|
// that has not been aborted:
|
678
|
-
var nextSlot = that._slots.shift()
|
679
|
-
isPending;
|
924
|
+
var nextSlot = that._slots.shift();
|
680
925
|
while (nextSlot) {
|
681
|
-
|
682
|
-
// while jQuery 1.8+ removed .isRejected():
|
683
|
-
isPending = nextSlot.state ?
|
684
|
-
nextSlot.state() === 'pending' :
|
685
|
-
!nextSlot.isRejected();
|
686
|
-
if (isPending) {
|
926
|
+
if (that._getDeferredState(nextSlot) === 'pending') {
|
687
927
|
nextSlot.resolve();
|
688
928
|
break;
|
689
929
|
}
|
690
930
|
nextSlot = that._slots.shift();
|
691
931
|
}
|
692
932
|
}
|
933
|
+
if (that._active === 0) {
|
934
|
+
// The stop callback is triggered when all uploads have
|
935
|
+
// been completed, equivalent to the global ajaxStop event:
|
936
|
+
that._trigger('stop');
|
937
|
+
}
|
693
938
|
});
|
694
939
|
return jqXHR;
|
695
940
|
};
|
@@ -702,7 +947,8 @@
|
|
702
947
|
this._slots.push(slot);
|
703
948
|
pipe = slot.pipe(send);
|
704
949
|
} else {
|
705
|
-
|
950
|
+
this._sequence = this._sequence.pipe(send, send);
|
951
|
+
pipe = this._sequence;
|
706
952
|
}
|
707
953
|
// Return the piped Promise object, enhanced with an abort method,
|
708
954
|
// which is delegated to the jqXHR object of the current upload,
|
@@ -726,49 +972,83 @@
|
|
726
972
|
var that = this,
|
727
973
|
result = true,
|
728
974
|
options = $.extend({}, this.options, data),
|
975
|
+
files = data.files,
|
976
|
+
filesLength = files.length,
|
729
977
|
limit = options.limitMultiFileUploads,
|
978
|
+
limitSize = options.limitMultiFileUploadSize,
|
979
|
+
overhead = options.limitMultiFileUploadSizeOverhead,
|
980
|
+
batchSize = 0,
|
730
981
|
paramName = this._getParamName(options),
|
731
982
|
paramNameSet,
|
732
983
|
paramNameSlice,
|
733
984
|
fileSet,
|
734
|
-
i
|
735
|
-
|
985
|
+
i,
|
986
|
+
j = 0;
|
987
|
+
if (!filesLength) {
|
988
|
+
return false;
|
989
|
+
}
|
990
|
+
if (limitSize && files[0].size === undefined) {
|
991
|
+
limitSize = undefined;
|
992
|
+
}
|
993
|
+
if (!(options.singleFileUploads || limit || limitSize) ||
|
736
994
|
!this._isXHRUpload(options)) {
|
737
|
-
fileSet = [
|
995
|
+
fileSet = [files];
|
738
996
|
paramNameSet = [paramName];
|
739
|
-
} else if (!options.singleFileUploads && limit) {
|
997
|
+
} else if (!(options.singleFileUploads || limitSize) && limit) {
|
740
998
|
fileSet = [];
|
741
999
|
paramNameSet = [];
|
742
|
-
for (i = 0; i <
|
743
|
-
fileSet.push(
|
1000
|
+
for (i = 0; i < filesLength; i += limit) {
|
1001
|
+
fileSet.push(files.slice(i, i + limit));
|
744
1002
|
paramNameSlice = paramName.slice(i, i + limit);
|
745
1003
|
if (!paramNameSlice.length) {
|
746
1004
|
paramNameSlice = paramName;
|
747
1005
|
}
|
748
1006
|
paramNameSet.push(paramNameSlice);
|
749
1007
|
}
|
1008
|
+
} else if (!options.singleFileUploads && limitSize) {
|
1009
|
+
fileSet = [];
|
1010
|
+
paramNameSet = [];
|
1011
|
+
for (i = 0; i < filesLength; i = i + 1) {
|
1012
|
+
batchSize += files[i].size + overhead;
|
1013
|
+
if (i + 1 === filesLength ||
|
1014
|
+
((batchSize + files[i + 1].size + overhead) > limitSize) ||
|
1015
|
+
(limit && i + 1 - j >= limit)) {
|
1016
|
+
fileSet.push(files.slice(j, i + 1));
|
1017
|
+
paramNameSlice = paramName.slice(j, i + 1);
|
1018
|
+
if (!paramNameSlice.length) {
|
1019
|
+
paramNameSlice = paramName;
|
1020
|
+
}
|
1021
|
+
paramNameSet.push(paramNameSlice);
|
1022
|
+
j = i + 1;
|
1023
|
+
batchSize = 0;
|
1024
|
+
}
|
1025
|
+
}
|
750
1026
|
} else {
|
751
1027
|
paramNameSet = paramName;
|
752
1028
|
}
|
753
|
-
data.originalFiles =
|
754
|
-
$.each(fileSet ||
|
1029
|
+
data.originalFiles = files;
|
1030
|
+
$.each(fileSet || files, function (index, element) {
|
755
1031
|
var newData = $.extend({}, data);
|
756
1032
|
newData.files = fileSet ? element : [element];
|
757
1033
|
newData.paramName = paramNameSet[index];
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
1034
|
+
that._initResponseObject(newData);
|
1035
|
+
that._initProgressObject(newData);
|
1036
|
+
that._addConvenienceMethods(e, newData);
|
1037
|
+
result = that._trigger(
|
1038
|
+
'add',
|
1039
|
+
$.Event('add', {delegatedEvent: e}),
|
1040
|
+
newData
|
1041
|
+
);
|
765
1042
|
return result;
|
766
1043
|
});
|
767
1044
|
return result;
|
768
1045
|
},
|
769
1046
|
|
770
|
-
_replaceFileInput: function (
|
771
|
-
var
|
1047
|
+
_replaceFileInput: function (data) {
|
1048
|
+
var input = data.fileInput,
|
1049
|
+
inputClone = input.clone(true);
|
1050
|
+
// Add a reference for the new cloned file input to the data argument:
|
1051
|
+
data.fileInputClone = inputClone;
|
772
1052
|
$('<form></form>').append(inputClone)[0].reset();
|
773
1053
|
// Detaching allows to insert the fileInput on another form
|
774
1054
|
// without loosing the file input value:
|
@@ -804,7 +1084,25 @@
|
|
804
1084
|
// to be returned together in one set:
|
805
1085
|
dfd.resolve([e]);
|
806
1086
|
},
|
807
|
-
|
1087
|
+
successHandler = function (entries) {
|
1088
|
+
that._handleFileTreeEntries(
|
1089
|
+
entries,
|
1090
|
+
path + entry.name + '/'
|
1091
|
+
).done(function (files) {
|
1092
|
+
dfd.resolve(files);
|
1093
|
+
}).fail(errorHandler);
|
1094
|
+
},
|
1095
|
+
readEntries = function () {
|
1096
|
+
dirReader.readEntries(function (results) {
|
1097
|
+
if (!results.length) {
|
1098
|
+
successHandler(entries);
|
1099
|
+
} else {
|
1100
|
+
entries = entries.concat(results);
|
1101
|
+
readEntries();
|
1102
|
+
}
|
1103
|
+
}, errorHandler);
|
1104
|
+
},
|
1105
|
+
dirReader, entries = [];
|
808
1106
|
path = path || '';
|
809
1107
|
if (entry.isFile) {
|
810
1108
|
if (entry._file) {
|
@@ -819,14 +1117,7 @@
|
|
819
1117
|
}
|
820
1118
|
} else if (entry.isDirectory) {
|
821
1119
|
dirReader = entry.createReader();
|
822
|
-
|
823
|
-
that._handleFileTreeEntries(
|
824
|
-
entries,
|
825
|
-
path + entry.name + '/'
|
826
|
-
).done(function (files) {
|
827
|
-
dfd.resolve(files);
|
828
|
-
}).fail(errorHandler);
|
829
|
-
}, errorHandler);
|
1120
|
+
readEntries();
|
830
1121
|
} else {
|
831
1122
|
// Return an empy list for file system items
|
832
1123
|
// other than files or directories:
|
@@ -928,84 +1219,99 @@
|
|
928
1219
|
this._getFileInputFiles(data.fileInput).always(function (files) {
|
929
1220
|
data.files = files;
|
930
1221
|
if (that.options.replaceFileInput) {
|
931
|
-
that._replaceFileInput(data
|
1222
|
+
that._replaceFileInput(data);
|
932
1223
|
}
|
933
|
-
if (that._trigger(
|
1224
|
+
if (that._trigger(
|
1225
|
+
'change',
|
1226
|
+
$.Event('change', {delegatedEvent: e}),
|
1227
|
+
data
|
1228
|
+
) !== false) {
|
934
1229
|
that._onAdd(e, data);
|
935
1230
|
}
|
936
1231
|
});
|
937
1232
|
},
|
938
1233
|
|
939
1234
|
_onPaste: function (e) {
|
940
|
-
var
|
941
|
-
|
1235
|
+
var items = e.originalEvent && e.originalEvent.clipboardData &&
|
1236
|
+
e.originalEvent.clipboardData.items,
|
942
1237
|
data = {files: []};
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
1238
|
+
if (items && items.length) {
|
1239
|
+
$.each(items, function (index, item) {
|
1240
|
+
var file = item.getAsFile && item.getAsFile();
|
1241
|
+
if (file) {
|
1242
|
+
data.files.push(file);
|
1243
|
+
}
|
1244
|
+
});
|
1245
|
+
if (this._trigger(
|
1246
|
+
'paste',
|
1247
|
+
$.Event('paste', {delegatedEvent: e}),
|
1248
|
+
data
|
1249
|
+
) !== false) {
|
1250
|
+
this._onAdd(e, data);
|
947
1251
|
}
|
948
|
-
});
|
949
|
-
if (this._trigger('paste', e, data) === false ||
|
950
|
-
this._onAdd(e, data) === false) {
|
951
|
-
return false;
|
952
1252
|
}
|
953
1253
|
},
|
954
1254
|
|
955
1255
|
_onDrop: function (e) {
|
1256
|
+
e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
|
956
1257
|
var that = this,
|
957
|
-
dataTransfer = e.dataTransfer
|
1258
|
+
dataTransfer = e.dataTransfer,
|
958
1259
|
data = {};
|
959
1260
|
if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
|
960
1261
|
e.preventDefault();
|
1262
|
+
this._getDroppedFiles(dataTransfer).always(function (files) {
|
1263
|
+
data.files = files;
|
1264
|
+
if (that._trigger(
|
1265
|
+
'drop',
|
1266
|
+
$.Event('drop', {delegatedEvent: e}),
|
1267
|
+
data
|
1268
|
+
) !== false) {
|
1269
|
+
that._onAdd(e, data);
|
1270
|
+
}
|
1271
|
+
});
|
961
1272
|
}
|
962
|
-
this._getDroppedFiles(dataTransfer).always(function (files) {
|
963
|
-
data.files = files;
|
964
|
-
if (that._trigger('drop', e, data) !== false) {
|
965
|
-
that._onAdd(e, data);
|
966
|
-
}
|
967
|
-
});
|
968
1273
|
},
|
969
1274
|
|
970
|
-
_onDragOver:
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) {
|
976
|
-
dataTransfer.dropEffect = 'copy';
|
977
|
-
e.preventDefault();
|
978
|
-
}
|
979
|
-
},
|
1275
|
+
_onDragOver: getDragHandler('dragover'),
|
1276
|
+
|
1277
|
+
_onDragEnter: getDragHandler('dragenter'),
|
1278
|
+
|
1279
|
+
_onDragLeave: getDragHandler('dragleave'),
|
980
1280
|
|
981
1281
|
_initEventHandlers: function () {
|
982
1282
|
if (this._isXHRUpload(this.options)) {
|
983
1283
|
this._on(this.options.dropZone, {
|
984
1284
|
dragover: this._onDragOver,
|
985
|
-
drop: this._onDrop
|
1285
|
+
drop: this._onDrop,
|
1286
|
+
// event.preventDefault() on dragenter is required for IE10+:
|
1287
|
+
dragenter: this._onDragEnter,
|
1288
|
+
// dragleave is not required, but added for completeness:
|
1289
|
+
dragleave: this._onDragLeave
|
986
1290
|
});
|
987
1291
|
this._on(this.options.pasteZone, {
|
988
1292
|
paste: this._onPaste
|
989
1293
|
});
|
990
1294
|
}
|
991
|
-
|
992
|
-
|
993
|
-
|
1295
|
+
if ($.support.fileInput) {
|
1296
|
+
this._on(this.options.fileInput, {
|
1297
|
+
change: this._onChange
|
1298
|
+
});
|
1299
|
+
}
|
994
1300
|
},
|
995
1301
|
|
996
1302
|
_destroyEventHandlers: function () {
|
997
|
-
this._off(this.options.dropZone, 'dragover drop');
|
1303
|
+
this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
|
998
1304
|
this._off(this.options.pasteZone, 'paste');
|
999
1305
|
this._off(this.options.fileInput, 'change');
|
1000
1306
|
},
|
1001
1307
|
|
1002
1308
|
_setOption: function (key, value) {
|
1003
|
-
var
|
1004
|
-
if (
|
1309
|
+
var reinit = $.inArray(key, this._specialOptions) !== -1;
|
1310
|
+
if (reinit) {
|
1005
1311
|
this._destroyEventHandlers();
|
1006
1312
|
}
|
1007
1313
|
this._super(key, value);
|
1008
|
-
if (
|
1314
|
+
if (reinit) {
|
1009
1315
|
this._initSpecialOptions();
|
1010
1316
|
this._initEventHandlers();
|
1011
1317
|
}
|
@@ -1027,19 +1333,65 @@
|
|
1027
1333
|
}
|
1028
1334
|
},
|
1029
1335
|
|
1030
|
-
|
1031
|
-
var
|
1336
|
+
_getRegExp: function (str) {
|
1337
|
+
var parts = str.split('/'),
|
1338
|
+
modifiers = parts.pop();
|
1339
|
+
parts.shift();
|
1340
|
+
return new RegExp(parts.join('/'), modifiers);
|
1341
|
+
},
|
1342
|
+
|
1343
|
+
_isRegExpOption: function (key, value) {
|
1344
|
+
return key !== 'url' && $.type(value) === 'string' &&
|
1345
|
+
/^\/.*\/[igm]{0,3}$/.test(value);
|
1346
|
+
},
|
1347
|
+
|
1348
|
+
_initDataAttributes: function () {
|
1349
|
+
var that = this,
|
1350
|
+
options = this.options,
|
1351
|
+
data = this.element.data();
|
1032
1352
|
// Initialize options set via HTML5 data-attributes:
|
1033
|
-
$.
|
1353
|
+
$.each(
|
1354
|
+
this.element[0].attributes,
|
1355
|
+
function (index, attr) {
|
1356
|
+
var key = attr.name.toLowerCase(),
|
1357
|
+
value;
|
1358
|
+
if (/^data-/.test(key)) {
|
1359
|
+
// Convert hyphen-ated key to camelCase:
|
1360
|
+
key = key.slice(5).replace(/-[a-z]/g, function (str) {
|
1361
|
+
return str.charAt(1).toUpperCase();
|
1362
|
+
});
|
1363
|
+
value = data[key];
|
1364
|
+
if (that._isRegExpOption(key, value)) {
|
1365
|
+
value = that._getRegExp(value);
|
1366
|
+
}
|
1367
|
+
options[key] = value;
|
1368
|
+
}
|
1369
|
+
}
|
1370
|
+
);
|
1371
|
+
},
|
1372
|
+
|
1373
|
+
_create: function () {
|
1374
|
+
this._initDataAttributes();
|
1034
1375
|
this._initSpecialOptions();
|
1035
1376
|
this._slots = [];
|
1036
1377
|
this._sequence = this._getXHRPromise(true);
|
1037
|
-
this._sending = this._active =
|
1378
|
+
this._sending = this._active = 0;
|
1379
|
+
this._initProgressObject(this);
|
1038
1380
|
this._initEventHandlers();
|
1039
1381
|
},
|
1040
1382
|
|
1041
|
-
|
1042
|
-
|
1383
|
+
// This method is exposed to the widget API and allows to query
|
1384
|
+
// the number of active uploads:
|
1385
|
+
active: function () {
|
1386
|
+
return this._active;
|
1387
|
+
},
|
1388
|
+
|
1389
|
+
// This method is exposed to the widget API and allows to query
|
1390
|
+
// the widget upload progress.
|
1391
|
+
// It returns an object with loaded, total and bitrate properties
|
1392
|
+
// for the running uploads:
|
1393
|
+
progress: function () {
|
1394
|
+
return this._progress;
|
1043
1395
|
},
|
1044
1396
|
|
1045
1397
|
// This method is exposed to the widget API and allows adding files
|
@@ -1088,8 +1440,13 @@
|
|
1088
1440
|
if (aborted) {
|
1089
1441
|
return;
|
1090
1442
|
}
|
1443
|
+
if (!files.length) {
|
1444
|
+
dfd.reject();
|
1445
|
+
return;
|
1446
|
+
}
|
1091
1447
|
data.files = files;
|
1092
|
-
jqXHR = that._onSend(null, data)
|
1448
|
+
jqXHR = that._onSend(null, data);
|
1449
|
+
jqXHR.then(
|
1093
1450
|
function (result, textStatus, jqXHR) {
|
1094
1451
|
dfd.resolve(result, textStatus, jqXHR);
|
1095
1452
|
},
|