A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/mailru/FileAPI below:

mailru/FileAPI: FileAPI — a set of javascript tools for working with files. Multiupload, drag'n'drop and chunked file upload. Images: crop, resize and auto orientation by EXIF.

FileAPI

A set of JavaScript tools for working with files.

Download the files from the dist directory, and then:

	<div>
		<!-- "js-fileapi-wrapper" -- required class -->
		<div class="js-fileapi-wrapper upload-btn">
			<div class="upload-btn__txt">Choose files</div>
			<input id="choose" name="files" type="file" multiple />
		</div>
		<div id="images"><!-- previews --></div>
	</div>

	<script>window.FileAPI = { staticPath: '/js/FileAPI/dist/' };</script>
	<script src="/js/FileAPI/dist/FileAPI.min.js"></script>
	<script>
		var choose = document.getElementById('choose');
		FileAPI.event.on(choose, 'change', function (evt){
			var files = FileAPI.getFiles(evt); // Retrieve file list

			FileAPI.filterFiles(files, function (file, info/**Object*/){
				if( /^image/.test(file.type) ){
					return	info.width >= 320 && info.height >= 240;
				}
				return	false;
			}, function (files/**Array*/, rejected/**Array*/){
				if( files.length ){
					// Make preview 100x100
					FileAPI.each(files, function (file){
						FileAPI.Image(file).preview(100).get(function (err, img){
							images.appendChild(img);
						});
					});

					// Uploading Files
					FileAPI.upload({
						url: './ctrl.php',
						files: { images: files },
						progress: function (evt){ /* ... */ },
						complete: function (err, xhr){ /* ... */ }
					});
				}
			});
		});
	</script>

Edit the file crossdomain.xml and place it to the root of the domain to which files will be uploaded.

	<script>
		window.FileAPI = {
			  debug: false   // debug mode, see Console
			, cors: false    // if used CORS, set `true`
			, media: false   // if used WebCam, set `true`
			, staticPath: '/js/FileAPI/dist/' // path to '*.swf'
			, postNameConcat: function (name, idx){
				// Default: object[foo]=1&object[bar][baz]=2
				// .NET: https://github.com/mailru/FileAPI/issues/121#issuecomment-24590395
				return	name + (idx != null ? '['+ idx +']' : '');
			}
		};
	</script>
	<script src="/js/FileAPI/dist/FileAPI.min.js"></script>

	<!-- OR -->

	<script>
		window.FileAPI = { /* options */ };
		require(['FileAPI'], function (FileAPI){
			// ...
		});
	</script>
getFiles(input:HTMLInputElement|Event|$.Event):Array

Retrieve file list from input element or event object, also support jQuery.

var el = document.getElement('my-input');
FileAPI.event.on(el, 'change', function (evt/**Event*/){
	// Retrieve file list
	var files = FileAPI.getFiles(el);

	// or event
	var files = FileAPI.getFiles(evt);
});
getInfo(file:Object, callback:Function):void

Get info of file (see also: FileAPI.addInfoReader).

// Get info of image file (FileAPI.exif.js included)
FileAPI.getInfo(file, function (err/**String*/, info/**Object*/){
	if( !err ){
		console.log(info); // { width: 800, height: 600, exif: {..} }
	}
});

// Get info of mp3 file (FileAPI.id3.js included)
FileAPI.getInfo(file, function (err/**String*/, info/**Object*/){
	if( !err ){
		console.log(info); // { title: "...", album: "...", artists: "...", ... }
	}
});
filterFiles(files:Array, filter:Function, callback:Function):void

Filtering the list of files, with additional information about files. See also: FileAPI.getInfo and FileAPI.addInfoReader.

// Get list of file
var files = FileAPI.getFiles(input);

// Filter the List
FileAPI.filterFiles(files, function (file/**Object*/, info/**Object*/){
	if( /^image/.test(file.type) && info ){
		return	info.width > 320 && info.height > 240;
	} else {
		return	file.size < 20 * FileAPI.MB;
	}
}, function (list/**Array*/, other/**Array*/){
	if( list.length ){
		// ..
	}
});
getDropFiles(evt:Event|$.Event, callback:Function):void

