New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SpiffsEditor #102
Comments
yeah... i keep forgetting to add save button, but Ctrl+S on Windows/Linux and Cmd+S on Mac do the job of saving |
So for creating a new file the steps is the following? |
yes, or open->edit->ctrl+s |
Here is the modified index.htm file extracted, with monokai theme and save button :-) To use directly edit.htm file I just changed info //AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", edit_htm_gz, edit_htm_gz_len);
//response->addHeader("Content-Encoding", "gzip");
//request->send(response);
//Send edit.htm with default content type
request->send(SPIFFS, "/edit.htm"); and of course removed edit_htm_gz Modified file index.htm <!-- 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;
}
#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>
if (typeof XMLHttpRequest === "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
}
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> |
[STALE_SET] This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions. |
[STALE_DEL] This stale issue has been automatically closed. Thank you for your contributions. |
I'm probably just stupid and missing something, but is the editor actually supposed to be able to edit files?
I get the file listing and kan view file content.
I can also upload files.
But how can I edit existing files? My first thought is that a save-button is missing.
I can also create new files, but the content of the file is always just a singel dot
.
The text was updated successfully, but these errors were encountered: