项目

一般

简介

attachments.js

Redmine Admin, 2022-05-15 22:17

 
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>&nbsp</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
});

添加评论

评论