1
|
/* Redmine - project management software
|
2
|
Copyright (C) 2006-2017 Jean-Philippe Lang */
|
3
|
|
4
|
function addFile(inputEl, file, eagerUpload) {
|
5
|
var attachmentsFields = $(inputEl).closest('.attachments_form').find('.attachments_fields');
|
6
|
var addAttachment = $(inputEl).closest('.attachments_form').find('.add_attachment');
|
7
|
var maxFiles = ($(inputEl).attr('multiple') == 'multiple' ? 10 : 1);
|
8
|
|
9
|
if (attachmentsFields.children().length < maxFiles) {
|
10
|
var attachmentId = addFile.nextAttachmentId++;
|
11
|
var fileSpan = $('<span>', { id: 'attachments_' + attachmentId });
|
12
|
var param = $(inputEl).data('param');
|
13
|
if (!param) {param = 'attachments'};
|
14
|
|
15
|
fileSpan.append(
|
16
|
$('<input>', { type: 'text', 'class': 'icon icon-attachment filename readonly', name: param +'[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name),
|
17
|
$('<input>', { type: 'text', 'class': 'description', name: param + '[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload),
|
18
|
$('<input>', { type: 'hidden', 'class': 'token', name: param + '[' + attachmentId + '][token]'} ),
|
19
|
$('<a> </a>').attr({ href: "#", 'class': 'icon-only icon-del remove-upload' }).click(removeFile).toggle(!eagerUpload)
|
20
|
).appendTo(attachmentsFields);
|
21
|
|
22
|
if ($(inputEl).data('description') == 0) {
|
23
|
fileSpan.find('input.description').remove();
|
24
|
}
|
25
|
|
26
|
if(eagerUpload) {
|
27
|
ajaxUpload(file, attachmentId, fileSpan, inputEl);
|
28
|
}
|
29
|
|
30
|
addAttachment.toggle(attachmentsFields.children().length < maxFiles);
|
31
|
return attachmentId;
|
32
|
}
|
33
|
return null;
|
34
|
}
|
35
|
|
36
|
addFile.nextAttachmentId = 1;
|
37
|
|
38
|
function ajaxUpload(file, attachmentId, fileSpan, inputEl) {
|
39
|
|
40
|
function onLoadstart(e) {
|
41
|
fileSpan.removeClass('ajax-waiting');
|
42
|
fileSpan.addClass('ajax-loading');
|
43
|
$('input:submit', $(this).parents('form')).attr('disabled', 'disabled');
|
44
|
}
|
45
|
|
46
|
function onProgress(e) {
|
47
|
if(e.lengthComputable) {
|
48
|
this.progressbar( 'value', e.loaded * 100 / e.total );
|
49
|
}
|
50
|
}
|
51
|
|
52
|
function actualUpload(file, attachmentId, fileSpan, inputEl) {
|
53
|
|
54
|
ajaxUpload.uploading++;
|
55
|
|
56
|
uploadBlob(file, $(inputEl).data('upload-path'), attachmentId, {
|
57
|
loadstartEventHandler: onLoadstart.bind(progressSpan),
|
58
|
progressEventHandler: onProgress.bind(progressSpan)
|
59
|
})
|
60
|
.done(function(result) {
|
61
|
addInlineAttachmentMarkup(file);
|
62
|
progressSpan.progressbar( 'value', 100 ).remove();
|
63
|
fileSpan.find('input.description, a').css('display', 'inline-block');
|
64
|
})
|
65
|
.fail(function(result) {
|
66
|
progressSpan.text(result.statusText);
|
67
|
}).always(function() {
|
68
|
ajaxUpload.uploading--;
|
69
|
fileSpan.removeClass('ajax-loading');
|
70
|
var form = fileSpan.parents('form');
|
71
|
if (form.queue('upload').length == 0 && ajaxUpload.uploading == 0) {
|
72
|
$('input:submit', form).removeAttr('disabled');
|
73
|
}
|
74
|
form.dequeue('upload');
|
75
|
});
|
76
|
}
|
77
|
|
78
|
var progressSpan = $('<div>').insertAfter(fileSpan.find('input.filename'));
|
79
|
progressSpan.progressbar();
|
80
|
fileSpan.addClass('ajax-waiting');
|
81
|
|
82
|
var maxSyncUpload = $(inputEl).data('max-concurrent-uploads');
|
83
|
|
84
|
if(maxSyncUpload == null || maxSyncUpload <= 0 || ajaxUpload.uploading < maxSyncUpload)
|
85
|
actualUpload(file, attachmentId, fileSpan, inputEl);
|
86
|
else
|
87
|
$(inputEl).parents('form').queue('upload', actualUpload.bind(this, file, attachmentId, fileSpan, inputEl));
|
88
|
}
|
89
|
|
90
|
ajaxUpload.uploading = 0;
|
91
|
|
92
|
function removeFile() {
|
93
|
$(this).closest('.attachments_form').find('.add_attachment').show();
|
94
|
$(this).parent('span').remove();
|
95
|
return false;
|
96
|
}
|
97
|
|
98
|
function uploadBlob(blob, uploadUrl, attachmentId, options) {
|
99
|
|
100
|
var actualOptions = $.extend({
|
101
|
loadstartEventHandler: $.noop,
|
102
|
progressEventHandler: $.noop
|
103
|
}, options);
|
104
|
|
105
|
uploadUrl = uploadUrl + '?attachment_id=' + attachmentId;
|
106
|
if (blob instanceof window.File) {
|
107
|
uploadUrl += '&filename=' + encodeURIComponent(blob.name);
|
108
|
uploadUrl += '&content_type=' + encodeURIComponent(blob.type);
|
109
|
}
|
110
|
|
111
|
return $.ajax(uploadUrl, {
|
112
|
type: 'POST',
|
113
|
contentType: 'application/octet-stream',
|
114
|
beforeSend: function(jqXhr, settings) {
|
115
|
jqXhr.setRequestHeader('Accept', 'application/js');
|
116
|
// attach proper File object
|
117
|
settings.data = blob;
|
118
|
},
|
119
|
xhr: function() {
|
120
|
var xhr = $.ajaxSettings.xhr();
|
121
|
xhr.upload.onloadstart = actualOptions.loadstartEventHandler;
|
122
|
xhr.upload.onprogress = actualOptions.progressEventHandler;
|
123
|
return xhr;
|
124
|
},
|
125
|
data: blob,
|
126
|
cache: false,
|
127
|
processData: false
|
128
|
});
|
129
|
}
|
130
|
|
131
|
function addInputFiles(inputEl) {
|
132
|
var attachmentsFields = $(inputEl).closest('.attachments_form').find('.attachments_fields');
|
133
|
var addAttachment = $(inputEl).closest('.attachments_form').find('.add_attachment');
|
134
|
var clearedFileInput = $(inputEl).clone().val('');
|
135
|
var sizeExceeded = false;
|
136
|
var param = $(inputEl).data('param');
|
137
|
if (!param) {param = 'attachments'};
|
138
|
|
139
|
if ($.ajaxSettings.xhr().upload && inputEl.files) {
|
140
|
// upload files using ajax
|
141
|
sizeExceeded = uploadAndAttachFiles(inputEl.files, inputEl);
|
142
|
$(inputEl).remove();
|
143
|
} else {
|
144
|
// browser not supporting the file API, upload on form submission
|
145
|
var attachmentId;
|
146
|
var aFilename = inputEl.value.split(/\/|\\/);
|
147
|
attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false);
|
148
|
if (attachmentId) {
|
149
|
$(inputEl).attr({ name: param + '[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId);
|
150
|
}
|
151
|
}
|
152
|
|
153
|
clearedFileInput.prependTo(addAttachment);
|
154
|
}
|
155
|
|
156
|
function uploadAndAttachFiles(files, inputEl) {
|
157
|
|
158
|
var maxFileSize = $(inputEl).data('max-file-size');
|
159
|
var maxFileSizeExceeded = $(inputEl).data('max-file-size-message');
|
160
|
|
161
|
var sizeExceeded = false;
|
162
|
$.each(files, function() {
|
163
|
if (this.size && maxFileSize != null && this.size > parseInt(maxFileSize)) {sizeExceeded=true;}
|
164
|
});
|
165
|
if (sizeExceeded) {
|
166
|
window.alert(maxFileSizeExceeded);
|
167
|
} else {
|
168
|
$.each(files, function() {addFile(inputEl, this, true);});
|
169
|
}
|
170
|
return sizeExceeded;
|
171
|
}
|
172
|
|
173
|
function handleFileDropEvent(e) {
|
174
|
|
175
|
$(this).removeClass('fileover');
|
176
|
blockEventPropagation(e);
|
177
|
|
178
|
if ($.inArray('Files', e.dataTransfer.types) > -1) {
|
179
|
handleFileDropEvent.target = e.target;
|
180
|
uploadAndAttachFiles(e.dataTransfer.files, $('input:file.filedrop').first());
|
181
|
}
|
182
|
}
|
183
|
handleFileDropEvent.target = '';
|
184
|
|
185
|
function dragOverHandler(e) {
|
186
|
$(this).addClass('fileover');
|
187
|
blockEventPropagation(e);
|
188
|
}
|
189
|
|
190
|
function dragOutHandler(e) {
|
191
|
$(this).removeClass('fileover');
|
192
|
blockEventPropagation(e);
|
193
|
}
|
194
|
|
195
|
function setupFileDrop() {
|
196
|
if (window.File && window.FileList && window.ProgressEvent && window.FormData) {
|
197
|
|
198
|
$.event.fixHooks.drop = { props: [ 'dataTransfer' ] };
|
199
|
|
200
|
$('form div.box:not(.filedroplistner)').has('input:file.filedrop').each(function() {
|
201
|
$(this).on({
|
202
|
dragover: dragOverHandler,
|
203
|
dragleave: dragOutHandler,
|
204
|
drop: handleFileDropEvent,
|
205
|
paste: copyImageFromClipboard
|
206
|
}).addClass('filedroplistner');
|
207
|
});
|
208
|
}
|
209
|
}
|
210
|
|
211
|
function addInlineAttachmentMarkup(file) {
|
212
|
// insert uploaded image inline if dropped area is currently focused textarea
|
213
|
if($(handleFileDropEvent.target).hasClass('wiki-edit') && $.inArray(file.type, window.wikiImageMimeTypes) > -1) {
|
214
|
var $textarea = $(handleFileDropEvent.target);
|
215
|
var cursorPosition = $textarea.prop('selectionStart');
|
216
|
var description = $textarea.val();
|
217
|
var sanitizedFilename = file.name.replace(/[\/\?\%\*\:\|\"\'<>\n\r]+/, '_');
|
218
|
var inlineFilename = encodeURIComponent(sanitizedFilename)
|
219
|
.replace(/[!()]/g, function(match) { return "%" + match.charCodeAt(0).toString(16) });
|
220
|
var newLineBefore = true;
|
221
|
var newLineAfter = true;
|
222
|
if(cursorPosition === 0 || description.substr(cursorPosition-1,1).match(/\r|\n/)) {
|
223
|
newLineBefore = false;
|
224
|
}
|
225
|
if(description.substr(cursorPosition,1).match(/\r|\n/)) {
|
226
|
newLineAfter = false;
|
227
|
}
|
228
|
|
229
|
$textarea.val(
|
230
|
description.substring(0, cursorPosition)
|
231
|
+ (newLineBefore ? '\n' : '')
|
232
|
+ inlineFilename
|
233
|
+ (newLineAfter ? '\n' : '')
|
234
|
+ description.substring(cursorPosition, description.length)
|
235
|
);
|
236
|
|
237
|
$textarea.prop({
|
238
|
'selectionStart': cursorPosition + newLineBefore,
|
239
|
'selectionEnd': cursorPosition + inlineFilename.length + newLineBefore
|
240
|
});
|
241
|
$textarea.parents('.jstBlock')
|
242
|
.find('.jstb_img').click();
|
243
|
|
244
|
// move cursor into next line
|
245
|
cursorPosition = $textarea.prop('selectionStart');
|
246
|
$textarea.prop({
|
247
|
'selectionStart': cursorPosition + 1,
|
248
|
'selectionEnd': cursorPosition + 1
|
249
|
});
|
250
|
|
251
|
}
|
252
|
}
|
253
|
|
254
|
function copyImageFromClipboard(e) {
|
255
|
if (!$(e.target).hasClass('wiki-edit')) { return; }
|
256
|
var clipboardData = e.clipboardData || e.originalEvent.clipboardData
|
257
|
if (!clipboardData) { return; }
|
258
|
|
259
|
var items = clipboardData.items
|
260
|
for (var i = 0 ; i < items.length ; i++) {
|
261
|
var item = items[i];
|
262
|
if (item.type.indexOf("image") != -1) {
|
263
|
var blob = item.getAsFile();
|
264
|
var date = new Date();
|
265
|
var filename = 'clipboard-'
|
266
|
+ date.getFullYear()
|
267
|
+ ('0'+(date.getMonth()+1)).slice(-2)
|
268
|
+ ('0'+date.getDate()).slice(-2)
|
269
|
+ ('0'+date.getHours()).slice(-2)
|
270
|
+ ('0'+date.getMinutes()).slice(-2)
|
271
|
+ '-' + randomKey(5).toLocaleLowerCase()
|
272
|
+ '.' + blob.name.split('.').pop();
|
273
|
var file = new File([blob], filename, {type: blob.type});
|
274
|
var inputEl = $('input:file.filedrop').first()
|
275
|
handleFileDropEvent.target = e.target;
|
276
|
addFile(inputEl, file, true);
|
277
|
}
|
278
|
}
|
279
|
}
|
280
|
|
281
|
$(document).ready(setupFileDrop);
|
282
|
$(document).ready(function(){
|
283
|
$("input.deleted_attachment").change(function(){
|
284
|
$(this).parents('.existing-attachment').toggleClass('deleted', $(this).is(":checked"));
|
285
|
}).change();
|
286
|
});
|
评论