attache-rails 0.4.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/javascripts/attache.js +423 -4
- data/app/index.html +39 -0
- data/lib/attache/rails/model.rb +10 -6
- data/lib/attache/rails/version.rb +1 -1
- metadata +5 -10
- data/app/assets/javascripts/attache/bootstrap3.js +0 -77
- data/app/assets/javascripts/attache/bootstrap3.js.jsx +0 -77
- data/app/assets/javascripts/attache/cors_upload.js +0 -87
- data/app/assets/javascripts/attache/file_input.js +0 -153
- data/app/assets/javascripts/attache/file_input.js.jsx +0 -153
- data/app/assets/javascripts/attache/ujs.js +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d102ed58a94d38425b805352694aa5011340006
|
4
|
+
data.tar.gz: 62d859eb1f12efe92f25f4a3ef2da1a28d00ed87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2977a0f4f997292c63d7aa824209dc113c655385c50f23ca64007bbdca8b7ed38e2e1f674bd7bceb6140191003d2c1b76f162879fc6ac41b8f0185d4b1d6865
|
7
|
+
data.tar.gz: eeddf1577a1f8824800cdddb005a686748eec36f8784612e836fa125362b2ce5ef2f8d11ec5cf3844a3bcc8e8e34768289dda8504904a4a5fb34f61226ff922d
|
data/README.md
CHANGED
@@ -31,7 +31,7 @@ Add the attache javascript to your `application.js`
|
|
31
31
|
//= require attache
|
32
32
|
```
|
33
33
|
|
34
|
-
If you want to customize the file upload look and feel, define your own
|
34
|
+
If you want to customize the file upload look and feel, define your own React `<AttacheFilePreview/>`, `<AttacheHeader/>`, `<AttachePlaceholder/>` renderer *before* including the attache js. For example,
|
35
35
|
|
36
36
|
``` javascript
|
37
37
|
//= require ./my_attache_file_preview.js
|
@@ -1,4 +1,423 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
var _file_input = require('./attache/file_input');
|
5
|
+
|
6
|
+
var upgradeFileInput = function upgradeFileInput() {
|
7
|
+
var safeWords = { 'class': 'className', 'for': 'htmlFor' };
|
8
|
+
var sel = document.getElementsByClassName('enable-attache');
|
9
|
+
var ele, attrs, name, value;
|
10
|
+
for (var i = sel.length - 1; i >= 0; i--) {
|
11
|
+
ele = sel[i];
|
12
|
+
attrs = {};
|
13
|
+
for (var j = 0; j < ele.attributes.length; j++) {
|
14
|
+
name = ele.attributes[j].name;
|
15
|
+
value = ele.attributes[j].value;
|
16
|
+
if (name === 'class') value = value.replace('enable-attache', 'attache-enabled');
|
17
|
+
name = safeWords[name] || name;
|
18
|
+
attrs[name] = value;
|
19
|
+
}
|
20
|
+
var wrap = document.createElement('div');
|
21
|
+
ele.parentNode.replaceChild(wrap, ele);
|
22
|
+
ReactDOM.render(React.createElement(_file_input.AttacheFileInput, React.__spread({}, attrs)), wrap);
|
23
|
+
}
|
24
|
+
}; /*global $*/
|
25
|
+
/*global React*/
|
26
|
+
/*global ReactDOM*/
|
27
|
+
|
28
|
+
$(document).on('page:change', upgradeFileInput);
|
29
|
+
$(upgradeFileInput);
|
30
|
+
|
31
|
+
},{"./attache/file_input":4}],2:[function(require,module,exports){
|
32
|
+
'use strict';
|
33
|
+
|
34
|
+
Object.defineProperty(exports, "__esModule", {
|
35
|
+
value: true
|
36
|
+
});
|
37
|
+
/*global $*/
|
38
|
+
/*global React*/
|
39
|
+
|
40
|
+
var Bootstrap3FilePreview = exports.Bootstrap3FilePreview = React.createClass({
|
41
|
+
displayName: 'Bootstrap3FilePreview',
|
42
|
+
getInitialState: function getInitialState() {
|
43
|
+
return { srcWas: '' };
|
44
|
+
},
|
45
|
+
onSrcLoaded: function onSrcLoaded(event) {
|
46
|
+
this.setState({ srcWas: this.props.src });
|
47
|
+
$(event.target).trigger('attache:imgload');
|
48
|
+
},
|
49
|
+
onSrcError: function onSrcError(event) {
|
50
|
+
$(event.target).trigger('attache:imgerror');
|
51
|
+
},
|
52
|
+
render: function render() {
|
53
|
+
var previewClassName = 'attache-file-preview';
|
54
|
+
|
55
|
+
// progressbar
|
56
|
+
if (this.state.srcWas !== this.props.src) {
|
57
|
+
previewClassName = previewClassName + ' attache-loading';
|
58
|
+
var className = this.props.className || 'progress-bar progress-bar-striped active' + (this.props.src ? ' progress-bar-success' : '');
|
59
|
+
var pctString = this.props.pctString || (this.props.src ? 100 : this.props.percentLoaded) + '%';
|
60
|
+
var pctDesc = this.props.pctDesc || (this.props.src ? 'Loading...' : pctString);
|
61
|
+
var pctStyle = { width: pctString, minWidth: '3em' };
|
62
|
+
var progress = React.createElement(
|
63
|
+
'div',
|
64
|
+
{ className: 'progress' },
|
65
|
+
React.createElement(
|
66
|
+
'div',
|
67
|
+
{
|
68
|
+
className: className,
|
69
|
+
role: 'progressbar',
|
70
|
+
'aria-valuenow': this.props.percentLoaded,
|
71
|
+
'aria-valuemin': '0',
|
72
|
+
'aria-valuemax': '100',
|
73
|
+
style: pctStyle },
|
74
|
+
pctDesc
|
75
|
+
)
|
76
|
+
);
|
77
|
+
}
|
78
|
+
|
79
|
+
// img tag
|
80
|
+
if (this.props.src) {
|
81
|
+
var img = React.createElement('img', { src: this.props.src, onLoad: this.onSrcLoaded, onError: this.onSrcError });
|
82
|
+
}
|
83
|
+
|
84
|
+
// combined
|
85
|
+
return React.createElement(
|
86
|
+
'div',
|
87
|
+
{ className: previewClassName },
|
88
|
+
progress,
|
89
|
+
img,
|
90
|
+
React.createElement(
|
91
|
+
'div',
|
92
|
+
{ className: 'clearfix' },
|
93
|
+
React.createElement(
|
94
|
+
'div',
|
95
|
+
{ className: 'pull-left' },
|
96
|
+
this.props.filename
|
97
|
+
),
|
98
|
+
React.createElement(
|
99
|
+
'a',
|
100
|
+
{
|
101
|
+
href: '#remove',
|
102
|
+
className: 'pull-right',
|
103
|
+
onClick: this.props.onRemove,
|
104
|
+
title: 'Click to remove' },
|
105
|
+
'×'
|
106
|
+
)
|
107
|
+
)
|
108
|
+
);
|
109
|
+
}
|
110
|
+
});
|
111
|
+
|
112
|
+
var Bootstrap3Placeholder = exports.Bootstrap3Placeholder = React.createClass({
|
113
|
+
displayName: 'Bootstrap3Placeholder',
|
114
|
+
render: function render() {
|
115
|
+
return React.createElement(
|
116
|
+
'div',
|
117
|
+
{ className: 'attache-file-preview' },
|
118
|
+
React.createElement('img', { src: this.props.src })
|
119
|
+
);
|
120
|
+
}
|
121
|
+
});
|
122
|
+
|
123
|
+
var Bootstrap3Header = exports.Bootstrap3Header = React.createClass({
|
124
|
+
displayName: 'Bootstrap3Header',
|
125
|
+
render: function render() {
|
126
|
+
return React.createElement('noscript', null);
|
127
|
+
}
|
128
|
+
});
|
129
|
+
|
130
|
+
},{}],3:[function(require,module,exports){
|
131
|
+
'use strict';
|
132
|
+
|
133
|
+
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
134
|
+
|
135
|
+
Object.defineProperty(exports, "__esModule", {
|
136
|
+
value: true
|
137
|
+
});
|
138
|
+
|
139
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
140
|
+
|
141
|
+
/*global $*/
|
142
|
+
/*global alert*/
|
143
|
+
/*global XMLHttpRequest*/
|
144
|
+
/*global XDomainRequest*/
|
145
|
+
|
146
|
+
var counter = 0;
|
147
|
+
|
148
|
+
var CORSUpload = exports.CORSUpload = (function () {
|
149
|
+
function CORSUpload(options) {
|
150
|
+
_classCallCheck(this, CORSUpload);
|
151
|
+
|
152
|
+
if (options == null) options = {};
|
153
|
+
var option;
|
154
|
+
for (option in options) {
|
155
|
+
this[option] = options[option];
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
// for overwriting
|
160
|
+
|
161
|
+
_createClass(CORSUpload, [{
|
162
|
+
key: 'createLocalThumbnail',
|
163
|
+
value: function createLocalThumbnail() {}
|
164
|
+
}, {
|
165
|
+
key: 'onComplete',
|
166
|
+
value: function onComplete(uid, json) {}
|
167
|
+
}, {
|
168
|
+
key: 'onProgress',
|
169
|
+
value: function onProgress(uid, json) {}
|
170
|
+
}, {
|
171
|
+
key: 'onError',
|
172
|
+
value: function onError(uid, status) {
|
173
|
+
alert(status);
|
174
|
+
}
|
175
|
+
}, {
|
176
|
+
key: 'handleFileSelect',
|
177
|
+
value: function handleFileSelect() {
|
178
|
+
var f, _i, _len, _results, url, $ele, prefix;
|
179
|
+
$ele = $(this.file_element);
|
180
|
+
url = $ele.data('uploadurl');
|
181
|
+
if ($ele.data('hmac')) {
|
182
|
+
url = url + '?hmac=' + encodeURIComponent($ele.data('hmac')) + '&uuid=' + encodeURIComponent($ele.data('uuid')) + '&expiration=' + encodeURIComponent($ele.data('expiration')) + '';
|
183
|
+
}
|
184
|
+
|
185
|
+
prefix = Date.now() + '_';
|
186
|
+
_results = [];
|
187
|
+
for (_i = 0, _len = this.files.length; _i < _len; _i++) {
|
188
|
+
f = this.files[_i];
|
189
|
+
this.createLocalThumbnail(f); // if any
|
190
|
+
f.uid = prefix + counter++;
|
191
|
+
this.onProgress(f.uid, { src: f.src, filename: f.name, percentLoaded: 0, bytesLoaded: 0, bytesTotal: f.size });
|
192
|
+
_results.push(this.performUpload(f, url));
|
193
|
+
}
|
194
|
+
return _results;
|
195
|
+
}
|
196
|
+
}, {
|
197
|
+
key: 'createCORSRequest',
|
198
|
+
value: function createCORSRequest(method, url) {
|
199
|
+
var xhr;
|
200
|
+
xhr = new XMLHttpRequest();
|
201
|
+
if (xhr.withCredentials != null) {
|
202
|
+
xhr.open(method, url, true);
|
203
|
+
} else if (typeof XDomainRequest !== 'undefined') {
|
204
|
+
xhr = new XDomainRequest();
|
205
|
+
xhr.open(method, url);
|
206
|
+
} else {
|
207
|
+
xhr = null;
|
208
|
+
}
|
209
|
+
return xhr;
|
210
|
+
}
|
211
|
+
}, {
|
212
|
+
key: 'performUpload',
|
213
|
+
value: function performUpload(file, url) {
|
214
|
+
var this_s3upload, xhr;
|
215
|
+
this_s3upload = this;
|
216
|
+
url = url + (url.indexOf('?') === -1 ? '?' : '&') + 'file=' + encodeURIComponent(file.name);
|
217
|
+
xhr = this.createCORSRequest('PUT', url);
|
218
|
+
if (!xhr) {
|
219
|
+
this.onError(file.uid, 'CORS not supported');
|
220
|
+
} else {
|
221
|
+
xhr.onload = function (e) {
|
222
|
+
if (xhr.status === 200) {
|
223
|
+
this_s3upload.onComplete(file.uid, JSON.parse(e.target.responseText));
|
224
|
+
} else {
|
225
|
+
return this_s3upload.onError(file.uid, xhr.status + ' ' + xhr.statusText);
|
226
|
+
}
|
227
|
+
};
|
228
|
+
xhr.onerror = function () {
|
229
|
+
return this_s3upload.onError(file.uid, 'Unable to reach server');
|
230
|
+
};
|
231
|
+
xhr.upload.onprogress = function (e) {
|
232
|
+
var percentLoaded;
|
233
|
+
if (e.lengthComputable) {
|
234
|
+
percentLoaded = Math.round(e.loaded / e.total * 100);
|
235
|
+
return this_s3upload.onProgress(file.uid, { src: file.src, filename: file.name, percentLoaded: percentLoaded, bytesLoaded: e.loaded, bytesTotal: e.total });
|
236
|
+
}
|
237
|
+
};
|
238
|
+
}
|
239
|
+
return xhr.send(file);
|
240
|
+
}
|
241
|
+
}]);
|
242
|
+
|
243
|
+
return CORSUpload;
|
244
|
+
})();
|
245
|
+
|
246
|
+
},{}],4:[function(require,module,exports){
|
247
|
+
'use strict';
|
248
|
+
|
249
|
+
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /*global $*/
|
250
|
+
/*global window*/
|
251
|
+
/*global React*/
|
252
|
+
/*global ReactDOM*/
|
253
|
+
|
254
|
+
Object.defineProperty(exports, "__esModule", {
|
255
|
+
value: true
|
256
|
+
});
|
257
|
+
exports.AttacheFileInput = undefined;
|
258
|
+
|
259
|
+
var _cors_upload = require('./cors_upload');
|
260
|
+
|
261
|
+
var _bootstrap = require('./bootstrap3');
|
262
|
+
|
263
|
+
var AttacheFileInput = exports.AttacheFileInput = React.createClass({
|
264
|
+
displayName: 'AttacheFileInput',
|
265
|
+
getInitialState: function getInitialState() {
|
266
|
+
var files = {};
|
267
|
+
if (this.props['data-value']) {
|
268
|
+
$.each(JSON.parse(this.props['data-value']), function (uid, json) {
|
269
|
+
if (json) files[uid] = json;
|
270
|
+
});
|
271
|
+
}
|
272
|
+
return { files: files, attaches_discarded: [], uploading: 0 };
|
273
|
+
},
|
274
|
+
onRemove: function onRemove(uid, e) {
|
275
|
+
e.preventDefault();
|
276
|
+
e.stopPropagation();
|
277
|
+
|
278
|
+
var fieldname = ReactDOM.findDOMNode(this).firstChild.name; // when 'user[avatar]'
|
279
|
+
var newfield = fieldname.replace(/\w+\](\[\]|)$/, 'attaches_discarded][]'); // become 'user[attaches_discarded][]'
|
280
|
+
|
281
|
+
this.state.attaches_discarded.push({ fieldname: newfield, path: this.state.files[uid].path });
|
282
|
+
delete this.state.files[uid];
|
283
|
+
|
284
|
+
this.setState(this.state);
|
285
|
+
},
|
286
|
+
performUpload: function performUpload(file_element, files) {
|
287
|
+
// user cancelled file chooser dialog. ignore
|
288
|
+
if (!files || files.length === 0) return;
|
289
|
+
if (!this.props.multiple) {
|
290
|
+
this.state.files = {};
|
291
|
+
files = [files[0]]; // array of 1 element
|
292
|
+
}
|
293
|
+
|
294
|
+
this.setState(this.state);
|
295
|
+
// upload the file via CORS
|
296
|
+
var that = this;
|
297
|
+
|
298
|
+
that.state.uploading = that.state.uploading + files.length;
|
299
|
+
if (!that.state.submit_buttons) that.state.submit_buttons = $("button,input[type='submit']", $(file_element).parents('form')[0]).filter(':not(:disabled)');
|
300
|
+
|
301
|
+
var upload = new _cors_upload.CORSUpload({
|
302
|
+
file_element: file_element,
|
303
|
+
files: files,
|
304
|
+
onProgress: this.setFileValue,
|
305
|
+
onComplete: function onComplete() {
|
306
|
+
that.state.uploading--;
|
307
|
+
that.setFileValue.apply(this, arguments);
|
308
|
+
},
|
309
|
+
onError: function onError(uid, status) {
|
310
|
+
that.state.uploading--;
|
311
|
+
that.setFileValue(uid, { pctString: '90%', pctDesc: status, className: 'progress-bar progress-bar-danger' });
|
312
|
+
}
|
313
|
+
});
|
314
|
+
upload.handleFileSelect();
|
315
|
+
|
316
|
+
// we don't want the file binary to be uploaded in the main form
|
317
|
+
// so the actual file input is neutered
|
318
|
+
file_element.value = '';
|
319
|
+
},
|
320
|
+
onChange: function onChange() {
|
321
|
+
var file_element = ReactDOM.findDOMNode(this).firstChild;
|
322
|
+
this.performUpload(file_element, file_element && file_element.files);
|
323
|
+
},
|
324
|
+
onDragOver: function onDragOver(e) {
|
325
|
+
e.stopPropagation();
|
326
|
+
e.preventDefault();
|
327
|
+
$(ReactDOM.findDOMNode(this)).addClass('attache-dragover');
|
328
|
+
},
|
329
|
+
onDragLeave: function onDragLeave(e) {
|
330
|
+
e.stopPropagation();
|
331
|
+
e.preventDefault();
|
332
|
+
$(ReactDOM.findDOMNode(this)).removeClass('attache-dragover');
|
333
|
+
},
|
334
|
+
onDrop: function onDrop(e) {
|
335
|
+
e.stopPropagation();
|
336
|
+
e.preventDefault();
|
337
|
+
var file_element = ReactDOM.findDOMNode(this).firstChild;
|
338
|
+
this.performUpload(file_element, e.target.files || e.dataTransfer.files);
|
339
|
+
$(ReactDOM.findDOMNode(this)).removeClass('attache-dragover');
|
340
|
+
},
|
341
|
+
setFileValue: function setFileValue(key, value) {
|
342
|
+
this.state.files[key] = value;
|
343
|
+
this.setState(this.state);
|
344
|
+
},
|
345
|
+
render: function render() {
|
346
|
+
var that = this;
|
347
|
+
var Header = window.AttacheHeader || _bootstrap.Bootstrap3Header;
|
348
|
+
var FilePreview = window.AttacheFilePreview || _bootstrap.Bootstrap3FilePreview;
|
349
|
+
var Placeholder = window.AttachePlaceholder || _bootstrap.Bootstrap3Placeholder;
|
350
|
+
|
351
|
+
if (that.state.uploading > 0) {
|
352
|
+
that.state.submit_buttons.attr('disabled', true);
|
353
|
+
} else if (that.state.submit_buttons) {
|
354
|
+
that.state.submit_buttons.attr('disabled', null);
|
355
|
+
}
|
356
|
+
|
357
|
+
var previews = [];
|
358
|
+
$.each(that.state.files, function (key, result) {
|
359
|
+
// json is input[value], drop non essential values
|
360
|
+
var copy = JSON.parse(JSON.stringify(result));
|
361
|
+
delete copy.src;
|
362
|
+
delete copy.filename;
|
363
|
+
var json = JSON.stringify(copy);
|
364
|
+
//
|
365
|
+
result.multiple = that.props.multiple;
|
366
|
+
if (result.path) {
|
367
|
+
var parts = result.path.split('/');
|
368
|
+
result.filename = parts.pop().split(/[#?]/).shift();
|
369
|
+
parts.push(encodeURIComponent(that.props['data-geometry'] || '128x128#'));
|
370
|
+
parts.push(encodeURIComponent(result.filename));
|
371
|
+
result.src = that.props['data-downloadurl'] + '/' + parts.join('/');
|
372
|
+
}
|
373
|
+
var previewKey = 'preview' + key;
|
374
|
+
previews.push(React.createElement(
|
375
|
+
'div',
|
376
|
+
{ key: previewKey, className: 'attache-file-input' },
|
377
|
+
React.createElement('input', {
|
378
|
+
type: 'hidden',
|
379
|
+
name: that.props.name,
|
380
|
+
value: json,
|
381
|
+
readOnly: 'true' }),
|
382
|
+
React.createElement(FilePreview, _extends({}, result, { key: key, onRemove: that.onRemove.bind(that, key) }))
|
383
|
+
));
|
384
|
+
});
|
385
|
+
|
386
|
+
var placeholders = [];
|
387
|
+
if (previews.length === 0 && that.props['data-placeholder']) {
|
388
|
+
$.each(JSON.parse(that.props['data-placeholder']), function (uid, src) {
|
389
|
+
placeholders.push(React.createElement(Placeholder, _extends({ key: 'placeholder' }, that.props, { src: src })));
|
390
|
+
});
|
391
|
+
}
|
392
|
+
|
393
|
+
var discards = [];
|
394
|
+
$.each(that.state.attaches_discarded, function (index, discard) {
|
395
|
+
var discardKey = 'discard' + discard.path;
|
396
|
+
discards.push(React.createElement('input', {
|
397
|
+
key: discardKey,
|
398
|
+
type: 'hidden',
|
399
|
+
name: discard.fieldname,
|
400
|
+
value: discard.path }));
|
401
|
+
});
|
402
|
+
|
403
|
+
var className = ['attache-file-selector', 'attache-placeholders-count-' + placeholders.length, 'attache-previews-count-' + previews.length, this.props['data-classname']].join(' ').trim();
|
404
|
+
return React.createElement(
|
405
|
+
'label',
|
406
|
+
{
|
407
|
+
htmlFor: that.props.id,
|
408
|
+
className: className,
|
409
|
+
onDragOver: this.onDragOver,
|
410
|
+
onDragLeave: this.onDragLeave,
|
411
|
+
onDrop: this.onDrop },
|
412
|
+
React.createElement('input', _extends({ type: 'file' }, that.props, { onChange: this.onChange })),
|
413
|
+
React.createElement('input', { type: 'hidden', name: that.props.name, value: '' }),
|
414
|
+
React.createElement(Header, that.props),
|
415
|
+
previews,
|
416
|
+
placeholders,
|
417
|
+
discards
|
418
|
+
);
|
419
|
+
}
|
420
|
+
});
|
421
|
+
|
422
|
+
},{"./bootstrap3":2,"./cors_upload":3}]},{},[1])
|
423
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy5udm0vdjQuMS4wL2xpYi9ub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwic3JjL2phdmFzY3JpcHRzL2F0dGFjaGUuanMiLCJzcmMvamF2YXNjcmlwdHMvYXR0YWNoZS9ib290c3RyYXAzLmpzIiwic3JjL2phdmFzY3JpcHRzL2F0dGFjaGUvY29yc191cGxvYWQuanMiLCJzcmMvamF2YXNjcmlwdHMvYXR0YWNoZS9maWxlX2lucHV0LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7OztBQ01BLElBQUksZ0JBQWdCLEdBQUcsU0FBbkIsZ0JBQWdCLEdBQWU7QUFDakMsTUFBSSxTQUFTLEdBQUcsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsQ0FBQTtBQUMxRCxNQUFJLEdBQUcsR0FBRyxRQUFRLENBQUMsc0JBQXNCLENBQUMsZ0JBQWdCLENBQUMsQ0FBQTtBQUMzRCxNQUFJLEdBQUcsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLEtBQUssQ0FBQTtBQUMzQixPQUFLLElBQUksQ0FBQyxHQUFHLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUU7QUFDeEMsT0FBRyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQTtBQUNaLFNBQUssR0FBRyxFQUFFLENBQUE7QUFDVixTQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsR0FBRyxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUU7QUFDOUMsVUFBSSxHQUFHLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFBO0FBQzdCLFdBQUssR0FBRyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQTtBQUMvQixVQUFJLElBQUks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Q0FBQyxXQUFXLEFBQUM7QUFDOUIsY0FBTSxFQUFFLElBQUksQ0FBQyxNQUFNLEFBQUM7TUFDcEIsd0NBQU8sSUFBSSxFQUFDLE1BQU0sSUFBSyxJQUFJLENBQUMsS0FBSyxJQUFFLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxBQUFDLElBQUc7TUFDOUQsK0JBQU8sSUFBSSxFQUFDLFFBQVEsRUFBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLEFBQUMsRUFBQyxLQUFLLEVBQUMsRUFBRSxHQUFHO01BQ3ZELG9CQUFDLE1BQU0sRUFBSyxJQUFJLENBQUMsS0FBSyxDQUFJO01BQ3pCLFFBQVE7TUFDUixZQUFZO01BQ1osUUFBUTtLQUNILENBQ1A7R0FDRjtDQUNGLENBQUMsQ0FBQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt2YXIgZj1uZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpO3Rocm93IGYuY29kZT1cIk1PRFVMRV9OT1RfRk9VTkRcIixmfXZhciBsPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChsLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGwsbC5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCIvKmdsb2JhbCAkKi9cbi8qZ2xvYmFsIFJlYWN0Ki9cbi8qZ2xvYmFsIFJlYWN0RE9NKi9cblxuaW1wb3J0IHsgQXR0YWNoZUZpbGVJbnB1dCB9IGZyb20gJy4vYXR0YWNoZS9maWxlX2lucHV0J1xuXG52YXIgdXBncmFkZUZpbGVJbnB1dCA9IGZ1bmN0aW9uICgpIHtcbiAgdmFyIHNhZmVXb3JkcyA9IHsgJ2NsYXNzJzogJ2NsYXNzTmFtZScsICdmb3InOiAnaHRtbEZvcicgfVxuICB2YXIgc2VsID0gZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnZW5hYmxlLWF0dGFjaGUnKVxuICB2YXIgZWxlLCBhdHRycywgbmFtZSwgdmFsdWVcbiAgZm9yICh2YXIgaSA9IHNlbC5sZW5ndGggLSAxOyBpID49IDA7IGktLSkge1xuICAgIGVsZSA9IHNlbFtpXVxuICAgIGF0dHJzID0ge31cbiAgICBmb3IgKHZhciBqID0gMDsgaiA8IGVsZS5hdHRyaWJ1dGVzLmxlbmd0aDsgaisrKSB7XG4gICAgICBuYW1lID0gZWxlLmF0dHJpYnV0ZXNbal0ubmFtZVxuICAgICAgdmFsdWUgPSBlbGUuYXR0cmlidXRlc1tqXS52YWx1ZVxuICAgICAgaWYgKG5hbWUgPT09ICdjbGFzcycpIHZhbHVlID0gdmFsdWUucmVwbGFjZSgnZW5hYmxlLWF0dGFjaGUnLCAnYXR0YWNoZS1lbmFibGVkJylcbiAgICAgIG5hbWUgPSBzYWZlV29yZHNbbmFtZV0gfHwgbmFtZVxuICAgICAgYXR0cnNbbmFtZV0gPSB2YWx1ZVxuICAgIH1cbiAgICB2YXIgd3JhcCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpXG4gICAgZWxlLnBhcmVudE5vZGUucmVwbGFjZUNoaWxkKHdyYXAsIGVsZSlcbiAgICBSZWFjdERPTS5yZW5kZXIoUmVhY3QuY3JlYXRlRWxlbWVudChBdHRhY2hlRmlsZUlucHV0LCBSZWFjdC5fX3NwcmVhZCh7fSwgYXR0cnMpKSwgd3JhcClcbiAgfVxufVxuXG4kKGRvY3VtZW50KS5vbigncGFnZTpjaGFuZ2UnLCB1cGdyYWRlRmlsZUlucHV0KVxuJCh1cGdyYWRlRmlsZUlucHV0KVxuIiwiLypnbG9iYWwgJCovXG4vKmdsb2JhbCBSZWFjdCovXG5cbmV4cG9ydCB2YXIgQm9vdHN0cmFwM0ZpbGVQcmV2aWV3ID0gUmVhY3QuY3JlYXRlQ2xhc3Moe1xuICBnZXRJbml0aWFsU3RhdGUgKCkge1xuICAgIHJldHVybiB7IHNyY1dhczogJycgfVxuICB9LFxuXG4gIG9uU3JjTG9hZGVkIChldmVudCkge1xuICAgIHRoaXMuc2V0U3RhdGUoeyBzcmNXYXM6IHRoaXMucHJvcHMuc3JjIH0pXG4gICAgJChldmVudC50YXJnZXQpLnRyaWdnZXIoJ2F0dGFjaGU6aW1nbG9hZCcpXG4gIH0sXG5cbiAgb25TcmNFcnJvciAoZXZlbnQpIHtcbiAgICAkKGV2ZW50LnRhcmdldCkudHJpZ2dlcignYXR0YWNoZTppbWdlcnJvcicpXG4gIH0sXG5cbiAgcmVuZGVyICgpIHtcbiAgICB2YXIgcHJldmlld0NsYXNzTmFtZSA9ICdhdHRhY2hlLWZpbGUtcHJldmlldydcblxuICAgIC8vIHByb2dyZXNzYmFyXG4gICAgaWYgKHRoaXMuc3RhdGUuc3JjV2FzICE9PSB0aGlzLnByb3BzLnNyYykge1xuICAgICAgcHJldmlld0NsYXNzTmFtZSA9IHByZXZpZXdDbGFzc05hbWUgKyAnIGF0dGFjaGUtbG9hZGluZydcbiAgICAgIHZhciBjbGFzc05hbWUgPSB0aGlzLnByb3BzLmNsYXNzTmFtZSB8fCAncHJvZ3Jlc3MtYmFyIHByb2dyZXNzLWJhci1zdHJpcGVkIGFjdGl2ZScgKyAodGhpcy5wcm9wcy5zcmMgPyAnIHByb2dyZXNzLWJhci1zdWNjZXNzJyA6ICcnKVxuICAgICAgdmFyIHBjdFN0cmluZyA9IHRoaXMucHJvcHMucGN0U3RyaW5nIHx8ICh0aGlzLnByb3BzLnNyYyA/IDEwMCA6IHRoaXMucHJvcHMucGVyY2VudExvYWRlZCkgKyAnJSdcbiAgICAgIHZhciBwY3REZXNjID0gdGhpcy5wcm9wcy5wY3REZXNjIHx8ICh0aGlzLnByb3BzLnNyYyA/ICdMb2FkaW5nLi4uJyA6IHBjdFN0cmluZylcbiAgICAgIHZhciBwY3RTdHlsZSA9IHsgd2lkdGg6IHBjdFN0cmluZywgbWluV2lkdGg6ICczZW0nIH1cbiAgICAgIHZhciBwcm9ncmVzcyA9IChcbiAgICAgIDxkaXYgY2xhc3NOYW1lPVwicHJvZ3Jlc3NcIj5cbiAgICAgICAgPGRpdlxuICAgICAgICAgIGNsYXNzTmFtZT17Y2xhc3NOYW1lfVxuICAgICAgICAgIHJvbGU9XCJwcm9ncmVzc2JhclwiXG4gICAgICAgICAgYXJpYS12YWx1ZW5vdz17dGhpcy5wcm9wcy5wZXJjZW50TG9hZGVkfVxuICAgICAgICAgIGFyaWEtdmFsdWVtaW49XCIwXCJcbiAgICAgICAgICBhcmlhLXZhbHVlbWF4PVwiMTAwXCJcbiAgICAgICAgICBzdHlsZT17cGN0U3R5bGV9PlxuICAgICAgICAgIHtwY3REZXNjfVxuICAgICAgICA8L2Rpdj5cbiAgICAgIDwvZGl2PlxuICAgICAgKVxuICAgIH1cblxuICAgIC8vIGltZyB0YWdcbiAgICBpZiAodGhpcy5wcm9wcy5zcmMpIHtcbiAgICAgIHZhciBpbWcgPSA8aW1nIHNyYz17dGhpcy5wcm9wcy5zcmN9IG9uTG9hZD17dGhpcy5vblNyY0xvYWRlZH0gb25FcnJvcj17dGhpcy5vblNyY0Vycm9yfSAvPlxuICAgIH1cblxuICAgIC8vIGNvbWJpbmVkXG4gICAgcmV0dXJuIChcbiAgICA8ZGl2IGNsYXNzTmFtZT17cHJldmlld0NsYXNzTmFtZX0+XG4gICAgICB7cHJvZ3Jlc3N9XG4gICAgICB7aW1nfVxuICAgICAgPGRpdiBjbGFzc05hbWU9XCJjbGVhcmZpeFwiPlxuICAgICAgICA8ZGl2IGNsYXNzTmFtZT1cInB1bGwtbGVmdFwiPlxuICAgICAgICAgIHt0aGlzLnByb3BzLmZpbGVuYW1lfVxuICAgICAgICA8L2Rpdj5cbiAgICAgICAgPGFcbiAgICAgICAgICBocmVmPVwiI3JlbW92ZVwiXG4gICAgICAgICAgY2xhc3NOYW1lPVwicHVsbC1yaWdodFwiXG4gICAgICAgICAgb25DbGljaz17dGhpcy5wcm9wcy5vblJlbW92ZX1cbiAgICAgICAgICB0aXRsZT1cIkNsaWNrIHRvIHJlbW92ZVwiPiZ0aW1lczs8L2E+XG4gICAgICA8L2Rpdj5cbiAgICA8L2Rpdj5cbiAgICApXG4gIH1cbn0pXG5cbmV4cG9ydCB2YXIgQm9vdHN0cmFwM1BsYWNlaG9sZGVyID0gUmVhY3QuY3JlYXRlQ2xhc3Moe1xuICByZW5kZXIgKCkge1xuICAgIHJldHVybiAoXG4gICAgPGRpdiBjbGFzc05hbWU9XCJhdHRhY2hlLWZpbGUtcHJldmlld1wiPlxuICAgICAgPGltZyBzcmM9e3RoaXMucHJvcHMuc3JjfSAvPlxuICAgIDwvZGl2PlxuICAgIClcbiAgfVxufSlcblxuZXhwb3J0IHZhciBCb290c3RyYXAzSGVhZGVyID0gUmVhY3QuY3JlYXRlQ2xhc3Moe1xuICByZW5kZXIgKCkge1xuICAgIHJldHVybiAoXG4gICAgPG5vc2NyaXB0IC8+XG4gICAgKVxuICB9XG59KVxuIiwiLypnbG9iYWwgJCovXG4vKmdsb2JhbCBhbGVydCovXG4vKmdsb2JhbCBYTUxIdHRwUmVxdWVzdCovXG4vKmdsb2JhbCBYRG9tYWluUmVxdWVzdCovXG5cbnZhciBjb3VudGVyID0gMFxuXG5leHBvcnQgY2xhc3MgQ09SU1VwbG9hZCB7XG4gIGNvbnN0cnVjdG9yIChvcHRpb25zKSB7XG4gICAgaWYgKG9wdGlvbnMgPT0gbnVsbCkgb3B0aW9ucyA9IHt9XG4gICAgdmFyIG9wdGlvblxuICAgIGZvciAob3B0aW9uIGluIG9wdGlvbnMpIHtcbiAgICAgIHRoaXNbb3B0aW9uXSA9IG9wdGlvbnNbb3B0aW9uXVxuICAgIH1cbiAgfVxuXG4gIC8vIGZvciBvdmVyd3JpdGluZ1xuICBjcmVhdGVMb2NhbFRodW1ibmFpbCAoKSB7IH1cbiAgb25Db21wbGV0ZSAodWlkLCBqc29uKSB7IH1cbiAgb25Qcm9ncmVzcyAodWlkLCBqc29uKSB7IH1cbiAgb25FcnJvciAodWlkLCBzdGF0dXMpIHsgYWxlcnQoc3RhdHVzKSB9XG5cbiAgaGFuZGxlRmlsZVNlbGVjdCAoKSB7XG4gICAgdmFyIGYsIF9pLCBfbGVuLCBfcmVzdWx0cywgdXJsLCAkZWxlLCBwcmVmaXhcbiAgICAkZWxlID0gJCh0aGlzLmZpbGVfZWxlbWVudClcbiAgICB1cmwgPSAkZWxlLmRhdGEoJ3VwbG9hZHVybCcpXG4gICAgaWYgKCRlbGUuZGF0YSgnaG1hYycpKSB7XG4gICAgICB1cmwgPSB1cmwgK1xuICAgICAgICAnP2htYWM9JyArIGVuY29kZVVSSUNvbXBvbmVudCgkZWxlLmRhdGEoJ2htYWMnKSkgK1xuICAgICAgICAnJnV1aWQ9JyArIGVuY29kZVVSSUNvbXBvbmVudCgkZWxlLmRhdGEoJ3V1aWQnKSkgK1xuICAgICAgICAnJmV4cGlyYXRpb249JyArIGVuY29kZVVSSUNvbXBvbmVudCgkZWxlLmRhdGEoJ2V4cGlyYXRpb24nKSkgK1xuICAgICAgICAnJ1xuICAgIH1cblxuICAgIHByZWZpeCA9IERhdGUubm93KCkgKyAnXydcbiAgICBfcmVzdWx0cyA9IFtdXG4gICAgZm9yIChfaSA9IDAsIF9sZW4gPSB0aGlzLmZpbGVzLmxlbmd0aDsgX2kgPCBfbGVuOyBfaSsrKSB7XG4gICAgICBmID0gdGhpcy5maWxlc1tfaV1cbiAgICAgIHRoaXMuY3JlYXRlTG9jYWxUaHVtYm5haWwoZikgLy8gaWYgYW55XG4gICAgICBmLnVpZCA9IHByZWZpeCArIChjb3VudGVyKyspXG4gICAgICB0aGlzLm9uUHJvZ3Jlc3MoZi51aWQsIHsgc3JjOiBmLnNyYywgZmlsZW5hbWU6IGYubmFtZSwgcGVyY2VudExvYWRlZDogMCwgYnl0ZXNMb2FkZWQ6IDAsIGJ5dGVzVG90YWw6IGYuc2l6ZSB9KVxuICAgICAgX3Jlc3VsdHMucHVzaCh0aGlzLnBlcmZvcm1VcGxvYWQoZiwgdXJsKSlcbiAgICB9XG4gICAgcmV0dXJuIF9yZXN1bHRzXG4gIH1cblxuICBjcmVhdGVDT1JTUmVxdWVzdCAobWV0aG9kLCB1cmwpIHtcbiAgICB2YXIgeGhyXG4gICAgeGhyID0gbmV3IFhNTEh0dHBSZXF1ZXN0KClcbiAgICBpZiAoeGhyLndpdGhDcmVkZW50aWFscyAhPSBudWxsKSB7XG4gICAgICB4aHIub3BlbihtZXRob2QsIHVybCwgdHJ1ZSlcbiAgICB9IGVsc2UgaWYgKHR5cGVvZiBYRG9tYWluUmVxdWVzdCAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIHhociA9IG5ldyBYRG9tYWluUmVxdWVzdCgpXG4gICAgICB4aHIub3BlbihtZXRob2QsIHVybClcbiAgICB9IGVsc2Uge1xuICAgICAgeGhyID0gbnVsbFxuICAgIH1cbiAgICByZXR1cm4geGhyXG4gIH1cblxuICBwZXJmb3JtVXBsb2FkIChmaWxlLCB1cmwpIHtcbiAgICB2YXIgdGhpc19zM3VwbG9hZCwgeGhyXG4gICAgdGhpc19zM3VwbG9hZCA9IHRoaXNcbiAgICB1cmwgPSB1cmwgKyAodXJsLmluZGV4T2YoJz8nKSA9PT0gLTEgPyAnPycgOiAnJicpICsgJ2ZpbGU9JyArIGVuY29kZVVSSUNvbXBvbmVudChmaWxlLm5hbWUpXG4gICAgeGhyID0gdGhpcy5jcmVhdGVDT1JTUmVxdWVzdCgnUFVUJywgdXJsKVxuICAgIGlmICgheGhyKSB7XG4gICAgICB0aGlzLm9uRXJyb3IoZmlsZS51aWQsICdDT1JTIG5vdCBzdXBwb3J0ZWQnKVxuICAgIH0gZWxzZSB7XG4gICAgICB4aHIub25sb2FkID0gZnVuY3Rpb24gKGUpIHtcbiAgICAgICAgaWYgKHhoci5zdGF0dXMgPT09IDIwMCkge1xuICAgICAgICAgIHRoaXNfczN1cGxvYWQub25Db21wbGV0ZShmaWxlLnVpZCwgSlNPTi5wYXJzZShlLnRhcmdldC5yZXNwb25zZVRleHQpKVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHJldHVybiB0aGlzX3MzdXBsb2FkLm9uRXJyb3IoZmlsZS51aWQsIHhoci5zdGF0dXMgKyAnICcgKyB4aHIuc3RhdHVzVGV4dClcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgeGhyLm9uZXJyb3IgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiB0aGlzX3MzdXBsb2FkLm9uRXJyb3IoZmlsZS51aWQsICdVbmFibGUgdG8gcmVhY2ggc2VydmVyJylcbiAgICAgIH1cbiAgICAgIHhoci51cGxvYWQub25wcm9ncmVzcyA9IGZ1bmN0aW9uIChlKSB7XG4gICAgICAgIHZhciBwZXJjZW50TG9hZGVkXG4gICAgICAgIGlmIChlLmxlbmd0aENvbXB1dGFibGUpIHtcbiAgICAgICAgICBwZXJjZW50TG9hZGVkID0gTWF0aC5yb3VuZCgoZS5sb2FkZWQgLyBlLnRvdGFsKSAqIDEwMClcbiAgICAgICAgICByZXR1cm4gdGhpc19zM3VwbG9hZC5vblByb2dyZXNzKGZpbGUudWlkLCB7IHNyYzogZmlsZS5zcmMsIGZpbGVuYW1lOiBmaWxlLm5hbWUsIHBlcmNlbnRMb2FkZWQ6IHBlcmNlbnRMb2FkZWQsIGJ5dGVzTG9hZGVkOiBlLmxvYWRlZCwgYnl0ZXNUb3RhbDogZS50b3RhbCB9KVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiB4aHIuc2VuZChmaWxlKVxuICB9XG59XG4iLCIvKmdsb2JhbCAkKi9cbi8qZ2xvYmFsIHdpbmRvdyovXG4vKmdsb2JhbCBSZWFjdCovXG4vKmdsb2JhbCBSZWFjdERPTSovXG5cbmltcG9ydCB7IENPUlNVcGxvYWQgfSBmcm9tICcuL2NvcnNfdXBsb2FkJ1xuaW1wb3J0IHsgQm9vdHN0cmFwM0hlYWRlciwgQm9vdHN0cmFwM0ZpbGVQcmV2aWV3LCBCb290c3RyYXAzUGxhY2Vob2xkZXIgfSBmcm9tICcuL2Jvb3RzdHJhcDMnXG5cbmV4cG9ydCB2YXIgQXR0YWNoZUZpbGVJbnB1dCA9IFJlYWN0LmNyZWF0ZUNsYXNzKHtcbiAgZ2V0SW5pdGlhbFN0YXRlICgpIHtcbiAgICB2YXIgZmlsZXMgPSB7fVxuICAgIGlmICh0aGlzLnByb3BzWydkYXRhLXZhbHVlJ10pIHtcbiAgICAgICQuZWFjaChKU09OLnBhcnNlKHRoaXMucHJvcHNbJ2RhdGEtdmFsdWUnXSksIGZ1bmN0aW9uICh1aWQsIGpzb24pIHtcbiAgICAgICAgaWYgKGpzb24pIGZpbGVzW3VpZF0gPSBqc29uXG4gICAgICB9KVxuICAgIH1cbiAgICByZXR1cm4geyBmaWxlczogZmlsZXMsIGF0dGFjaGVzX2Rpc2NhcmRlZDogW10sIHVwbG9hZGluZzogMCB9XG4gIH0sXG5cbiAgb25SZW1vdmUgKHVpZCwgZSkge1xuICAgIGUucHJldmVudERlZmF1bHQoKVxuICAgIGUuc3RvcFByb3BhZ2F0aW9uKClcblxuICAgIHZhciBmaWVsZG5hbWUgPSBSZWFjdERPTS5maW5kRE9NTm9kZSh0aGlzKS5maXJzdENoaWxkLm5hbWUgLy8gd2hlbiAgICd1c2VyW2F2YXRhcl0nXG4gICAgdmFyIG5ld2ZpZWxkID0gZmllbGRuYW1lLnJlcGxhY2UoL1xcdytcXF0oXFxbXFxdfCkkLywgJ2F0dGFjaGVzX2Rpc2NhcmRlZF1bXScpIC8vIGJlY29tZSAndXNlclthdHRhY2hlc19kaXNjYXJkZWRdW10nXG5cbiAgICB0aGlzLnN0YXRlLmF0dGFjaGVzX2Rpc2NhcmRlZC5wdXNoKHsgZmllbGRuYW1lOiBuZXdmaWVsZCwgcGF0aDogdGhpcy5zdGF0ZS5maWxlc1t1aWRdLnBhdGggfSlcbiAgICBkZWxldGUgdGhpcy5zdGF0ZS5maWxlc1t1aWRdXG5cbiAgICB0aGlzLnNldFN0YXRlKHRoaXMuc3RhdGUpXG4gIH0sXG5cbiAgcGVyZm9ybVVwbG9hZCAoZmlsZV9lbGVtZW50LCBmaWxlcykge1xuICAgIC8vIHVzZXIgY2FuY2VsbGVkIGZpbGUgY2hvb3NlciBkaWFsb2cuIGlnbm9yZVxuICAgIGlmICghZmlsZXMgfHwgZmlsZXMubGVuZ3RoID09PSAwKSByZXR1cm5cbiAgICBpZiAoIXRoaXMucHJvcHMubXVsdGlwbGUpIHtcbiAgICAgIHRoaXMuc3RhdGUuZmlsZXMgPSB7fVxuICAgICAgZmlsZXMgPSBbZmlsZXNbMF1dIC8vIGFycmF5IG9mIDEgZWxlbWVudFxuICAgIH1cblxuICAgIHRoaXMuc2V0U3RhdGUodGhpcy5zdGF0ZSlcbiAgICAvLyB1cGxvYWQgdGhlIGZpbGUgdmlhIENPUlNcbiAgICB2YXIgdGhhdCA9IHRoaXNcblxuICAgIHRoYXQuc3RhdGUudXBsb2FkaW5nID0gdGhhdC5zdGF0ZS51cGxvYWRpbmcgKyBmaWxlcy5sZW5ndGhcbiAgICBpZiAoIXRoYXQuc3RhdGUuc3VibWl0X2J1dHRvbnMpIHRoYXQuc3RhdGUuc3VibWl0X2J1dHRvbnMgPSAkKFwiYnV0dG9uLGlucHV0W3R5cGU9J3N1Ym1pdCddXCIsICQoZmlsZV9lbGVtZW50KS5wYXJlbnRzKCdmb3JtJylbMF0pLmZpbHRlcignOm5vdCg6ZGlzYWJsZWQpJylcblxuICAgIHZhciB1cGxvYWQgPSBuZXcgQ09SU1VwbG9hZCh7XG4gICAgICBmaWxlX2VsZW1lbnQ6IGZpbGVfZWxlbWVudCxcbiAgICAgIGZpbGVzOiBmaWxlcyxcbiAgICAgIG9uUHJvZ3Jlc3M6IHRoaXMuc2V0RmlsZVZhbHVlLFxuICAgICAgb25Db21wbGV0ZSAoKSB7XG4gICAgICAgIHRoYXQuc3RhdGUudXBsb2FkaW5nLS1cbiAgICAgICAgdGhhdC5zZXRGaWxlVmFsdWUuYXBwbHkodGhpcywgYXJndW1lbnRzKVxuICAgICAgfSxcbiAgICAgIG9uRXJyb3IgKHVpZCwgc3RhdHVzKSB7XG4gICAgICAgIHRoYXQuc3RhdGUudXBsb2FkaW5nLS1cbiAgICAgICAgdGhhdC5zZXRGaWxlVmFsdWUodWlkLCB7IHBjdFN0cmluZzogJzkwJScsIHBjdERlc2M6IHN0YXR1cywgY2xhc3NOYW1lOiAncHJvZ3Jlc3MtYmFyIHByb2dyZXNzLWJhci1kYW5nZXInIH0pXG4gICAgICB9XG4gICAgfSlcbiAgICB1cGxvYWQuaGFuZGxlRmlsZVNlbGVjdCgpXG5cbiAgICAvLyB3ZSBkb24ndCB3YW50IHRoZSBmaWxlIGJpbmFyeSB0byBiZSB1cGxvYWRlZCBpbiB0aGUgbWFpbiBmb3JtXG4gICAgLy8gc28gdGhlIGFjdHVhbCBmaWxlIGlucHV0IGlzIG5ldXRlcmVkXG4gICAgZmlsZV9lbGVtZW50LnZhbHVlID0gJydcbiAgfSxcblxuICBvbkNoYW5nZSAoKSB7XG4gICAgdmFyIGZpbGVfZWxlbWVudCA9IFJlYWN0RE9NLmZpbmRET01Ob2RlKHRoaXMpLmZpcnN0Q2hpbGRcbiAgICB0aGlzLnBlcmZvcm1VcGxvYWQoZmlsZV9lbGVtZW50LCBmaWxlX2VsZW1lbnQgJiYgZmlsZV9lbGVtZW50LmZpbGVzKVxuICB9LFxuXG4gIG9uRHJhZ092ZXIgKGUpIHtcbiAgICBlLnN0b3BQcm9wYWdhdGlvbigpXG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpXG4gICAgJChSZWFjdERPTS5maW5kRE9NTm9kZSh0aGlzKSkuYWRkQ2xhc3MoJ2F0dGFjaGUtZHJhZ292ZXInKVxuICB9LFxuXG4gIG9uRHJhZ0xlYXZlIChlKSB7XG4gICAgZS5zdG9wUHJvcGFnYXRpb24oKVxuICAgIGUucHJldmVudERlZmF1bHQoKVxuICAgICQoUmVhY3RET00uZmluZERPTU5vZGUodGhpcykpLnJlbW92ZUNsYXNzKCdhdHRhY2hlLWRyYWdvdmVyJylcbiAgfSxcblxuICBvbkRyb3AgKGUpIHtcbiAgICBlLnN0b3BQcm9wYWdhdGlvbigpXG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpXG4gICAgdmFyIGZpbGVfZWxlbWVudCA9IFJlYWN0RE9NLmZpbmRET01Ob2RlKHRoaXMpLmZpcnN0Q2hpbGRcbiAgICB0aGlzLnBlcmZvcm1VcGxvYWQoZmlsZV9lbGVtZW50LCBlLnRhcmdldC5maWxlcyB8fCBlLmRhdGFUcmFuc2Zlci5maWxlcylcbiAgICAkKFJlYWN0RE9NLmZpbmRET01Ob2RlKHRoaXMpKS5yZW1vdmVDbGFzcygnYXR0YWNoZS1kcmFnb3ZlcicpXG4gIH0sXG5cbiAgc2V0RmlsZVZhbHVlIChrZXksIHZhbHVlKSB7XG4gICAgdGhpcy5zdGF0ZS5maWxlc1trZXldID0gdmFsdWVcbiAgICB0aGlzLnNldFN0YXRlKHRoaXMuc3RhdGUpXG4gIH0sXG5cbiAgcmVuZGVyICgpIHtcbiAgICB2YXIgdGhhdCA9IHRoaXNcbiAgICB2YXIgSGVhZGVyID0gd2luZG93LkF0dGFjaGVIZWFkZXIgfHwgQm9vdHN0cmFwM0hlYWRlclxuICAgIHZhciBGaWxlUHJldmlldyA9IHdpbmRvdy5BdHRhY2hlRmlsZVByZXZpZXcgfHwgQm9vdHN0cmFwM0ZpbGVQcmV2aWV3XG4gICAgdmFyIFBsYWNlaG9sZGVyID0gd2luZG93LkF0dGFjaGVQbGFjZWhvbGRlciB8fCBCb290c3RyYXAzUGxhY2Vob2xkZXJcblxuICAgIGlmICh0aGF0LnN0YXRlLnVwbG9hZGluZyA+IDApIHtcbiAgICAgIHRoYXQuc3RhdGUuc3VibWl0X2J1dHRvbnMuYXR0cignZGlzYWJsZWQnLCB0cnVlKVxuICAgIH0gZWxzZSBpZiAodGhhdC5zdGF0ZS5zdWJtaXRfYnV0dG9ucykge1xuICAgICAgdGhhdC5zdGF0ZS5zdWJtaXRfYnV0dG9ucy5hdHRyKCdkaXNhYmxlZCcsIG51bGwpXG4gICAgfVxuXG4gICAgdmFyIHByZXZpZXdzID0gW11cbiAgICAkLmVhY2godGhhdC5zdGF0ZS5maWxlcywgZnVuY3Rpb24gKGtleSwgcmVzdWx0KSB7XG4gICAgICAvLyBqc29uIGlzIGlucHV0W3ZhbHVlXSwgZHJvcCBub24gZXNzZW50aWFsIHZhbHVlc1xuICAgICAgdmFyIGNvcHkgPSBKU09OLnBhcnNlKEpTT04uc3RyaW5naWZ5KHJlc3VsdCkpXG4gICAgICBkZWxldGUgY29weS5zcmNcbiAgICAgIGRlbGV0ZSBjb3B5LmZpbGVuYW1lXG4gICAgICB2YXIganNvbiA9IEpTT04uc3RyaW5naWZ5KGNvcHkpXG4gICAgICAvL1xuICAgICAgcmVzdWx0Lm11bHRpcGxlID0gdGhhdC5wcm9wcy5tdWx0aXBsZVxuICAgICAgaWYgKHJlc3VsdC5wYXRoKSB7XG4gICAgICAgIHZhciBwYXJ0cyA9IHJlc3VsdC5wYXRoLnNwbGl0KCcvJylcbiAgICAgICAgcmVzdWx0LmZpbGVuYW1lID0gcGFydHMucG9wKCkuc3BsaXQoL1sjP10vKS5zaGlmdCgpXG4gICAgICAgIHBhcnRzLnB1c2goZW5jb2RlVVJJQ29tcG9uZW50KHRoYXQucHJvcHNbJ2RhdGEtZ2VvbWV0cnknXSB8fCAnMTI4eDEyOCMnKSlcbiAgICAgICAgcGFydHMucHVzaChlbmNvZGVVUklDb21wb25lbnQocmVzdWx0LmZpbGVuYW1lKSlcbiAgICAgICAgcmVzdWx0LnNyYyA9IHRoYXQucHJvcHNbJ2RhdGEtZG93bmxvYWR1cmwnXSArICcvJyArIHBhcnRzLmpvaW4oJy8nKVxuICAgICAgfVxuICAgICAgdmFyIHByZXZpZXdLZXkgPSAncHJldmlldycgKyBrZXlcbiAgICAgIHByZXZpZXdzLnB1c2goXG4gICAgICAgIDxkaXYga2V5PXtwcmV2aWV3S2V5fSBjbGFzc05hbWU9XCJhdHRhY2hlLWZpbGUtaW5wdXRcIj5cbiAgICAgICAgICA8aW5wdXRcbiAgICAgICAgICAgIHR5cGU9XCJoaWRkZW5cIlxuICAgICAgICAgICAgbmFtZT17dGhhdC5wcm9wcy5uYW1lfVxuICAgICAgICAgICAgdmFsdWU9e2pzb259XG4gICAgICAgICAgICByZWFkT25seT1cInRydWVcIiAvPlxuICAgICAgICAgIDxGaWxlUHJldmlldyB7Li4ucmVzdWx0fSBrZXk9e2tleX0gb25SZW1vdmU9e3RoYXQub25SZW1vdmUuYmluZCh0aGF0LCBrZXkpfSAvPlxuICAgICAgICA8L2Rpdj5cbiAgICAgIClcbiAgICB9KVxuXG4gICAgdmFyIHBsYWNlaG9sZGVycyA9IFtdXG4gICAgaWYgKHByZXZpZXdzLmxlbmd0aCA9PT0gMCAmJiB0aGF0LnByb3BzWydkYXRhLXBsYWNlaG9sZGVyJ10pIHtcbiAgICAgICQuZWFjaChKU09OLnBhcnNlKHRoYXQucHJvcHNbJ2RhdGEtcGxhY2Vob2xkZXInXSksIGZ1bmN0aW9uICh1aWQsIHNyYykge1xuICAgICAgICBwbGFjZWhvbGRlcnMucHVzaChcbiAgICAgICAgICA8UGxhY2Vob2xkZXIga2V5PVwicGxhY2Vob2xkZXJcIiB7Li4udGhhdC5wcm9wc30gc3JjPXtzcmN9IC8+XG4gICAgICAgIClcbiAgICAgIH0pXG4gICAgfVxuXG4gICAgdmFyIGRpc2NhcmRzID0gW11cbiAgICAkLmVhY2godGhhdC5zdGF0ZS5hdHRhY2hlc19kaXNjYXJkZWQsIGZ1bmN0aW9uIChpbmRleCwgZGlzY2FyZCkge1xuICAgICAgdmFyIGRpc2NhcmRLZXkgPSAnZGlzY2FyZCcgKyBkaXNjYXJkLnBhdGhcbiAgICAgIGRpc2NhcmRzLnB1c2goXG4gICAgICAgIDxpbnB1dFxuICAgICAgICAgIGtleT17ZGlzY2FyZEtleX1cbiAgICAgICAgICB0eXBlPVwiaGlkZGVuXCJcbiAgICAgICAgICBuYW1lPXtkaXNjYXJkLmZpZWxkbmFtZX1cbiAgICAgICAgICB2YWx1ZT17ZGlzY2FyZC5wYXRofSAvPlxuICAgICAgKVxuICAgIH0pXG5cbiAgICB2YXIgY2xhc3NOYW1lID0gWydhdHRhY2hlLWZpbGUtc2VsZWN0b3InLCAnYXR0YWNoZS1wbGFjZWhvbGRlcnMtY291bnQtJyArIHBsYWNlaG9sZGVycy5sZW5ndGgsICdhdHRhY2hlLXByZXZpZXdzLWNvdW50LScgKyBwcmV2aWV3cy5sZW5ndGgsIHRoaXMucHJvcHNbJ2RhdGEtY2xhc3NuYW1lJ11dLmpvaW4oJyAnKS50cmltKClcbiAgICByZXR1cm4gKFxuICAgIDxsYWJlbFxuICAgICAgaHRtbEZvcj17dGhhdC5wcm9wcy5pZH1cbiAgICAgIGNsYXNzTmFtZT17Y2xhc3NOYW1lfVxuICAgICAgb25EcmFnT3Zlcj17dGhpcy5vbkRyYWdPdmVyfVxuICAgICAgb25EcmFnTGVhdmU9e3RoaXMub25EcmFnTGVhdmV9XG4gICAgICBvbkRyb3A9e3RoaXMub25Ecm9wfT5cbiAgICAgIDxpbnB1dCB0eXBlPVwiZmlsZVwiIHsuLi50aGF0LnByb3BzfSBvbkNoYW5nZT17dGhpcy5vbkNoYW5nZX0gLz5cbiAgICAgIDxpbnB1dCB0eXBlPVwiaGlkZGVuXCIgbmFtZT17dGhhdC5wcm9wcy5uYW1lfSB2YWx1ZT1cIlwiIC8+XG4gICAgICA8SGVhZGVyIHsuLi50aGF0LnByb3BzfSAvPlxuICAgICAge3ByZXZpZXdzfVxuICAgICAge3BsYWNlaG9sZGVyc31cbiAgICAgIHtkaXNjYXJkc31cbiAgICA8L2xhYmVsPlxuICAgIClcbiAgfVxufSlcbiJdfQ==
|
data/app/index.html
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div class="container">
|
11
|
+
<div class="row marketing">
|
12
|
+
<div class="col-sm-12">
|
13
|
+
<div class="page-header">
|
14
|
+
<h1>Test Form</h1>
|
15
|
+
</div>
|
16
|
+
<div class="panel">
|
17
|
+
<form class="form-horizontal">
|
18
|
+
<div class="form-group">
|
19
|
+
<label for="inputFile" class="col-sm-2 control-label">File</label>
|
20
|
+
<div class="col-sm-10">
|
21
|
+
<input type="file" class="form-control enable-attache" id="inputFile" placeholder="File">
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
<div class="form-group">
|
25
|
+
<div class="col-sm-offset-2 col-sm-10">
|
26
|
+
<button type="submit" class="btn btn-default">Sign in</button>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</form>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.js"></script>
|
35
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.js"></script>
|
36
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.js"></script>
|
37
|
+
<script src="assets/javascripts/attache.js"></script>
|
38
|
+
</body>
|
39
|
+
</html>
|
data/lib/attache/rails/model.rb
CHANGED
@@ -14,9 +14,13 @@ module Attache
|
|
14
14
|
|
15
15
|
# `discard` management
|
16
16
|
base.class_eval do
|
17
|
+
attr_accessor :attaches_to_backup
|
17
18
|
attr_accessor :attaches_discarded
|
18
|
-
after_commit
|
19
|
-
instance.attaches_discard!(instance.attaches_discarded)
|
19
|
+
after_commit do |instance|
|
20
|
+
instance.attaches_discard!(instance.attaches_discarded) if instance.attaches_discarded.present?
|
21
|
+
instance.attaches_discarded = []
|
22
|
+
instance.attaches_backup!(instance.attaches_to_backup) if instance.attaches_to_backup.present?
|
23
|
+
instance.attaches_to_backup = []
|
20
24
|
end
|
21
25
|
end
|
22
26
|
end
|
@@ -36,8 +40,8 @@ module Attache
|
|
36
40
|
define_method "#{name}_url", -> (geometry) { attache_field_urls(self.send(name), geometry).try(:first) }
|
37
41
|
define_method "#{name}_attributes", -> (geometry) { attache_field_attributes(self.send(name), geometry).try(:first) }
|
38
42
|
define_method "#{name}=", -> (value) { super(attache_field_set(Array.wrap(value)).try(:first)) }
|
39
|
-
|
40
|
-
after_destroy -> {
|
43
|
+
after_save -> { attache_update_pending_diffs(self.send("#{name}_was"), self.send("#{name}"), self.attaches_to_backup ||= [], self.attaches_discarded ||= []) }
|
44
|
+
after_destroy -> { attache_update_pending_diffs(self.send("#{name}_was"), [], self.attaches_to_backup ||= [], self.attaches_discarded ||= []) }
|
41
45
|
end
|
42
46
|
|
43
47
|
def has_many_attaches(name)
|
@@ -46,8 +50,8 @@ module Attache
|
|
46
50
|
define_method "#{name}_urls", -> (geometry) { attache_field_urls(self.send(name), geometry) }
|
47
51
|
define_method "#{name}_attributes", -> (geometry) { attache_field_attributes(self.send(name), geometry) }
|
48
52
|
define_method "#{name}=", -> (value) { super(attache_field_set(Array.wrap(value))) }
|
49
|
-
|
50
|
-
after_destroy -> {
|
53
|
+
after_save -> { attache_update_pending_diffs(self.send("#{name}_was"), self.send("#{name}"), self.attaches_to_backup ||= [], self.attaches_discarded ||= []) }
|
54
|
+
after_destroy -> { attache_update_pending_diffs(self.send("#{name}_was"), [], self.attaches_to_backup ||= [], self.attaches_discarded ||= []) }
|
51
55
|
end
|
52
56
|
end
|
53
57
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attache-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- choonkeat
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 1.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 1.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: httpclient
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -119,12 +119,7 @@ files:
|
|
119
119
|
- README.md
|
120
120
|
- Rakefile
|
121
121
|
- app/assets/javascripts/attache.js
|
122
|
-
- app/
|
123
|
-
- app/assets/javascripts/attache/bootstrap3.js.jsx
|
124
|
-
- app/assets/javascripts/attache/cors_upload.js
|
125
|
-
- app/assets/javascripts/attache/file_input.js
|
126
|
-
- app/assets/javascripts/attache/file_input.js.jsx
|
127
|
-
- app/assets/javascripts/attache/ujs.js
|
122
|
+
- app/index.html
|
128
123
|
- lib/attache/rails.rb
|
129
124
|
- lib/attache/rails/engine.rb
|
130
125
|
- lib/attache/rails/model.rb
|
@@ -1,77 +0,0 @@
|
|
1
|
-
if (typeof AttacheFilePreview === 'undefined') {
|
2
|
-
|
3
|
-
var AttacheFilePreview = React.createClass({displayName: "AttacheFilePreview",
|
4
|
-
|
5
|
-
getInitialState: function() {
|
6
|
-
return { srcWas: '' };
|
7
|
-
},
|
8
|
-
|
9
|
-
onSrcLoaded: function() {
|
10
|
-
this.setState({ srcWas: this.props.src });
|
11
|
-
},
|
12
|
-
|
13
|
-
render: function() {
|
14
|
-
var previewClassName = "attache-file-preview";
|
15
|
-
|
16
|
-
// progressbar
|
17
|
-
if (this.state.srcWas != this.props.src) {
|
18
|
-
previewClassName = previewClassName + " attache-loading";
|
19
|
-
var className = this.props.className || "progress-bar progress-bar-striped active" + (this.props.src ? " progress-bar-success" : "");
|
20
|
-
var pctString = this.props.pctString || (this.props.src ? 100 : this.props.percentLoaded) + "%";
|
21
|
-
var pctDesc = this.props.pctDesc || (this.props.src ? 'Loading...' : pctString);
|
22
|
-
var pctStyle = { width: pctString, minWidth: '3em' };
|
23
|
-
var progress = (
|
24
|
-
React.createElement("div", {className: "progress"},
|
25
|
-
React.createElement("div", {className: className, role: "progressbar", "aria-valuenow": this.props.percentLoaded, "aria-valuemin": "0", "aria-valuemax": "100", style: pctStyle},
|
26
|
-
pctDesc
|
27
|
-
)
|
28
|
-
)
|
29
|
-
);
|
30
|
-
}
|
31
|
-
|
32
|
-
// img tag
|
33
|
-
if (this.props.src) {
|
34
|
-
var img = React.createElement("img", {src: this.props.src, onLoad: this.onSrcLoaded});
|
35
|
-
}
|
36
|
-
|
37
|
-
// combined
|
38
|
-
return (
|
39
|
-
React.createElement("div", {className: previewClassName},
|
40
|
-
progress,
|
41
|
-
img,
|
42
|
-
React.createElement("div", {className: "clearfix"},
|
43
|
-
React.createElement("div", {className: "pull-left"}, this.props.filename),
|
44
|
-
React.createElement("a", {href: "#remove", className: "pull-right", onClick: this.props.onRemove, title: "Click to remove"}, "×")
|
45
|
-
)
|
46
|
-
)
|
47
|
-
);
|
48
|
-
}
|
49
|
-
});
|
50
|
-
|
51
|
-
}
|
52
|
-
|
53
|
-
if (typeof AttachePlaceholder === 'undefined') {
|
54
|
-
|
55
|
-
var AttachePlaceholder = React.createClass({displayName: "AttachePlaceholder",
|
56
|
-
render: function() {
|
57
|
-
return (
|
58
|
-
React.createElement("div", {className: "attache-file-preview"},
|
59
|
-
React.createElement("img", {src: this.props.src})
|
60
|
-
)
|
61
|
-
);
|
62
|
-
}
|
63
|
-
});
|
64
|
-
|
65
|
-
}
|
66
|
-
|
67
|
-
if (typeof AttacheHeader === 'undefined') {
|
68
|
-
|
69
|
-
var AttacheHeader = React.createClass({displayName: "AttacheHeader",
|
70
|
-
render: function() {
|
71
|
-
return (
|
72
|
-
React.createElement("noscript", null)
|
73
|
-
);
|
74
|
-
}
|
75
|
-
});
|
76
|
-
|
77
|
-
}
|
@@ -1,77 +0,0 @@
|
|
1
|
-
if (typeof AttacheFilePreview === 'undefined') {
|
2
|
-
|
3
|
-
var AttacheFilePreview = React.createClass({
|
4
|
-
|
5
|
-
getInitialState: function() {
|
6
|
-
return { srcWas: '' };
|
7
|
-
},
|
8
|
-
|
9
|
-
onSrcLoaded: function() {
|
10
|
-
this.setState({ srcWas: this.props.src });
|
11
|
-
},
|
12
|
-
|
13
|
-
render: function() {
|
14
|
-
var previewClassName = "attache-file-preview";
|
15
|
-
|
16
|
-
// progressbar
|
17
|
-
if (this.state.srcWas != this.props.src) {
|
18
|
-
previewClassName = previewClassName + " attache-loading";
|
19
|
-
var className = this.props.className || "progress-bar progress-bar-striped active" + (this.props.src ? " progress-bar-success" : "");
|
20
|
-
var pctString = this.props.pctString || (this.props.src ? 100 : this.props.percentLoaded) + "%";
|
21
|
-
var pctDesc = this.props.pctDesc || (this.props.src ? 'Loading...' : pctString);
|
22
|
-
var pctStyle = { width: pctString, minWidth: '3em' };
|
23
|
-
var progress = (
|
24
|
-
<div className="progress">
|
25
|
-
<div className={className} role="progressbar" aria-valuenow={this.props.percentLoaded} aria-valuemin="0" aria-valuemax="100" style={pctStyle}>
|
26
|
-
{pctDesc}
|
27
|
-
</div>
|
28
|
-
</div>
|
29
|
-
);
|
30
|
-
}
|
31
|
-
|
32
|
-
// img tag
|
33
|
-
if (this.props.src) {
|
34
|
-
var img = <img src={this.props.src} onLoad={this.onSrcLoaded} />;
|
35
|
-
}
|
36
|
-
|
37
|
-
// combined
|
38
|
-
return (
|
39
|
-
<div className={previewClassName}>
|
40
|
-
{progress}
|
41
|
-
{img}
|
42
|
-
<div className="clearfix">
|
43
|
-
<div className="pull-left">{this.props.filename}</div>
|
44
|
-
<a href="#remove" className="pull-right" onClick={this.props.onRemove} title="Click to remove">×</a>
|
45
|
-
</div>
|
46
|
-
</div>
|
47
|
-
);
|
48
|
-
}
|
49
|
-
});
|
50
|
-
|
51
|
-
}
|
52
|
-
|
53
|
-
if (typeof AttachePlaceholder === 'undefined') {
|
54
|
-
|
55
|
-
var AttachePlaceholder = React.createClass({
|
56
|
-
render: function() {
|
57
|
-
return (
|
58
|
-
<div className="attache-file-preview">
|
59
|
-
<img src={this.props.src} />
|
60
|
-
</div>
|
61
|
-
);
|
62
|
-
}
|
63
|
-
});
|
64
|
-
|
65
|
-
}
|
66
|
-
|
67
|
-
if (typeof AttacheHeader === 'undefined') {
|
68
|
-
|
69
|
-
var AttacheHeader = React.createClass({
|
70
|
-
render: function() {
|
71
|
-
return (
|
72
|
-
<noscript />
|
73
|
-
);
|
74
|
-
}
|
75
|
-
});
|
76
|
-
|
77
|
-
}
|
@@ -1,87 +0,0 @@
|
|
1
|
-
var AttacheCORSUpload = (function() {
|
2
|
-
var counter = 0;
|
3
|
-
|
4
|
-
AttacheCORSUpload.prototype.onComplete = function(uid, json) { };
|
5
|
-
AttacheCORSUpload.prototype.onProgress = function(uid, json) { };
|
6
|
-
AttacheCORSUpload.prototype.onError = function(uid, status) { alert(status); };
|
7
|
-
AttacheCORSUpload.prototype.createLocalThumbnail = function() { }; // for overwriting
|
8
|
-
|
9
|
-
function AttacheCORSUpload(options) {
|
10
|
-
if (options == null) options = {};
|
11
|
-
for (option in options) {
|
12
|
-
this[option] = options[option];
|
13
|
-
}
|
14
|
-
this.handleFileSelect(options.file_element, options.files);
|
15
|
-
}
|
16
|
-
|
17
|
-
AttacheCORSUpload.prototype.handleFileSelect = function(file_element, files) {
|
18
|
-
var f, output, _i, _len, _results, url, $ele, prefix;
|
19
|
-
$ele = $(file_element);
|
20
|
-
url = $ele.data('uploadurl');
|
21
|
-
if ($ele.data('hmac')) {
|
22
|
-
url = url +
|
23
|
-
"?hmac=" + encodeURIComponent($ele.data('hmac')) +
|
24
|
-
"&uuid=" + encodeURIComponent($ele.data('uuid')) +
|
25
|
-
"&expiration=" + encodeURIComponent($ele.data('expiration')) +
|
26
|
-
""
|
27
|
-
}
|
28
|
-
|
29
|
-
prefix = Date.now() + "_";
|
30
|
-
output = [];
|
31
|
-
_results = [];
|
32
|
-
for (_i = 0, _len = files.length; _i < _len; _i++) {
|
33
|
-
f = files[_i];
|
34
|
-
this.createLocalThumbnail(f); // if any
|
35
|
-
f.uid = prefix + (counter++);
|
36
|
-
this.onProgress(f.uid, { src: f.src, filename: f.name, percentLoaded: 0, bytesLoaded: 0, bytesTotal: f.size });
|
37
|
-
_results.push(this.performUpload(f, url));
|
38
|
-
}
|
39
|
-
return _results;
|
40
|
-
};
|
41
|
-
|
42
|
-
AttacheCORSUpload.prototype.createCORSRequest = function(method, url) {
|
43
|
-
var xhr;
|
44
|
-
xhr = new XMLHttpRequest();
|
45
|
-
if (xhr.withCredentials != null) {
|
46
|
-
xhr.open(method, url, true);
|
47
|
-
} else if (typeof XDomainRequest !== "undefined") {
|
48
|
-
xhr = new XDomainRequest();
|
49
|
-
xhr.open(method, url);
|
50
|
-
} else {
|
51
|
-
xhr = null;
|
52
|
-
}
|
53
|
-
return xhr;
|
54
|
-
};
|
55
|
-
|
56
|
-
AttacheCORSUpload.prototype.performUpload = function(file, url) {
|
57
|
-
var this_s3upload, xhr;
|
58
|
-
this_s3upload = this;
|
59
|
-
url = url + (url.indexOf('?') == -1 ? '?' : '&') + 'file=' + encodeURIComponent(file.name);
|
60
|
-
xhr = this.createCORSRequest('PUT', url);
|
61
|
-
if (!xhr) {
|
62
|
-
this.onError(file.uid, 'CORS not supported');
|
63
|
-
} else {
|
64
|
-
xhr.onload = function(e) {
|
65
|
-
if (xhr.status === 200) {
|
66
|
-
this_s3upload.onComplete(file.uid, JSON.parse(e.target.responseText));
|
67
|
-
} else {
|
68
|
-
return this_s3upload.onError(file.uid, xhr.status + ' ' + xhr.statusText);
|
69
|
-
}
|
70
|
-
};
|
71
|
-
xhr.onerror = function() {
|
72
|
-
return this_s3upload.onError(file.uid, 'Unable to reach server');
|
73
|
-
};
|
74
|
-
xhr.upload.onprogress = function(e) {
|
75
|
-
var percentLoaded;
|
76
|
-
if (e.lengthComputable) {
|
77
|
-
percentLoaded = Math.round((e.loaded / e.total) * 100);
|
78
|
-
return this_s3upload.onProgress(file.uid, { src: file.src, filename: file.name, percentLoaded: percentLoaded, bytesLoaded: e.loaded, bytesTotal: e.total });
|
79
|
-
}
|
80
|
-
};
|
81
|
-
}
|
82
|
-
return xhr.send(file);
|
83
|
-
};
|
84
|
-
|
85
|
-
return AttacheCORSUpload;
|
86
|
-
|
87
|
-
})();
|
@@ -1,153 +0,0 @@
|
|
1
|
-
var AttacheFileInput = React.createClass({displayName: "AttacheFileInput",
|
2
|
-
|
3
|
-
getInitialState: function() {
|
4
|
-
var files = {};
|
5
|
-
if (this.props['data-value']) $.each(JSON.parse(this.props['data-value']), function(uid, json) {
|
6
|
-
if (json) files[uid] = json;
|
7
|
-
});
|
8
|
-
return {files: files, attaches_discarded: [], uploading: 0 };
|
9
|
-
},
|
10
|
-
|
11
|
-
onRemove: function(uid, e) {
|
12
|
-
e.preventDefault();
|
13
|
-
e.stopPropagation();
|
14
|
-
|
15
|
-
var fieldname = this.getDOMNode().firstChild.name; // when 'user[avatar]'
|
16
|
-
var newfield = fieldname.replace(/\w+\](\[\]|)$/, 'attaches_discarded][]'); // become 'user[attaches_discarded][]'
|
17
|
-
|
18
|
-
this.state.attaches_discarded.push({ fieldname: newfield, path: this.state.files[uid].path });
|
19
|
-
delete this.state.files[uid];
|
20
|
-
|
21
|
-
this.setState(this.state);
|
22
|
-
},
|
23
|
-
|
24
|
-
performUpload: function(file_element, files) {
|
25
|
-
// user cancelled file chooser dialog. ignore
|
26
|
-
if (! files || files.length == 0) return;
|
27
|
-
if (! this.props.multiple) {
|
28
|
-
this.state.files = {};
|
29
|
-
files = [files[0]]; // array of 1 element
|
30
|
-
}
|
31
|
-
|
32
|
-
this.setState(this.state);
|
33
|
-
// upload the file via CORS
|
34
|
-
var that = this;
|
35
|
-
|
36
|
-
that.state.uploading = that.state.uploading + files.length;
|
37
|
-
if (! that.state.submit_buttons) that.state.submit_buttons = $("button,input[type='submit']", $(file_element).parents('form')[0]).filter(':not(:disabled)');
|
38
|
-
|
39
|
-
new AttacheCORSUpload({
|
40
|
-
file_element: file_element,
|
41
|
-
files: files,
|
42
|
-
onComplete: function() {
|
43
|
-
that.state.uploading--;
|
44
|
-
that.setFileValue.apply(this, arguments);
|
45
|
-
},
|
46
|
-
onProgress: this.setFileValue,
|
47
|
-
onError: function(uid, status) {
|
48
|
-
that.state.uploading--;
|
49
|
-
that.setFileValue(uid, { pctString: '90%', pctDesc: status, className: 'progress-bar progress-bar-danger' });
|
50
|
-
}
|
51
|
-
});
|
52
|
-
|
53
|
-
// we don't want the file binary to be uploaded in the main form
|
54
|
-
// so the actual file input is neutered
|
55
|
-
file_element.value = '';
|
56
|
-
},
|
57
|
-
|
58
|
-
onChange: function() {
|
59
|
-
var file_element = this.getDOMNode().firstChild;
|
60
|
-
this.performUpload(file_element, file_element && file_element.files);
|
61
|
-
},
|
62
|
-
|
63
|
-
onDragOver: function(e) {
|
64
|
-
e.stopPropagation();
|
65
|
-
e.preventDefault();
|
66
|
-
$(this.getDOMNode()).addClass('attache-dragover');
|
67
|
-
},
|
68
|
-
|
69
|
-
onDragLeave: function(e) {
|
70
|
-
e.stopPropagation();
|
71
|
-
e.preventDefault();
|
72
|
-
$(this.getDOMNode()).removeClass('attache-dragover');
|
73
|
-
},
|
74
|
-
|
75
|
-
onDrop: function(e) {
|
76
|
-
e.stopPropagation();
|
77
|
-
e.preventDefault();
|
78
|
-
var file_element = this.getDOMNode().firstChild;
|
79
|
-
this.performUpload(file_element, e.target.files || e.dataTransfer.files);
|
80
|
-
$(this.getDOMNode()).removeClass('attache-dragover');
|
81
|
-
},
|
82
|
-
|
83
|
-
setFileValue: function(key, value) {
|
84
|
-
this.state.files[key] = value;
|
85
|
-
this.setState(this.state);
|
86
|
-
},
|
87
|
-
|
88
|
-
render: function() {
|
89
|
-
var that = this;
|
90
|
-
|
91
|
-
if (that.state.uploading > 0) {
|
92
|
-
that.state.submit_buttons.attr('disabled', true);
|
93
|
-
} else if (that.state.submit_buttons) {
|
94
|
-
that.state.submit_buttons.attr('disabled', null);
|
95
|
-
}
|
96
|
-
|
97
|
-
var Header = eval(this.props['data-header-component'] || 'AttacheHeader');
|
98
|
-
var Preview = eval(this.props['data-preview-component'] || 'AttacheFilePreview');
|
99
|
-
var Placeholder = eval(this.props['data-placeholder-component'] || 'AttachePlaceholder');
|
100
|
-
|
101
|
-
var previews = [];
|
102
|
-
$.each(that.state.files, function(key, result) {
|
103
|
-
// json is input[value], drop non essential values
|
104
|
-
var copy = JSON.parse(JSON.stringify(result));
|
105
|
-
delete copy.src;
|
106
|
-
delete copy.filename;
|
107
|
-
var json = JSON.stringify(copy);
|
108
|
-
//
|
109
|
-
result.multiple = that.props.multiple;
|
110
|
-
if (result.path) {
|
111
|
-
var parts = result.path.split('/');
|
112
|
-
result.filename = parts.pop().split(/[#?]/).shift();
|
113
|
-
parts.push(encodeURIComponent(that.props['data-geometry'] || '128x128#'));
|
114
|
-
parts.push(encodeURIComponent(result.filename));
|
115
|
-
result.src = that.props['data-downloadurl'] + '/' + parts.join('/');
|
116
|
-
}
|
117
|
-
var previewKey = "preview" + key;
|
118
|
-
previews.push(
|
119
|
-
React.createElement("div", {key: previewKey, className: "attache-file-input"},
|
120
|
-
React.createElement("input", {type: "hidden", name: that.props.name, value: json, readOnly: "true"}),
|
121
|
-
React.createElement(Preview, React.__spread({}, result, {key: key, onRemove: that.onRemove.bind(that, key)}))
|
122
|
-
)
|
123
|
-
);
|
124
|
-
});
|
125
|
-
|
126
|
-
var placeholders = [];
|
127
|
-
if (previews.length == 0 && that.props['data-placeholder']) $.each(JSON.parse(that.props['data-placeholder']), function(uid, src) {
|
128
|
-
placeholders.push(
|
129
|
-
React.createElement(Placeholder, React.__spread({key: "placeholder"}, that.props, {src: src}))
|
130
|
-
);
|
131
|
-
});
|
132
|
-
|
133
|
-
var discards = [];
|
134
|
-
$.each(that.state.attaches_discarded, function(index, discard) {
|
135
|
-
var discardKey = "discard" + discard.path;
|
136
|
-
discards.push(
|
137
|
-
React.createElement("input", {key: discardKey, type: "hidden", name: discard.fieldname, value: discard.path})
|
138
|
-
);
|
139
|
-
});
|
140
|
-
|
141
|
-
var className = ["attache-file-selector", "attache-placeholders-count-" + placeholders.length, "attache-previews-count-" + previews.length, this.props['data-classname']].join(' ').trim();
|
142
|
-
return (
|
143
|
-
React.createElement("label", {htmlFor: that.props.id, className: className, onDragOver: this.onDragOver, onDragLeave: this.onDragLeave, onDrop: this.onDrop},
|
144
|
-
React.createElement("input", React.__spread({type: "file"}, that.props, {onChange: this.onChange})),
|
145
|
-
React.createElement("input", {type: "hidden", name: that.props.name, value: ""}),
|
146
|
-
React.createElement(Header, React.__spread({}, that.props)),
|
147
|
-
previews,
|
148
|
-
placeholders,
|
149
|
-
discards
|
150
|
-
)
|
151
|
-
);
|
152
|
-
}
|
153
|
-
});
|
@@ -1,153 +0,0 @@
|
|
1
|
-
var AttacheFileInput = React.createClass({
|
2
|
-
|
3
|
-
getInitialState: function() {
|
4
|
-
var files = {};
|
5
|
-
if (this.props['data-value']) $.each(JSON.parse(this.props['data-value']), function(uid, json) {
|
6
|
-
if (json) files[uid] = json;
|
7
|
-
});
|
8
|
-
return {files: files, attaches_discarded: [], uploading: 0 };
|
9
|
-
},
|
10
|
-
|
11
|
-
onRemove: function(uid, e) {
|
12
|
-
e.preventDefault();
|
13
|
-
e.stopPropagation();
|
14
|
-
|
15
|
-
var fieldname = this.getDOMNode().firstChild.name; // when 'user[avatar]'
|
16
|
-
var newfield = fieldname.replace(/\w+\](\[\]|)$/, 'attaches_discarded][]'); // become 'user[attaches_discarded][]'
|
17
|
-
|
18
|
-
this.state.attaches_discarded.push({ fieldname: newfield, path: this.state.files[uid].path });
|
19
|
-
delete this.state.files[uid];
|
20
|
-
|
21
|
-
this.setState(this.state);
|
22
|
-
},
|
23
|
-
|
24
|
-
performUpload: function(file_element, files) {
|
25
|
-
// user cancelled file chooser dialog. ignore
|
26
|
-
if (! files || files.length == 0) return;
|
27
|
-
if (! this.props.multiple) {
|
28
|
-
this.state.files = {};
|
29
|
-
files = [files[0]]; // array of 1 element
|
30
|
-
}
|
31
|
-
|
32
|
-
this.setState(this.state);
|
33
|
-
// upload the file via CORS
|
34
|
-
var that = this;
|
35
|
-
|
36
|
-
that.state.uploading = that.state.uploading + files.length;
|
37
|
-
if (! that.state.submit_buttons) that.state.submit_buttons = $("button,input[type='submit']", $(file_element).parents('form')[0]).filter(':not(:disabled)');
|
38
|
-
|
39
|
-
new AttacheCORSUpload({
|
40
|
-
file_element: file_element,
|
41
|
-
files: files,
|
42
|
-
onComplete: function() {
|
43
|
-
that.state.uploading--;
|
44
|
-
that.setFileValue.apply(this, arguments);
|
45
|
-
},
|
46
|
-
onProgress: this.setFileValue,
|
47
|
-
onError: function(uid, status) {
|
48
|
-
that.state.uploading--;
|
49
|
-
that.setFileValue(uid, { pctString: '90%', pctDesc: status, className: 'progress-bar progress-bar-danger' });
|
50
|
-
}
|
51
|
-
});
|
52
|
-
|
53
|
-
// we don't want the file binary to be uploaded in the main form
|
54
|
-
// so the actual file input is neutered
|
55
|
-
file_element.value = '';
|
56
|
-
},
|
57
|
-
|
58
|
-
onChange: function() {
|
59
|
-
var file_element = this.getDOMNode().firstChild;
|
60
|
-
this.performUpload(file_element, file_element && file_element.files);
|
61
|
-
},
|
62
|
-
|
63
|
-
onDragOver: function(e) {
|
64
|
-
e.stopPropagation();
|
65
|
-
e.preventDefault();
|
66
|
-
$(this.getDOMNode()).addClass('attache-dragover');
|
67
|
-
},
|
68
|
-
|
69
|
-
onDragLeave: function(e) {
|
70
|
-
e.stopPropagation();
|
71
|
-
e.preventDefault();
|
72
|
-
$(this.getDOMNode()).removeClass('attache-dragover');
|
73
|
-
},
|
74
|
-
|
75
|
-
onDrop: function(e) {
|
76
|
-
e.stopPropagation();
|
77
|
-
e.preventDefault();
|
78
|
-
var file_element = this.getDOMNode().firstChild;
|
79
|
-
this.performUpload(file_element, e.target.files || e.dataTransfer.files);
|
80
|
-
$(this.getDOMNode()).removeClass('attache-dragover');
|
81
|
-
},
|
82
|
-
|
83
|
-
setFileValue: function(key, value) {
|
84
|
-
this.state.files[key] = value;
|
85
|
-
this.setState(this.state);
|
86
|
-
},
|
87
|
-
|
88
|
-
render: function() {
|
89
|
-
var that = this;
|
90
|
-
|
91
|
-
if (that.state.uploading > 0) {
|
92
|
-
that.state.submit_buttons.attr('disabled', true);
|
93
|
-
} else if (that.state.submit_buttons) {
|
94
|
-
that.state.submit_buttons.attr('disabled', null);
|
95
|
-
}
|
96
|
-
|
97
|
-
var Header = eval(this.props['data-header-component'] || 'AttacheHeader');
|
98
|
-
var Preview = eval(this.props['data-preview-component'] || 'AttacheFilePreview');
|
99
|
-
var Placeholder = eval(this.props['data-placeholder-component'] || 'AttachePlaceholder');
|
100
|
-
|
101
|
-
var previews = [];
|
102
|
-
$.each(that.state.files, function(key, result) {
|
103
|
-
// json is input[value], drop non essential values
|
104
|
-
var copy = JSON.parse(JSON.stringify(result));
|
105
|
-
delete copy.src;
|
106
|
-
delete copy.filename;
|
107
|
-
var json = JSON.stringify(copy);
|
108
|
-
//
|
109
|
-
result.multiple = that.props.multiple;
|
110
|
-
if (result.path) {
|
111
|
-
var parts = result.path.split('/');
|
112
|
-
result.filename = parts.pop().split(/[#?]/).shift();
|
113
|
-
parts.push(encodeURIComponent(that.props['data-geometry'] || '128x128#'));
|
114
|
-
parts.push(encodeURIComponent(result.filename));
|
115
|
-
result.src = that.props['data-downloadurl'] + '/' + parts.join('/');
|
116
|
-
}
|
117
|
-
var previewKey = "preview" + key;
|
118
|
-
previews.push(
|
119
|
-
<div key={previewKey} className="attache-file-input">
|
120
|
-
<input type="hidden" name={that.props.name} value={json} readOnly="true" />
|
121
|
-
<Preview {...result} key={key} onRemove={that.onRemove.bind(that, key)}/>
|
122
|
-
</div>
|
123
|
-
);
|
124
|
-
});
|
125
|
-
|
126
|
-
var placeholders = [];
|
127
|
-
if (previews.length == 0 && that.props['data-placeholder']) $.each(JSON.parse(that.props['data-placeholder']), function(uid, src) {
|
128
|
-
placeholders.push(
|
129
|
-
<Placeholder key="placeholder" {...that.props} src={src} />
|
130
|
-
);
|
131
|
-
});
|
132
|
-
|
133
|
-
var discards = [];
|
134
|
-
$.each(that.state.attaches_discarded, function(index, discard) {
|
135
|
-
var discardKey = "discard" + discard.path;
|
136
|
-
discards.push(
|
137
|
-
<input key={discardKey} type="hidden" name={discard.fieldname} value={discard.path} />
|
138
|
-
);
|
139
|
-
});
|
140
|
-
|
141
|
-
var className = ["attache-file-selector", "attache-placeholders-count-" + placeholders.length, "attache-previews-count-" + previews.length, this.props['data-classname']].join(' ').trim();
|
142
|
-
return (
|
143
|
-
<label htmlFor={that.props.id} className={className} onDragOver={this.onDragOver} onDragLeave={this.onDragLeave} onDrop={this.onDrop}>
|
144
|
-
<input type="file" {...that.props} onChange={this.onChange}/>
|
145
|
-
<input type="hidden" name={that.props.name} value="" />
|
146
|
-
<Header {...that.props} />
|
147
|
-
{previews}
|
148
|
-
{placeholders}
|
149
|
-
{discards}
|
150
|
-
</label>
|
151
|
-
);
|
152
|
-
}
|
153
|
-
});
|
@@ -1,23 +0,0 @@
|
|
1
|
-
window.AttacheRails = {
|
2
|
-
upgrade_fileinputs: function() {
|
3
|
-
var safeWords = { 'class': 'className', 'for': 'htmlFor' };
|
4
|
-
var sel = document.getElementsByClassName('enable-attache');
|
5
|
-
var ele, attrs, name, value;
|
6
|
-
for (var i = sel.length-1; i >= 0; i--) {
|
7
|
-
ele = sel[i];
|
8
|
-
attrs = {};
|
9
|
-
for (var j = 0; j < ele.attributes.length; j++) {
|
10
|
-
name = ele.attributes[j].name;
|
11
|
-
value = ele.attributes[j].value;
|
12
|
-
if (name === 'class') value = value.replace('enable-attache', 'attache-enabled');
|
13
|
-
name = safeWords[name] || name;
|
14
|
-
attrs[name] = value;
|
15
|
-
}
|
16
|
-
var wrap = document.createElement('div');
|
17
|
-
ele.parentNode.replaceChild(wrap, ele); // ele.parentNode.insertBefore(wrap, ele.nextSibling);
|
18
|
-
React.render(React.createElement(AttacheFileInput, React.__spread({}, attrs)), wrap);
|
19
|
-
}
|
20
|
-
}
|
21
|
-
};
|
22
|
-
|
23
|
-
$(document).on('page:change', AttacheRails.upgrade_fileinputs);
|