586 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			586 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!-- This file has been written by Me-No-Dev
 | |
| see https://github.com/me-no-dev/ and is part
 | |
| of the sample code of ESPAsyncWebServer
 | |
| see https://github.com/me-no-dev/ESPAsyncWebServer -->
 | |
| <!DOCTYPE html>
 | |
| <html lang="en">
 | |
| <head>
 | |
| <title>ESP8266 SPIFFS File Editor</title>
 | |
| <meta name="author" content="Me-No-Dev" />
 | |
| <meta name="Description" content="ESP8266 SPIFFS File Web Editor"/>
 | |
| <link rel="shortcut icon" href="favicon.ico"/>
 | |
| <style type="text/css" media="screen">
 | |
| .cm {
 | |
|   z-index: 300;
 | |
|   position: absolute;
 | |
|   left: 5px;
 | |
|   border: 1px solid #444;
 | |
|   background-color: #F5F5F5;
 | |
|   display: none;
 | |
|   box-shadow: 0 0 10px rgba( 0, 0, 0, .4 );
 | |
|   font-size: 12px;
 | |
|   font-family: sans-serif;
 | |
|   font-weight:bold;
 | |
| }
 | |
| .cm ul {
 | |
|   list-style: none;
 | |
|   top: 0;
 | |
|   left: 0;
 | |
|   margin: 0;
 | |
|   padding: 0;
 | |
| }
 | |
| .cm li {
 | |
|   position: relative;
 | |
|   min-width: 60px;
 | |
|   cursor: pointer;
 | |
| }
 | |
| .cm span {
 | |
|   color: #444;
 | |
|   display: inline-block;
 | |
|   padding: 6px;
 | |
| }
 | |
| .cm li:hover { background: #444; }
 | |
| .cm li:hover span { color: #EEE; }
 | |
| .tvu ul, .tvu li {
 | |
|   padding: 0;
 | |
|   margin: 0;
 | |
|   list-style: none;
 | |
| }
 | |
| .tvu input {
 | |
|   position: absolute;
 | |
|   opacity: 0;
 | |
| }
 | |
| .tvu {
 | |
|   font: normal 12px Verdana, Arial, Sans-serif;
 | |
|   -moz-user-select: none;
 | |
|   -webkit-user-select: none;
 | |
|   user-select: none;
 | |
|   color: #444;
 | |
|   line-height: 16px;
 | |
| }
 | |
| .tvu span {
 | |
|   margin-bottom:5px;
 | |
|   padding: 0 0 0 18px;
 | |
|   cursor: pointer;
 | |
|   display: inline-block;
 | |
|   height: 16px;
 | |
|   vertical-align: middle;
 | |
|   background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKOgiihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQIV2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4trRCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC') no-repeat;
 | |
|   background-position: 0px 0px;
 | |
| }
 | |
| .tvu span:hover {
 | |
|   text-decoration: underline;
 | |
| }
 | |
| @media screen and (-webkit-min-device-pixel-ratio:0){
 | |
|   .tvu{
 | |
|     -webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s;
 | |
|   }
 | |
| 
 | |
|   @-webkit-keyframes webkit-adjacent-element-selector-bugfix {
 | |
|     from { 
 | |
|       padding: 0;
 | |
|     } 
 | |
|     to { 
 | |
|       padding: 0;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| #uploader { 
 | |
|   position: absolute;
 | |
|   top: 0;
 | |
|   right: 0;
 | |
|   left: 0;
 | |
|   height:28px;
 | |
|   line-height: 24px;
 | |
|   padding-left: 10px;
 | |
|   background-color: #444;
 | |
|   color:#EEE;
 | |
| }
 | |
| #tree { 
 | |
|   position: absolute;
 | |
|   top: 28px;
 | |
|   bottom: 0;
 | |
|   left: 0;
 | |
|   width:160px;
 | |
|   padding: 8px;
 | |
|   background-color: #222;
 | |
| }
 | |
| #editor, #preview { 
 | |
|   position: absolute;
 | |
|   top: 28px;
 | |
|   right: 0;
 | |
|   bottom: 0;
 | |
|   left: 160px;
 | |
|   border-left:1px solid #EEE;
 | |
| }
 | |
| #preview {
 | |
|   background-color: #EEE;
 | |
|   padding:5px;
 | |
| }
 | |
| #loader { 
 | |
|   position: absolute;
 | |
|   top: 36%;
 | |
|   right: 40%;
 | |
| }
 | |