Get a list of files, including directories.

FileAPI.event.on(document, 'drop', function (evt/**Event*/){
	evt.preventDefault();

	// Get a list of files
	FileAPI.getDropFiles(evt, function (files/**Array*/){
		// ...
	});
});
upload(opts:Object):XmlHttpRequest

Uploading files to the server (successively). Returns XHR-like object. It is important to remember to correctly worked flash-transport server response body must not be empty, for example, you can pass, just text "ok".

var el = document.getElementById('my-input');
FileAPI.event.on(el, 'change', function (evt/**Event*/){
	var files = FileAPI.getFiles(evt);
	var xhr = FileAPI.upload({
		url: 'http://rubaxa.org/FileAPI/server/ctrl.php',
		files: { file: files[0] },
		complete: function (err, xhr){
			if( !err ){
				var result = xhr.responseText;
				// ...
			}
		}
	});
});
addInfoReader(mime:RegExp, handler:Function):void

Adds a handler for the collection of information about a file. See also: FileAPI.getInfo and FileAPI.filterFiles.

FileAPI.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
	// http://www.nihilogic.dk/labs/exif/exif.js
	// http://www.nihilogic.dk/labs/binaryajax/binaryajax.js
	FileAPI.readAsBinaryString(file, function (evt/**Object*/){
		if( evt.type == 'load' ){
			var binaryString = evt.result;
			var oFile = new BinaryFile(binaryString, 0, file.size);
			var exif  = EXIF.readFromBinaryFile(oFile);
			callback(false, { 'exif': exif || {} });
		}
		else if( evt.type == 'error' ){
			callback('read_as_binary_string');
		}
		else if( evt.type == 'progress' ){
			// ...
		}
	});
});
readAsDataURL(file:Object, callback:Function):void

Reading the contents of the specified File as dataURL.

FileAPI.readAsDataURL(file, function (evt/**Object*/){
	if( evt.type == 'load' ){
		// Success
	 	var dataURL = evt.result;
	} else if( evt.type =='progress' ){
		var pr = evt.loaded/evt.total * 100;
	} else {
		// Error
	}
})
readAsBinaryString(file:Object, callback:Function):void

Reading the contents of the specified File as BinaryString.

FileAPI.readAsBinaryString(file, function (evt/**Object*/){
	if( evt.type == 'load' ){
		// Success
	 	var binaryString = evt.result;
	} else if( evt.type =='progress' ){
		var pr = evt.loaded/evt.total * 100;
	} else {
		// Error
	}
})
readAsArrayBuffer(file:Object, callback:Function):void

Reading the contents of the specified File as ArrayBuffer.

FileAPI.readAsArrayBuffer(file, function (evt/**Object*/){
	if( evt.type == 'load' ){
		// Success
	 	var arrayBuffer = evt.result;
	} else if( evt.type =='progress' ){
		var pr = evt.loaded/evt.total * 100;
	} else {
		// Error
	}
})
readAsText(file:Object, callback:Function):void

Reading the contents of the specified File as text.

FileAPI.readAsText(file, function (evt/**Object*/){
	if( evt.type == 'load' ){
		// Success
	 	var text = evt.result;
	} else if( evt.type =='progress' ){
		var pr = evt.loaded/evt.total * 100;
	} else {
		// Error
	}
})
readAsText(file:Object, encoding:String, callback:Function):void

Reading the contents of the specified File as text.

FileAPI.readAsText(file, "utf-8", function (evt/**Object*/){
	if( evt.type == 'load' ){
		// Success
	 	var text = evt.result;
	} else if( evt.type =='progress' ){
		var pr = evt.loaded/evt.total * 100;
	} else {
		// Error
	}
})

A string containing the URL to which the request is sent.

Additional post data to be sent along with the file uploads.

var xhr = FileAPI.upload({
	url: '...',
	data: { 'session-id': 123 },
	files: { ... },
});

