<!DOCTYPE html> <html lang="en"> <head> <title>SD Editor</title> <style type="text/css" media="screen"> .contextMenu { 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; } .contextMenu ul { list-style: none; top: 0; left: 0; margin: 0; padding: 0; } .contextMenu li { position: relative; min-width: 60px; cursor: pointer; } .contextMenu span { color: #444; display: inline-block; padding: 6px; } .contextMenu li:hover { background: #444; } .contextMenu li:hover span { color: #EEE; } .css-treeview ul, .css-treeview li { padding: 0; margin: 0; list-style: none; } .css-treeview input { position: absolute; opacity: 0; } .css-treeview { font: normal 11px Verdana, Arial, Sans-serif; -moz-user-select: none; -webkit-user-select: none; user-select: none; } .css-treeview span { color: #00f; cursor: pointer; } .css-treeview span:hover { text-decoration: underline; } .css-treeview input + label + ul { margin: 0 0 0 22px; } .css-treeview input ~ ul { display: none; } .css-treeview label, .css-treeview label::before { cursor: pointer; } .css-treeview input:disabled + label { cursor: default; opacity: .6; } .css-treeview input:checked:not(:disabled) ~ ul { display: block; } .css-treeview label, .css-treeview label::before { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAACgCAYAAAAFOewUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAApxJREFUeNrslM1u00AQgGdthyalFFOK+ClIIKQKyqUVQvTEE3DmAhLwAhU8QZoH4A2Q2gMSFace4MCtJ8SPBFwAkRuiHKpA6sRN/Lu7zG5i14kctaUqRGhGXnu9O/Pt7MzsMiklvF+9t2kWTDvyIrAsA0aKRRi1T0C/hJ4LUbt5/8rNpWVlp8RSr9J40b48fxFaTQ9+ft8EZ6MJYb0Ok+dnYGpmPgXwKIAvLx8vYXc5GdMAQJgQEkpjRTh36TS2U+DWW/D17WuYgm8pwJyY1npZsZKOxImOV1I/h4+O6vEg5GCZBpgmA6hX8wHKUHDRBXQYicQ4rlc3Tf0VMs8DHBS864F2YFspjgUYjKX/Az3gsdQd2eeBHwmdGWXHcgBGSkZXOXohcEXebRoQcAgjqediNY+AVyu3Z3sAKqfKoGMsewBeEIOPgQxxPJIjcGH6qtL/0AdADzKGnuuD+2tLK7Q8DhHHbOBW+KEzcHLuYc82MkEUekLiwuvVH+guQBQzOG4XdAb8EOcRcqQvDkY2iCLuxECJ43JobMXoutqGgDa2T7UqLKwt9KRyuxKVByqVXXqIoCCUCAqhUOioTWC7G4TQEOD0APy2/7G2Xpu1J4+lxeQ4TXBbITDpoVelRN/BVFbwu5oMMJUBhoXy5tmdRcMwymP2OLQaLjx9/vnBo6V3K6izATmSnMa0Dq7ferIohJhr1p01zrlz49rZF4OMs8JkX23vVQzYp+wbYGV/KpXKjvspl8tsIKCrMNAYFxj2GKS5ZWxg4ewKsJfaGMIY5KXqPz8LBBj6+yDvVP79+yDp/9F9oIx3OisHWwe7Oal0HxCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgwD8E/BZgAP0qhKj3rXO7AAAAAElFTkSuQmCC") no-repeat; } .css-treeview label, .css-treeview span, .css-treeview label::before { display: inline-block; height: 16px; line-height: 16px; vertical-align: middle; } .css-treeview label { background-position: 18px 0; } .css-treeview label::before { content: ""; width: 16px; margin: 0 22px 0 0; vertical-align: middle; background-position: 0 -32px; } .css-treeview input:checked + label::before { background-position: 0 -16px; } /* webkit adjacent element selector bugfix */ @media screen and (-webkit-min-device-pixel-ratio:0) { .css-treeview{ -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:200px; padding: 8px; } #editor, #preview { position: absolute; top: 28px; right: 0; bottom: 0; left: 200px; } #preview { background-color: #EEE; padding:5px; } </style> <script> function createFileUploader(element, tree, editor){ var xmlHttp; var input = document.createElement("input"); input.type = "file"; input.multiple = false; input.name = "data"; document.getElementById(element).appendChild(input); var path = document.createElement("input"); path.id = "upload-path"; path.type = "text"; path.name = "path"; path.defaultValue = "/"; document.getElementById(element).appendChild(path); var button = document.createElement("button"); button.innerHTML = 'Upload'; document.getElementById(element).appendChild(button); var mkdir = document.createElement("button"); mkdir.innerHTML = 'MkDir'; document.getElementById(element).appendChild(mkdir); var mkfile = document.createElement("button"); mkfile.innerHTML = 'MkFile'; document.getElementById(element).appendChild(mkfile); function httpPostProcessRequest(){ if (xmlHttp.readyState == 4){ if(xmlHttp.status != 200) alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText); else { tree.refreshPath(path.value); } } } function createPath(p){ xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = httpPostProcessRequest; var formData = new FormData(); formData.append("path", p); xmlHttp.open("PUT", "/edit"); xmlHttp.send(formData); } mkfile.onclick = function(e){ if(path.value.indexOf(".") === -1) return; createPath(path.value); editor.loadUrl(path.value); }; mkdir.onclick = function(e){ if(path.value.length < 2) return; var dir = path.value if(dir.indexOf(".") !== -1){ if(dir.lastIndexOf("/") === 0) return; dir = dir.substring(0, dir.lastIndexOf("/")); } createPath(dir); }; button.onclick = function(e){ if(input.files.length === 0){ return; } xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = httpPostProcessRequest; var formData = new FormData(); formData.append("data", input.files[0], path.value); xmlHttp.open("POST", "/edit"); xmlHttp.send(formData); } 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){ if(name.length > 8) name = name.substring(0, 8); 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 = document.getElementById("preview"); var treeRoot = document.createElement("div"); treeRoot.className = "css-treeview"; document.getElementById(element).appendChild(treeRoot); function loadDownload(path){ document.getElementById('download-frame').src = path+"?download=true"; } function loadPreview(path){ document.getElementById("editor").style.display = "none"; preview.style.display = "block"; preview.innerHTML = '<img src="'+path+'" style="max-width:100%; max-height:100%; margin:auto; display:block;" />'; } function fillFolderMenu(el, path){ var list = document.createElement("ul"); el.appendChild(list); var action = document.createElement("li"); list.appendChild(action); var isChecked = document.getElementById(path).checked; var expnd = document.createElement("li"); list.appendChild(expnd); if(isChecked){ expnd.innerHTML = "<span>Collapse</span>"; expnd.onclick = function(e){ document.getElementById(path).checked = false; if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el); }; var refrsh = document.createElement("li"); list.appendChild(refrsh); refrsh.innerHTML = "<span>Refresh</span>"; refrsh.onclick = function(e){ var leaf = document.getElementById(path).parentNode; if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]); httpGet(leaf, path); if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el); }; } else { expnd.innerHTML = "<span>Expand</span>"; expnd.onclick = function(e){ document.getElementById(path).checked = true; var leaf = document.getElementById(path).parentNode; if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]); httpGet(leaf, path); if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el); }; } var upload = document.createElement("li"); list.appendChild(upload); upload.innerHTML = "<span>Upload</span>"; upload.onclick = function(e){ var pathEl = document.getElementById("upload-path"); if(pathEl){ var subPath = pathEl.value; if(subPath.lastIndexOf("/") < 1) pathEl.value = path+subPath; else pathEl.value = path.substring(subPath.lastIndexOf("/"))+subPath; } if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el); }; var delFile = document.createElement("li"); list.appendChild(delFile); delFile.innerHTML = "<span>Delete</span>"; delFile.onclick = function(e){ httpDelete(path); if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el); }; } function fillFileMenu(el, path){ var list = document.createElement("ul"); el.appendChild(list); var action = document.createElement("li"); list.appendChild(action); if(isTextFile(path)){ action.innerHTML = "<span>Edit</span>"; action.onclick = function(e){ editor.loadUrl(path); if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el); }; } else if(isImageFile(path)){ action.innerHTML = "<span>Preview</span>"; action.onclick = function(e){ loadPreview(path); if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el); }; } var download = document.createElement("li"); list.appendChild(download); download.innerHTML = "<span>Download</span>"; download.onclick = function(e){ loadDownload(path); if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el); }; var delFile = document.createElement("li"); list.appendChild(delFile); delFile.innerHTML = "<span>Delete</span>"; delFile.onclick = function(e){ httpDelete(path); if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el); }; } function showContextMenu(e, path, isfile){ var divContext = document.createElement("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 = e.clientX + scrollLeft; var top = e.clientY + scrollTop; divContext.className = 'contextMenu'; divContext.style.display = 'block'; divContext.style.left = left + 'px'; divContext.style.top = top + 'px'; if(isfile) fillFileMenu(divContext, path); else fillFolderMenu(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('contextMenu').length > 0) document.body.removeChild(divContext); } }; } function createTreeLeaf(path, name, size){ var leaf = document.createElement("li"); leaf.id = (((path == "/")?"":path)+"/"+name).toLowerCase(); var label = document.createElement("span"); label.textContent = name.toLowerCase(); leaf.appendChild(label); leaf.onclick = function(e){ if(isTextFile(leaf.id)){ editor.loadUrl(leaf.id); } else if(isImageFile(leaf.id)){ loadPreview(leaf.id); } }; leaf.oncontextmenu = function(e){ e.preventDefault(); e.stopPropagation(); showContextMenu(e, leaf.id, true); }; return leaf; } function createTreeBranch(path, name, disabled){ var leaf = document.createElement("li"); var check = document.createElement("input"); check.type = "checkbox"; check.id = (((path == "/")?"":path)+"/"+name).toLowerCase(); if(typeof disabled !== "undefined" && disabled) check.disabled = "disabled"; leaf.appendChild(check); var label = document.createElement("label"); label.for = check.id; label.textContent = name.toLowerCase(); leaf.appendChild(label); check.onchange = function(e){ if(check.checked){ if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]); httpGet(leaf, check.id); } }; label.onclick = function(e){ if(!check.checked){ check.checked = true; if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]); httpGet(leaf, check.id); } else { check.checked = false; } }; leaf.oncontextmenu = function(e){ e.preventDefault(); e.stopPropagation(); showContextMenu(e, check.id, false); } return leaf; } function addList(parent, path, items){ var list = document.createElement("ul"); parent.appendChild(list); var ll = items.length; for(var i = 0; i < ll; i++){ var item = items[i]; var itemEl; if(item.type === "file"){ itemEl = createTreeLeaf(path, item.name, item.size); } else { itemEl = createTreeBranch(path, item.name); } list.appendChild(itemEl); } } function isTextFile(path){ var ext = /(?:\.([^.]+))?$/.exec(path)[1]; if(typeof ext !== undefined){ switch(ext){ case "txt": case "htm": case "html": case "js": case "json": case "c": case "h": case "cpp": case "css": case "xml": return true; } } return false; } function isImageFile(path){ var ext = /(?:\.([^.]+))?$/.exec(path)[1]; if(typeof ext !== undefined){ switch(ext){ case "png": case "jpg": case "gif": case "ico": return true; } } return false; } this.refreshPath = function(path){ if(path.lastIndexOf('/') < 1){ path = '/'; treeRoot.removeChild(treeRoot.childNodes[0]); httpGet(treeRoot, "/"); } else { path = path.substring(0, path.lastIndexOf('/')); var leaf = document.getElementById(path).parentNode; if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]); httpGet(leaf, path); } }; function delCb(path){ return function(){ if (xmlHttp.readyState == 4){ if(xmlHttp.status != 200){ alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText); } else { if(path.lastIndexOf('/') < 1){ path = '/'; treeRoot.removeChild(treeRoot.childNodes[0]); httpGet(treeRoot, "/"); } else { path = path.substring(0, path.lastIndexOf('/')); var leaf = document.getElementById(path).parentNode; if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]); httpGet(leaf, path); } } } } } function httpDelete(filename){ xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = delCb(filename); var formData = new FormData(); formData.append("path", filename); xmlHttp.open("DELETE", "/edit"); xmlHttp.send(formData); } function getCb(parent, path){ return function(){ if (xmlHttp.readyState == 4){ //clear loading if(xmlHttp.status == 200) addList(parent, path, JSON.parse(xmlHttp.responseText)); } } } function httpGet(parent, path){ xmlHttp = new XMLHttpRequest(parent, path); xmlHttp.onreadystatechange = getCb(parent, path); xmlHttp.open("GET", "/list?dir="+path, true); xmlHttp.send(null); //start loading } 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 "htm": lang = "html"; break; case "js": lang = "javascript"; 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": lang = ext; } } return lang; } if(typeof file === "undefined") file = "/index.htm"; if(typeof lang === "undefined"){ lang = getLangFromFilename(file); } if(typeof theme === "undefined") theme = "textmate"; if(typeof type === "undefined"){ type = "text/"+lang; if(lang === "c_cpp") type = "text/plain"; } var xmlHttp = null; var editor = ace.edit(element); //post function httpPostProcessRequest(){ if (xmlHttp.readyState == 4){ if(xmlHttp.status != 200) alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText); } } function httpPost(filename, data, type){ xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = httpPostProcessRequest; var formData = new FormData(); formData.append("data", new Blob([data], { type: type }), filename); xmlHttp.open("POST", "/edit"); xmlHttp.send(formData); } //get function httpGetProcessRequest(){ if (xmlHttp.readyState == 4){ document.getElementById("preview").style.display = "none"; document.getElementById("editor").style.display = "block"; if(xmlHttp.status == 200) editor.setValue(xmlHttp.responseText); else editor.setValue(""); editor.clearSelection(); } } function httpGet(theUrl){ xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = httpGetProcessRequest; xmlHttp.open("GET", theUrl, true); xmlHttp.send(null); } 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.1.9/ace.js" type="text/javascript" charset="utf-8"></script> </head> <body onload="onBodyLoad();"> <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>