| .loader {
 | |
|     z-index: 10000;
 | |
|     border: 8px solid #b5b5b5; /* Grey */
 | |
|     border-top: 8px solid #3498db; /* Blue */
 | |
|     border-bottom: 8px solid #3498db; /* Blue */
 | |
|     border-radius: 50%;
 | |
|     width: 240px;
 | |
|     height: 240px;
 | |
|     animation: spin 2s linear infinite;
 | |
|     display:none;
 | |
| }
 | |
| 
 | |
| @keyframes spin {
 | |
|     0% { transform: rotate(0deg); }
 | |
|     100% { transform: rotate(360deg); }
 | |
| }
 | |
| </style>
 | |
| <script>
 | |
| function ge(a){
 | |
|   return document.getElementById(a);
 | |
| }
 | |
| function ce(a){
 | |
|   return document.createElement(a);
 | |
| }
 | |
| 
 | |
| var QueuedRequester = function () {
 | |
|   this.queue = [];
 | |
|   this.running = false;
 | |
|   this.xmlhttp = null;
 | |
| }
 | |
| QueuedRequester.prototype = {
 | |
|   _request: function(req){
 | |
|     this.running = true;
 | |
|     if(!req instanceof Object) return;
 | |
|     var that = this;
 | |
|     
 | |
|     function ajaxCb(x,d){ return function(){
 | |
|       if (x.readyState == 4){
 | |
|         ge("loader").style.display = "none";
 | |
|         d.callback(x.status, x.responseText);
 | |
|         if(that.queue.length === 0) that.running = false;
 | |
|         if(that.running) that._request(that.queue.shift());
 | |
|       }
 | |
|     }}
 | |
|     
 | |
|     ge("loader").style.display = "block";
 | |
|     
 | |
|     var p = "";
 | |
|     if(req.params instanceof FormData){
 | |
|       p = req.params;
 | |
|     } else if(req.params instanceof Object){
 | |
|       for (var key in req.params) {
 | |
|         if(p === "")
 | |
|           p += (req.method === "GET")?"?":"";
 | |
|         else
 | |
|           p += "&";
 | |
|         p += encodeURIComponent(key+"="+req.params[key]);
 | |
|       };
 | |
|     }
 | |
|     
 | |
|     this.xmlhttp = new XMLHttpRequest();
 | |
|     this.xmlhttp.onreadystatechange = ajaxCb(this.xmlhttp, req);
 | |
|     if(req.method === "GET"){
 | |
|       this.xmlhttp.open(req.method, req.url+p, true);
 | |
|       this.xmlhttp.send();
 | |
|     } else {
 | |
|       this.xmlhttp.open(req.method, req.url, true);
 | |
|       if(p instanceof String)
 | |
|         this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
 | |
|       this.xmlhttp.send(p);
 | |
|     }
 | |
|   },
 | |
|   stop: function(){
 | |
|     if(this.running) this.running = false;
 | |
|     if(this.xmlhttp && this.xmlhttp.readyState < 4){
 | |
|       this.xmlhttp.abort();
 | |
|     }
 | |
|   },
 | |
|   add: function(method, url, params, callback){
 | |
|     this.queue.push({url:url,method:method,params:params,callback:callback});
 | |
|     if(!this.running){
 | |
|       this._request(this.queue.shift());
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| var requests = new QueuedRequester();
 | |
| 
 | |
| function createFileUploader(element, tree, editor){
 | |
|   var xmlHttp;
 | |
|   var input = ce("input");
 | |
|   input.type = "file";
 | |
|   input.multiple = false;
 | |
|   input.name = "data";
 | |
|   ge(element).appendChild(input);
 | |
|   var path = ce("input");
 | |
|   path.id = "upload-path";
 | |
|   path.type = "text";
 | |
|   path.name = "path";
 | |
|   path.defaultValue = "/";
 | |
|   ge(element).appendChild(path);
 | |
|   var button = ce("button");
 | |
|   button.innerHTML = 'Upload';
 | |
|   ge(element).appendChild(button);
 | |
|   var mkfile = ce("button");
 | |
|   mkfile.innerHTML = 'Create';
 | |
|   ge(element).appendChild(mkfile);
 | |
| 
 | |
|   var savefile = ce("button");
 | |
|   savefile.innerHTML = ' Save ' ;
 | |
|   ge(element).appendChild(savefile);
 | |
| 
 | |
|   function httpPostProcessRequest(status, responseText){
 | |
|     if(status != 200)
 | |
|       alert("ERROR "+status+": "+responseText);
 | |
|     else
 | |
|       tree.refreshPath(path.value);
 | |
|   }
 | |
|   function createPath(p){
 | |
|     var formData = new FormData();
 | |
|     formData.append("path", p);
 | |
|     requests.add("PUT", "/edit", formData, httpPostProcessRequest);
 | |
|   }
 | |
|   
 | |
|   mkfile.onclick = function(e){
 | |
|     if(path.value.indexOf(".") === -1) return;
 | |
|     createPath(path.value);
 | |
|     editor.loadUrl(path.value);
 | |
|   };
 | |
| 
 | |
|   savefile.onclick = function(e){
 | |
|     editor.execCommand('saveCommand');
 | |
|   };
 | |
|   
 | |
|   button.onclick = function(e){
 | |
|     if(input.files.length === 0){
 | |
|       return;
 | |
|     }
 | |
|     var formData = new FormData();
 | |
|     formData.append("data", input.files[0], path.value);
 | |
|     requests.add("POST", "/edit", formData, httpPostProcessRequest);
 | |
|   };
 | |
|   input.onchange = function(e){
 | |
|     if(input.files.length === 0) return;
 | |
|     var filename = input.files[0].name;
 | |
|     var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
 | |
|     var name = /(.*)\.[^.]+$/.exec(filename)[1];
 | |
|     if(typeof name !== undefined){
 | |
|       filename = name;
 | |
|     }
 | |
|     if(typeof ext !== undefined){
 | |
|       if(ext === "html") ext = "htm";
 | |
|       else if(ext === "jpeg") ext = "jpg";
 | |
|       filename = filename + "." + ext;
 | |
|     }
 | |
|     if(path.value === "/" || path.value.lastIndexOf("/") === 0){
 | |
|       path.value = "/"+filename;
 | |
|     } else {
 | |
|       path.value = path.value.substring(0, path.value.lastIndexOf("/")+1)+filename;
 | |
|     }
 | |
|   };
 | |
| }
 | |
| 
 | |
| function createTree(element, editor){
 | |
|   var preview = ge("preview");
 | |
|   var treeRoot = ce("div");
 | |
|   treeRoot.className = "tvu";
 | |
|   ge(element).appendChild(treeRoot);
 | |
| 
 | |
|   function loadDownload(path){
 | |
|     ge('download-frame').src = "/edit?download="+path;
 | |
|   }
 | |
| 
 | |
|   function loadPreview(path){
 | |
|     ge("editor").style.display = "none";
 | |
|     preview.style.display = "block";
 | |
|     preview.innerHTML = '<img src="/edit?edit='+path+'&_cb='+Date.now()+'" style="max-width:100%; max-height:100%; margin:auto; display:block;" />';
 | |
|   }
 | |
| 
 | |
|   function fillFileMenu(el, path){
 | |
|     var list = ce("ul");
 | |
|     el.appendChild(list);
 | |
|     var action = ce("li");
 | |
|     list.appendChild(action);
 | |
|     if(isImageFile(path)){
 | |
|       action.innerHTML = "<span>Preview</span>";
 | |
|       action.onclick = function(e){
 | |
|         loadPreview(path);
 | |
|         if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
 | |
|       };
 | |
|     } else if(isTextFile(path)){
 | |
|       action.innerHTML = "<span>Edit</span>";
 | |
|       action.onclick = function(e){
 | |
|         editor.loadUrl(path);
 | |
|         if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
 | |
|       };
 | |
|     }
 | |
|     var download = ce("li");
 | |
|     list.appendChild(download);
 | |
|     download.innerHTML = "<span>Download</span>";
 | |
|     download.onclick = function(e){
 | |
|       loadDownload(path);
 | |
|       if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
 | |
|     };
 | |
|     var delFile = ce("li");
 | |
|     list.appendChild(delFile);
 | |
|     delFile.innerHTML = "<span>Delete</span>";
 | |
|     delFile.onclick = function(e){
 | |
|       httpDelete(path);
 | |
|       if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   function showContextMenu(event, path, isfile){
 | |
|     var divContext = ce("div");
 | |
|     var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop;
 | |
|     var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft;
 | |
|     var left = event.clientX + scrollLeft;
 | |
|     var top = event.clientY + scrollTop;
 | |
|     divContext.className = 'cm';
 | |
|     divContext.style.display = 'block';
 | |
|     divContext.style.left = left + 'px';
 | |
|     divContext.style.top = top + 'px';
 | |
|     fillFileMenu(divContext, path);
 | |
|     document.body.appendChild(divContext);
 | |
|     var width = divContext.offsetWidth;
 | |
|     var height = divContext.offsetHeight;
 | |
|     divContext.onmouseout = function(e){
 | |
|       if(e.clientX < left || e.clientX > (left + width) || e.clientY < top || e.clientY > (top + height)){
 | |
|         if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(divContext);
 | |
|       }
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   function createTreeLeaf(path, name, size){
 | |
|     var leaf = ce("li");
 | |
|     leaf.id = (((path == "/")?"":path)+"/"+name);
 | |
|     var label = ce("span");
 | |
|     label.innerHTML = name;
 | |
|     leaf.appendChild(label);
 | |
|     leaf.onclick = function(e){
 | |
|       if(isTextFile(leaf.id.toLowerCase())){
 | |
|         editor.loadUrl(leaf.id);
 | |
|       } else if(isImageFile(leaf.id.toLowerCase())){
 | |
|         loadPreview(leaf.id);
 | |
|       }
 | |
|     };
 | |
|     leaf.oncontextmenu = function(e){
 | |
|       e.preventDefault();
 | |
|       e.stopPropagation();
 | |
|       showContextMenu(e, leaf.id, true);
 | |
|     };
 | |
|     return leaf;
 | |
|   }
 | |
| 
 | |
|   function addList(parent, path, items){
 | |
|     var list = ce("ul");
 | |
|     parent.appendChild(list);
 | |
|     var ll = items.length;
 | |
|     for(var i = 0; i < ll; i++){
 | |
|       if(items[i].type === "file")
 | |
|         list.appendChild(createTreeLeaf(path, items[i].name, items[i].size));
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
|   function isTextFile(path){
 | |
|     var ext = /(?:\.([^.]+))?$/.exec(path)[1];
 | |
|     if(typeof ext !== undefined){
 | |
|       switch(ext){
 | |
|         case "txt":
 | |
|         case "htm":
 | |
|         case "html":
 | |
|         case "js":
 | |
|         case "css":
 | |
|         case "xml":
 | |
|         case "json":
 | |
|         case "conf":
 | |
|         case "ini":
 | |
|         case "h":
 | |
|         case "c":
 | |
|         case "cpp":
 | |
|         case "php":
 | |
|         case "hex":
 | |
|           return true;
 | |
|       }
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   function isImageFile(path){
 | |
|     var ext = /(?:\.([^.]+))?$/.exec(path)[1];
 | |
|     if(typeof ext !== undefined){
 | |
|       switch(ext){
 | |
|         case "png":
 | |
|         case "jpg":
 | |
|         case "gif":
 | |
|           return true;
 | |
|       }
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   this.refreshPath = function(path){
 | |
|     treeRoot.removeChild(treeRoot.childNodes[0]);
 | |
|     httpGet(treeRoot, "/");
 | |
|   };
 | |
| 
 | |
|   function delCb(path){
 | |
|     return function(status, responseText){
 | |
|       if(status != 200){
 | |
|         alert("ERROR "+status+": "+responseText);
 | |
|       } else {
 | |
|         treeRoot.removeChild(treeRoot.childNodes[0]);
 | |
|         httpGet(treeRoot, "/");
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function httpDelete(filename){
 | |
|     var formData = new FormData();
 | |
|     formData.append("path", filename);
 | |
|     requests.add("DELETE", "/edit", formData, delCb(filename));
 | |
|   }
 | |
| 
 | |
|   function getCb(parent, path){
 | |
|     return function(status, responseText){
 | |
|       if(status == 200)
 | |
|         addList(parent, path, JSON.parse(responseText));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function httpGet(parent, path){
 | |
|     requests.add("GET", "/edit", { list: path }, getCb(parent, path));
 | |
|   }
 | |
| 
 | |
|   httpGet(treeRoot, "/");
 | |
|   return this;
 | |
| }
 | |
| 
 | |
| function createEditor(element, file, lang, theme, type){
 | |
|   function getLangFromFilename(filename){
 | |
|     var lang = "plain";
 | |
|     var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
 | |
|     if(typeof ext !== undefined){
 | |
|       switch(ext){
 | |
|         case "txt": lang = "plain"; break;
 | |
|         case "hex": lang = "plain"; break;
 | |
|         case "conf": lang = "plain"; break;
 | |
|         case "htm": lang = "html"; break;
 | |
|         case "js": lang = "javascript"; break;
 | |
|         case "h": lang = "c_cpp"; break;
 | |
|         case "c": lang = "c_cpp"; break;
 | |
|         case "cpp": lang = "c_cpp"; break;
 | |
|         case "css":
 | |
|         case "scss":
 | |
|         case "php":
 | |
|         case "html":
 | |
|         case "json":
 | |
|         case "xml":
 | |
|         case "ini":
 | |
|           lang = ext;
 | |
|       }
 | |
|     }
 | |
|     return lang;
 | |
|   }
 | |
| 
 | |
|   if(typeof file === "undefined") file = "/index.htm";
 | |
| 
 | |
|   if(typeof lang === "undefined"){
 | |
|     lang = getLangFromFilename(file);
 | |
|   }
 | |
| 
 | |
|   if(typeof theme === "undefined") theme = "monokai";
 | |
| 
 | |
|   if(typeof type === "undefined"){
 | |
|     type = "text/"+lang;
 | |
|     if(lang === "c_cpp") type = "text/plain";
 | |
|   }
 | |
| 
 | |
|   var editor = ace.edit(element);
 | |
|   function httpPostProcessRequest(status, responseText){
 | |
|     if(status != 200) alert("ERROR "+status+": "+responseText);
 | |
|   }
 | |
|   function httpPost(filename, data, type){
 | |
|     var formData = new FormData();
 | |
|     formData.append("data", new Blob([data], { type: type }), filename);
 | |
|     requests.add("POST", "/edit", formData, httpPostProcessRequest);
 | |
|   }
 | |
|   function httpGetProcessRequest(status, responseText){
 | |
|       ge("preview").style.display = "none";
 | |
|       ge("editor").style.display = "block";
 | |
|       if(status == 200)
 | |
|         editor.setValue(responseText);
 | |
|       else
 | |
|         editor.setValue("");
 | |
|       editor.clearSelection();
 | |
|   }
 | |
|   function httpGet(theUrl){
 | |
|       requests.add("GET", "/edit", { edit: theUrl }, httpGetProcessRequest);
 | |
|   }
 | |
| 
 | |
|   if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
 | |
|   editor.setTheme("ace/theme/"+theme);
 | |
|   editor.$blockScrolling = Infinity;
 | |
|   editor.getSession().setUseSoftTabs(true);
 | |
|   editor.getSession().setTabSize(2);
 | |
|   editor.setHighlightActiveLine(true);
 | |
|   editor.setShowPrintMargin(false);
 | |
|   editor.commands.addCommand({
 | |
|       name: 'saveCommand',
 | |
|       bindKey: {win: 'Ctrl-S',  mac: 'Command-S'},
 | |
|       exec: function(editor) {
 | |
|         httpPost(file, editor.getValue()+"", type);
 | |
|       },
 | |
|       readOnly: false
 | |
|   });
 | |
|   editor.commands.addCommand({
 | |
|       name: 'undoCommand',
 | |
|       bindKey: {win: 'Ctrl-Z',  mac: 'Command-Z'},
 | |
|       exec: function(editor) {
 | |
|         editor.getSession().getUndoManager().undo(false);
 | |
|       },
 | |
|       readOnly: false
 | |
|   });
 | |
|   editor.commands.addCommand({
 | |
|       name: 'redoCommand',
 | |
|       bindKey: {win: 'Ctrl-Shift-Z',  mac: 'Command-Shift-Z'},
 | |
|       exec: function(editor) {
 | |
|         editor.getSession().getUndoManager().redo(false);
 | |
|       },
 | |
|       readOnly: false
 | |
|   });
 | |
|   httpGet(file);
 | |
|   editor.loadUrl = function(filename){
 | |
|     file = filename;
 | |
|     lang = getLangFromFilename(file);
 | |
|     type = "text/"+lang;
 | |
|     if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
 | |
|     httpGet(file);
 | |
|   };
 | |
|   return editor;
 | |
| }
 | |
| function onBodyLoad(){
 | |
|   var vars = {};
 | |
|   var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; });
 | |
|   var editor = createEditor("editor", vars.file, vars.lang, vars.theme);
 | |
|   var tree = createTree("tree", editor);
 | |
|   createFileUploader("uploader", tree, editor);
 | |
| };
 | |
| </script>
 | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/ace.js" type="text/javascript" charset="utf-8"></script>
 | |
| </head>
 | |
| <body onload="onBodyLoad();">
 | |
|   <div id="loader" class="loader"></div>
 | |
|   <div id="uploader"></div>
 | |
|   <div id="tree"></div>
 | |
|   <div id="editor"></div>
 | |
|   <div id="preview" style="display:none;"></div>
 | |
|   <iframe id=download-frame style='display:none;'></iframe>
 | |
| </body>
 | |
| </html> | 
