From 2f5a1f12b48b9ba7a4fedf5218a3ba568f9a150f Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Sun, 2 Dec 2012 21:57:15 +0300
Subject: [PATCH 01/66] major refactoring
---
History.md | 10 ++
README.md | 92 +++++++++++-
index.js | 328 +++++++++++++------------------------------
lib/fileinfo.js | 42 ++++++
lib/uploadhandler.js | 183 ++++++++++++++++++++++++
package.json | 7 +-
6 files changed, 423 insertions(+), 239 deletions(-)
create mode 100644 lib/fileinfo.js
create mode 100644 lib/uploadhandler.js
diff --git a/History.md b/History.md
index 18202c8..6234861 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,13 @@
+0.0.4 / 2012-12-02
+==================
+
+ * Syntax changed
+ * middleware now provides 'begin','end','abort' events
+ * if file was renamed during upload to avoid conflict,
+ new field 'originalName' keeps original name
+ * dynamic uploadDir and uploadUrl
+ * upload.getFiles() added, see README.md for example
+
0.0.3 / 2012-11-25
==================
diff --git a/README.md b/README.md
index 9debff1..8ee687d 100644
--- a/README.md
+++ b/README.md
@@ -18,9 +18,9 @@ Usage:
var app = express();
app.configure(function () {
...
- app.use('/upload', upload({
+ app.use('/upload', upload.fileHandler({
uploadDir: __dirname + '/public/uploads',
- uploadUrl: '/uploads/'
+ uploadUrl: '/uploads'
}));
app.use(express.bodyParser());
...
@@ -34,17 +34,99 @@ On the frontend:
```
+More sophisticated example - Events
+
+```javascript
+ app.use('/upload', upload.fileHandler({
+ uploadDir: __dirname + '/public/uploads',
+ uploadUrl: '/uploads'
+ }));
+
+ // events
+ upload.on('begin', function (fileInfo) { ... });
+ upload.on('abort', function (fileInfo) { ... });
+ upload.on('end', function (fileInfo) {
+ // fileInfo structure is the same as returned to browser
+ // {
+ // name: '3 (3).jpg',
+ // originalName: '3.jpg',
+ // size: 79262,
+ // type: 'image/jpeg',
+ // delete_type: 'DELETE',
+ // delete_url: 'http://yourhost/upload/3%20(3).jpg',
+ // url: 'http://yourhost/uploads/3%20(3).jpg',
+ // thumbnail_url: 'http://youhost/uploads/thumbnail/3%20(3).jpg'
+ // }
+ });
+ upload.on('error', function (e) {
+ console.log(e.message);
+ });
+```
+
+Dynamic upload directory and url, isolating user files:
+
+```javascript
+ app.use('/upload', function (req, res, next) {
+ upload.fileHandler({
+ uploadDir: function () {
+ return __dirname + '/public/uploads/' + req.sessionID
+ },
+ uploadUrl: function () {
+ return '/uploads/' + req.sessionID
+ },
+ imageVersions: {
+ thumbnail: {
+ width: 80,
+ height: 80
+ }
+ }
+ })(req, res, next);
+ });
+```
+
+Getting uploaded files mapped to their fs locations:
+
+```javascript
+ app.use('/list', function (req, res, next) {
+ upload.getFiles({
+ uploadDir: function () {
+ return __dirname + '/public/uploads/' + req.sessionID
+ },
+ uploadUrl: function () {
+ return '/uploads/' + req.sessionID
+ },
+ imageVersions: {
+ thumbnail: {
+ width: 80,
+ height: 80
+ }
+ }
+ }, function (files) {
+ // {
+ // "00001.MTS": {
+ // "path": "/home/.../public/uploads/ekE6k4j9PyrGtcg+SA6a5za3/00001.MTS"
+ // },
+ // "DSC00030.JPG": {
+ // "path": "/home/.../public/uploads/ekE6k4j9PyrGtcg+SA6a5za3/DSC00030.JPG",
+ // "thumbnail": "/home/.../public/uploads/ekE6k4j9PyrGtcg+SA6a5za3/thumbnail/DSC00030.JPG"
+ // }
+ // }
+ res.json(files);
+ });
+ });
+```
+
Other options and their default values:
+
```javascript
{
tmpDir: '/tmp',
+ uploadDir: __dirname + '/public/uploads',
+ uploadUrl: '/uploads',
maxPostSize: 11000000000, // 11 GB
minFileSize: 1,
maxFileSize: 10000000000, // 10 GB
acceptFileTypes: /.+/i,
- // Files not matched by this regular expression force a download dialog,
- // to prevent executing any scripts in the context of the service domain:
- safeFileTypes: /\.(gif|jpe?g|png)$/i,
imageTypes: /\.(gif|jpe?g|png)$/i,
imageVersions: {
thumbnail: {
diff --git a/index.js b/index.js
index c1bb81f..4abb708 100644
--- a/index.js
+++ b/index.js
@@ -1,250 +1,40 @@
-module.exports = function (options) {
+var _ = require('lodash'),
+ fs = require('fs');
- var path = require('path'),
- fs = require('fs'),
- formidable = require('formidable'),
- imageMagick = require('imagemagick'),
- _ = require('lodash'),
- // Since Node 0.8, .existsSync() moved from path to fs:
- _existsSync = fs.existsSync || path.existsSync,
- utf8encode = function (str) {
- return unescape(encodeURIComponent(str));
- },
- nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/,
- nameCountFunc = function (s, index, ext) {
- return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || '');
- };
-
- options = _.extend({
- tmpDir: '/tmp',
- uploadDir: __dirname + '/public/files',
- uploadUrl: '/files/',
- maxPostSize: 11000000000, // 11 GB
- minFileSize: 1,
- maxFileSize: 10000000000, // 10 GB
- acceptFileTypes: /.+/i,
- // Files not matched by this regular expression force a download dialog,
- // to prevent executing any scripts in the context of the service domain:
- safeFileTypes: /\.(gif|jpe?g|png)$/i,
- imageTypes: /\.(gif|jpe?g|png)$/i,
- imageVersions: {
- /*
- thumbnail: {
- width: 80,
- height: 80
- }
- */
- },
- accessControl: {
- allowOrigin: '*',
- allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
- }
- }, options);
-
- var FileInfo = function (file) {
- this.name = file.name;
- this.size = file.size;
- this.type = file.type;
- this.delete_type = 'DELETE';
- };
-
- FileInfo.prototype.validate = function () {
- if (options.minFileSize && options.minFileSize > this.size) {
- this.error = 'File is too small';
- } else if (options.maxFileSize && options.maxFileSize < this.size) {
- this.error = 'File is too big';
- } else if (!options.acceptFileTypes.test(this.name)) {
- this.error = 'Filetype not allowed';
- }
- return !this.error;
- };
-
- FileInfo.prototype.safeName = function () {
- // Prevent directory traversal and creating hidden system files:
- this.name = path.basename(this.name).replace(/^\.+/, '');
- // Prevent overwriting existing files:
- while (_existsSync(options.uploadDir + '/' + this.name)) {
- this.name = this.name.replace(nameCountRegexp, nameCountFunc);
- }
- };
-
- FileInfo.prototype.initUrls = function (req) {
- if (!this.error) {
- var that = this,
- baseUrl = (options.ssl ? 'https:' : 'http:') +
- '//' + req.headers.host;
- this.delete_url = baseUrl + req.originalUrl + '/' + encodeURIComponent(this.name);
- this.url = baseUrl + options.uploadUrl + '/' + encodeURIComponent(this.name);
- Object.keys(options.imageVersions).forEach(function (version) {
- if (_existsSync(
- options.uploadDir + '/' + version + '/' + that.name
- )) {
- that[version + '_url'] = baseUrl + options.uploadUrl + '/' + version + '/' + encodeURIComponent(that.name);
- }
- });
- }
- };
-
- var UploadHandler = function (req, res, callback) {
- this.req = req;
- this.res = res;
- this.callback = callback;
- };
-
- UploadHandler.prototype.noCache = function () {
- this.res.set({
- 'Pragma': 'no-cache',
- 'Cache-Control': 'no-store, no-cache, must-revalidate',
- 'Content-Disposition': 'inline; filename="files.json"'
- });
- };
-
- UploadHandler.prototype.get = function () {
- var handler = this,
- files = [];
- handler.noCache();
- fs.readdir(options.uploadDir, function (err, list) {
- _.each(list, function (name) {
- var stats = fs.statSync(options.uploadDir + '/' + name),
- fileInfo;
- if (stats.isFile()) {
- fileInfo = new FileInfo({
- name: name,
- size: stats.size
- });
- fileInfo.initUrls(handler.req);
- files.push(fileInfo);
- }
- });
- handler.callback(files);
- });
- };
-
- UploadHandler.prototype.post = function () {
-
- var handler = this,
- form = new formidable.IncomingForm(),
- tmpFiles = [],
- files = [],
- map = {},
- counter = 1,
- redirect,
- finish = function () {
- if (!--counter) {
- files.forEach(function (fileInfo) {
- fileInfo.initUrls(handler.req);
- });
- handler.callback(files, redirect);
- }
- };
-
- handler.noCache();
-
- form.uploadDir = options.tmpDir;
- form
- .on('fileBegin', function (name, file) {
- tmpFiles.push(file.path);
- var fileInfo = new FileInfo(file, handler.req, true);
- fileInfo.safeName();
- map[path.basename(file.path)] = fileInfo;
- files.push(fileInfo);
- })
- .on('field', function (name, value) {
- if (name === 'redirect') {
- redirect = value;
- }
- })
- .on('file', function (name, file) {
- var fileInfo = map[path.basename(file.path)];
- if (_existsSync(file.path)) {
- fileInfo.size = file.size;
- if (!fileInfo.validate()) {
- fs.unlink(file.path);
- return;
- }
-
- var generatePreviews = function () {
- if (options.imageTypes.test(fileInfo.name) && _.keys(options.imageVersions).length) {
- _.keys(options.imageVersions).forEach(function (version) {
- if (!_existsSync(options.uploadDir + '/' + version + '/'))
- throw new Error(options.uploadDir + '/' + version + '/' + ' not exists');
- counter++;
- var opts = options.imageVersions[version];
- imageMagick.resize({
- width: opts.width,
- height: opts.height,
- srcPath: options.uploadDir + '/' + fileInfo.name,
- dstPath: options.uploadDir + '/' + version + '/' + fileInfo.name
- }, finish);
- });
- }
- }
-
- counter++;
- fs.rename(file.path, options.uploadDir + '/' + fileInfo.name, function (err) {
- if (!err) {
- generatePreviews();
- finish();
- } else {
- var is = fs.createReadStream(file.path);
- var os = fs.createWriteStream(options.uploadDir + '/' + fileInfo.name);
- is.on('end', function (err) {
- if (!err) {
- fs.unlinkSync(file.path);
- generatePreviews();
- }
- finish();
- });
- is.pipe(os);
- }
- });
- }
- })
- .on('aborted', function () {
- tmpFiles.forEach(function (file) {
- fs.unlink(file);
- });
- })
- .on('error', function (e) {
- console.log(e);
- })
- .on('progress', function (bytesReceived, bytesExpected) {
- if (bytesReceived > options.maxPostSize)
- handler.req.connection.destroy();
- })
- .on('end', finish)
- .parse(handler.req);
- };
-
- UploadHandler.prototype.destroy = function () {
- var handler = this,
- fileName = path.basename(decodeURIComponent(this.req.url));
-
- fs.unlink(options.uploadDir + '/' + fileName, function (ex) {
- Object.keys(options.imageVersions).forEach(function (version) {
- fs.unlink(options.uploadDir + '/' + version + '/' + fileName);
- });
- handler.callback(!ex);
- });
- };
+var FileHandler = function (middleware, options, callback) {
return function (req, res, next) {
res.set({
'Access-Control-Allow-Origin': options.accessControl.allowOrigin,
'Access-Control-Allow-Methods': options.accessControl.allowMethods
});
+ var UploadHandler = require('./lib/uploadhandler')(options);
var handler = new UploadHandler(req, res, function (result, redirect) {
if (redirect) {
res.redirect(redirect.replace(/%s/, encodeURIComponent(JSON.stringify(result))));
} else {
res.set({
- 'Content-Type': req.headers.accept.indexOf('application/json') !== -1
+ 'Content-Type': (req.headers.accept || '').indexOf('application/json') !== -1
? 'application/json'
: 'text/plain'
});
res.json(200, result);
}
});
+
+ handler.on('begin', function (fileInfo) {
+ middleware.emit('begin', fileInfo);
+ });
+ handler.on('end', function (fileInfo) {
+ middleware.emit('end', fileInfo);
+ });
+ handler.on('abort', function (fileInfo) {
+ middleware.emit('abort', fileInfo);
+ });
+ handler.on('error', function (e) {
+ middleware.emit('abort', e);
+ });
+
switch (req.method) {
case 'OPTIONS':
res.end();
@@ -262,7 +52,83 @@ module.exports = function (options) {
default:
res.send(405);
}
-
}
+};
+
+var EventEmitter = require('events').EventEmitter;
+var JqueryFileUploadMiddleware = function () {
+ EventEmitter.call(this);
+};
+require('util').inherits(JqueryFileUploadMiddleware, EventEmitter);
+
+JqueryFileUploadMiddleware.prototype.prepareOptions = function (options) {
+ options = _.extend({
+ tmpDir: '/tmp',
+ uploadDir: __dirname + '/public/files',
+ uploadUrl: '/files/',
+ maxPostSize: 11000000000, // 11 GB
+ minFileSize: 1,
+ maxFileSize: 10000000000, // 10 GB
+ acceptFileTypes: /.+/i,
+ imageTypes: /\.(gif|jpe?g|png)$/i,
+ imageVersions: {
+// thumbnail: {
+// width: 80,
+// height: 80
+// }
+ },
+ accessControl: {
+ allowOrigin: '*',
+ allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
+ }
+ }, options);
+
+ _.each(['uploadDir', 'uploadUrl', 'deleteUrl'], function (key) {
+ if (!_.isFunction(options[key])) {
+ var originalValue = options[key];
+ options[key] = function () {
+ return originalValue
+ };
+ }
+ });
+
+ return options;
+}
+
+JqueryFileUploadMiddleware.prototype.fileHandler = function (options) {
+ return FileHandler(this, this.prepareOptions(options));
+};
+
+JqueryFileUploadMiddleware.prototype.getFiles = function (options, callback) {
+ options = this.prepareOptions(options);
+
+ var files = {};
+ var counter = 1;
+ var finish = function () {
+ if (!--counter)
+ callback(files);
+ };
+
+ fs.readdir(options.uploadDir(), _.bind(function (err, list) {
+ _.each(list, function (name) {
+ var stats = fs.statSync(options.uploadDir() + '/' + name);
+ if (stats.isFile()) {
+ files[name] = {
+ path: options.uploadDir() + '/' + name
+ };
+ _.each(options.imageVersions, function (value, version) {
+ counter++;
+ fs.exists(options.uploadDir() + '/' + version + '/' + name, function (exists) {
+ if (exists)
+ files[name][version] = options.uploadDir() + '/' + version + '/' + name;
+ finish();
+ });
+ });
+ }
+ }, this);
+ finish();
+ }, this));
+};
+
+module.exports = new JqueryFileUploadMiddleware();
-};
\ No newline at end of file
diff --git a/lib/fileinfo.js b/lib/fileinfo.js
new file mode 100644
index 0000000..0944269
--- /dev/null
+++ b/lib/fileinfo.js
@@ -0,0 +1,42 @@
+var fs = require('fs'),
+ _ = require('lodash');
+
+module.exports = function (options) {
+
+ var FileInfo = function (file) {
+ this.name = file.name;
+ this.originalName = file.name;
+ this.size = file.size;
+ this.type = file.type;
+ this.delete_type = 'DELETE';
+ };
+
+ FileInfo.prototype.validate = function () {
+ if (options.minFileSize && options.minFileSize > this.size) {
+ this.error = 'File is too small';
+ } else if (options.maxFileSize && options.maxFileSize < this.size) {
+ this.error = 'File is too big';
+ } else if (!options.acceptFileTypes.test(this.name)) {
+ this.error = 'Filetype not allowed';
+ }
+ return !this.error;
+ };
+
+ FileInfo.prototype.safeName = function () {
+ // Prevent directory traversal and creating hidden system files:
+ this.name = require('path').basename(this.name).replace(/^\.+/, '');
+ // Prevent overwriting existing files:
+ while (fs.existsSync(options.baseDir() + '/' + this.name)) {
+ this.name = this.name.replace(/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/, function (s, index, ext) {
+ return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || '');
+ });
+ }
+ };
+
+ FileInfo.prototype.setUrl = function (type, baseUrl) {
+ var key = type ? type + '_url' : 'url';
+ this[key] = baseUrl + '/' + encodeURIComponent(this.name);
+ }
+
+ return FileInfo;
+};
\ No newline at end of file
diff --git a/lib/uploadhandler.js b/lib/uploadhandler.js
new file mode 100644
index 0000000..dcd81b2
--- /dev/null
+++ b/lib/uploadhandler.js
@@ -0,0 +1,183 @@
+var EventEmitter = require('events').EventEmitter,
+ path = require('path'),
+ fs = require('fs'),
+ formidable = require('formidable'),
+ imageMagick = require('imagemagick'),
+ mkdirp = require('mkdirp'),
+ _ = require('lodash');
+
+module.exports = function (options) {
+
+ var FileInfo = require('./fileinfo')(
+ _.extend({
+ baseDir: options.uploadDir
+ }, _.pick(options, 'minFileSize', 'maxFileSize', 'acceptFileTypes'))
+ );
+
+ var UploadHandler = function (req, res, callback) {
+ EventEmitter.call(this);
+ this.req = req;
+ this.res = res;
+ this.callback = callback;
+ };
+ require('util').inherits(UploadHandler, EventEmitter);
+
+ UploadHandler.prototype.noCache = function () {
+ this.res.set({
+ 'Pragma': 'no-cache',
+ 'Cache-Control': 'no-store, no-cache, must-revalidate',
+ 'Content-Disposition': 'inline; filename="files.json"'
+ });
+ };
+
+ UploadHandler.prototype.get = function () {
+ this.noCache();
+ var files = [];
+ fs.readdir(options.uploadDir(), _.bind(function (err, list) {
+ _.each(list, function (name) {
+ var stats = fs.statSync(options.uploadDir() + '/' + name),
+ fileInfo;
+ if (stats.isFile()) {
+ fileInfo = new FileInfo({
+ name: name,
+ size: stats.size
+ });
+ this.initUrls(fileInfo);
+ files.push(fileInfo);
+ }
+ }, this);
+ this.callback(files);
+ }, this));
+ };
+
+ UploadHandler.prototype.post = function () {
+
+ var self = this,
+ form = new formidable.IncomingForm(),
+ tmpFiles = [],
+ files = [],
+ map = {},
+ counter = 1,
+ redirect,
+ finish = _.bind(function () {
+ if (!--counter) {
+ _.each(files, function (fileInfo) {
+ this.initUrls(fileInfo);
+ this.emit('end', fileInfo);
+ }, this);
+ this.callback(files, redirect);
+ }
+ }, this);
+
+ this.noCache();
+
+ form.uploadDir = options.tmpDir;
+ form
+ .on('fileBegin', function (name, file) {
+ tmpFiles.push(file.path);
+ var fileInfo = new FileInfo(file);
+ fileInfo.safeName();
+ map[path.basename(file.path)] = fileInfo;
+ files.push(fileInfo);
+ self.emit('begin', fileInfo);
+ })
+ .on('field', function (name, value) {
+ if (name === 'redirect') {
+ redirect = value;
+ }
+ })
+ .on('file', function (name, file) {
+ var fileInfo = map[path.basename(file.path)];
+ if (fs.existsSync(file.path)) {
+ fileInfo.size = file.size;
+ if (!fileInfo.validate()) {
+ fs.unlink(file.path);
+ return;
+ }
+
+ var generatePreviews = function () {
+ if (options.imageTypes.test(fileInfo.name)) {
+ _.each(options.imageVersions, function (value, version) {
+ // creating directory recursive
+ if (!fs.existsSync(options.uploadDir() + '/' + version + '/'))
+ mkdirp.sync(options.uploadDir() + '/' + version + '/');
+
+ counter++;
+ var opts = options.imageVersions[version];
+ imageMagick.resize({
+ width: opts.width,
+ height: opts.height,
+ srcPath: options.uploadDir() + '/' + fileInfo.name,
+ dstPath: options.uploadDir() + '/' + version + '/' + fileInfo.name
+ }, finish);
+ });
+ }
+ }
+
+ if (!fs.existsSync(options.uploadDir() + '/'))
+ mkdirp.sync(options.uploadDir() + '/');
+
+ counter++;
+ fs.rename(file.path, options.uploadDir() + '/' + fileInfo.name, function (err) {
+ if (!err) {
+ generatePreviews();
+ finish();
+ } else {
+ var is = fs.createReadStream(file.path);
+ var os = fs.createWriteStream(options.uploadDir() + '/' + fileInfo.name);
+ is.on('end', function (err) {
+ if (!err) {
+ fs.unlinkSync(file.path);
+ generatePreviews();
+ }
+ finish();
+ });
+ is.pipe(os);
+ }
+ });
+ }
+ })
+ .on('aborted', function () {
+ _.each(tmpFiles, function (file) {
+ var fileInfo = map[path.basename(file)];
+ self.emit('abort', fileInfo);
+ fs.unlink(file);
+ });
+ })
+ .on('error', function (e) {
+ self.emit('error', e);
+ })
+ .on('progress', function (bytesReceived, bytesExpected) {
+ if (bytesReceived > options.maxPostSize)
+ self.req.connection.destroy();
+ })
+ .on('end', finish)
+ .parse(self.req);
+ };
+
+ UploadHandler.prototype.destroy = function () {
+ var self = this,
+ fileName = path.basename(decodeURIComponent(this.req.url));
+
+ fs.unlink(options.uploadDir() + '/' + fileName, function (ex) {
+ _.each(options.imageVersions, function (value, version) {
+ fs.unlink(options.uploadDir() + '/' + version + '/' + fileName);
+ });
+ self.callback(!ex);
+ });
+ };
+
+ UploadHandler.prototype.initUrls = function (fileInfo) {
+ var baseUrl = (options.ssl ? 'https:' : 'http:') + '//' + this.req.headers.host;
+ fileInfo.setUrl(null, baseUrl + options.uploadUrl());
+ fileInfo.setUrl('delete', baseUrl + this.req.originalUrl);
+ _.each(options.imageVersions, function (value, version) {
+ if (fs.existsSync(options.uploadDir() + '/' + version + '/' + fileInfo.name)) {
+ fileInfo.setUrl(version, baseUrl + options.uploadUrl() + '/' + version);
+ }
+ }, this);
+ };
+
+ return UploadHandler;
+}
+
diff --git a/package.json b/package.json
index d0775ed..8ab826b 100644
--- a/package.json
+++ b/package.json
@@ -8,11 +8,12 @@
"express",
"middleware"
],
- "version": "0.0.3",
+ "version": "0.0.4",
"dependencies": {
"formidable": ">=1.0.11",
"imagemagick": ">=0.1.2",
- "lodash": ">= 0.9.2"
+ "lodash": ">= 0.9.2",
+ "mkdirp": ">= 0.3.4"
},
"engines": {
"node": ">= 0.8.8"
@@ -27,6 +28,6 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
- "_id": "jquery-file-upload-middleware@0.0.3",
+ "_id": "jquery-file-upload-middleware@0.0.4",
"license": "MIT"
}
From 5e814a37b957e41cf50ccfdfba84816123b250e9 Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Sun, 9 Dec 2012 21:35:00 +0300
Subject: [PATCH 02/66] 0.0.4
---
History.md | 2 +-
README.md | 85 +++++++++++++++++++++++++++++++++++++++---------------
index.js | 17 +++++++++--
3 files changed, 76 insertions(+), 28 deletions(-)
diff --git a/History.md b/History.md
index 6234861..f3ba650 100644
--- a/History.md
+++ b/History.md
@@ -6,7 +6,7 @@
* if file was renamed during upload to avoid conflict,
new field 'originalName' keeps original name
* dynamic uploadDir and uploadUrl
- * upload.getFiles() added, see README.md for example
+ * upload.getFiles() added
0.0.3 / 2012-11-25
==================
diff --git a/README.md b/README.md
index 8ee687d..4f1ff19 100644
--- a/README.md
+++ b/README.md
@@ -16,15 +16,26 @@ Usage:
upload = require('jquery-file-upload-middleware');
var app = express();
+
+ // configure upload middleware
+ upload.configure({
+ uploadDir: __dirname + '/public/uploads',
+ uploadUrl: '/uploads',
+ imageVersions: {
+ thumbnail: {
+ width: 80,
+ height: 80
+ }
+ }
+ });
+
app.configure(function () {
...
- app.use('/upload', upload.fileHandler({
- uploadDir: __dirname + '/public/uploads',
- uploadUrl: '/uploads'
- }));
+ app.use('/upload', upload.fileHandler());
app.use(express.bodyParser());
...
});
+
```
On the frontend:
@@ -34,13 +45,27 @@ On the frontend:
```
+Overriding global configuration
+
+```javascript
+
+ app.use('/upload2', upload.fileHandler({
+ uploadDir: __dirname + '/public/uploads2',
+ uploadUrl: '/uploads2',
+ imageVersions: {
+ thumbnail: {
+ width: 100,
+ height: 100
+ }
+ }
+ }));
+
+```
+
More sophisticated example - Events
```javascript
- app.use('/upload', upload.fileHandler({
- uploadDir: __dirname + '/public/uploads',
- uploadUrl: '/uploads'
- }));
+ app.use('/upload', upload.fileHandler());
// events
upload.on('begin', function (fileInfo) { ... });
@@ -66,19 +91,23 @@ More sophisticated example - Events
Dynamic upload directory and url, isolating user files:
```javascript
+ upload.configure({
+ imageVersions: {
+ thumbnail: {
+ width: 80,
+ height: 80
+ }
+ }
+ });
+
app.use('/upload', function (req, res, next) {
+ // imageVersions are taken from upload.configure()
upload.fileHandler({
uploadDir: function () {
return __dirname + '/public/uploads/' + req.sessionID
},
uploadUrl: function () {
return '/uploads/' + req.sessionID
- },
- imageVersions: {
- thumbnail: {
- width: 80,
- height: 80
- }
}
})(req, res, next);
});
@@ -94,12 +123,6 @@ Getting uploaded files mapped to their fs locations:
},
uploadUrl: function () {
return '/uploads/' + req.sessionID
- },
- imageVersions: {
- thumbnail: {
- width: 80,
- height: 80
- }
}
}, function (files) {
// {
@@ -116,6 +139,24 @@ Getting uploaded files mapped to their fs locations:
});
```
+Passing uploaded files down the request chain:
+
+```javascript
+ app.use('/api', function (req, res, next) {
+ upload.getFiles({
+ uploadDir: function () {
+ return __dirname + '/public/uploads/' + req.sessionID
+ },
+ uploadUrl: function () {
+ return '/uploads/' + req.sessionID
+ }
+ }, function (files) {
+ res.jquploadfiles = files;
+ next();
+ });
+ });
+```
+
Other options and their default values:
```javascript
@@ -144,7 +185,3 @@ Other options and their default values:
## License
Copyright (c) 2012 [Aleksandr Guidrevitch](http://aguidrevitch.blogspot.com/)
Released under the [MIT license](http://www.opensource.org/licenses/MIT).
-
-
-
-
diff --git a/index.js b/index.js
index 4abb708..fe23751 100644
--- a/index.js
+++ b/index.js
@@ -58,6 +58,8 @@ var FileHandler = function (middleware, options, callback) {
var EventEmitter = require('events').EventEmitter;
var JqueryFileUploadMiddleware = function () {
EventEmitter.call(this);
+ // setting default options
+ this.options = this.prepareOptions({});
};
require('util').inherits(JqueryFileUploadMiddleware, EventEmitter);
@@ -83,7 +85,7 @@ JqueryFileUploadMiddleware.prototype.prepareOptions = function (options) {
}
}, options);
- _.each(['uploadDir', 'uploadUrl', 'deleteUrl'], function (key) {
+ _.each(['uploadDir', 'uploadUrl'], function (key) {
if (!_.isFunction(options[key])) {
var originalValue = options[key];
options[key] = function () {
@@ -95,12 +97,21 @@ JqueryFileUploadMiddleware.prototype.prepareOptions = function (options) {
return options;
}
+JqueryFileUploadMiddleware.prototype.configure = function (options) {
+ this.options = this.prepareOptions(options);
+};
+
JqueryFileUploadMiddleware.prototype.fileHandler = function (options) {
- return FileHandler(this, this.prepareOptions(options));
+ return FileHandler(this, this.prepareOptions(_.extend( this.options, options )));
};
JqueryFileUploadMiddleware.prototype.getFiles = function (options, callback) {
- options = this.prepareOptions(options);
+ if (_.isFunction(options)) {
+ callback = options;
+ options = this.options;
+ } else {
+ options = this.prepareOptions(_.extend( this.options, options ));
+ }
var files = {};
var counter = 1;
From ad0261b89c62d606473a1e02df885ff48ee608c6 Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Wed, 12 Dec 2012 19:18:37 +0300
Subject: [PATCH 03/66] #4 fixed - orientation for iphone / ipad photos
---
lib/uploadhandler.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/uploadhandler.js b/lib/uploadhandler.js
index dcd81b2..0cb4ddc 100644
--- a/lib/uploadhandler.js
+++ b/lib/uploadhandler.js
@@ -108,7 +108,8 @@ module.exports = function (options) {
width: opts.width,
height: opts.height,
srcPath: options.uploadDir() + '/' + fileInfo.name,
- dstPath: options.uploadDir() + '/' + version + '/' + fileInfo.name
+ dstPath: options.uploadDir() + '/' + version + '/' + fileInfo.name,
+ customArgs: ['-auto-orient']
}, finish);
});
}
From 91db9bc2de5341645d689c892d5b690907c110b3 Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Wed, 12 Dec 2012 19:18:50 +0300
Subject: [PATCH 04/66] History.md updated
---
History.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/History.md b/History.md
index f3ba650..dac3225 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,8 @@
+0.0.5 / 2012-12-12
+==================
+
+ * #4 fixed - orientation for iphone / ipad photos
+
0.0.4 / 2012-12-02
==================
From e992b2e704a4fba6785d92ed185da3a10a414cc1 Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Wed, 12 Dec 2012 19:19:19 +0300
Subject: [PATCH 05/66] 0.0.5
---
package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 8ab826b..888fcf6 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"express",
"middleware"
],
- "version": "0.0.4",
+ "version": "0.0.5",
"dependencies": {
"formidable": ">=1.0.11",
"imagemagick": ">=0.1.2",
@@ -28,6 +28,6 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
- "_id": "jquery-file-upload-middleware@0.0.4",
+ "_id": "jquery-file-upload-middleware@0.0.5",
"license": "MIT"
}
From 1b2a107c585765d752875a278aa7cd02bcb7d29d Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Thu, 20 Dec 2012 17:29:20 +0300
Subject: [PATCH 06/66] 0.0.6
---
History.md | 6 ++
README.md | 90 +++++++++++++++++----
index.js | 96 +----------------------
lib/filehandler.js | 53 +++++++++++++
lib/filemanager.js | 181 +++++++++++++++++++++++++++++++++++++++++++
lib/uploadhandler.js | 2 +-
package.json | 4 +-
7 files changed, 321 insertions(+), 111 deletions(-)
create mode 100644 lib/filehandler.js
create mode 100644 lib/filemanager.js
diff --git a/History.md b/History.md
index dac3225..3eb34cb 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,9 @@
+0.0.6 / 2012-12-20
+==================
+
+ * #6 hostname option added
+ * upload.fileManager() added, which allows moving file
+
0.0.5 / 2012-12-12
==================
diff --git a/README.md b/README.md
index 4f1ff19..3ac00f9 100644
--- a/README.md
+++ b/README.md
@@ -113,18 +113,74 @@ Dynamic upload directory and url, isolating user files:
});
```
-Getting uploaded files mapped to their fs locations:
+Moving uploaded files to different dir:
```javascript
- app.use('/list', function (req, res, next) {
- upload.getFiles({
- uploadDir: function () {
- return __dirname + '/public/uploads/' + req.sessionID
+ app.use('/api', function (req, res, next) {
+ req.filemanager = upload.fileManager();
+ next();
+ });
+
+ app.use('/api/endpoint', function (req, res, next) {
+ // your real /api handler that will actually move the file
+ ...
+ // req.filemanager.move(filename, path, function (err, result))
+ req.filemanager.move('SomeFile.jpg', 'project1', function (err, result) {
+ // SomeFile.jpg gets moved from uploadDir/SomeFile.jpg to
+ // uploadDir/project1/SomeFile.jpg
+ // if path is relative (no leading slash), uploadUrl will
+ // be used to generate relevant urls,
+ // for absolute paths urls are not generated
+ if (!err) {
+ // result structure
+ // {
+ // filename: 'SomeFile.jpg',
+ // url: '/uploads/project1/SomeFile.jpg',
+ // thumbail_url : '/uploads/project1/thumbnail/SomeFile.jpg'
+ // }
+ ...
+ } else {
+ console.log(err);
+ }
+ });
+ });
+```
+
+Moving uploaded files out of uploadDir:
+
+```
+ app.use('/api', function (req, res, next) {
+ var user = db.find(...);
+
+ req.filemanager = upload.fileManager({
+ targetDir: __dirname + '/public/u/' + user._id,
+ targetUrl: '/u/' + user._id,
+ });
+
+ // or
+ req.filemanager = upload.fileManager({
+ targetDir: function () {
+ return __dirname + '/public/u/' + user._id
},
- uploadUrl: function () {
- return '/uploads/' + req.sessionID
+ targetUrl: function () {
+ return'/u/' + user._id
+ }
+ });
+ ...
+ req.filemanager.move(req.body.filename, 'profile', function (err, result) {
+ // file gets moved to __dirname + '/public/u/' + user._id + '/profile'
+ if (!err) {
+
}
- }, function (files) {
+ });
+ });
+```
+
+Getting uploaded files mapped to their fs locations:
+
+```javascript
+ app.use('/list', function (req, res, next) {
+ upload.fileManager().getFiles(function (files) {
// {
// "00001.MTS": {
// "path": "/home/.../public/uploads/ekE6k4j9PyrGtcg+SA6a5za3/00001.MTS"
@@ -137,22 +193,19 @@ Getting uploaded files mapped to their fs locations:
res.json(files);
});
});
-```
-Passing uploaded files down the request chain:
+ // with dynamic upload directories
-```javascript
- app.use('/api', function (req, res, next) {
- upload.getFiles({
+ app.use('/list', function (req, res, next) {
+ upload.fileManager({
uploadDir: function () {
return __dirname + '/public/uploads/' + req.sessionID
},
uploadUrl: function () {
return '/uploads/' + req.sessionID
}
- }, function (files) {
- res.jquploadfiles = files;
- next();
+ }).getFiles(function (files) {
+ res.json(files);
});
});
```
@@ -164,6 +217,11 @@ Other options and their default values:
tmpDir: '/tmp',
uploadDir: __dirname + '/public/uploads',
uploadUrl: '/uploads',
+ targetDir: uploadDir,
+ targetUrl: uploadUrl,
+ ssl: false,
+ hostname: null, // in case your reverse proxy doesn't set Host header
+ // eg 'google.com'
maxPostSize: 11000000000, // 11 GB
minFileSize: 1,
maxFileSize: 10000000000, // 10 GB
diff --git a/index.js b/index.js
index fe23751..056dd89 100644
--- a/index.js
+++ b/index.js
@@ -1,61 +1,6 @@
var _ = require('lodash'),
- fs = require('fs');
+ EventEmitter = require('events').EventEmitter;
-var FileHandler = function (middleware, options, callback) {
-
- return function (req, res, next) {
- res.set({
- 'Access-Control-Allow-Origin': options.accessControl.allowOrigin,
- 'Access-Control-Allow-Methods': options.accessControl.allowMethods
- });
- var UploadHandler = require('./lib/uploadhandler')(options);
- var handler = new UploadHandler(req, res, function (result, redirect) {
- if (redirect) {
- res.redirect(redirect.replace(/%s/, encodeURIComponent(JSON.stringify(result))));
- } else {
- res.set({
- 'Content-Type': (req.headers.accept || '').indexOf('application/json') !== -1
- ? 'application/json'
- : 'text/plain'
- });
- res.json(200, result);
- }
- });
-
- handler.on('begin', function (fileInfo) {
- middleware.emit('begin', fileInfo);
- });
- handler.on('end', function (fileInfo) {
- middleware.emit('end', fileInfo);
- });
- handler.on('abort', function (fileInfo) {
- middleware.emit('abort', fileInfo);
- });
- handler.on('error', function (e) {
- middleware.emit('abort', e);
- });
-
- switch (req.method) {
- case 'OPTIONS':
- res.end();
- break;
- case 'HEAD':
- case 'GET':
- handler.get();
- break;
- case 'POST':
- handler.post();
- break;
- case 'DELETE':
- handler.destroy();
- break;
- default:
- res.send(405);
- }
- }
-};
-
-var EventEmitter = require('events').EventEmitter;
var JqueryFileUploadMiddleware = function () {
EventEmitter.call(this);
// setting default options
@@ -102,44 +47,11 @@ JqueryFileUploadMiddleware.prototype.configure = function (options) {
};
JqueryFileUploadMiddleware.prototype.fileHandler = function (options) {
- return FileHandler(this, this.prepareOptions(_.extend( this.options, options )));
+ return require('./lib/filehandler')(this, this.prepareOptions(_.extend(this.options, options)));
};
-JqueryFileUploadMiddleware.prototype.getFiles = function (options, callback) {
- if (_.isFunction(options)) {
- callback = options;
- options = this.options;
- } else {
- options = this.prepareOptions(_.extend( this.options, options ));
- }
-
- var files = {};
- var counter = 1;
- var finish = function () {
- if (!--counter)
- callback(files);
- };
-
- fs.readdir(options.uploadDir(), _.bind(function (err, list) {
- _.each(list, function (name) {
- var stats = fs.statSync(options.uploadDir() + '/' + name);
- if (stats.isFile()) {
- files[name] = {
- path: options.uploadDir() + '/' + name
- };
- _.each(options.imageVersions, function (value, version) {
- counter++;
- fs.exists(options.uploadDir() + '/' + version + '/' + name, function (exists) {
- if (exists)
- files[name][version] = options.uploadDir() + '/' + version + '/' + name;
- finish();
- });
- });
- }
- }, this);
- finish();
- }, this));
+JqueryFileUploadMiddleware.prototype.fileManager = function (options) {
+ return require('./lib/filemanager')(this, this.prepareOptions(_.extend(this.options, options)));
};
module.exports = new JqueryFileUploadMiddleware();
-
diff --git a/lib/filehandler.js b/lib/filehandler.js
new file mode 100644
index 0000000..7075898
--- /dev/null
+++ b/lib/filehandler.js
@@ -0,0 +1,53 @@
+module.exports = function (middleware, options) {
+
+ return function (req, res, next) {
+ res.set({
+ 'Access-Control-Allow-Origin': options.accessControl.allowOrigin,
+ 'Access-Control-Allow-Methods': options.accessControl.allowMethods
+ });
+ var UploadHandler = require('./uploadhandler')(options);
+ var handler = new UploadHandler(req, res, function (result, redirect) {
+ if (redirect) {
+ res.redirect(redirect.replace(/%s/, encodeURIComponent(JSON.stringify(result))));
+ } else {
+ res.set({
+ 'Content-Type': (req.headers.accept || '').indexOf('application/json') !== -1
+ ? 'application/json'
+ : 'text/plain'
+ });
+ res.json(200, result);
+ }
+ });
+
+ handler.on('begin', function (fileInfo) {
+ middleware.emit('begin', fileInfo);
+ });
+ handler.on('end', function (fileInfo) {
+ middleware.emit('end', fileInfo);
+ });
+ handler.on('abort', function (fileInfo) {
+ middleware.emit('abort', fileInfo);
+ });
+ handler.on('error', function (e) {
+ middleware.emit('abort', e);
+ });
+
+ switch (req.method) {
+ case 'OPTIONS':
+ res.end();
+ break;
+ case 'HEAD':
+ case 'GET':
+ handler.get();
+ break;
+ case 'POST':
+ handler.post();
+ break;
+ case 'DELETE':
+ handler.destroy();
+ break;
+ default:
+ res.send(405);
+ }
+ }
+};
diff --git a/lib/filemanager.js b/lib/filemanager.js
new file mode 100644
index 0000000..5e2ff7f
--- /dev/null
+++ b/lib/filemanager.js
@@ -0,0 +1,181 @@
+var _ = require('lodash'),
+ fs = require('fs'),
+ path = require('path'),
+ mkdirp = require('mkdirp');
+
+module.exports = function (middleware, options) {
+
+ options = _.extend({
+ targetDir: function () {
+ return options.uploadDir();
+ },
+ targetUrl: function () {
+ return options.uploadUrl();
+ }
+ }, options);
+
+ _.each(['targetDir', 'targetUrl'], function (key) {
+ if (!_.isFunction(options[key])) {
+ var originalValue = options[key];
+ options[key] = function () {
+ return originalValue
+ };
+ }
+ });
+
+ var FileManager = function () {
+ };
+
+ FileManager.prototype.getFiles = function (callback) {
+
+ var files = {};
+ var counter = 1;
+ var finish = function () {
+ if (!--counter)
+ callback(files);
+ };
+
+ fs.readdir(options.uploadDir(), _.bind(function (err, list) {
+ _.each(list, function (name) {
+ var stats = fs.statSync(options.uploadDir() + '/' + name);
+ if (stats.isFile()) {
+ files[name] = {
+ path: options.uploadDir() + '/' + name
+ };
+ _.each(options.imageVersions, function (value, version) {
+ counter++;
+ fs.exists(options.uploadDir() + '/' + version + '/' + name, function (exists) {
+ if (exists)
+ files[name][version] = options.uploadDir() + '/' + version + '/' + name;
+ finish();
+ });
+ });
+ }
+ }, this);
+ finish();
+ }, this));
+ };
+
+ var safeName = function (dir, filename, callback) {
+ fs.exists(dir + '/' + filename, function (exists) {
+ if (exists) {
+ filename = filename.replace(/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/, function (s, index, ext) {
+ return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || '');
+ });
+ safeName(dir, filename, callback)
+ } else {
+ callback(filename);
+ }
+ });
+ };
+
+ var moveFile = function (source, target, callback) {
+ fs.rename(source, target, function (err) {
+ if (!err)
+ callback();
+ else {
+ var is = fs.createReadStream(source);
+ var os = fs.createWriteStream(target);
+ is.on('end', function (err) {
+ if (!err) {
+ fs.unlink(source, callback);
+ } else {
+ callback(err);
+ }
+ });
+ is.pipe(os);
+ }
+ });
+ };
+
+ var move = function (source, targetDir, callback) {
+ fs.exists(targetDir, function (exists) {
+ if (!exists) {
+ mkdirp(targetDir, function (err) {
+ if (err)
+ callback(err);
+ else
+ move(source, targetDir, callback);
+ });
+ } else {
+ fs.stat(source, function (err, stat) {
+ if (!err) {
+ if (stat.isFile()) {
+ safeName(targetDir, path.basename(source), function (safename) {
+ moveFile(source, targetDir + '/' + safename, function (err) {
+ callback(err, safename);
+ });
+ });
+ } else {
+ callback(new Error(source + ' is not a file'));
+ }
+ } else {
+ callback(err);
+ }
+ });
+ }
+ });
+ };
+
+ FileManager.prototype.move = function (filename, targetDir, callback) {
+
+ var targetUrl;
+
+ // for safety
+ filename = path.basename(filename).replace(/^\.+/, '');
+
+ if (!targetDir.match(/^\//)) {
+ targetUrl = options.targetUrl() + '/' + targetDir;
+ targetDir = options.targetDir() + '/' + targetDir;
+ relative = true;
+ }
+
+ fs.stat(options.uploadDir() + '/' + filename, function (err, stat) {
+ if (!err) {
+ if (stat.isFile()) {
+ move(options.uploadDir() + '/' + filename, targetDir, function (err, safename) {
+ if (err) {
+ callback(err);
+ } else {
+ var urls = {
+ filename: safename
+ };
+
+ var counter = 1;
+ var finish = function (err) {
+ if (err)
+ counter = 1;
+ if (!--counter)
+ callback(err, err ? null : urls);
+ };
+
+ if (targetUrl)
+ urls.url = targetUrl + '/' + safename;
+
+ _.each(options.imageVersions, function (value, version) {
+ counter++;
+ fs.exists(options.uploadDir() + '/' + version + '/' + filename, function (exists) {
+ if (exists) {
+ move(options.uploadDir() + '/' + version + '/' + filename, targetDir + '/' + version + '/', function (err, safename) {
+ if (!err && relative)
+ urls[version + '_url'] = targetUrl + '/' + version + '/' + safename;
+ finish(err);
+ });
+ }
+ });
+ });
+ finish();
+ }
+ });
+ } else {
+ callback(new Error('File not found'));
+ }
+ } else {
+ callback(err);
+ }
+ });
+ }
+
+ return new FileManager();
+};
+
diff --git a/lib/uploadhandler.js b/lib/uploadhandler.js
index 0cb4ddc..f6808f0 100644
--- a/lib/uploadhandler.js
+++ b/lib/uploadhandler.js
@@ -169,7 +169,7 @@ module.exports = function (options) {
};
UploadHandler.prototype.initUrls = function (fileInfo) {
- var baseUrl = (options.ssl ? 'https:' : 'http:') + '//' + this.req.headers.host;
+ var baseUrl = (options.ssl ? 'https:' : 'http:') + '//' + (options.hostname || this.req.get('Host'));
fileInfo.setUrl(null, baseUrl + options.uploadUrl());
fileInfo.setUrl('delete', baseUrl + this.req.originalUrl);
_.each(options.imageVersions, function (value, version) {
diff --git a/package.json b/package.json
index 888fcf6..80e8b47 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"express",
"middleware"
],
- "version": "0.0.5",
+ "version": "0.0.6",
"dependencies": {
"formidable": ">=1.0.11",
"imagemagick": ">=0.1.2",
@@ -28,6 +28,6 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
- "_id": "jquery-file-upload-middleware@0.0.5",
+ "_id": "jquery-file-upload-middleware@0.0.6",
"license": "MIT"
}
From 4199a39d99a18036d4c860a7b3b69cb61f193da6 Mon Sep 17 00:00:00 2001
From: soomtong
Date: Tue, 25 Dec 2012 21:43:16 +0900
Subject: [PATCH 07/66] Update lib/filehandler.js
need delete event
---
lib/filehandler.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lib/filehandler.js b/lib/filehandler.js
index 7075898..6870b5d 100644
--- a/lib/filehandler.js
+++ b/lib/filehandler.js
@@ -31,6 +31,9 @@ module.exports = function (middleware, options) {
handler.on('error', function (e) {
middleware.emit('abort', e);
});
+ handler.on('delete', function (fileName) {
+ middleware.emit('delete', fileName);
+ });
switch (req.method) {
case 'OPTIONS':
From 284f8d1a5285a259f1303fc04c3d3dcc296f222e Mon Sep 17 00:00:00 2001
From: soomtong
Date: Tue, 25 Dec 2012 21:44:42 +0900
Subject: [PATCH 08/66] Update lib/uploadhandler.js
emit delete event
---
lib/uploadhandler.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/uploadhandler.js b/lib/uploadhandler.js
index f6808f0..45d9cb9 100644
--- a/lib/uploadhandler.js
+++ b/lib/uploadhandler.js
@@ -163,6 +163,7 @@ module.exports = function (options) {
fs.unlink(options.uploadDir() + '/' + fileName, function (ex) {
_.each(options.imageVersions, function (value, version) {
fs.unlink(options.uploadDir() + '/' + version + '/' + fileName);
+ self.emit('delete', fileName);
});
self.callback(!ex);
});
From ffa1758fe24aa3fe5e3b5799e00c175079876571 Mon Sep 17 00:00:00 2001
From: soomtong
Date: Tue, 25 Dec 2012 21:47:04 +0900
Subject: [PATCH 09/66] Update lib/uploadhandler.js
modify emit line
---
lib/uploadhandler.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/uploadhandler.js b/lib/uploadhandler.js
index 45d9cb9..7f21ffc 100644
--- a/lib/uploadhandler.js
+++ b/lib/uploadhandler.js
@@ -163,8 +163,8 @@ module.exports = function (options) {
fs.unlink(options.uploadDir() + '/' + fileName, function (ex) {
_.each(options.imageVersions, function (value, version) {
fs.unlink(options.uploadDir() + '/' + version + '/' + fileName);
- self.emit('delete', fileName);
});
+ self.emit('delete', fileName);
self.callback(!ex);
});
};
From 45ce0cb6d67091129b247d811287033363388339 Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Tue, 25 Dec 2012 16:03:48 +0300
Subject: [PATCH 10/66] 0.0.7
---
History.md | 6 ++++++
README.md | 7 ++++---
package.json | 4 ++--
3 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/History.md b/History.md
index 3eb34cb..3f3c6d5 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,9 @@
+0.0.7 / 2012-12-25
+==================
+
+ * 'delete' event merged in,
+ thanks to https://github.com/soomtong
+
0.0.6 / 2012-12-20
==================
diff --git a/README.md b/README.md
index 3ac00f9..75403e7 100644
--- a/README.md
+++ b/README.md
@@ -68,9 +68,7 @@ More sophisticated example - Events
app.use('/upload', upload.fileHandler());
// events
- upload.on('begin', function (fileInfo) { ... });
- upload.on('abort', function (fileInfo) { ... });
- upload.on('end', function (fileInfo) {
+ upload.on('begin', function (fileInfo) {
// fileInfo structure is the same as returned to browser
// {
// name: '3 (3).jpg',
@@ -83,6 +81,9 @@ More sophisticated example - Events
// thumbnail_url: 'http://youhost/uploads/thumbnail/3%20(3).jpg'
// }
});
+ upload.on('abort', function (fileInfo) { ... });
+ upload.on('end', function (fileInfo) { ... });
+ upload.on('delete', function (fileInfo) { ... });
upload.on('error', function (e) {
console.log(e.message);
});
diff --git a/package.json b/package.json
index 80e8b47..6b1809d 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"express",
"middleware"
],
- "version": "0.0.6",
+ "version": "0.0.7",
"dependencies": {
"formidable": ">=1.0.11",
"imagemagick": ">=0.1.2",
@@ -28,6 +28,6 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
- "_id": "jquery-file-upload-middleware@0.0.6",
+ "_id": "jquery-file-upload-middleware@0.0.7",
"license": "MIT"
}
From b0d884ef81fd1af324d1131f14c9ae484fe1e59d Mon Sep 17 00:00:00 2001
From: soomtong
Date: Tue, 1 Jan 2013 12:46:17 +0900
Subject: [PATCH 11/66] Update lib/uploadhandler.js
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
update resize with cropping custom options : customArgs
ex)
thumbnail:{
width:80,
height:"80^",
customArgs: [
"-gravity", "center",
"-extent", "80x80"
]
}
---
lib/uploadhandler.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/uploadhandler.js b/lib/uploadhandler.js
index 7f21ffc..53439ba 100644
--- a/lib/uploadhandler.js
+++ b/lib/uploadhandler.js
@@ -109,7 +109,7 @@ module.exports = function (options) {
height: opts.height,
srcPath: options.uploadDir() + '/' + fileInfo.name,
dstPath: options.uploadDir() + '/' + version + '/' + fileInfo.name,
- customArgs: ['-auto-orient']
+ customArgs: opts.customArgs || ['-auto-orient']
}, finish);
});
}
From d3d1611f424180a5843760826da85991fcc614e5 Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Tue, 1 Jan 2013 14:43:11 +0300
Subject: [PATCH 12/66] option renamed: customArgs -> imageArgs
---
History.md | 5 +++++
README.md | 1 +
lib/uploadhandler.js | 2 +-
package.json | 4 ++--
4 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/History.md b/History.md
index 3f3c6d5..c7f6f5e 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,8 @@
+0.0.8 / 2013-01-01
+==================
+
+ * #11 update resize with cropping custom options : imageArgs
+
0.0.7 / 2012-12-25
==================
diff --git a/README.md b/README.md
index 75403e7..10b13e2 100644
--- a/README.md
+++ b/README.md
@@ -234,6 +234,7 @@ Other options and their default values:
height: 80
}
},
+ imageArgs: ['-auto-orient'],
accessControl: {
allowOrigin: '*',
allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
diff --git a/lib/uploadhandler.js b/lib/uploadhandler.js
index 53439ba..bdab992 100644
--- a/lib/uploadhandler.js
+++ b/lib/uploadhandler.js
@@ -109,7 +109,7 @@ module.exports = function (options) {
height: opts.height,
srcPath: options.uploadDir() + '/' + fileInfo.name,
dstPath: options.uploadDir() + '/' + version + '/' + fileInfo.name,
- customArgs: opts.customArgs || ['-auto-orient']
+ customArgs: opts.imageArgs || ['-auto-orient']
}, finish);
});
}
diff --git a/package.json b/package.json
index 6b1809d..11f4056 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"express",
"middleware"
],
- "version": "0.0.7",
+ "version": "0.0.8",
"dependencies": {
"formidable": ">=1.0.11",
"imagemagick": ">=0.1.2",
@@ -28,6 +28,6 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
- "_id": "jquery-file-upload-middleware@0.0.7",
+ "_id": "jquery-file-upload-middleware@0.0.8",
"license": "MIT"
}
From 8e69dac3464d3cfcce8c070fcdf4c2c06e051cd3 Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Tue, 1 Jan 2013 14:53:43 +0300
Subject: [PATCH 13/66] credits
---
History.md | 1 +
README.md | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/History.md b/History.md
index c7f6f5e..b47f606 100644
--- a/History.md
+++ b/History.md
@@ -2,6 +2,7 @@
==================
* #11 update resize with cropping custom options : imageArgs
+ thanks to https://github.com/soomtong
0.0.7 / 2012-12-25
==================
diff --git a/README.md b/README.md
index 10b13e2..73ece01 100644
--- a/README.md
+++ b/README.md
@@ -242,6 +242,10 @@ Other options and their default values:
}
```
+## Contributors
+
+ * [@soomtong](http://twitter.com/soomtong)
+
## License
Copyright (c) 2012 [Aleksandr Guidrevitch](http://aguidrevitch.blogspot.com/)
Released under the [MIT license](http://www.opensource.org/licenses/MIT).
From 5add61f92ef8016c43b138093990f4b63225c537 Mon Sep 17 00:00:00 2001
From: Aleksandr Guidrevitch
Date: Tue, 1 Jan 2013 14:57:40 +0300
Subject: [PATCH 14/66] credits
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 73ece01..0aaac0a 100644
--- a/README.md
+++ b/README.md
@@ -244,7 +244,7 @@ Other options and their default values:
## Contributors
- * [@soomtong](http://twitter.com/soomtong)
+ * [@soomtong](http://github.com/soomtong)
## License
Copyright (c) 2012 [Aleksandr Guidrevitch](http://aguidrevitch.blogspot.com/)
From de8da7c8473305f95929e1853cb73bd51420af91 Mon Sep 17 00:00:00 2001
From: soomtong
Date: Tue, 1 Jan 2013 23:05:34 +0900
Subject: [PATCH 15/66] make readme.md
---
examples/README.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 examples/README.md
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..f5e2c64
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,7 @@
+## YET ANATHER JQUERY FILE UPLOAD MIDDLEWARE EXAMPLES
+
+# to run
+
+```
+project/examples > node app.js
+```
From fa6b5b92e188615a3ca6a7db0690cb09fdb254eb Mon Sep 17 00:00:00 2001
From: soomtong
Date: Tue, 1 Jan 2013 23:07:10 +0900
Subject: [PATCH 16/66] make readme.md
---
examples/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/README.md b/examples/README.md
index f5e2c64..30a705f 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,6 +1,6 @@
## YET ANATHER JQUERY FILE UPLOAD MIDDLEWARE EXAMPLES
-# to run
+to run
```
project/examples > node app.js
From 38d6248984d4a490cd5a7291435ef25aa78298b4 Mon Sep 17 00:00:00 2001
From: soomtong
Date: Wed, 2 Jan 2013 22:53:29 +0900
Subject: [PATCH 17/66] check this
---
examples/README.md | 3 +
examples/app.js | 141 ++++++++++++++++++++
examples/config.js | 56 ++++++++
examples/package.json | 10 ++
examples/public/scripts/file_upload.js | 174 +++++++++++++++++++++++++
examples/public/styles/style.css | 151 +++++++++++++++++++++
examples/views/form.html | 80 ++++++++++++
7 files changed, 615 insertions(+)
create mode 100644 examples/app.js
create mode 100644 examples/config.js
create mode 100644 examples/package.json
create mode 100644 examples/public/scripts/file_upload.js
create mode 100644 examples/public/styles/style.css
create mode 100644 examples/views/form.html
diff --git a/examples/README.md b/examples/README.md
index 30a705f..d94f76a 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -3,5 +3,8 @@
to run
```
+project/examples > npm insatll
project/examples > node app.js
```
+
+- to image resize : need imagemagick `http://www.imagemagick.org/script/binary-releases.php`
\ No newline at end of file
diff --git a/examples/app.js b/examples/app.js
new file mode 100644
index 0000000..dcdc23b
--- /dev/null
+++ b/examples/app.js
@@ -0,0 +1,141 @@
+/*
+* to run :
+* node app.js
+* */
+
+/*
+* dependencies
+* */
+var express = require('express'),
+ http = require('http'),
+ upload = require('jquery-file-upload-middleware');
+
+var cons = require('consolidate'),
+ swig = require('swig');
+
+
+// configuration
+var resizeConf = require('./config').resizeVersion;
+var dirs = require('./config').directors;
+
+
+
+// express setup
+var app = express();
+
+
+// set template engine
+app.engine('html', cons.swig);
+swig.init({
+ root: __dirname + '/views',
+ allowErrors: true,
+ cache: false
+});
+
+
+
+// jquery-file-upload helper
+app.use('/upload/default', function (req, res, next) {
+ upload.fileHandler({
+ tmpDir: dirs.temp,
+ uploadDir: __dirname + dirs.default,
+ uploadUrl: dirs.default_url,
+ imageVersions: resizeConf.default
+ })(req, res, next);
+});
+
+app.use('/upload/location', upload.fileHandler({
+ tmpDir: dirs.temp,
+ uploadDir: __dirname + dirs.location,
+ uploadUrl: dirs.location_url,
+ imageVersions: resizeConf.location
+}));
+
+app.use('/upload/location/list', function (req, res, next) {
+ upload.fileManager({
+ uploadDir: function () {
+ return __dirname + dirs.location;
+ },
+ uploadUrl: function () {
+ return dirs.location_url;
+ }
+ }).getFiles(function (files) {
+ res.json(files);
+ });
+});
+
+// bind event
+upload.on('end', function (fileInfo) {
+ // insert file info
+ console.log("files upload complete");
+ console.log(fileInfo);
+});
+
+upload.on('delete', function (fileName) {
+ // remove file info
+ console.log(fileName);
+});
+
+upload.on('error', function (e) {
+ console.log(e.message);
+});
+
+
+
+// Configuration
+app.configure(function () {
+ app.set('port', process.env.PORT || 3001);
+ app.set('view engine', 'html');
+ app.set('view options', { layout: false });
+ app.set('views', __dirname + '/views');
+
+ app.use(express.bodyParser());
+ app.use(express.methodOverride());
+ app.use(express.cookieParser('token'));
+ app.use(express.session({ secret: 'secret' }));
+ app.use(express.favicon());
+ app.use(express.static(__dirname + '/public'));
+});
+
+app.configure('development', function () {
+ app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
+ app.set('view cache', false);
+});
+
+app.configure('production', function () {
+ app.use(express.errorHandler());
+ app.set('view cache', true);
+});
+
+
+
+/*
+* routes
+* */
+app.get('/', function (req, res) {
+ res.send('Call this url in browser : http://localhost:3001/location/input');
+});
+
+
+app.get('/location/input', function (req, res) {
+ var params = {
+ title: "jquery file upload example"
+ };
+
+ res.render('form', params);
+});
+
+app.post('/location/input', function (req, res) {
+ console.log(req.body, req.files);
+ res.send('' + req.body + '
' + '' + req.files + '
');
+});
+
+
+
+/*
+ * start server
+ * */
+http.createServer(app).listen(app.get('port'), function () {
+ console.log("Express server listening on port " + app.get('port'));
+ console.log("access url /location/input");
+});
diff --git a/examples/config.js b/examples/config.js
new file mode 100644
index 0000000..129c04e
--- /dev/null
+++ b/examples/config.js
@@ -0,0 +1,56 @@
+exports.resizeVersion = {
+ default: {
+ thumbnail:{
+ width:80,
+ height:"80!"
+ },
+ small: {
+ width:200,
+ height:"150!"
+ },
+ medium:{
+ width:400,
+ height:300
+ },
+ large: {
+ width: 800,
+ height: 600
+ }
+ },
+ location : {
+ thumbnail:{
+ width:80,
+ height:"80^",
+ imageArgs: [
+ "-gravity", "center",
+ "-extent", "80x80"
+ ]
+ },
+ small: {
+ width:"200",
+ height:"150^",
+ imageArgs: [
+ "-gravity", "center",
+ "-extent", "200x150"
+ ]
+ },
+ medium:{
+ width:400,
+ height:300
+ },
+ large: {
+ width: 800,
+ height: 600
+ }
+ }
+};
+
+exports.directors = {
+ temp: './public/tmp',
+
+ default: '/public/uploads/default',
+ default_url: '/uploads/default',
+
+ location: '/public/uploads/location',
+ location_url: '/uploads/location'
+};
\ No newline at end of file
diff --git a/examples/package.json b/examples/package.json
new file mode 100644
index 0000000..68a668c
--- /dev/null
+++ b/examples/package.json
@@ -0,0 +1,10 @@
+{
+ "name":"examples",
+ "version":"0.0.1",
+ "dependencies":{
+ "express":">= 3.0.0",
+ "consolidate":">= 0.4.0",
+ "swig":">= 0.12.0",
+ "jquery-file-upload-middleware": ">= 0.0.8"
+ }
+}
\ No newline at end of file
diff --git a/examples/public/scripts/file_upload.js b/examples/public/scripts/file_upload.js
new file mode 100644
index 0000000..ea8d49d
--- /dev/null
+++ b/examples/public/scripts/file_upload.js
@@ -0,0 +1,174 @@
+$('#location_gallery').fileupload({
+ dataType:'json',
+ url:'/upload/location',
+ autoUpload: true,
+ sequentialUploads:true,
+ acceptFileTypes:/(\.|\/)(gif|jpe?g|png)$/i,
+ process:[
+ {
+ action:'load',
+ fileTypes:/^image\/(gif|jpeg|png)$/,
+ maxFileSize: 20000000 // 20MB
+ },
+ {
+ action:'resize',
+ maxWidth:1200,
+ maxHeight:800
+ },
+ {
+ action:'save'
+ }
+ ],
+ dropZone: $('#gallery_dropzone'),
+ progressall:function (e, data) {
+ var progress = parseInt(data.loaded / data.total * 100, 10);
+ $('#gallery_progress .bar').css('width', progress + '%');
+ },
+ filesContainer:$('#upload_gallery_files'),
+ uploadTemplate:function (o) {
+ var rows = $();
+ $.each(o.files, function (index, file) {
+ var row = $('' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ (file.error ? '
Error' :
+ '
' +
+ '
') +
+ '
');
+ row.find('.name').text(file.name);
+ row.find('.size').text(o.formatFileSize(file.size));
+ if (file.error) {
+ row.find('.error').text(file.error || 'File upload error');
+ }
+ rows = rows.add(row);
+ });
+ return rows;
+ },
+ downloadTemplate: function (o) {
+ var rows = $();
+ $.each(o.files, function (index, file) {
+ var row = $('' +
+ '' +
+ (file.error ? '
' +
+ '
' :
+ '
' +
+ '
' +
+ ' [
]') +
+ '
');
+ row.find('.size').text(o.formatFileSize(file.size));
+ if (file.error) {
+ row.find('.name').text(file.name);
+ row.find('.error').text(file.error || 'File upload error');
+ } else {
+ row.find('.name a').text(file.name);
+ if (file.url) {
+ row.find('.preview').append('
')
+ .find('img').prop('src', file.small_url);
+ row.find('a').prop('rel', 'gallery');
+ }
+ row.find('a').prop('href', file.url);
+ row.find('.delete button')
+ .attr('data-type', file.delete_type)
+ .attr('data-url', file.delete_url);
+ // add file data input
+ row.append('')
+ .find('input[name="galleryImage[]"]').val(file.name);
+ row.find('img').data('fileinfo',file);
+ }
+ rows = rows.add(row);
+ });
+ return rows;
+ }
+});
+
+$('#location_cover').fileupload({
+ dataType:'json',
+ url:'/upload/location',
+ autoUpload: true,
+ acceptFileTypes:/(\.|\/)(gif|jpe?g|png)$/i,
+ process:[
+ {
+ action:'load',
+ fileTypes:/^image\/(gif|jpeg|png)$/,
+ maxFileSize: 20000000 // 20MB
+ },
+ {
+ action:'resize',
+ maxWidth:1200,
+ maxHeight:600
+ },
+ {
+ action:'save'
+ }
+ ],
+ dropZone: $('#cover_dropzone'),
+ progressall:function (e, data) {
+ var progress = parseInt(data.loaded / data.total * 100, 10);
+ $('#cover_progress .bar').css('width', progress + '%');
+ },
+ filesContainer:$('#upload_cover_files'),
+ uploadTemplate:function (o) {
+ var rows = $();
+ $.each(o.files, function (index, file) {
+ var row = $('' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ (file.error ? '
Error' :
+ '
' +
+ '
') +
+ '
');
+ row.find('.name').text(file.name);
+ row.find('.size').text(o.formatFileSize(file.size));
+ if (file.error) {
+ row.find('.error').text(file.error || 'File upload error');
+ }
+ rows = rows.add(row);
+ });
+ return rows;
+ },
+ downloadTemplate: function (o) {
+ var rows = $();
+ $.each(o.files, function (index, file) {
+ var row = $('' +
+ '' +
+ (file.error ? '
' +
+ '
' :
+ '
' +
+ '
' +
+ '
') +
+ '
');
+ row.find('.size').text(o.formatFileSize(file.size));
+ if (file.error) {
+ row.find('.name').text(file.name);
+ row.find('.error').text(file.error || 'File upload error');
+ } else {
+ row.find('.name a').text(file.name);
+ if (file.url) {
+ row.find('.preview').append('
')
+ .find('img').prop('src', file.medium_url);
+ row.find('a').prop('rel', 'gallery');
+ }
+ row.find('a').prop('href', file.url);
+ row.find('.delete button')
+ .attr('data-type', file.delete_type)
+ .attr('data-url', file.delete_url);
+ // add file data input
+ row.append('')
+ .find('input[name="excerptImage"]').val(file.name);
+ row.find('img').data('fileinfo',file);
+ }
+ rows = rows.add(row);
+ });
+ return rows;
+ }
+});
+
+$(document).bind('drop dragover', function (e) {
+ e.preventDefault();
+});
diff --git a/examples/public/styles/style.css b/examples/public/styles/style.css
new file mode 100644
index 0000000..6ece518
--- /dev/null
+++ b/examples/public/styles/style.css
@@ -0,0 +1,151 @@
+/* custom css for location upload form */
+.form-location {
+ border: 1px solid #dddddd;
+ padding: 1em;
+ margin: 1em auto;
+}
+
+.form-location legend {
+ font-size: 1.45em;
+ color: #858585;
+ padding: 0 0.5em;
+}
+
+.form-location label {
+ font-size: 1.45em;
+ padding: 0 0.35em;
+ display: inline;
+ float: left;
+}
+.form-location input {
+ vertical-align: baseline;
+}
+
+.form-location .step1 p {
+ padding: 4px 0;
+}
+.form-location .step1 input[type=text] {
+ display: inline;
+ width: 98%;
+ padding: 3px;
+}
+
+.form-location .step2 textarea {
+ font-size: 1.8em;
+ line-height: 160%;
+ width: 98%;
+ padding: 2px;
+}
+
+.form-location .step3 input[type="range"] {
+ display: inline;
+ width: 98%;
+}
+
+.fileupload-buttonbar {
+ display: block;
+}
+
+.btn {
+ font-size: 14px;
+ line-height: 20px;
+ text-align: center;
+ display: inline-block;
+ padding: 4px 12px;
+ margin-top: 0;
+ margin-bottom: 0;
+ font-size: 14px;
+ line-height: 20px;
+ color: #333333;
+ text-align: center;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+ vertical-align: middle;
+ cursor: pointer;
+ background-color: #f5f5f5;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+ background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+ background-repeat: repeat-x;
+ border: 1px solid #bbbbbb;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+ border-bottom-color: #a2a2a2;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.fileinput-button {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #5bb75b;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+ background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+ background-image: -o-linear-gradient(top, #62c462, #51a351);
+ background-image: linear-gradient(to bottom, #62c462, #51a351);
+ background-image: -moz-linear-gradient(top, #62c462, #51a351);
+ background-repeat: repeat-x;
+ border-color: #51a351 #51a351 #387038;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.progressbar {
+ clear: both;
+}
+.progressbar, .bar {
+ height: 16px;
+}
+
+.template-upload, .template-download {
+ width: 25%;
+ float: left;
+ list-style-type: none;
+}
+
+#upload_cover_files .template-upload, #upload_cover_files .template-download {
+ width: 50%;
+}
+
+.template-upload button, .template-download button {
+ border: 1px solid #696969;
+ background: #f5f5f5;
+}
+
+.template-upload .outer {
+ position: relative;
+ border: 1px solid #dcdcdc;
+ margin: 2px;
+ padding: 2px;
+}
+.template-upload .outer span.cancel {
+ position: absolute;
+ top:0;
+ right: 0;
+}
+.template-download .outer {
+ position: relative;
+ border: 1px solid #dcdcdc;
+ margin: 2px;
+ padding: 2px;
+}
+.template-download .outer span.delete {
+ position: absolute;
+ top:0;
+ right: 0;
+}
+
+#location_upload label.error {
+ display: none;
+ position: relative;
+ margin-top: -1.7em;
+ color: #cd5c5c;
+}
\ No newline at end of file
diff --git a/examples/views/form.html b/examples/views/form.html
new file mode 100644
index 0000000..4a6b78d
--- /dev/null
+++ b/examples/views/form.html
@@ -0,0 +1,80 @@
+
+
+ {{title}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+