Request method, HTML5 only.

var xhr = FileAPI.upload({
	url: '...',
	uploadMethod: 'PUT',
	files: { .. },
});
uploadCredentials:Boolean

Pass credentials to upload request, HTML5 only.

var xhr = FileAPI.upload({
	url: '...',
	uploadCredentials: false,
	files: { .. },
});

Additional request headers, HTML5 only.

var xhr = FileAPI.upload({
	url: '...',
	headers: { 'x-upload': 'fileapi' },
	files: { .. },
});

Setting to true removes the default timestamp URL parameter.

Key-value object, key — post name, value — File or FileAPI.Image object.

var xhr = FileAPI.upload({
	url: '...',
	files: {
		audio: files
	}
});

Chunk size in bytes, HTML5 only.

var xhr = FileAPI.upload({
	url: '...',
	files: { images: fileList },
	chunkSize: 0.5 * FileAPI.MB
});

Number of retries during upload chunks, HTML5 only.

var xhr = FileAPI.upload({
	url: '...',
	files: { images: fileList },
	chunkSize: 0.5 * FileAPI.MB,
	chunkUploadRetry: 3
});

--

Rules of changes the original image on the client.

var xhr = FileAPI.upload({
	url: '...',
	files: { image: imageFiles },
	// Changes the original image
	imageTransform: {
		// resize by max side
		maxWidth: 800,
		maxHeight: 600,
		// Add watermark
		overlay: [{ x: 10, y: 10, src: '/i/watemark.png', rel: FileAPI.Image.RIGHT_BOTTOM }]
	}
});

--

Rules of image transformation on the client, for more images.

var xhr = FileAPI.upload({
	url: '...',
	files: { image: imageFiles },
	imageTransform: {
		// resize by max side
		'huge': { maxWidth: 800, maxHeight: 600 },
		// crop & resize
		'medium': { width: 320, height: 240, preview: true },
		// crop & resize + watemark
		'small': {
			width: 100, height: 100,
			// Add watermark
			overlay: [{ x: 5, y: 5, src: '/i/watemark.png', rel: FileAPI.Image.RIGHT_BOTTOM }]
		}
	}
});

--

Convert all images to jpeg or png.

var xhr = FileAPI.upload({
	url: '...',
	files: { image: imageFiles },
	imageTransform: {
		type: 'image/jpeg',
		quality: 0.86 // jpeg quality
	}
});

Sent to the server the original image or not, if defined imageTransform option.

--

imageAutoOrientation:Boolean

Auto-rotate images on the basis of EXIF.

--

Prepare options upload for a particular file.

var xhr = FileAPI.upload({
	url: '...',
	files: { .. }
	prepare: function (file/**Object*/, options/**Object*/){
		options.data.secret = utils.getSecretKey(file.name);
	}
});

--

Start uploading.

var xhr = FileAPI.upload({
	url: '...',
	files: { .. }
	upload: function (xhr/**Object*/, options/**Object*/){
		// ...
	}
});

--

Start file uploading.

var xhr = FileAPI.upload({
	url: '...',
	files: { .. }
	fileupload: function (file/**Object*/, xhr/**Object*/, options/**Object*/){
		// ...
	}
});

--

Callback for upload progress events.

var xhr = FileAPI.upload({
	url: '...',
	files: { .. }
	progress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){
		var pr = evt.loaded/evt.total * 100;
	}
});

--

Callback for upload file progress events.

var xhr = FileAPI.upload({
	url: '...',
	files: { .. }
	fileprogress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){
		var pr = evt.loaded/evt.total * 100;
	}
});

--

Callback for end upload requests.

var xhr = FileAPI.upload({
	url: '...',
	files: { .. }
	complete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){
		if( !err ){
			// All files successfully uploaded.
		}
	}
});

--

Callback for end upload requests.

var xhr = FileAPI.upload({
	url: '...',
	files: { .. }
	filecomplete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){
		if( !err ){
			// File successfully uploaded
			var result = xhr.responseText;
		}
	}
});

The name of the file referenced by the File object.

The type (MIME type) of the file referenced by the File object.

The size (in bytes) of the file referenced by the File object.

on(el:HTMLElement, events:String, handler:Function):void

Attach an event handler function.

off(el:HTMLElement, events:String, handler:Function):void

Remove an event handler.

one(el:HTMLElement, events:String, handler:Function):void

Attach an event handler function. The handler is executed at most once.

dnd(el:HTMLElement, hover:Function, handler:Function):void

Attach an drag and drop event handler function.

var el = document.getElementById('dropzone');
FileAPI.event.dnd(el, function (over){
	el.style.backgroundColor = over ? '#f60': '';
}, function (files){
	if( files.length ){
		// Upload their.
	}
});

// or jQuery
$('#dropzone').dnd(hoverFn, dropFn);
dnd.off(el:HTMLElement, hover:Function, handler:Function):void

Remove an drag and drop event handler function.

// Native
FileAPI.event.dnd.off(el, hoverFn, dropFn);

// jQuery
$('#dropzone').dndoff(hoverFn, dropFn);

--

Class for working with images

constructor(file:Object):void

The constructor takes a single argument, the File object.

FileAPI.Image(imageFile).get(function (err/**String*/, img/**HTMLElement*/){
	if( !err ){
		document.body.appendChild( img );
	}
});
crop(width:Number, height:Number):FileAPI.Image

Crop image by width and height.

FileAPI.Image(imageFile)
	.crop(640, 480)
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;
crop(x:Number, y:Number, width:Number, height:Number):FileAPI.Image

Crop image by x, y, width and height.

FileAPI.Image(imageFile)
	.crop(100, 50, 320, 240)
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;
resize(width:Number, height:Number[, strategy:String]):FileAPI.Image

Resize image.

FileAPI.Image(imageFile)
	.resize(320, 240)
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;

// Resize image on by max side.
FileAPI.Image(imageFile)
	.resize(320, 240, 'max')
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;

// Resize image on by fixed height.
FileAPI.Image(imageFile)
	.resize(240, 'height')
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;
preview(width:Number[, height:Number]):FileAPI.Image

Crop and resize image.

FileAPI.Image(imageFile)
	.preview(100, 100)
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;
rotate(deg:Number):FileAPI.Image

Rotate image.

FileAPI.Image(imageFile)
	.rotate(90)
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;
filter(callback:Function):FileAPI.Image

Apply filter function. Only HTML5.

FileAPI.Image(imageFile)
	.filter(function (canvas/**HTMLCanvasElement*/, doneFn/**Function*/){
		// bla-bla-lba
		doneFn();
	})
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;
filter(name:String):FileAPI.Image

Uses CamanJS, include it before FileAPI library.

Caman.Filter.register("my-funky-filter", function () {
	// http://camanjs.com/guides/#Extending
});

FileAPI.Image(imageFile)
	.filter("my-funky-filter") // or .filter("vintage")
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;
overlay(images:Array):FileAPI.Image

Add overlay images, eg: watermark.

FileAPI.Image(imageFile)
	.overlay([
		// Left corner.
		{ x: 10, y: 10, w: 100, h: 10, src: '/i/watermark.png' },

		// Right bottom corner.
		{ x: 10, y: 10, src: '/i/watermark.png', rel: FileAPI.Image.RIGHT_BOTTOM }
	])
	.get(function (err/**String*/, img/**HTMLElement*/){

	})
;
get(fn:Function):FileAPI.Image

Get the final image.

To work with a webcam, be sure to set FileAPI.media: true.

publish(el:HTMLElement, options:Object, callback:Function):void

Publication of the camera.

var el = document.getElementById('cam');
FileAPI.Camera.publish(el, { width: 320, height: 240 }, function (err, cam/**FileAPI.Camera*/){
	if( !err ){
		// The webcam is ready, you can use it.
	}
});
start(callback:Function):void

Turn on the camera.

var el = document.getElementById('cam');
FileAPI.Camera.publish(el, { start: false }, function (err, cam/**FileAPI.Camera*/){
	if( !err ){
		// Turn on
		cam.start(function (err){
			if( !err ){
				// The camera is ready for use.
			}
		});
	}
});

Turn off the camera.

Take a picture with the camera.

var el = document.getElementById('cam');
FileAPI.Camera.publish(el, function (err, cam/**FileAPI.Camera*/){
	if( !err ){
		var shot = cam.shot(); // take a picture

		// create thumbnail 100x100
		shot.preview(100).get(function (err, img){
			previews.appendChild(img);
		});

		// and/or
		FileAPI.upload({
			url: '...',
			files: { cam: shot
		});
	}
});

1024 bytes

1048576 bytes

1073741824 bytes

1.0995116e+12 bytes

FileAPI.each(obj:Object|Array, callback:Function[, thisObject:Mixed]):void

Iterate over an object or array, executing a function for each matched element.

--

FileAPI.extend(dst:Object, src:Object):Object

Merge the contents of two objects together into the first object.

--

FileAPI.filter(array:Array, callback:Function[, thisObject:Mixed):Object

Creates a new array with all elements that pass the test implemented by the provided function.

FileAPI.support.html5:Boolean

HTML5 browser support

FileAPI.support.cors:Boolean

This cross-origin resource sharing is used to enable cross-site HTTP requests.

FileAPI.support.dnd:Boolean

Drag'n'drop events support.

FileAPI.support.flash:Boolean

Availability Flash plugin.

FileAPI.support.canvas:Boolean

Canvas support.

FileAPI.support.dataURI:Boolean

Support dataURI as src for image.

FileAPI.support.chunked:Boolean

Support chunked upload.

Flash is very "buggy" thing :] The server response can not be empty. Therefore, in the event of a successful uploading http status should be only 200 OK.

Flash settings. It is advisable to place flash on the same server where the files will be uploaded.

<script>
	var FileAPI = {
	 	// @default: "./dist/"
		staticPath: '/js/',

		 // @default: FileAPI.staticPath + "FileAPI.flash.swf"
		flashUrl: '/statics/FileAPI.flash.swf',

		// @default: FileAPI.staticPath + "FileAPI.flash.image.swf"
		flashImageUrl: '/statics/FileAPI.flash.image.swf'
	};
</script>
<script src="/js/FileAPI.min.js"></script>

Necessarily make this file on the server. Do not forget to replace youdomain.com on the name of your domain.

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
	<site-control permitted-cross-domain-policies="all"/>
	<allow-access-from domain="youdomain.com" secure="false"/>
	<allow-access-from domain="*.youdomain.com" secure="false"/>
	<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
</cross-domain-policy>

The following sample HTTP POST request is sent from Flash Player to a server-side script if no parameters are specified:

POST /server/ctrl.php HTTP/1.1
Accept: text/*
Content-Type: multipart/form-data;
boundary=----------Ij5ae0ae0KM7GI3KM7
User-Agent: Shockwave Flash
Host: www.youdomain.com
Content-Length: 421
Connection: Keep-Alive
Cache-Control: no-cache

------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Filename"

MyFile.jpg
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Filedata"; filename="MyFile.jpg"
Content-Type: application/octet-stream

[[..FILE_DATA_HERE..]]
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Upload"

Submit Query
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7--

By default FileAPI.flash.swf allows access from any domain via Security.allowDomain("*"). This can lead to same origin bypass vulnerability if swf is loaded from the same domain as your critical data. To prevent this, allow only your domains here and rebuild flash.

<script>
(function (ctx, jsonp){
	'use strict';
	var status = {{httpStatus}}, statusText = "{{httpStatusText}}", response = "{{responseBody}}";
	try {
		ctx[jsonp](status, statusText, response);
	} catch (e){
		var data = "{\"id\":\""+jsonp+"\",\"status\":"+status+",\"statusText\":\""+statusText+"\",\"response\":\""+response.replace(/\"/g, '\\\\\"')+"\"}";
		try {
			ctx.postMessage(data, document.referrer);
		} catch (e){}
	}
})(window.parent, '{{request_param_callback}}');
</script>

<!-- or -->

<?php
	include './FileAPI.class.php';

	if( strtoupper($_SERVER['REQUEST_METHOD']) == 'POST' ){
		// Retrieve File List
		$files	= FileAPI::getFiles();

		// ... your logic

		// JSONP callback name
		$jsonp	= isset($_REQUEST['callback']) ? trim($_REQUEST['callback']) : null;

		// Server response: "HTTP/1.1 200 OK"
		FileAPI::makeResponse(array(
			  'status' => FileAPI::OK
			, 'statusText' => 'OK'
			, 'body' => array('count' => sizeof($files))
		), $jsonp);
		exit;
	}
?>

Enable CORS.

<?php
	// Permitted types of request
    header('Access-Control-Allow-Methods: POST, OPTIONS');

    // Describe custom headers
    header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type');

    // A comma-separated list of domains
    header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);

	// Allow cookie
	header('Access-Control-Allow-Credentials: true');

    if( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ){
        exit;
    }

    if( $_SERVER['REQUEST_METHOD'] == 'POST' ){
        // ...
    }
?>

Client and server communicate to each other using the following HTTP headers and status codes.
Client explicitly sets the following headers:

Any other headers are set by a target browser and are not used by client. Library does not provide any facilities to track a file uniqueness across requests, it's left on developer's consideration.
Response codes: For recoverable errors server tries to resend chunk `chunkUploadRetry` times then fails.
  • X-Last-Known-Byte: int, library tries to resend chunk from the given offset. Applicable to response codes 200 and 416
  • All the other codes - fatal error, user's involvement is recommended.

    Simple input[type="file"]

    <span class="js-fileapi-wrapper" style="position: relative; display: inline-block;">
        <input name="files" type="file" multiple/>
    </span>

    Stylized button.

    <style>
    .upload-btn {
        width: 130px;
        height: 25px;
        overflow: hidden;
        position: relative;
        border: 3px solid #06c;
        border-radius: 5px;
        background: #0cf;
    
    }
        .upload-btn:hover {
            background: #09f;
        }
        .upload-btn__txt {
            z-index: 1;
            position: relative;
            color: #fff;
            font-size: 18px;
            font-family: "Helvetica Neue";
            line-height: 24px;
            text-align: center;
            text-shadow: 0 1px 1px #000;
        }
        .upload-btn input {
            top: -10px;
            right: -40px;
            z-index: 2;
            position: absolute;
            cursor: pointer;
            opacity: 0;
            filter: alpha(opacity=0);
            font-size: 50px;
        }
    </style>
    <div class="js-fileapi-wrapper upload-btn">
        <div class="upload-btn__txt">Upload files</div>
        <input name="files" type="file" multiple />
    </div>

    Button like link.

    <style>
    .upload-link {
        color: #36c;
        display: inline-block;
        *zoom: 1;
        *display: inline;
        overflow: hidden;
        position: relative;
        padding-bottom: 2px;
        text-decoration: none;
    }
        .upload-link__txt {
            z-index: 1;
            position: relative;
            border-bottom: 1px dotted #36c;
        }
            .upload-link:hover .upload-link__txt {
                color: #f00;
                border-bottom-color: #f00;
            }
    
        .upload-link input {
            top: -10px;
            right: -40px;
            z-index: 2;
            position: absolute;
            cursor: pointer;
            opacity: 0;
            filter: alpha(opacity=0);
            font-size: 50px;
        }
    </style>
    <a class="js-fileapi-wrapper upload-link">
        <span class="upload-link__txt">Upload photo</span>
        <input name="photo" type="file" accept="image/*" />
    </a>
    Installation, testing, assembling

    npm install fileapi
    cd fileapi
    npm install
    grunt


    RetroSearch is an open source project built by @garambo | Open a GitHub Issue

    Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

    HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4