Open ldijkman opened 2 years ago
still playing with the editor
would like to have html preview and a colorpicker addon
html preview working a bit
<!--
save upload or create/copy this file (as *watheveryouwanttonameit* .html)
to your ESP8266 ESP32 espasync webserver
will give you an ace js web/cloud editor with live HTML preview
not perfect but it works a bit
function update on editor textcontent change
shows only changes after save button is pressed
reload on every key stroke
iframe shows html result
i am not handy with divs
would be nice if the source and preview divs are resizable by mouse drag
maybe a checkbox for preview panel on / off
would like to have the colorpicker addon in the code editor
https://codepen.io/ldijkman/pen/LYdZpYp
-->
<!DOCTYPE html>
<html lang="en">
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Electra's ESP Editor</title>
<link rel="apple-touch-icon" href="/ace.ico" type="image/x-icon">
<link rel="shortcut icon" href="/ace.ico" type="image/x-icon">
<link rel="icon" href="/ace.ico" type="image/x-icon">
<style type="text/css" media="screen">
label {
font-size: 12px;
font-family: sans-serif
}
.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: 700
}
.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 li,
.tvu ul {
padding: 0;
margin: 0;
list-style: none
}
.tvu input {
position: absolute;
opacity: 0
}
.tvu {
font: 400 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: 0 0
}
.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: 100px;
padding: 8px
}
#editor,
#preview {
position: absolute;
top: 28px;
right: 0;
bottom: 0;
left: 120px;
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;
border-top: 8px solid #3498db;
border-bottom: 8px solid #3498db;
border-radius: 50%;
width: 240px;
height: 240px;
animation: spin 2s linear infinite;
display: none
}
@keyframes spin {
0% {
transform: rotate(0)
}
100% {
transform: rotate(360deg)
}
}
</style>
<style>
body {
font-family: 'Roboto', sans-serif;
background-color: whitesmoke;
}
.p1,
.header {
font-size: 32px;
text-align: center;
}
.button {
text-align: center;
font-size: 16px;
justify-content: center;
align-items: center;
}
.p2 {
text-align: left;
font-size: 24px;
}
html,
body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
ooverflow: hidden;
}
#editor {
height: 95%;
width: 50%;
display: inline-block;
border:1px solid black;
}
#container {
height: 100%;
width: auto;
white-space: nowrap;
ooverflow: hidden;
position: relative;
}
#iframediv {
float: right;
margin-top: 28px;
height: 100%;
width: 38%;
display: inline-block;
border:1px solid black;
}
#iframe {
top: 28px;
height: 100%;
width: 100%;
display: inline-block;
}
.ace_editor .ace_marker-layer .ace_bracket {
display: none
}
.color {
color: orange;
}
</style>
<!--
AceEditor ColorPicker AddOn
https://codepen.io/easylogic/pen/RwVOGed
I made a colorpicker for ace editor. #4725
easylogic started this conversation in Ideas
https://github.com/ajaxorg/ace/discussions/4725
-->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ace-colorpicker@0.0.12/addon/ace-colorpicker.css" />
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/ace-colorpicker@0.0.12/addon/ace-colorpicker.min.js" ></script>
<script>
function loadpick() {
// does not work
console.log("load colorpicker function loadpick");
AceColorPicker.load(ace.edit,editor);
}
function update() {
if(document.getElementById("Preview").checked){
var idoc = document.getElementById('iframe').contentWindow.document;
idoc.open();
idoc.write(ace.edit("editor").getValue());
//console.log(ace.edit("editor").getValue());
idoc.close();
}
}
function updatefrombutton() {
var idoc = document.getElementById('iframe').contentWindow.document;
idoc.open();
idoc.write(ace.edit("editor").getValue());
//console.log(ace.edit("editor").getValue());
idoc.close();
document.getElementById("save").setAttribute("title", "Save file to ESP LittleFS");
}
function ge(e) {
return document.getElementById(e)
}
function ce(e) {
return document.createElement(e)
}
function sortByKey(e, t) {
return e.sort((function(e, n) {
var a = e[t],
i = n[t];
return i > a ? -1 : a > i ? 1 : 0
}))
}
function createFileUploader(e, t, n) {
var a = /(iPhone)*(OS ([7-9]|1[0-1])_)/i.test(navigator.userAgent);
function i(e, n) {
200 != e ? alert("ERROR[" + e + "]: " + n) : t.refreshPath(d.value)
}
var o = ce("button");
o.innerHTML = "Root Dir", ge(e).appendChild(o);
var c = ce("input");
c.type = "file", c.multiple = !1, c.name = "data", c.id = "upload-select", ge(e).appendChild(c);
var d = ce("input");
d.id = "upload-path", d.type = "text", d.name = "path", d.defaultValue = "/", ge(e).appendChild(d);
var s = ce("button");
s.innerHTML = "Upload", ge(e).appendChild(s);
var r = ce("button");
r.innerHTML = "Create", ge(e).appendChild(r);
var l = ce("input");
l.id = "editor-filename", l.type = "text", l.disabled = !0, l.size = 20, ge(e).appendChild(l);
var u = ce("input");
u.id = "ipad-fix", u.title="i have no idea what this checkbox does", u.name = "ipad-fix", u.type = "checkbox", u.checked = !!a;
var p = ce("label");
p.for = u.id, p.innerHTML = " Alt.";
var m = ce("button");
m.innerHTML = " Save ",m.id = "save",m.title="Save file to ESP LittleFS", ge(e).appendChild(m), ge(e).appendChild(p), ge(e).appendChild(u), r.onclick = function(e) {
(function(e) {
var t = new FormData;
t.append("path", e), requests.add("PUT", "/edit", t, i)
})(d.value), n.loadUrl(d.value), d.value = "/"
}, m.onclick = function(e) {
if (u.checked) {
var t = ace.edit("editor").getValue(),
a = new FormData;
a.append("rawname", l.value);
var o = 0;
const e = 4096;
for (var c = 0; c < t.length; c += e) {
var d = t.substring(c, c + e);
a.append("raw" + o, d), o++
}
requests.add("POST", "/edit", a, i)
} else n.execCommand("saveCommand")
}, o.onclick = function(e) {
t.refreshPath(d.value)
}, s.onclick = function(e) {
if (0 !== c.files.length) {
var t = new FormData;
t.append("data", c.files[0], d.value), requests.add("POST", "/edit", t, i), ge("upload-path").value = "/", ge("upload-select").value = ""
}
}, c.onchange = function(e) {
if (0 !== c.files.length) {
var t = c.files[0].name,
n = /(?:\.([^.]+))?$/.exec(t)[1],
a = /(.*)\.[^.]+$/.exec(t)[1];
void 0 !== typeof a && (t = a), d.value = "/" + t + "." + n
}
}
}
function createTree(e, t) {
function n(e) {
ge("editor-filename").value = e, ge("editor").style.display = "none", p.style.display = "block", p.innerHTML = '<img src="/edit?edit=' + e + "&_cb=" + Date.now() + '" style="max-width:100%; max-height:100%; margin:auto; display:block;" />'
}
function a(e, a) {
var i = ce("ul");
e.appendChild(i);
var o = ce("li");
i.appendChild(o), s(a) ? (o.innerHTML = "<span>Preview</span>", o.onclick = function(t) {
n(a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
}) : d(a) && (o.innerHTML = "<span>Edit</span>", o.onclick = function(n) {
t.loadUrl(a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
});
var l = ce("li");
i.appendChild(l), d(a) || s(a) || function(e) {
var t = /(?:.([^.]+))?$/.exec(e)[1];
if (void 0 !== typeof t) switch (t) {
case "ico":
case "gz":
case "zip":
case "wav":
case "mp3":
case "pdf":
return !0
}
return !1
}(a) ? (l.innerHTML = "<span>Download</span>", l.onclick = function(t) {
(function(e) {
ge("download-frame").src = "/edit?download=" + e
})(a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
}) : c(a) && (i.appendChild(l), l.innerHTML = "<span>ChDir</span>", l.onclick = function(t) {
m.removeChild(m.childNodes[0]), u(m, a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
});
var p = ce("li");
i.appendChild(p), p.innerHTML = "<span>Delete</span>", p.onclick = function(t) {
r(a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
}
}
function i(e, t, n) {
var i = ce("div"),
o = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop,
c = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft,
d = e.clientX + c,
s = e.clientY + o;
i.className = "cm", i.style.display = "block", i.style.left = d + "px", i.style.top = s + "px", a(i, t), document.body.appendChild(i);
var r = i.offsetWidth,
l = i.offsetHeight;
i.onmouseout = function(e) {
(e.clientX < d || e.clientX > d + r || e.clientY < s || e.clientY > s + l) && document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(i)
}
}
function o(e, a, o) {
var r = ce("li");
r.id = a;
var l = ce("span");
return l.innerHTML = a, r.appendChild(l), r.onclick = function(e) {
d(r.id.toLowerCase()) ? t.loadUrl(r.id) : s(r.id.toLowerCase()) ? n(r.id) : c(r.id) && (m.removeChild(m.childNodes[0]) && u(m, r.id.toLowerCase()))
}, r.oncontextmenu = function(e) {
e.preventDefault(), e.stopPropagation(), i(e, r.id)
}, r
}
function c(e) {
return -1 == e.indexOf(".")
}
function d(e) {
var t = /(?:.([^.]+))?$/.exec(e)[1];
if (void 0 !== typeof t) switch (t) {
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":
case "ino":
case "pde":
return !0
}
return !1
}
function s(e) {
var t = /(?:.([^.]+))?$/.exec(e)[1];
if (void 0 !== typeof t) switch (t) {
case "png":
case "jpg":
case "gif":
case "bmp":
return !0
}
return !1
}
function r(e) {
var t = new FormData;
t.append("path", e), requests.add("DELETE", "/edit", t, (function(e, t) {
200 != e ? alert("ERROR[" + e + "]: " + t) : (m.removeChild(m.childNodes[0]), u(m, "/"))
}))
}
function l(e, t) {
return function(t, n) {
200 == t && function(e, t, n) {
sortByKey(n, "name");
var a = ce("ul");
e.appendChild(a);
for (var i = n.length, c = 0; i > c; c++) "file" === n[c].type && a.appendChild(o(0, n[c].name, n[c].size))
}(e, 0, JSON.parse(n))
}
}
function u(e, t) {
requests.add("GET", "/edit", {
list: t
}, l(e))
}
var p = ge("preview"),
m = ce("div");
return m.className = "tvu", ge(e).appendChild(m), this.refreshPath = function(e) {
m.removeChild(m.childNodes[0]), u(m, "/")
}, u(m, "/"), this
}
function createEditor(e, t, n, a, i) {
function o(e) {
var t = "plain",
n = /(?:.([^.]+))?$/.exec(e)[1];
if (void 0 !== typeof n) switch (n) {
case "txt":
case "hex":
case "conf":
t = "plain";
break;
case "htm":
t = "html";
break;
case "js":
t = "javascript";
break;
case "h":
case "c":
case "cpp":
t = "c_cpp";
break;
case "css":
case "scss":
case "php":
case "html":
case "json":
case "xml":
case "ini":
t = n
}
return t
}
function c(e, t) {
200 != e && alert("ERROR[" + e + "]: " + t)
}
function d(e, t) {
ge("preview").style.display = "none",
ge("editor").style.display = "block",
200 == e ? s.setValue(t) : s.setValue(""), s.clearSelection()
}
void 0 === t && (t = "/schedule.html"),
void 0 === n && (n = o(t)),
void 0 === a && (a = "monokai"),
void 0 === i && (i = "text/" + n, "c_cpp" === n && (i = "text/plain"));
var s = ace.edit(e);
return "plain" !== n &&
//s.session.setMode("ace/mode/"+n, () => { AceColorPicker.load(ace.edit, editor))}),
s.session.setMode("ace/mode/"+n, function() {loadpick()}),
s.setTheme("ace/theme/" + a),
s.$blockScrolling = 1 / 0,
s.getSession().setUseSoftTabs(!0),
s.getSession().setTabSize(2),
s.getSession().setUseWorker(!0),
s.setHighlightActiveLine(!0),
s.setShowPrintMargin(!1),
s.getSession().on('change', function() {update()}),
s.commands.addCommand({
name: "saveCommand",
bindKey: {
win: "Ctrl-S",
mac: "Command-S"
},
exec: function(e) {
! function(e, t, n) {
var a = new FormData;
a.append("data", new Blob([t], {
type: n
}), e), requests.add("POST", "/edit", a, c)
}(t, e.getValue() + "", i)
},
readOnly: !1
}), s.commands.addCommand({
name: "undoCommand",
bindKey: {
win: "Ctrl-Z",
mac: "Command-Z"
},
exec: function(e) {
e.getSession().getUndoManager().undo(!1)
},
readOnly: !1
}), s.commands.addCommand({
name: "redoCommand",
bindKey: {
win: "Ctrl-Shift-Z",
mac: "Command-Shift-Z"
},
exec: function(e) {
e.getSession().getUndoManager().redo(!1)
},
readOnly: !1
}), s.loadUrl = function(e) {
ge("editor-filename").value = e, n = o(t = e), i = "text/" + n, "plain" !== n && s.getSession().setMode("ace/mode/" + n),
function(e) {
requests.add("GET", "/edit", {
edit: e
}, d)
}(t)
}, s
}
function onBodyLoad() {
var e = {},
t = (window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, (function(t, n, a) {
e[n] = a
})), createEditor("editor", e.file, e.lang, e.theme)),
n = createTree("tree", t);
window.define = ace.define, window.require = ace.require, ace.config.set("basePath", "/"), ace.config.set("workerPath", "/"), createFileUploader("uploader", n, t), void 0 === e.file && (e.file = "/schedule.html"), t.loadUrl(e.file)
}
"undefined" == typeof XMLHttpRequest && (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.")
});
var QueuedRequester = function() {
this.queue = [], this.running = !1, this.xmlhttp = null
};
QueuedRequester.prototype = {
_request: function(e) {
if (this.running = !0, !(!e instanceof Object)) {
var t = this;
ge("loader").style.display = "block";
var n = "";
if (e.params instanceof FormData) n = e.params;
else if (e.params instanceof Object)
for (var a in e.params) n += "" === n ? "GET" === e.method ? "?" : "" : "&", n += encodeURIComponent(a) + "=" + encodeURIComponent(e.params[a]);
this.xmlhttp = new XMLHttpRequest, this.xmlhttp.onreadystatechange = function(e, n) {
return function() {
4 == e.readyState && (ge("loader").style.display = "none", n.callback(e.status, e.responseText), 0 === t.queue.length && (t.running = !1), t.running && t._request(t.queue.shift()))
}
}(this.xmlhttp, e), "GET" === e.method ? (this.xmlhttp.open(e.method, e.url + n, !0), this.xmlhttp.send()) : (this.xmlhttp.open(e.method, e.url, !0), n instanceof String && this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"), this.xmlhttp.send(n))
}
},
stop: function() {
this.running && (this.running = !1), this.xmlhttp && this.xmlhttp.readyState < 4 && this.xmlhttp.abort()
},
add: function(e, t, n, a) {
this.queue.push({
url: t,
method: e,
params: n,
callback: a
}), this.running || this._request(this.queue.shift())
}
};
var requests = new QueuedRequester
</script>
<script type="text/javascript" src="https://ajaxorg.github.io/ace-builds/src/ace.js"></script>
<script>
if (void 0 === ace.edit) {
var script = document.createElement("script");
script.src = "/ace.js", script.async = !1, document.head.appendChild(script)
}
</script>
<body onload="onBodyLoad()">
<!--
would like to use / have another the colorpicker addon in the code editor
active on edited text
but i do not know how to implement it in this html/script code
https://codepen.io/ldijkman/pen/LYdZpYp
-->
<script type="text/javascript" src="https://jscolor.com/release/2.4/jscolor-2.4.8/jscolor.js" ></script>
<div id="container">
<div id="loader" class="loader"></div>
<div id="uploader">
<div style="float:right;">
<input type="checkbox" class="Preview" id ="Preview" name="Preview" title="reload preview on every keystroke on/off" checked>
<input type="button" value="Preview" onClick="updatefrombutton();" title="reload by button if checkbox is off" ></label>
Color: <input id="to-select-text" onClick="this.select();" value="#3399FF80" data-jscolor="{}" title="colorpicker" >
<button id="copy-button" title="maybe copy works" >Copy</button>
</div>
</div>
<div id="tree"></div>
<div id="editor"></div>
<div id="iframediv"><iframe id="iframe" frameborder="0"></iframe></div>
<div id="preview" style="display:none"></div>
<iframe id="download-frame" style="display:none"></iframe>
</div>
<script>
// https://codepen.io/fabean/pen/GprQJa
let button = document.getElementById('copy-button');
button.addEventListener('click', function(e) {
e.preventDefault();
document.execCommand('copy', false, document.getElementById('to-select-text').select());
});
</script>
<!-- https://ldijkman.github.io/Electra/ -->
thank you so much 4 the espasync littlefs fsbrowser
https://www.youtube.com/watch?v=1gPBMtsRDFA
greet luberth netherlands