UNPKG

lightview

Version:

Small, simple, powerful web UI and micro front end creation ... Great ideas from Svelte, React, Vue and Riot combined.

320 lines (292 loc) 13 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Lightview:REPL</title> </head> <body> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/default.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <div id="content" style="flex:auto;min-height:100%;width:750px;max-width:750px;height:100%;border:1px solid;padding:10px;margin:0 auto"> <div style="display:flex;flex-direction:column;"> <div id="tabs" style="flex-grow:0;width:100%;border:1px;padding-bottom:5px;text-align:center"> <span style="padding-right:10px"><label for="headhtml" l-on:click="${onTabClick}">HTML Head</label><input for="headhtml" value="${headhtmlPinned}" type="checkbox" l-on:click="${onPinClick}"></span> <span style="padding-right:10px"><label for="bodyhtml" l-on:click="${onTabClick}">HTML Body</label><input for="bodyhtml" value="${bodyhtmlPinned}" type="checkbox" l-on:click="${onPinClick}" checked></span> <!--span style="padding-right:10px"><label for="markdown" l-on:click="${onTabClick}">Markdown (Body)</label><input for="markdown" value="${markdownPinned}" type="checkbox" l-on:click="${onPinClick}"></span--> <span style="padding-right:10px"><label for="css" l-on:click="${onTabClick}">Style</label><input for="css" value="${cssPinned}" type="checkbox" l-on:click="${onPinClick}"></span> <span style="padding-right:10px"><label for="script" l-on:click="${onTabClick}">Script</label><input for="script" value="${scriptPinned}" type="checkbox" l-on:click="${onPinClick}"></span> <span style="padding-right:10px"><label for="preview" l-on:click="${onTabClick}">Preview</label><input for="preview" value="${previewPinned}" type="checkbox" l-on:click="${onPinClick}"></span> </div> </div> <div id="headhtml" style="display:none;margin:10px" class="language-html">&lt;head&gt;<slot name="head"></slot>&lt;/head&gt;</div> <div id="bodyhtml" style="margin:10px" class="language-html">&lt;body&gt;<slot name="body"></slot>&lt;/body&gt;</div> <!--textarea id="markdown" style="margin-right:2px;display:none">${markdown}</textarea--> <div id="css" style="display:none;margin:10px" class="language-css">&lt;style&gt;<slot name="css"></slot>&lt;/style&gt;</div> <div id="script" style="display:none;margin:10px" class="language-javascript">&lt;script&gt;<slot name="script"></slot>&lt;/script&gt;</div> <iframe id="preview" style="max-width:99%;width:99%;margin:10px;display:none" src="./blank.html"></iframe> </div> <div style="width:100%;text-align:center"> <div style="padding:5px">${source}</div> <button l-on:click="${doSave}">Save</button>&nbsp;&nbsp; <button l-if="${!source.trim().startsWith('http')}" l-on:click="${doReset}">Reset</button> </div> <style id="style"> label:hover { text-decoration: underline } </style> <script id="lightview"> (document.currentComponent||(document.currentComponent=document.body)).mount = async function() { const {CodeJar} = await import("https://cdn.jsdelivr.net/npm/codejar@3.6.0/codejar.min.js"); const turndownService = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced", emDelimiter: "*" }); turndownService.keep(() => true); const {html, css, script} = await import("../types.js"); self.variables({ onTabClick: "function", onPinClick: "function", doSave: "function", doReset: "function" }); self.variables({ wysiwygPinned: "boolean", bodyhtmlPinned: "boolean", bodyhtml: html, markdownPinned: "boolean", markdown: "string", cssPinned: "boolean", cssText: css, scriptPinned: "boolean", scriptText: script, headhtmlPinned: "boolean", headhtml: html, previewPinned: "boolean", source: "string", }, {reactive}); self.variables({ src: "string", }, {imported}); bodyhtmlPinned = true; const loadFromFile = async () => { const html = await fs.readFile(url.pathname, {encoding: "utf8"}); source = `IndexedDB://${url.hostname + "_repl"}${url.pathname}`; return html; }; const loadFromServer = async () => { const response = await fetch(url.href); const html = await response.text(); source = url.href; return html; }; const parseFullHTML = (fullHTML) => { const parser = new DOMParser(), fragment = parser.parseFromString(fullHTML, "text/html"), body_el = fragment?.querySelector("body"), style_el = fragment?.querySelector("style"), script_el = body_el?.querySelector("script"); if (style_el) { cssText = style_el?.innerHTML.trim(); style_el.remove(); } else { cssText = ""; } if (script_el) { scriptText = script_el?.innerHTML.trim(), script_el.remove(); } else { scriptText = ""; } headhtml = (fragment?.head.innerHTML || "").trim(); bodyhtml = body_el?.innerHTML.trim(); }; let fs, url; if (src) { url = new URL(src, window.location.href); fs = new LightningFS(url.hostname + "_repl").promises; try { parseFullHTML(await loadFromFile()); } catch (e) { try { parseFullHTML(await loadFromServer()); } catch (e) { fullHTML = e.message; } } } else { url = new URL(window.location.href); fs = new LightningFS(url.hostname + "_repl").promises; bodyhtml = ""; headhtml = ""; scriptText = ""; cssText = ""; } ; // initialize variables markdown = ""; const tabs = [...self.querySelectorAll("label[for]")] .map((label) => { const id = label.getAttribute("for"); return [id, self.getElementById(id), label]; }); const showTab = (targetid) => { tabs.forEach(([id, el, label]) => { if (id === targetid || self.varsProxy[`${id}Pinned`]) { el.style.display = "block"; } else if (!self.varsProxy[`${id}Pinned`]) { el.style.display = "none"; } }); }; const hideTab = (targetid) => { tabs.forEach(([id, el, label]) => { if (id === targetid) el.style.display = "none"; }); }; onTabClick = (event) => { showTab(event.target.getAttribute("for")); }; onPinClick = (event) => { const id = event.target.getAttribute("for"), checked = self.varsProxy[`${id}Pinned`] = event.target.checked; if (checked) onTabClick(event); else hideTab(id); }; doSave = async () => { const parts = url.pathname.split("/"); let dir = ""; parts.shift(); parts.pop(); for (const part of parts) { dir = dir + "/" + part; try { await fs.mkdir(dir); } catch (e) { if (e.message === "EEXIST") break; throw e; } } fs.writeFile(url.pathname, doPreview(), {encoding: "utf8"}, () => { }); source = `IndexedDB://${url.hostname + "_repl"}${url.pathname}`; }; doReset = async () => { try { await fs.unlink(url.pathname); } catch (e) { } if (src) { try { parseFullHTML(await loadFromServer()); doPreview(); } catch (e) { previewEl.innerHTML = fullHTML = e.message; } } else { bodyhtml = ""; headhtml = ""; scriptText = ""; cssText = ""; previewEl.innerHTML = ""; } }; const doPreview = () => { // not quite write, createComponent needs to be reworked to handle a head and import its links /*const template = document.createElement("template"); template.innerHTML = bodyhtml + "<style>" + cssText + "</style>" + '<script id="lightview">' + scriptText + "<" + "/script>"; const component = window.customElements.get("x-preview"); if (component) { component.setTemplateNode(template); } else { Lightview.createComponent("x-preview", template); } previewEl.innerHTML = "<x-preview></x-preview>"; return "<html><head>" + headhtml + "</head><body>" + template.innerHTML + "<body></html>"*/ const str = "<html>" + "<head>" + headhtml.replace("./lightview.js",new URL("./lightview.js",window.location.href).href) +" </head>" + "<body>" + bodyhtml + "<style>" + cssText + "</style>" + '<script id="lightview">debugger;' + scriptText + "<" + "/script>" + "</body>" + "</html>", blob = new Blob([str], {type : 'text/html'}), newurl = window.URL.createObjectURL(blob); previewEl.src = newurl; }; const tabsEl = self.getElementById("tabs"), headhtmlEl = document.body.querySelector('[slot="head"]'), bodyhtmlEl = document.body.querySelector('[slot="body"]'), markdownEl = self.getElementById("markdown"), cssEl = document.body.querySelector('[slot="css"]'), scriptEl = document.body.querySelector('[slot="script"]'), previewEl = self.getElementById("preview"); const highlight = (el,...args) => { hljs.highlightElement(el,...args); } if(headhtmlEl) { headhtmlEl.className = "language-html"; const bodyJar = CodeJar(headhtmlEl, highlight); bodyJar.updateCode(headhtml); bodyJar.onUpdate(code => { headhtml = code; doPreview(); }) } if(bodyhtmlEl) { bodyhtmlEl.className = "language-html"; const bodyJar = CodeJar(bodyhtmlEl, highlight); bodyJar.updateCode(bodyhtml); bodyJar.onUpdate(code => { bodyhtml = code; doPreview(); }) } if(cssEl) { cssEl.className = "language-css"; const bodyJar = CodeJar(cssEl, highlight); bodyJar.updateCode(cssText); bodyJar.onUpdate(code => { cssText = code; doPreview(); }) } if(scriptEl) { scriptEl.className = "language-javascript"; const bodyJar = CodeJar(scriptEl, highlight); bodyJar.updateCode(scriptText); bodyJar.onUpdate(code => { scriptText = code; doPreview(); }) } doPreview(); /*let prevmarkdown; // prevents indirect recursion observe(() => { const text = turndownService.turndown(bodyhtml).trim(); if (text && text !== prevtext) { markdown = markdownEl.innerHTML = prevmarkdown = text; } }); let prevbodyhtml; // prevents indirect recursion observe(() => { const html = marked.parse(markdown).trim(); if (html && html !== prevbodyhtml) { bodyhtml = bodyhtmlEl.innerText = prevbodyhtml = html; } });*/ const initEditor = () => { }; self.addEventListener("mounted", () => { initEditor(); doPreview(); }); } </script> </body> </html>