diff --git a/README.md b/README.md index a864638..12a4884 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,16 @@ Run **client-side python** in your browser to **prez**ent your code. - [About](#about) - [Getting Started](#getting-started) - [Use Cases](#use-cases) -- [Limitations](#limitations) +- [Limitations](#limitations) +- [Micropip](#micropip) - [Tags](#pyprez-tags): [``](#pyprez-editor) , [``](#pyprez-console), [``](#pyprez-import), [``](#pyprez-script) - [Custom Themes](#codemirror-themes) - [Feature Development](#feature-development) - [namespaces](#namespaces) - [input](#input) - [matplotlib](#matplotlib) + - [linting](#linting) +- [Keyboard Shortcuts](#keyboard-shortcuts) - [API](#pyprez-api) - [Pyodide](#pyodide) - [PyScript](#pyscript) @@ -58,7 +61,7 @@ Run **client-side python** in your browser to **prez**ent your code. ## Method 1 -Using any `` element (such as the one [here](https://modularizer.github.io/pyprez/samples/stackconverter.html)) to modify your code, then click `` button on the top bar to convert your python +Using any `` element (such as the one [here](https://modularizer.github.io/pyprez/samples/stackconverter.html)) to modify your code, then click `M↓` button on the top bar to convert your python into markdown which can be pasted into your StackOverflow answer to create a **runnable and editable snippet**. **The Question/Answer Editor will look something like this** @@ -188,6 +191,9 @@ Some such limitations are: * cannot access the local file system ( but can still read and write temporary files in webassembly) * `__builtins__.input` is tricky. Currently it works with the fully blocking `window.prompt` function +# Micropip +The `micropip` object from pyodide is available at `window.pyodide`. Furthermore, `` will autorecognize +comments like `# micropip install package_name` and call `micropip.install('package_name')`before running your code. # Pyprez Tags ## Pyprez-Editor @@ -376,6 +382,23 @@ Making it interactive would be tough (but not impossible). Bokeh and Plotly are + +## Linting +### autopep8 +`Ctrl+k` can be used to auto-format code to pep8 standards. `Ctrl+Z` will undo. + + +# Keyboard Shortcuts +custom: +* `Ctrl + k`: autopep8.fix_file +* `Shift + Enter`: run code +* `Shift + Backspace`: reset code + +builtin to codemirror: +* `Ctrl + z` +* `Ctrl + a` +* `Ctrl + c` + # Pyprez API ## elements Any html elements created by the pyprez custom tags get added to `pyprez.elements` object for easy retrieval. diff --git a/custom_commit.py b/dev/custom_commit.py similarity index 100% rename from custom_commit.py rename to dev/custom_commit.py diff --git a/dev/md.py b/dev/md.py new file mode 100644 index 0000000..2552421 --- /dev/null +++ b/dev/md.py @@ -0,0 +1,26 @@ +from markdown2 import Markdown + +import os +os.chdir('..') + +with open("README.md") as f: + mdt = f.read() + +markdown = Markdown() +mdh = markdown.convert(mdt) +h = f""" + + + + PyPrez + + + +
+ {mdh} +
+ + +""" +with open("../README.html", 'w') as f: + f.write(h) \ No newline at end of file diff --git a/server.py b/dev/server.py similarity index 51% rename from server.py rename to dev/server.py index cc254ed..a4e7e57 100644 --- a/server.py +++ b/dev/server.py @@ -4,13 +4,31 @@ """ import asyncio import sys +import os import tornado.web import tornado.ioloop +os.chdir(os.path.dirname(os.path.dirname(__file__))) +print(os.getcwd()) + + +class RedirectHandler(tornado.web.RequestHandler): + """main handler for requests to the base url""" + def initialize(self, homepage) -> None: + """function which initializes the handler and sets the homepage""" + self.homepage = homepage + + def get(self): + """redirects the landing page to the home page""" + self.redirect(self.homepage) + if __name__ == "__main__": - app = tornado.web.Application([(r"/(.*)", tornado.web.StaticFileHandler, {"path": "."})]) + app = tornado.web.Application([ + (r"/", RedirectHandler, {"homepage": "/index.html"}), + (r"/(.*)", tornado.web.StaticFileHandler, {"path": "./site"}) + ]) if sys.platform == 'win32': asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) diff --git a/todo b/dev/todo similarity index 100% rename from todo rename to dev/todo diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..b45a7b2 Binary files /dev/null and b/logo.png differ diff --git a/pyprez.js b/pyprez.js index 1c1d7bd..661635f 100644 --- a/pyprez.js +++ b/pyprez.js @@ -1,11 +1,11 @@ -/*Sun Oct 23 2022 17:13:21 GMT -0700 (Pacific Daylight Time)*/ +/*Sun Nov 06 2022 15:53:57 GMT -0800 (Pacific Standard Time)*/ if (!window.pyprezUpdateDate){ /* github pages can only serve one branch and takes a few minutes to update, this will help identify which version of code we are on */ - var pyprezUpdateDate = new Date("Sun Oct 23 2022 17:13:21 GMT -0700 (Pacific Daylight Time)"); - var pyprezCommitMessage = "enable matplotlib patch"; - var pyprezPrevCommit = "development:commit d91d03acf42c046159fef0e46c896271cafb32fc"; + var pyprezUpdateDate = new Date("Sun Nov 06 2022 15:53:57 GMT -0800 (Pacific Standard Time)"); + var pyprezCommitMessage = "fix imports"; + var pyprezPrevCommit = "development:commit b49c434f40e62e250ff08c4266e3fb9778947c0d"; } /* @@ -45,11 +45,12 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit showThemeSelect: true, showNamespaceSelect: false, patch: true, + lint: true } let strConfig = { patchSrc: "https://modularizer.github.io/pyprez/patches.py", codemirrorCDN: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/", - pyodideCDN: "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js", + pyodideCDN: "https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js", consolePrompt: ">>> ", consoleOut: "[Out] ", consoleEscape: "...", @@ -112,7 +113,9 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit var codemirrorImported = new DeferredPromise(); var workerReady = new DeferredPromise(); var pyodidePromise = new DeferredPromise("pyodidePromise"); + var micropipPromise = new DeferredPromise("micropipPromise"); var patches = new DeferredPromise(); + var linterPromise = new DeferredPromise(); if (patch){ patches = get(patchSrc); } @@ -286,7 +289,6 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit return this.proxy } - loadPackagesFromImports(){} _id = 0 get id(){ let id = this._id @@ -336,7 +338,7 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit receiveResponse(data){ if ( this.pendingRequests[data.id] === undefined){ - console.error(data.id, data, this, this.pendingRequests); + console.error(data.id, data); return } let [deferredPromise, sentData] = this.pendingRequests[data.id]; @@ -381,9 +383,7 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit workerReady.then(()=>{ pyodide.runPythonAsync("2+2").then(r=>{ if (r == 4){ - console.error(pyodide) window.pyodide = pyodide; - console.warn(window.pyodidePromise) window.pyodidePromise.resolve(true); } }) @@ -398,16 +398,24 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit `).then(()=>{ if (patch){ patches.then(code =>{ - console.warn("applying patches", code) + console.warn("applying python patches", code) pyodide.runPythonAsync(code).then(()=>{ console.log("patched") window.pyodide = pyodide; pyodidePromise.resolve(true); + pyodide.loadPackage("micropip").then(micropip =>{ + window.micropip = pyodide.pyimport("micropip"); + micropipPromise.resolve(true); + }) }) }) }else{ window.pyodide = pyodide; pyodidePromise.resolve(true); + pyodide.loadPackage("micropip").then(micropip =>{ + window.micropip = pyodide.pyimport("micropip"); + micropipPromise.resolve(true); + }) } }) @@ -419,6 +427,36 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit return pyodidePromise } + + /* linter */ + if (lint){ + micropipPromise.then(()=>{ + micropip.install("autopep8").then(()=>{ + pyodide.loadPackagesFromImports("autopep8").then(()=>{ + console.warn("autopep8_fix") + pyodide.runPythonAsync(` + def autopep8_fix(s, tmp_fn="autopep8_temp.py"): + # micropip install autopep8 + import autopep8 + import os + autopep8.detect_encoding = lambda *a, **kw: 'utf-8' + with open(tmp_fn,"w") as f: + f.write(s) + r = autopep8.fix_file(tmp_fn) + os.remove(tmp_fn) + return r + `).then(()=>{ + window.autopep8Fix = pyodide.globals.get("autopep8_fix"); + linterPromise.resolve(true); + }) + + }) + }) + }) + } + + + /* _______________________________________ scopeEval ____________________________________ */ function scopeEval(script) { return Function(( "with(this) { " + script + "}"))(); @@ -538,9 +576,7 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit return this.jsNamespaces[name] } namespaceEval(code, name){ - //.replace(/(^|[;\s])(let)\s/gm,'$1var ') let [scope, scopedEval] = this.getJSNamespace(name); - console.warn(scope) return scopedEval(code) } @@ -548,7 +584,6 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit namespaces = {} namespaceNames = ['global'] recordNamespaceName(name){ - console.log(this, this.namespaceNames) if (!this.namespaceNames.includes(name)){ this.namespaceNames = this.namespaceNames.concat([name]) } @@ -569,29 +604,43 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit load(code, requirements="detect"){ /*load required packages as soon as pyodide is loaded*/ return this.then(() =>{ - let requirementsLoaded; if (requirements === "detect"){ if (code){ console.debug("auto loading packages detected in code") - console.debug(code) - requirementsLoaded = window.pyodide.loadPackagesFromImports(code) + return this.installPackagesFromComments(code).then(()=>{ + console.warn("installed it here") + return window.pyodide.loadPackagesFromImports(code) + }) } }else{ console.debug("loading", requirements) - requirementsLoaded = window.pyodide.loadPackage(requirements); + return this.installPackagesFromComments(code).then(()=>{ + console.warn("installed it here2") + return window.pyodide.loadPackagesFromImports(code) + }) } - return requirementsLoaded + }) } + installPackagesFromComments(code){ +// let m = code.match(/#\s*(micro)?pip\s*install\s*(\S*)/g); +// console.warn(m); +// let packageNames = m?m.map(s => s.match(/#\s*(micro)?pip\s*install\s*(\S*)/)[2]):[]; +// if (packageNames.length){ +// console.warn("preparing to micropip install", packageNames) +// return micropipPromise.then(()=>{console.warn('installing');return micropip.install(packageNames)}) +// }else{ + return new Promise((res, rej)=>{res(true)}) +// } + + } loadAndRunAsync(code, namespace="global", requirements="detect"){ /* run a python script asynchronously as soon as pyodide is loaded and all required packages are imported*/ - console.warn(pyodidePromise) let p = this.then((() =>{ - console.error("here") if (code){ return this.load(code, requirements) - .then((r => this._runPythonAsync(code, namespace)).bind(this)) - .catch((e => this._runPythonAsync(code, namespace)).bind(this)) + .then((r => {console.warn('loaded', r);return this._runPythonAsync(code, namespace)}).bind(this)) + .catch((e => {console.error(e); return this._runPythonAsync(code, namespace)}).bind(this)) } }).bind(this)) return p @@ -623,6 +672,7 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit this.run = this.run.bind(this); this.copyRunnable = this.copyRunnable.bind(this); this.getRunnable = this.getRunnable.bind(this); + this.autopep8Fix = this.autopep8Fix.bind(this); // set language to python(default), javascript, or html let language = this.hasAttribute("language")?this.getAttribute("language").toLowerCase():"python" @@ -650,7 +700,6 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit this.language = "html" } fetch(src).then(r=>r.text()).then(code =>{ - console.warn("got", code) this.innerHTML = code; this.loadEl(); }) @@ -725,7 +774,7 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit } // now load packages detected in code imports - this.loadPackages() +// pyprez.installPackagesFromComments(this.code); } loadEditor(){ /* first load the editor as though codemirror does not and will not exist, then load codemirror*/ @@ -741,6 +790,12 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit
  • Shift + Enter
  • Double-Click
  • + + To lint: +
      +
    • Ctrl + K to reformat/li> +
    + To re-run:
    • Shift + Enter
    • @@ -755,7 +810,7 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit After code executes, try the runnable console at the bottom! Add to StackOverflow: - Click </> to copy markdown, then paste into your answer. + Click M↓ to copy markdown, then paste into your answer. ` _loadEditor(){ /* first load the editor as though codemirror does not and will not exist*/ @@ -779,7 +834,8 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit ${gh}
      -
      </>
      +
      </>
      +
      M↓
      @@ -797,7 +853,8 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit ${gh}
      -
      </>
      +
      </>
      +
      M↓
      @@ -823,9 +880,10 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit ` this.start = this.children[0] // start button this.messageBar = this.children[1].children[1] // top message bar to use to print status (Loading, Running, etc.) - this.copyRunnableLink = this.children[1].children[3] - this.namespaceSelect = this.children[1].children[4] - this.themeSelect = this.children[1].children[5] + this.copyEmbeddableLink = this.children[1].children[3] + this.copyRunnableLink = this.children[1].children[4] + this.namespaceSelect = this.children[1].children[5] + this.themeSelect = this.children[1].children[6] this.textarea = this.children[2] // textarea in case codemirror does not load this.endSpace = this.children[3] @@ -892,7 +950,6 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit } get namespaces(){return Array.from(this.namespaceSelect.children).map(el=>el.innerHTML)} set namespaces(namespaces){ - console.warn(namespaces) let sn = this.selectedNamespace; this.namespaceSelect.innerHTML = namespaces.map(name=>``).join("") this.namespaceSelect.value = sn; @@ -903,7 +960,6 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit get namespace(){return this.selectedNamespace} set namespace(name){ if (!this.namespaces.includes(name)){ - console.warn(this.namespaces, name) this.namespaces = this.namespaces.concat([name]); } this.selectedNamespace = name @@ -912,6 +968,7 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit /* ________________________ EVENTS _____________________________*/ keypressed(e){ /* Shift + Enter to run, Shift + Backspace to reload */ + if (e.ctrlKey && e.key == 'k'){this.autopep8Fix();e.preventDefault();} if (e.shiftKey && e.key == "Backspace"){this.reload(); e.preventDefault();} else if (e.key == "Enter"){ if (e.shiftKey && !this.done){this.run(); e.preventDefault();} @@ -981,8 +1038,8 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit this.editor.doc.setGutterMarker(0, "start", this.start); // scroll to bottom of element - let si = this.editor.getScrollInfo(); - this.editor.scrollTo(0, si.height); +// let si = this.editor.getScrollInfo(); +// this.editor.scrollTo(0, si.height); }else{ this.textarea.value = v; @@ -992,9 +1049,15 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit // scroll down on page until bottom of element is in view let bb = this.getBoundingClientRect(); - if (bb.bottom > window.innerHeight){ - window.scrollTo(0, bb.bottom) - } +// if (bb.bottom > window.innerHeight){ +// window.scroll(0, 20 + bb.bottom - window.innerHeight) +// } + } + + autopep8Fix(){ + linterPromise.then((()=>{ + this.code = autopep8Fix(this.code.split(this.separator)[0]) + }).bind(this)) } // get/set the codemirror theme, importing from cdn if needed @@ -1016,18 +1079,6 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit this._theme = v; } - /* ________________________ PYTHON IMPORTS _____________________________*/ - numImports = 0; // number of import statements we have already tried to auto-load - loadPackages(){ - /* autodetect if new import statements have been added to code, and if so try to install them*/ - let code = this.code; - let n = code.match(/import/g); - if (n && n.length !== this.numImports){ - this.message = "Loading packages..." - this.numImports = n.length; - pyprez.load(this.code).then((()=>{this.message = "Ready...(Double-Click to Run)"}).bind(this)) - } - } /* ________________________ RUN CODE _____________________________*/ reload(){ @@ -1037,16 +1088,20 @@ if (!window.pyprezInitStarted){// allow importing this script multiple times wit if (this.language !== "html"){ this.code = this.executed; } - console.warn("Setting code to ", this.executed, this.code) this.message = "Ready (Double-Click to Run)"; this.executed = false; this.done = false } run(){ - console.log("run", this.done) + console.log("running") if(this.done){ this.consoleRun() }else if (this.code){ + if (this.editor){ + let si = this.editor.getScrollInfo(); + this.editor.scrollTo(0, si.height); + } + this.message = "Running..." let code = this.code.split(this.separator)[0]; this.executed = code; @@ -1168,7 +1223,6 @@ ${c} ` } copyRunnable(){ - console.warn("copy runnable") let s = this.getRunnable(); console.log(s); navigator.clipboard.writeText(s); @@ -1178,6 +1232,18 @@ ${c} this.copyRunnableLink.style['background-color'] = originalColor; }).bind(this),300) } + copyEmbeddable(){ + let c = encodeURIComponent(this.code); + let t = this.theme; + let s = `` + console.log(s) + navigator.clipboard.writeText(s); + let originalColor = this.copyEmbeddableLink.style['background-color']; + this.copyEmbeddableLink.style['background-color'] = 'rgb(149, 255, 162)'; + setTimeout((()=>{ + this.copyEmbeddableLink.style['background-color'] = originalColor; + }).bind(this),300) + } } window.addEventListener("load", ()=>{ customElements.define("pyprez-editor", PyPrezEditor); @@ -1546,7 +1612,6 @@ ${this.header} }).bind(this)) } sync(){ - console.warn("syncing") this.stackOverflow.runnable = this.pyprezEditor.getRunnable(); } } @@ -1601,7 +1666,6 @@ ${this.header} }) let enabled = localStorage.getItem("learningModeEnabled") === "true" - console.log("enabled=", enabled) if (enabled){ this.enableLearningMode() }else{ @@ -1644,15 +1708,16 @@ ${this.header} } this.el.innerHTML = s } - + offsetX=5 + offsetY=5 show(s, clientY, clientX){ - this.el.style.top = window.scrollY + clientY + 1 + "px" + this.el.style.top = window.scrollY + clientY + this.offsetY + "px" let maxLeft = window.innerWidth - this.el.getBoundingClientRect().width - 200 - this.el.style.left = Math.min(clientX + 1, maxLeft) + "px" + this.el.style.left = Math.min(clientX + this.offsetX, maxLeft) + "px" this.setInnerHTML(s) this.el.style.display = 'block' maxLeft = window.innerWidth - this.el.getBoundingClientRect().width - this.el.style.left = Math.min(clientX + 1, maxLeft) + "px" + this.el.style.left = Math.min(clientX + this.offsetX, maxLeft) + "px" this.hold = true document.body.addEventListener("keydown", this.keydownToggle) setTimeout(()=>{this.hold = false;}, 100 ) @@ -1676,7 +1741,6 @@ ${this.header} } keydownToggle(e){ - console.log(e.key) if (e.key === " "){ this.toggleLearningMode() e.preventDefault(); @@ -1700,7 +1764,6 @@ ${this.header} localStorage.setItem("learningModeEnabled", false) } toggleLearningMode(){ - console.log(this.attrNames) if(this.learningMode){ this.disableLearningMode() }else{ @@ -1715,7 +1778,6 @@ ${this.header} } let p = event.path - // console.log(p.map(el => el.id)) let i=0; let found=false; if (p.length>4){ @@ -1724,7 +1786,6 @@ ${this.header} for (let attrName of attrNames){ if (p[i].hasAttribute){ if (p[i].hasAttribute(attrName)){ - // console.log(attrName, p[i], ) _found = p[i] this.show(_found.getAttribute(attrName), event.clientY, event.clientX) } diff --git a/pyprez.min.js b/pyprez.min.js index 57388d4..2430574 100644 --- a/pyprez.min.js +++ b/pyprez.min.js @@ -1,10 +1,21 @@ -if(!window.pyprezUpdateDate){var pyprezUpdateDate=new Date("Sun Oct 23 2022 17:13:21 GMT -0700 (Pacific Daylight Time)");var pyprezCommitMessage="enable matplotlib patch";var pyprezPrevCommit="development:commit d91d03acf42c046159fef0e46c896271cafb32fc"}if(!window.pyprezInitStarted){console.log("loaded pyprez.js from",location.href);var pyprezInitStarted=true;var preferredPyPrezImportSrc="https://modularizer.github.io/pyprez/pyprz.min.js";var githublinkImage='';var stackMode=false;var pyprezScript=document.currentScript;let boolConfig={help:true,useWorker:false,convert:true,includeGithubLink:true,showThemeSelect:true,showNamespaceSelect:false,patch:true};let strConfig={patchSrc:"https://modularizer.github.io/pyprez/patches.py",codemirrorCDN:"https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/",pyodideCDN:"https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js",consolePrompt:">>> ",consoleOut:"[Out] ",consoleEscape:"..."};var pyprezConfig={...boolConfig,...strConfig};for(let k of Object.keys(pyprezConfig)){if(window[k]!==undefined){pyprezConfig[k]=window[k]}}for(let k of Object.keys(pyprezConfig)){if(pyprezScript.hasAttribute(k)){let v=pyprezScript.getAttribute(k).toLowerCase();if(Object.keys(boolConfig).includes(k)){if(["true","1",""].includes(v)){pyprezConfig[k]=true}else if(["false","0"].includes(v)){pyprezConfig[k]=false}else{console.error(`Invalid value '${v}' for attribute '${k}'. Value must be "true", "false", "1", "0", or ""`)}}else{pyprezConfig[k]=v}}}Object.assign(window,pyprezConfig);class DeferredPromise{constructor(){this.promise=new Promise((resolve,reject)=>{this.resolve=resolve.bind();this.reject=reject.bind()});this.then=this.promise.then.bind(this.promise);this.catch=this.promise.catch.bind(this.promise)}}window.DeferredPromise=DeferredPromise;var domContentLoaded=new DeferredPromise;var pyodideImported=new DeferredPromise;var codemirrorImported=new DeferredPromise;var workerReady=new DeferredPromise;var pyodidePromise=new DeferredPromise("pyodidePromise");var patches=new DeferredPromise;if(patch){patches=get(patchSrc)}var loadedCodeMirrorStyles=["default"];if(document.readyState==="complete"||document.readyState==="loaded"){domContentLoaded.resolve()}else{document.addEventListener("DOMContentLoaded",domContentLoaded.resolve)}function pyprezConvert(){if(pyprezScript.innerHTML){if(convert){let script=pyprezScript;let ih=script.innerHTML;script.innerHTML="";let mode="editor";if(script.hasAttribute("mode")){mode=script.getAttribute("mode")}let el=document.createElement("pyprez-"+mode);el.innerHTML=ih;for(let k of script.getAttributeNames()){if(!["src"].includes(k)){el.setAttribute(k,script.getAttribute(k))}}let div=document.createElement("div");div.append(el);script.after(div)}}else{domContentLoaded.then(()=>{console.log("DOMContentLoaded");let isFirstScript=pyprezScript===document.scripts[0];let numBodyEls=document.body.children.length;let singleBodyChild=numBodyEls===1&&document.body.children[0].tagName==="SCRIPT";let twoDirectBodyChildren=numBodyEls===2&&pyprezScript.parentElement===document.body;let solo=isFirstScript&&(twoDirectBodyChildren||singleBodyChild);let convertScript=pyprezScript.hasAttribute("convert")?pyprezScript.getAttribute("convert")==="true":convert;if(solo&&convertScript){window.stackMode=true;let mode=pyprezScript.hasAttribute("mode")?pyprezScript.getAttribute("mode"):"editor";let specialAttributes={runonload:"true",theme:"darcula",githublink:"true"};let a="";for(let[k,defaultValue]of Object.entries(specialAttributes)){let v=pyprezScript.hasAttribute(k)?pyprezScript.getAttribute(k):defaultValue;a+=` ${k}="${v}"`}let lastScript=document.scripts[document.scripts.length-1];let demoCode=lastScript.innerHTML.trim();let stackEditor=document.createElement("div");document.body.appendChild(stackEditor);stackEditor.innerHTML=` +if(!window.pyprezUpdateDate){var pyprezUpdateDate=new Date("Sun Nov 06 2022 15:53:57 GMT -0800 (Pacific Standard Time)");var pyprezCommitMessage="fix imports";var pyprezPrevCommit="development:commit b49c434f40e62e250ff08c4266e3fb9778947c0d"}if(!window.pyprezInitStarted){console.log("loaded pyprez.js from",location.href);var pyprezInitStarted=true;var preferredPyPrezImportSrc="https://modularizer.github.io/pyprez/pyprz.min.js";var githublinkImage='';var stackMode=false;var pyprezScript=document.currentScript;let boolConfig={help:true,useWorker:false,convert:true,includeGithubLink:true,showThemeSelect:true,showNamespaceSelect:false,patch:true,lint:true};let strConfig={patchSrc:"https://modularizer.github.io/pyprez/patches.py",codemirrorCDN:"https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/",pyodideCDN:"https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js",consolePrompt:">>> ",consoleOut:"[Out] ",consoleEscape:"..."};var pyprezConfig={...boolConfig,...strConfig};for(let k of Object.keys(pyprezConfig)){if(window[k]!==undefined){pyprezConfig[k]=window[k]}}for(let k of Object.keys(pyprezConfig)){if(pyprezScript.hasAttribute(k)){let v=pyprezScript.getAttribute(k).toLowerCase();if(Object.keys(boolConfig).includes(k)){if(["true","1",""].includes(v)){pyprezConfig[k]=true}else if(["false","0"].includes(v)){pyprezConfig[k]=false}else{console.error(`Invalid value '${v}' for attribute '${k}'. Value must be "true", "false", "1", "0", or ""`)}}else{pyprezConfig[k]=v}}}Object.assign(window,pyprezConfig);class DeferredPromise{constructor(){this.promise=new Promise((resolve,reject)=>{this.resolve=resolve.bind();this.reject=reject.bind()});this.then=this.promise.then.bind(this.promise);this.catch=this.promise.catch.bind(this.promise)}}window.DeferredPromise=DeferredPromise;var domContentLoaded=new DeferredPromise;var pyodideImported=new DeferredPromise;var codemirrorImported=new DeferredPromise;var workerReady=new DeferredPromise;var pyodidePromise=new DeferredPromise("pyodidePromise");var micropipPromise=new DeferredPromise("micropipPromise");var patches=new DeferredPromise;var linterPromise=new DeferredPromise;if(patch){patches=get(patchSrc)}var loadedCodeMirrorStyles=["default"];if(document.readyState==="complete"||document.readyState==="loaded"){domContentLoaded.resolve()}else{document.addEventListener("DOMContentLoaded",domContentLoaded.resolve)}function pyprezConvert(){if(pyprezScript.innerHTML){if(convert){let script=pyprezScript;let ih=script.innerHTML;script.innerHTML="";let mode="editor";if(script.hasAttribute("mode")){mode=script.getAttribute("mode")}let el=document.createElement("pyprez-"+mode);el.innerHTML=ih;for(let k of script.getAttributeNames()){if(!["src"].includes(k)){el.setAttribute(k,script.getAttribute(k))}}let div=document.createElement("div");div.append(el);script.after(div)}}else{domContentLoaded.then(()=>{console.log("DOMContentLoaded");let isFirstScript=pyprezScript===document.scripts[0];let numBodyEls=document.body.children.length;let singleBodyChild=numBodyEls===1&&document.body.children[0].tagName==="SCRIPT";let twoDirectBodyChildren=numBodyEls===2&&pyprezScript.parentElement===document.body;let solo=isFirstScript&&(twoDirectBodyChildren||singleBodyChild);let convertScript=pyprezScript.hasAttribute("convert")?pyprezScript.getAttribute("convert")==="true":convert;if(solo&&convertScript){window.stackMode=true;let mode=pyprezScript.hasAttribute("mode")?pyprezScript.getAttribute("mode"):"editor";let specialAttributes={runonload:"true",theme:"darcula",githublink:"true"};let a="";for(let[k,defaultValue]of Object.entries(specialAttributes)){let v=pyprezScript.hasAttribute(k)?pyprezScript.getAttribute(k):defaultValue;a+=` ${k}="${v}"`}let lastScript=document.scripts[document.scripts.length-1];let demoCode=lastScript.innerHTML.trim();let stackEditor=document.createElement("div");document.body.appendChild(stackEditor);stackEditor.innerHTML=` ${demoCode} - `}})}}pyprezConvert();function addStyle(s){let style=document.createElement("style");style.innerHTML=s;document.head.appendChild(style)}function importScript(url){let el=document.createElement("script");el.src=url;return new Promise((resolve,reject)=>{domContentLoaded.then(()=>{console.log("attempting to import ",url);document.body.appendChild(el);el.addEventListener("load",()=>resolve(url))})})}function get(src){return fetch(src).then(r=>r.text())}function importStyle(url){console.log("importing",url);return get(url).then(addStyle)}function importPyodide(){if(pyodideCDN&&!useWorker&&window.importPyodide&&!window.pyodide){importScript(pyodideCDN).then(()=>{pyodideImported.resolve(true)})}}function importCodeMirror(){if(codemirrorCDN&&!window.CodeMirror){importScript(codemirrorCDN+"codemirror.min.js").then(()=>{importScript(codemirrorCDN+"mode/python/python.min.js").then(()=>{importStyle(codemirrorCDN+"codemirror.min.css").then(()=>{addStyle(".CodeMirror { border: 1px solid #eee !important; height: auto !important;}");codemirrorImported.resolve(CodeMirror)})})})}}importPyodide();importCodeMirror();class PyodideWorker extends Worker{constructor(src){super(src);this.parent=window;this.getMethod=this.getMethod.bind(this);this.postResponse=this.postResponse.bind(this);this.postError=this.postError.bind(this);this.postRequest=this.postRequest.bind(this);this.postCall=this.postCall.bind(this);this.receiveResponse=this.receiveResponse.bind(this);this.receiveCallRequest=this.receiveCallRequest.bind(this);this.receiveRequest=this.receiveRequest.bind(this);this.stdout=this.stdout.bind(this);this.stderr=this.stderr.bind(this);this.worker=this;this.proxy=new Proxy(this,{get(target,prop,receiver){if(target[prop]!==undefined){return target[prop]}function callMethod(){return target.postRequest(prop,Array.from(arguments))}return callMethod}});return this.proxy}loadPackagesFromImports(){}_id=0;get id(){let id=this._id;this._id=(id+1)%Number.MAX_SAFE_INTEGER;return id}pendingRequests={};receivemessage(event){let data=event.data;let type=data.type;if(type==="response"){this.receiveResponse(data)}else if(type==="call"){this.receiveCallRequest(data)}else if(type==="request"){this.receiveRequest(data)}else{this.postError(data,"unrecognized type")}}getMethod(methodName,scopes){if(!scopes){scopes=[this.parent,this]}for(let scope of scopes){if(scope[methodName]){return scope[methodName]}else if(methodName.includes(".")){let methodNames=methodName.split(".");for(let mn of methodNames){scope=scope[mn];if(!scope){return scope}}return scope}}}postResponse(data,results,error=null){data.type="response";data.results=results;data.error=error;this.postMessage(data)}postError(data,error){this.postResponse(data,null,error)}postRequest(method,args,type="request"){let id=this.id;let data={id:id,type:type,method:method,args:args,results:null,error:null};this.pendingRequests[id]=[new DeferredPromise,data];this.postMessage(data);return this.pendingRequests[id][0]}postCall(method,args){this.postRequest(method,args,"call")}receiveResponse(data){if(this.pendingRequests[data.id]===undefined){console.error(data.id,data,this,this.pendingRequests);return}let[deferredPromise,sentData]=this.pendingRequests[data.id];delete this.pendingRequests[data.id];if(data.results){deferredPromise.resolve(data.results)}else{deferredPromise.reject(data.error)}}receiveCallRequest(data){let f=this.getMethod(data.method);if(f){return f(...data.args)}else{this.postError(data,"method not found")}}receiveRequest(data){try{let results=this.receiveCallRequest(data);if(results.then){results.then(r=>this.postResponse(data,r)).catch(e=>this.postError(data,e))}else{this.postResponse(data,results)}}catch(error){this.postError(data,error)}}stdout(...args){console.log(args)}stderr(...args){console.log(args)}stdin(){return""}}function loadPyodideInterface(config){if(useWorker){window.pyodide=new PyodideWorker("./webworker.js");pyodide.worker.onmessage=pyodide.worker.receivemessage;if(config.stdin){pyodide.stdin=config.stdin}if(config.stdout){pyodide.stdout=config.stdout}if(config.stderr){pyodide.stderr=config.stderr}pyodideImported.resolve(true);workerReady.then(()=>{pyodide.runPythonAsync("2+2").then(r=>{if(r==4){console.error(pyodide);window.pyodide=pyodide;console.warn(window.pyodidePromise);window.pyodidePromise.resolve(true)}})})}else{pyodideImported.then(()=>{loadPyodide(config).then(pyodide=>{pyodide.runPythonAsync(` + `}})}}pyprezConvert();function addStyle(s){let style=document.createElement("style");style.innerHTML=s;document.head.appendChild(style)}function importScript(url){let el=document.createElement("script");el.src=url;return new Promise((resolve,reject)=>{domContentLoaded.then(()=>{console.log("attempting to import ",url);document.body.appendChild(el);el.addEventListener("load",()=>resolve(url))})})}function get(src){return fetch(src).then(r=>r.text())}function importStyle(url){console.log("importing",url);return get(url).then(addStyle)}function importPyodide(){if(pyodideCDN&&!useWorker&&window.importPyodide&&!window.pyodide){importScript(pyodideCDN).then(()=>{pyodideImported.resolve(true)})}}function importCodeMirror(){if(codemirrorCDN&&!window.CodeMirror){importScript(codemirrorCDN+"codemirror.min.js").then(()=>{importScript(codemirrorCDN+"mode/python/python.min.js").then(()=>{importStyle(codemirrorCDN+"codemirror.min.css").then(()=>{addStyle(".CodeMirror { border: 1px solid #eee !important; height: auto !important;}");codemirrorImported.resolve(CodeMirror)})})})}}importPyodide();importCodeMirror();class PyodideWorker extends Worker{constructor(src){super(src);this.parent=window;this.getMethod=this.getMethod.bind(this);this.postResponse=this.postResponse.bind(this);this.postError=this.postError.bind(this);this.postRequest=this.postRequest.bind(this);this.postCall=this.postCall.bind(this);this.receiveResponse=this.receiveResponse.bind(this);this.receiveCallRequest=this.receiveCallRequest.bind(this);this.receiveRequest=this.receiveRequest.bind(this);this.stdout=this.stdout.bind(this);this.stderr=this.stderr.bind(this);this.worker=this;this.proxy=new Proxy(this,{get(target,prop,receiver){if(target[prop]!==undefined){return target[prop]}function callMethod(){return target.postRequest(prop,Array.from(arguments))}return callMethod}});return this.proxy}_id=0;get id(){let id=this._id;this._id=(id+1)%Number.MAX_SAFE_INTEGER;return id}pendingRequests={};receivemessage(event){let data=event.data;let type=data.type;if(type==="response"){this.receiveResponse(data)}else if(type==="call"){this.receiveCallRequest(data)}else if(type==="request"){this.receiveRequest(data)}else{this.postError(data,"unrecognized type")}}getMethod(methodName,scopes){if(!scopes){scopes=[this.parent,this]}for(let scope of scopes){if(scope[methodName]){return scope[methodName]}else if(methodName.includes(".")){let methodNames=methodName.split(".");for(let mn of methodNames){scope=scope[mn];if(!scope){return scope}}return scope}}}postResponse(data,results,error=null){data.type="response";data.results=results;data.error=error;this.postMessage(data)}postError(data,error){this.postResponse(data,null,error)}postRequest(method,args,type="request"){let id=this.id;let data={id:id,type:type,method:method,args:args,results:null,error:null};this.pendingRequests[id]=[new DeferredPromise,data];this.postMessage(data);return this.pendingRequests[id][0]}postCall(method,args){this.postRequest(method,args,"call")}receiveResponse(data){if(this.pendingRequests[data.id]===undefined){console.error(data.id,data);return}let[deferredPromise,sentData]=this.pendingRequests[data.id];delete this.pendingRequests[data.id];if(data.results){deferredPromise.resolve(data.results)}else{deferredPromise.reject(data.error)}}receiveCallRequest(data){let f=this.getMethod(data.method);if(f){return f(...data.args)}else{this.postError(data,"method not found")}}receiveRequest(data){try{let results=this.receiveCallRequest(data);if(results.then){results.then(r=>this.postResponse(data,r)).catch(e=>this.postError(data,e))}else{this.postResponse(data,results)}}catch(error){this.postError(data,error)}}stdout(...args){console.log(args)}stderr(...args){console.log(args)}stdin(){return""}}function loadPyodideInterface(config){if(useWorker){window.pyodide=new PyodideWorker("./webworker.js");pyodide.worker.onmessage=pyodide.worker.receivemessage;if(config.stdin){pyodide.stdin=config.stdin}if(config.stdout){pyodide.stdout=config.stdout}if(config.stderr){pyodide.stderr=config.stderr}pyodideImported.resolve(true);workerReady.then(()=>{pyodide.runPythonAsync("2+2").then(r=>{if(r==4){window.pyodide=pyodide;window.pyodidePromise.resolve(true)}})})}else{pyodideImported.then(()=>{loadPyodide(config).then(pyodide=>{pyodide.runPythonAsync(` from js import prompt __builtins__.input = prompt 2+2 - `).then(()=>{if(patch){patches.then(code=>{console.warn("applying patches",code);pyodide.runPythonAsync(code).then(()=>{console.log("patched");window.pyodide=pyodide;pyodidePromise.resolve(true)})})}else{window.pyodide=pyodide;pyodidePromise.resolve(true)}})})})}return pyodidePromise}function scopeEval(script){return Function("with(this) { "+script+"}")()}class PyPrez{constructor(config={},load=true){this._stdout=this._stdout.bind(this);this._stderr=this._stderr.bind(this);this._stdin=this._stdin.bind(this);this.load=this.load.bind(this);this.loadPyodideInterface=this.loadPyodideInterface.bind(this);this.load=this.load.bind(this);this._runPythonAsync=this._runPythonAsync.bind(this);this.loadAndRunAsync=this.loadAndRunAsync.bind(this);this.recordNamespaceName=this.recordNamespaceName.bind(this);this.register=this.register.bind(this);Object.assign(this,config);this.config={stdout:this._stdout,stderr:this._stderr,stdin:this._stdin};if(load){this.loadPyodideInterface()}}loadPyodideInterface(){loadPyodideInterface(this.config).then(()=>{this.getNamespace("global")})}pending=[];then(successCb,errorCb){return pyodidePromise.then(successCb,errorCb)}catch(errorCb){return pyodidePromise.catch(errorCB)}stdout=console.log;stderr=console.error;stdin=()=>"";_stdout(...args){return this.stdout(...args)}_stderr(...args){return this.stderr(...args)}_stdin(...args){return this.stdin(...args)}elements={};editors={};consoles={};scripts={};imports={};overflows={};register(el){let mode=el.tagName.toLowerCase().split("-").pop();let m=this[mode+"s"];this.elements[Object.keys(this.elements).length]=el;m[Object.keys(m).length]=el;if(el.id){this.elements[el.id]=el;m[el.id]=el}}_runPythonAsync(code,namespace){if(code){console.debug("running code asynchronously:");console.debug(code);this.recordNamespaceName(namespace);if(useWorker){return pyodide.runPythonAsyncInNamespace(code,namespace).catch(this.stderr)}else{return pyodide.runPythonAsync(code,{globals:this.getNamespace(namespace)}).catch(this.stderr)}}}jsNamespaces={};getJSNamespace(name){if(this.jsNamespaces[name]===undefined){let scope={};Object.assign(scope,window,{console:console});this.jsNamespaces[name]=[scope,scopeEval.bind(scope)]}return this.jsNamespaces[name]}namespaceEval(code,name){let[scope,scopedEval]=this.getJSNamespace(name);console.warn(scope);return scopedEval(code)}namespaces={};namespaceNames=["global"];recordNamespaceName(name){console.log(this,this.namespaceNames);if(!this.namespaceNames.includes(name)){this.namespaceNames=this.namespaceNames.concat([name])}}getNamespace(name){pyodidePromise.then((()=>{if(this.namespaces[name]===undefined){if(!useWorker){this.namespaces[name]=pyodide.globals.get("dict")()}}return this.namespaces[name]}).bind(this))}load(code,requirements="detect"){return this.then(()=>{let requirementsLoaded;if(requirements==="detect"){if(code){console.debug("auto loading packages detected in code");console.debug(code);requirementsLoaded=window.pyodide.loadPackagesFromImports(code)}}else{console.debug("loading",requirements);requirementsLoaded=window.pyodide.loadPackage(requirements)}return requirementsLoaded})}loadAndRunAsync(code,namespace="global",requirements="detect"){console.warn(pyodidePromise);let p=this.then((()=>{console.error("here");if(code){return this.load(code,requirements).then((r=>this._runPythonAsync(code,namespace)).bind(this)).catch((e=>this._runPythonAsync(code,namespace)).bind(this))}}).bind(this));return p}}var pyprez=new PyPrez(load=true);class PyPrezEditor extends HTMLElement{constructor(){super();this.classList.add("pyprez");this.loadEl=this.loadEl.bind(this);this.loadEditor=this.loadEditor.bind(this);this.keypressed=this.keypressed.bind(this);this.run=this.run.bind(this);this.copyRunnable=this.copyRunnable.bind(this);this.getRunnable=this.getRunnable.bind(this);let language=this.hasAttribute("language")?this.getAttribute("language").toLowerCase():"python";let aliases={python:"python",javascript:"javascript",html:"html",py:"python",js:"javascript"};this.language=aliases[language];if(!this.hasAttribute("stdout")){this.setAttribute("stdout","true")}if(this.hasAttribute("src")&&this.getAttribute("src")&&this.getAttribute("src")!==pyprezScript.src){let src=this.getAttribute("src");console.debug("fetching script for pyprez-editor src",src);if(src.endsWith(".js")){this.language="javascript"}else if(src.endsWith(".py")){this.language="python"}else if(src.endsWith(".html")){this.language="html"}fetch(src).then(r=>r.text()).then(code=>{console.warn("got",code);this.innerHTML=code;this.loadEl()})}else{this.loadEl()}this.namespace=this.hasAttribute("namespace")?this.getAttribute("namespace"):"global";pyprez.recordNamespaceName(this.namespace);this.addEventListener("keydown",this.keypressed.bind(this));this.addEventListener("dblclick",this.dblclicked.bind(this));pyprez.register(this);if(this.hasAttribute("runonload")&this.getAttribute("runonload")==="true"){this.run()}}unindent(code,minIndent=0){let firstFound=false;let lines=code.replaceAll("\t"," ").split("\n").filter((v,i)=>{if(firstFound){return true}firstFound=v.trim().length>0;return firstFound});let nonemptylines=lines.filter(v=>v.trim().length);let leadingSpacesPerLine=nonemptylines.filter(v=>!v.trim().startsWith("#")).map(v=>v.match(/\s*/)[0].length);let extraLeadingSpaces=Math.min(...leadingSpacesPerLine)-minIndent;if(extraLeadingSpaces>0&&extraLeadingSpaces<1e3){let extraIndent=" ".repeat(extraLeadingSpaces);code=lines.map(v=>v.startsWith(extraIndent)?v.replace(extraIndent,""):v).join("\n")}return code}reformatIndentation(code){while(code.endsWith(">")){let newCode=code.replace(/<\/[a-z-]*>$/,"");if(newCode===code){break}code=newCode}code=this.unindent(code);let inem='\nif __name__=="__main__":';let blocks=code.split(inem);if(blocks.length==2){let[pre,post]=blocks;post=this.unindent(post,4);code=pre+inem+post}return code}loadEl(){let code=this.innerHTML?this.reformatIndentation(this.innerHTML):"";this.initialCode=code;this.innerHTML=`
      ${code}
      `;this.loadEditor();if(this.hasAttribute("theme")){this.theme=this.getAttribute("theme")}this.loadPackages()}loadEditor(){this._loadEditor();codemirrorImported.then(this._loadCodeMirror.bind(this))}helpInfo=` + `).then(()=>{if(patch){patches.then(code=>{console.warn("applying python patches",code);pyodide.runPythonAsync(code).then(()=>{console.log("patched");window.pyodide=pyodide;pyodidePromise.resolve(true);pyodide.loadPackage("micropip").then(micropip=>{window.micropip=pyodide.pyimport("micropip");micropipPromise.resolve(true)})})})}else{window.pyodide=pyodide;pyodidePromise.resolve(true);pyodide.loadPackage("micropip").then(micropip=>{window.micropip=pyodide.pyimport("micropip");micropipPromise.resolve(true)})}})})})}return pyodidePromise}if(lint){micropipPromise.then(()=>{micropip.install("autopep8").then(()=>{pyodide.loadPackagesFromImports("autopep8").then(()=>{console.warn("autopep8_fix");pyodide.runPythonAsync(` + def autopep8_fix(s, tmp_fn="autopep8_temp.py"): + # micropip install autopep8 + import autopep8 + import os + autopep8.detect_encoding = lambda *a, **kw: 'utf-8' + with open(tmp_fn,"w") as f: + f.write(s) + r = autopep8.fix_file(tmp_fn) + os.remove(tmp_fn) + return r + `).then(()=>{window.autopep8Fix=pyodide.globals.get("autopep8_fix");linterPromise.resolve(true)})})})})}function scopeEval(script){return Function("with(this) { "+script+"}")()}class PyPrez{constructor(config={},load=true){this._stdout=this._stdout.bind(this);this._stderr=this._stderr.bind(this);this._stdin=this._stdin.bind(this);this.load=this.load.bind(this);this.loadPyodideInterface=this.loadPyodideInterface.bind(this);this.load=this.load.bind(this);this._runPythonAsync=this._runPythonAsync.bind(this);this.loadAndRunAsync=this.loadAndRunAsync.bind(this);this.recordNamespaceName=this.recordNamespaceName.bind(this);this.register=this.register.bind(this);Object.assign(this,config);this.config={stdout:this._stdout,stderr:this._stderr,stdin:this._stdin};if(load){this.loadPyodideInterface()}}loadPyodideInterface(){loadPyodideInterface(this.config).then(()=>{this.getNamespace("global")})}pending=[];then(successCb,errorCb){return pyodidePromise.then(successCb,errorCb)}catch(errorCb){return pyodidePromise.catch(errorCB)}stdout=console.log;stderr=console.error;stdin=()=>"";_stdout(...args){return this.stdout(...args)}_stderr(...args){return this.stderr(...args)}_stdin(...args){return this.stdin(...args)}elements={};editors={};consoles={};scripts={};imports={};overflows={};register(el){let mode=el.tagName.toLowerCase().split("-").pop();let m=this[mode+"s"];this.elements[Object.keys(this.elements).length]=el;m[Object.keys(m).length]=el;if(el.id){this.elements[el.id]=el;m[el.id]=el}}_runPythonAsync(code,namespace){if(code){console.debug("running code asynchronously:");console.debug(code);this.recordNamespaceName(namespace);if(useWorker){return pyodide.runPythonAsyncInNamespace(code,namespace).catch(this.stderr)}else{return pyodide.runPythonAsync(code,{globals:this.getNamespace(namespace)}).catch(this.stderr)}}}jsNamespaces={};getJSNamespace(name){if(this.jsNamespaces[name]===undefined){let scope={};Object.assign(scope,window,{console:console});this.jsNamespaces[name]=[scope,scopeEval.bind(scope)]}return this.jsNamespaces[name]}namespaceEval(code,name){let[scope,scopedEval]=this.getJSNamespace(name);return scopedEval(code)}namespaces={};namespaceNames=["global"];recordNamespaceName(name){if(!this.namespaceNames.includes(name)){this.namespaceNames=this.namespaceNames.concat([name])}}getNamespace(name){pyodidePromise.then((()=>{if(this.namespaces[name]===undefined){if(!useWorker){this.namespaces[name]=pyodide.globals.get("dict")()}}return this.namespaces[name]}).bind(this))}load(code,requirements="detect"){return this.then(()=>{if(requirements==="detect"){if(code){console.debug("auto loading packages detected in code");return this.installPackagesFromComments(code).then(()=>{console.warn("installed it here");return window.pyodide.loadPackagesFromImports(code)})}}else{console.debug("loading",requirements);return this.installPackagesFromComments(code).then(()=>{console.warn("installed it here2");return window.pyodide.loadPackagesFromImports(code)})}})}installPackagesFromComments(code){return new Promise((res,rej)=>{res(true)})}loadAndRunAsync(code,namespace="global",requirements="detect"){let p=this.then((()=>{if(code){return this.load(code,requirements).then((r=>{console.warn("loaded",r);return this._runPythonAsync(code,namespace)}).bind(this)).catch((e=>{console.error(e);return this._runPythonAsync(code,namespace)}).bind(this))}}).bind(this));return p}}var pyprez=new PyPrez(load=true);class PyPrezEditor extends HTMLElement{constructor(){super();this.classList.add("pyprez");this.loadEl=this.loadEl.bind(this);this.loadEditor=this.loadEditor.bind(this);this.keypressed=this.keypressed.bind(this);this.run=this.run.bind(this);this.copyRunnable=this.copyRunnable.bind(this);this.getRunnable=this.getRunnable.bind(this);this.autopep8Fix=this.autopep8Fix.bind(this);let language=this.hasAttribute("language")?this.getAttribute("language").toLowerCase():"python";let aliases={python:"python",javascript:"javascript",html:"html",py:"python",js:"javascript"};this.language=aliases[language];if(!this.hasAttribute("stdout")){this.setAttribute("stdout","true")}if(this.hasAttribute("src")&&this.getAttribute("src")&&this.getAttribute("src")!==pyprezScript.src){let src=this.getAttribute("src");console.debug("fetching script for pyprez-editor src",src);if(src.endsWith(".js")){this.language="javascript"}else if(src.endsWith(".py")){this.language="python"}else if(src.endsWith(".html")){this.language="html"}fetch(src).then(r=>r.text()).then(code=>{this.innerHTML=code;this.loadEl()})}else{this.loadEl()}this.namespace=this.hasAttribute("namespace")?this.getAttribute("namespace"):"global";pyprez.recordNamespaceName(this.namespace);this.addEventListener("keydown",this.keypressed.bind(this));this.addEventListener("dblclick",this.dblclicked.bind(this));pyprez.register(this);if(this.hasAttribute("runonload")&this.getAttribute("runonload")==="true"){this.run()}}unindent(code,minIndent=0){let firstFound=false;let lines=code.replaceAll("\t"," ").split("\n").filter((v,i)=>{if(firstFound){return true}firstFound=v.trim().length>0;return firstFound});let nonemptylines=lines.filter(v=>v.trim().length);let leadingSpacesPerLine=nonemptylines.filter(v=>!v.trim().startsWith("#")).map(v=>v.match(/\s*/)[0].length);let extraLeadingSpaces=Math.min(...leadingSpacesPerLine)-minIndent;if(extraLeadingSpaces>0&&extraLeadingSpaces<1e3){let extraIndent=" ".repeat(extraLeadingSpaces);code=lines.map(v=>v.startsWith(extraIndent)?v.replace(extraIndent,""):v).join("\n")}return code}reformatIndentation(code){while(code.endsWith(">")){let newCode=code.replace(/<\/[a-z-]*>$/,"");if(newCode===code){break}code=newCode}code=this.unindent(code);let inem='\nif __name__=="__main__":';let blocks=code.split(inem);if(blocks.length==2){let[pre,post]=blocks;post=this.unindent(post,4);code=pre+inem+post}return code}loadEl(){let code=this.innerHTML?this.reformatIndentation(this.innerHTML):"";this.initialCode=code;this.innerHTML=`
      ${code}
      `;this.loadEditor();if(this.hasAttribute("theme")){this.theme=this.getAttribute("theme")}}loadEditor(){this._loadEditor();codemirrorImported.then(this._loadCodeMirror.bind(this))}helpInfo=` PyPrez is powered by Pyodide and runs fully in your browser!

      To run: @@ -13,6 +24,12 @@ if(!window.pyprezUpdateDate){var pyprezUpdateDate=new Date("Sun Oct 23 2022 17:1
    • Shift + Enter
    • Double-Click
    + + To lint: +
      +
    • Ctrl + K to reformat/li> +
    + To re-run:
    • Shift + Enter
    • @@ -27,12 +44,13 @@ if(!window.pyprezUpdateDate){var pyprezUpdateDate=new Date("Sun Oct 23 2022 17:1 After code executes, try the runnable console at the bottom! Add to StackOverflow: - Click </> to copy markdown, then paste into your answer. + Click M↓ to copy markdown, then paste into your answer. `;_loadEditor(){let githublink=this.hasAttribute("githublink")?this.getAttribute("githublink")==="true":includeGithubLink;let gh=githublink?githublinkImage:"
      ";let help=this.hasAttribute("help")?this.getAttribute("help")==="true":window.help;let snss=this.hasAttribute("showNamespaceSelect")?this.getAttribute("showNamespaceSelect"):window.showNamespaceSelect;snss=snss?"block":"none";let sts=this.hasAttribute("showThemeSelect")?this.getAttribute("showThemeSelect"):window.showThemeSelect;sts=sts?"block":"none";let top="";if(help){top=`
      ${gh}
      -
      </>
      +
      </>
      +
      M↓
      @@ -48,7 +66,8 @@ if(!window.pyprezUpdateDate){var pyprezUpdateDate=new Date("Sun Oct 23 2022 17:1 ${gh}
      -
      </>
      +
      </>
      +
      M↓
      @@ -65,14 +84,14 @@ if(!window.pyprezUpdateDate){var pyprezUpdateDate=new Date("Sun Oct 23 2022 17:1 ${top}
      
      -            `;this.start=this.children[0];this.messageBar=this.children[1].children[1];this.copyRunnableLink=this.children[1].children[3];this.namespaceSelect=this.children[1].children[4];this.themeSelect=this.children[1].children[5];this.textarea=this.children[2];this.endSpace=this.children[3];this.start.addEventListener("click",this.startClicked.bind(this));this.themeSelect.addEventListener("change",(e=>{this.theme=this.themeSelect.value;try{localStorage.setItem("codemirrorTheme",this.themeSelect.value)}catch{}}).bind(this));this.namespaceSelect.addEventListener("click",()=>{this.refreshNamespaces()});this.textarea.style.height=this.textarea.scrollHeight+"px";let longestLine=this.initialCode.split("\n").map(v=>v.length).reduce((a,b)=>a>b?a:b);let fontSize=1*window.getComputedStyle(this.textarea).fontSize.slice(0,-2);let w=Math.min(window.innerWidth-50,Math.ceil(longestLine*fontSize)+200);this.style.maxWidth="100%";this.style.width=stackMode?"100%":w+"px";if(!pyodideImported.promise.fullfilled){this.message="Loading pyodide";pyodideImported.then((()=>{this.message="Ready   (Double-Click to Run)"}).bind(this))}}_loadCodeMirror(){this.editor=CodeMirror.fromTextArea(this.textarea,{lineNumbers:true,mode:this.language,viewportMargin:Infinity,gutters:["Codemirror-linenumbers","start"]});this.editor.doc.setGutterMarker(0,"start",this.start);this.editor.display.lineDiv.addEventListener("dblclick",this.dblclicked.bind(this));try{if(!this.hasAttribute("theme")){this.themeSelect.value=this.theme;let cmt=localStorage.getItem("codemirrorTheme");cmt=cmt?cmt:this.theme;localStorage.setItem("codemirrorTheme",cmt);this.themeSelect.value=cmt;this.theme=cmt}}catch{}}get selectedNamespace(){return this.namespaceSelect.value}set selectedNamespace(name){this.namespaceSelect.value=name}get namespaces(){return Array.from(this.namespaceSelect.children).map(el=>el.innerHTML)}set namespaces(namespaces){console.warn(namespaces);let sn=this.selectedNamespace;this.namespaceSelect.innerHTML=namespaces.map(name=>``).join("");this.namespaceSelect.value=sn}refreshNamespaces(){this.namespaces=pyprez.namespaceNames}get namespace(){return this.selectedNamespace}set namespace(name){if(!this.namespaces.includes(name)){console.warn(this.namespaces,name);this.namespaces=this.namespaces.concat([name])}this.selectedNamespace=name}keypressed(e){if(e.shiftKey&&e.key=="Backspace"){this.reload();e.preventDefault()}else if(e.key=="Enter"){if(e.shiftKey&&!this.done){this.run();e.preventDefault()}if(this.done){if(!(e.shiftKey||this.code.endsWith(":"))){this.run();e.preventDefault()}else{let s="\n"+this.consoleEscape;this.code+=s;e.preventDefault();let lines=this.code.split("\n");this.editor.setCursor({line:lines.length,ch:lines[lines.length-1].length})}}}}dblclicked(e){if(this.done){this.reload()}this.run();e.stopPropagation()}startClicked(){if(!this.executed){this.run()}else{this.reload()}}done=false;executed=false;separator="\n____________________\n";startChar="➤";reloadChar="↻";consolePrompt=consolePrompt;consoleOut=consoleOut;consoleEscape=consoleEscape;get message(){return this.messageBar.innerHTML}set message(v){this.messageBar.innerHTML=v}get code(){return this.editor?this.editor.getValue():this.textarea.value}set code(v){if(this.language=="html"){v=v.replaceAll("<","<").replaceAll(">",">")}if(this.editor){this.editor.setValue(v);this.editor.doc.setGutterMarker(0,"start",this.start);let si=this.editor.getScrollInfo();this.editor.scrollTo(0,si.height)}else{this.textarea.value=v;this.textarea.scrollTop=this.textarea.scrollHeight}let bb=this.getBoundingClientRect();if(bb.bottom>window.innerHeight){window.scrollTo(0,bb.bottom)}}_theme="default";get theme(){return this._theme}set theme(v){codemirrorImported.then((()=>{if(loadedCodeMirrorStyles.includes(v)){this.editor.setOption("theme",v)}else{let src=codemirrorCDN+"theme/"+v+".min.css";get(src).then(addStyle).then((()=>{this.editor.setOption("theme",v);loadedCodeMirrorStyles.push(v)}).bind(this))}}).bind(this));this.themeSelect.value=v;this._theme=v}numImports=0;loadPackages(){let code=this.code;let n=code.match(/import/g);if(n&&n.length!==this.numImports){this.message="Loading packages...";this.numImports=n.length;pyprez.load(this.code).then((()=>{this.message="Ready...(Double-Click to Run)"}).bind(this))}}reload(){this.start.innerHTML=this.startChar;this.start.style.color="green";if(this.language!=="html"){this.code=this.executed}console.warn("Setting code to ",this.executed,this.code);this.message="Ready   (Double-Click to Run)";this.executed=false;this.done=false}run(){console.log("run",this.done);if(this.done){this.consoleRun()}else if(this.code){this.message="Running...";let code=this.code.split(this.separator)[0];this.executed=code;this.start.style.color="yellow";let promise;if(this.language=="python"){this.code=code;this.code+=this.separator;if(this.getAttribute("stdout")==="true"){this.attachStd()}promise=pyprez.loadAndRunAsync(code,this.namespace);promise.then(r=>{this.done=true;this.message="Complete! (Double-Click to Re-Run)";let s="\n"+this.consoleOut+(r?r.toString():"")+"\n"+this.consolePrompt;this.code+=s;this.start.style.color="red";this.start.innerHTML=this.reloadChar;if(this.getAttribute("stdout")==="true"){this.detachStd()}return r})}else if(this.language=="javascript"){this.code=code;let r=pyprez.namespaceEval(code,this.namespace);this.done=true;this.message="Complete! (Double-Click to Re-Run)";let s="\n"+this.consoleOut+(![null,undefined].includes(r)?JSON.stringify(r,null,2):"");this.code+=s;this.start.style.color="red";this.start.innerHTML=this.reloadChar;return r}else if(this.language=="html"){if(!this.htmlResponse){this.htmlResponse=document.createElement("div");this.after(this.htmlResponse)}this.htmlResponse.innerHTML=this.code.replaceAll("<","<").replaceAll(">",">");this.done=true;this.message="Complete! (Double-Click to Re-Run)";this.start.style.color="red";this.start.innerHTML=this.reloadChar}}}consoleRun(){let code=this.code.split(this.consolePrompt).pop().trim().replaceAll(this.consoleEscape,"");if(!code){this.code+="\n"+this.consolePrompt}else{if(this.language==="python"){if(this.getAttribute("stdout")==="true"){this.attachStd()}let r=pyprez.loadAndRunAsync(code,this.namespace).then((r=>{if(this.getAttribute("stdout")==="true"){this.detachStd()}this.printResult(r)}).bind(this));return r}else if(this.language==="javascript"){let r=pyprez.namespaceEval(code,this.namespace);return this.printResult(r)}}}printResult(r){let res;if(this.language==="python"){if(r===null){r=""}else{res=r?r.toString():""}}else{res=JSON.stringify(r,null,2)}if(!this.code.endsWith("\n")){this.code+="\n"}this.code+=this.consoleOut+res+"\n"+this.consolePrompt;return res}appendLine(v){if(v!==null){this.code+="\n"+v}}attachStd(){this.oldstdout=pyprez.stdout;this.oldstderr=pyprez.stderr;pyprez.stdout=this.appendLine.bind(this);pyprez.stderr=this.appendLine.bind(this)}detachStd(r){if(this.oldstdout){pyprez.stdout=this.oldstdout;pyprez.stderr=this.oldstderr}return r}getRunnable(){let c=this.code.split("\n").map(line=>"    "+line).join("\n");let t=this.theme;return`
      +            `;this.start=this.children[0];this.messageBar=this.children[1].children[1];this.copyEmbeddableLink=this.children[1].children[3];this.copyRunnableLink=this.children[1].children[4];this.namespaceSelect=this.children[1].children[5];this.themeSelect=this.children[1].children[6];this.textarea=this.children[2];this.endSpace=this.children[3];this.start.addEventListener("click",this.startClicked.bind(this));this.themeSelect.addEventListener("change",(e=>{this.theme=this.themeSelect.value;try{localStorage.setItem("codemirrorTheme",this.themeSelect.value)}catch{}}).bind(this));this.namespaceSelect.addEventListener("click",()=>{this.refreshNamespaces()});this.textarea.style.height=this.textarea.scrollHeight+"px";let longestLine=this.initialCode.split("\n").map(v=>v.length).reduce((a,b)=>a>b?a:b);let fontSize=1*window.getComputedStyle(this.textarea).fontSize.slice(0,-2);let w=Math.min(window.innerWidth-50,Math.ceil(longestLine*fontSize)+200);this.style.maxWidth="100%";this.style.width=stackMode?"100%":w+"px";if(!pyodideImported.promise.fullfilled){this.message="Loading pyodide";pyodideImported.then((()=>{this.message="Ready   (Double-Click to Run)"}).bind(this))}}_loadCodeMirror(){this.editor=CodeMirror.fromTextArea(this.textarea,{lineNumbers:true,mode:this.language,viewportMargin:Infinity,gutters:["Codemirror-linenumbers","start"]});this.editor.doc.setGutterMarker(0,"start",this.start);this.editor.display.lineDiv.addEventListener("dblclick",this.dblclicked.bind(this));try{if(!this.hasAttribute("theme")){this.themeSelect.value=this.theme;let cmt=localStorage.getItem("codemirrorTheme");cmt=cmt?cmt:this.theme;localStorage.setItem("codemirrorTheme",cmt);this.themeSelect.value=cmt;this.theme=cmt}}catch{}}get selectedNamespace(){return this.namespaceSelect.value}set selectedNamespace(name){this.namespaceSelect.value=name}get namespaces(){return Array.from(this.namespaceSelect.children).map(el=>el.innerHTML)}set namespaces(namespaces){let sn=this.selectedNamespace;this.namespaceSelect.innerHTML=namespaces.map(name=>``).join("");this.namespaceSelect.value=sn}refreshNamespaces(){this.namespaces=pyprez.namespaceNames}get namespace(){return this.selectedNamespace}set namespace(name){if(!this.namespaces.includes(name)){this.namespaces=this.namespaces.concat([name])}this.selectedNamespace=name}keypressed(e){if(e.ctrlKey&&e.key=="k"){this.autopep8Fix();e.preventDefault()}if(e.shiftKey&&e.key=="Backspace"){this.reload();e.preventDefault()}else if(e.key=="Enter"){if(e.shiftKey&&!this.done){this.run();e.preventDefault()}if(this.done){if(!(e.shiftKey||this.code.endsWith(":"))){this.run();e.preventDefault()}else{let s="\n"+this.consoleEscape;this.code+=s;e.preventDefault();let lines=this.code.split("\n");this.editor.setCursor({line:lines.length,ch:lines[lines.length-1].length})}}}}dblclicked(e){if(this.done){this.reload()}this.run();e.stopPropagation()}startClicked(){if(!this.executed){this.run()}else{this.reload()}}done=false;executed=false;separator="\n____________________\n";startChar="➤";reloadChar="↻";consolePrompt=consolePrompt;consoleOut=consoleOut;consoleEscape=consoleEscape;get message(){return this.messageBar.innerHTML}set message(v){this.messageBar.innerHTML=v}get code(){return this.editor?this.editor.getValue():this.textarea.value}set code(v){if(this.language=="html"){v=v.replaceAll("<","<").replaceAll(">",">")}if(this.editor){this.editor.setValue(v);this.editor.doc.setGutterMarker(0,"start",this.start)}else{this.textarea.value=v;this.textarea.scrollTop=this.textarea.scrollHeight}let bb=this.getBoundingClientRect()}autopep8Fix(){linterPromise.then((()=>{this.code=autopep8Fix(this.code.split(this.separator)[0])}).bind(this))}_theme="default";get theme(){return this._theme}set theme(v){codemirrorImported.then((()=>{if(loadedCodeMirrorStyles.includes(v)){this.editor.setOption("theme",v)}else{let src=codemirrorCDN+"theme/"+v+".min.css";get(src).then(addStyle).then((()=>{this.editor.setOption("theme",v);loadedCodeMirrorStyles.push(v)}).bind(this))}}).bind(this));this.themeSelect.value=v;this._theme=v}reload(){this.start.innerHTML=this.startChar;this.start.style.color="green";if(this.language!=="html"){this.code=this.executed}this.message="Ready   (Double-Click to Run)";this.executed=false;this.done=false}run(){console.log("running");if(this.done){this.consoleRun()}else if(this.code){if(this.editor){let si=this.editor.getScrollInfo();this.editor.scrollTo(0,si.height)}this.message="Running...";let code=this.code.split(this.separator)[0];this.executed=code;this.start.style.color="yellow";let promise;if(this.language=="python"){this.code=code;this.code+=this.separator;if(this.getAttribute("stdout")==="true"){this.attachStd()}promise=pyprez.loadAndRunAsync(code,this.namespace);promise.then(r=>{this.done=true;this.message="Complete! (Double-Click to Re-Run)";let s="\n"+this.consoleOut+(r?r.toString():"")+"\n"+this.consolePrompt;this.code+=s;this.start.style.color="red";this.start.innerHTML=this.reloadChar;if(this.getAttribute("stdout")==="true"){this.detachStd()}return r})}else if(this.language=="javascript"){this.code=code;let r=pyprez.namespaceEval(code,this.namespace);this.done=true;this.message="Complete! (Double-Click to Re-Run)";let s="\n"+this.consoleOut+(![null,undefined].includes(r)?JSON.stringify(r,null,2):"");this.code+=s;this.start.style.color="red";this.start.innerHTML=this.reloadChar;return r}else if(this.language=="html"){if(!this.htmlResponse){this.htmlResponse=document.createElement("div");this.after(this.htmlResponse)}this.htmlResponse.innerHTML=this.code.replaceAll("<","<").replaceAll(">",">");this.done=true;this.message="Complete! (Double-Click to Re-Run)";this.start.style.color="red";this.start.innerHTML=this.reloadChar}}}consoleRun(){let code=this.code.split(this.consolePrompt).pop().trim().replaceAll(this.consoleEscape,"");if(!code){this.code+="\n"+this.consolePrompt}else{if(this.language==="python"){if(this.getAttribute("stdout")==="true"){this.attachStd()}let r=pyprez.loadAndRunAsync(code,this.namespace).then((r=>{if(this.getAttribute("stdout")==="true"){this.detachStd()}this.printResult(r)}).bind(this));return r}else if(this.language==="javascript"){let r=pyprez.namespaceEval(code,this.namespace);return this.printResult(r)}}}printResult(r){let res;if(this.language==="python"){if(r===null){r=""}else{res=r?r.toString():""}}else{res=JSON.stringify(r,null,2)}if(!this.code.endsWith("\n")){this.code+="\n"}this.code+=this.consoleOut+res+"\n"+this.consolePrompt;return res}appendLine(v){if(v!==null){this.code+="\n"+v}}attachStd(){this.oldstdout=pyprez.stdout;this.oldstderr=pyprez.stderr;pyprez.stdout=this.appendLine.bind(this);pyprez.stderr=this.appendLine.bind(this)}detachStd(r){if(this.oldstdout){pyprez.stdout=this.oldstdout;pyprez.stderr=this.oldstderr}return r}getRunnable(){let c=this.code.split("\n").map(line=>"    "+line).join("\n");let t=this.theme;return`
       
         
           #!/usr/bin/env python
       ${c}
       
       
      -`}copyRunnable(){console.warn("copy runnable");let s=this.getRunnable();console.log(s);navigator.clipboard.writeText(s);let originalColor=this.copyRunnableLink.style["background-color"];this.copyRunnableLink.style["background-color"]="rgb(149, 255, 162)";setTimeout((()=>{this.copyRunnableLink.style["background-color"]=originalColor}).bind(this),300)}}window.addEventListener("load",()=>{customElements.define("pyprez-editor",PyPrezEditor)});class PyPrezImport extends HTMLElement{constructor(){super();this.classList.add("pyprez");this.style.display="none";let requirements=[...this.innerText.matchAll(this.re)].map(v=>v[1]);if(requirements.length){console.debug("importing requirements ",requirements," from ",this);pyprez.then(()=>{window.pyodide.loadPackage(requirements)}).then(()=>{console.timeEnd("pyprez-import load")})}pyprez.register(this)}re=/\s*-?\s*(.*?)\s*[==[0-9|.]*]?\s*[,|;|\n]/g}window.addEventListener("load",()=>{customElements.define("pyprez-import",PyPrezImport)});class PyPrezScript extends HTMLElement{constructor(){super();this.classList.add("pyprez");this.style.display="none";this.run=this.run.bind(this);this.namespace=this.hasAttribute("namespace")?this.getAttribute("namespace"):"global";pyprez.recordNamespaceName(this.namespace);if(this.hasAttribute("src")&&this.getAttribute("src")){console.debug("fetching script for pyprez-script src",this.getAttribute("src"));fetch(this.getAttribute("src")).then(r=>r.text()).then(this.run)}else{this.run(this.innerHTML)}pyprez.register(this)}run(code){console.debug("running code from ",code,this);this.innerHTML=code;this.promise=pyprez.loadAndRunAsync(code,this.namespace).then(v=>{this.value=v;this.innerHTML=v?v.toString():"";return v});return this.promise}}window.addEventListener("load",()=>{customElements.define("pyprez-script",PyPrezScript)});class PyPrezConsole extends HTMLElement{constructor(){super();this.classList.add("pyprez");this.attachStd=this.attachStd.bind(this);this.detachStd=this.detachStd.bind(this);this.printResult=this.printResult.bind(this);this.eval=this.eval.bind(this);this.namespace=this.hasAttribute("namespace")?this.getAttribute("namespace"):"global";pyprez.recordNamespaceName(this.namespace);let language=this.hasAttribute("language")?this.getAttribute("language").toLowerCase():"python";let aliases={python:"python",javascript:"javascript",html:"html",py:"python",js:"javascript"};this.language=aliases[language];let cols=this.hasAttribute("cols")?this.getAttribute("cols"):this.defaultCols;let rows=this.hasAttribute("rows")?this.getAttribute("rows"):this.defaultRows;let bg=this.style["background-color"]?this.style["background-color"]:this.defaultBackgroundColor;let c=this.style["color"]?this.style["color"]:this.defaultTextColor;this.innerHTML=``;this.textarea=this.children[0];let src=false;if(this.hasAttribute("src")&&this.getAttribute("src")&&this.getAttribute("src")!==pyprezScript.src){src=this.getAttribute("src");console.debug("fetching script for pyprez-editor src",src);if(src.endsWith(".js")){this.language="javascript"}else if(src.endsWith(".py")){this.language="python"}else if(src.endsWith(".html")){this.language="html"}}this.startup();if(src){this.text+="\n"+this.consolePrompt+"# importing code from "+src;fetch(src).then(r=>r.text()).then((code=>{this.text+="\n"+this.consolePrompt+"# running code from "+src;this.eval(code)}).bind(this))}this.textarea.addEventListener("keydown",this.keydown.bind(this));pyprez.register(this)}defaultCols=120;defaultRows=20;defaultBackgroundColor="#000";defaultTextColor="#fff";consolePrompt=consolePrompt;consoleOut=consoleOut;consoleEscape=consoleEscape;startup(){if(this.language==="python"){pyprez.loadAndRunAsync(`
      +`}copyRunnable(){let s=this.getRunnable();console.log(s);navigator.clipboard.writeText(s);let originalColor=this.copyRunnableLink.style["background-color"];this.copyRunnableLink.style["background-color"]="rgb(149, 255, 162)";setTimeout((()=>{this.copyRunnableLink.style["background-color"]=originalColor}).bind(this),300)}copyEmbeddable(){let c=encodeURIComponent(this.code);let t=this.theme;let s=``;console.log(s);navigator.clipboard.writeText(s);let originalColor=this.copyEmbeddableLink.style["background-color"];this.copyEmbeddableLink.style["background-color"]="rgb(149, 255, 162)";setTimeout((()=>{this.copyEmbeddableLink.style["background-color"]=originalColor}).bind(this),300)}}window.addEventListener("load",()=>{customElements.define("pyprez-editor",PyPrezEditor)});class PyPrezImport extends HTMLElement{constructor(){super();this.classList.add("pyprez");this.style.display="none";let requirements=[...this.innerText.matchAll(this.re)].map(v=>v[1]);if(requirements.length){console.debug("importing requirements ",requirements," from ",this);pyprez.then(()=>{window.pyodide.loadPackage(requirements)}).then(()=>{console.timeEnd("pyprez-import load")})}pyprez.register(this)}re=/\s*-?\s*(.*?)\s*[==[0-9|.]*]?\s*[,|;|\n]/g}window.addEventListener("load",()=>{customElements.define("pyprez-import",PyPrezImport)});class PyPrezScript extends HTMLElement{constructor(){super();this.classList.add("pyprez");this.style.display="none";this.run=this.run.bind(this);this.namespace=this.hasAttribute("namespace")?this.getAttribute("namespace"):"global";pyprez.recordNamespaceName(this.namespace);if(this.hasAttribute("src")&&this.getAttribute("src")){console.debug("fetching script for pyprez-script src",this.getAttribute("src"));fetch(this.getAttribute("src")).then(r=>r.text()).then(this.run)}else{this.run(this.innerHTML)}pyprez.register(this)}run(code){console.debug("running code from ",code,this);this.innerHTML=code;this.promise=pyprez.loadAndRunAsync(code,this.namespace).then(v=>{this.value=v;this.innerHTML=v?v.toString():"";return v});return this.promise}}window.addEventListener("load",()=>{customElements.define("pyprez-script",PyPrezScript)});class PyPrezConsole extends HTMLElement{constructor(){super();this.classList.add("pyprez");this.attachStd=this.attachStd.bind(this);this.detachStd=this.detachStd.bind(this);this.printResult=this.printResult.bind(this);this.eval=this.eval.bind(this);this.namespace=this.hasAttribute("namespace")?this.getAttribute("namespace"):"global";pyprez.recordNamespaceName(this.namespace);let language=this.hasAttribute("language")?this.getAttribute("language").toLowerCase():"python";let aliases={python:"python",javascript:"javascript",html:"html",py:"python",js:"javascript"};this.language=aliases[language];let cols=this.hasAttribute("cols")?this.getAttribute("cols"):this.defaultCols;let rows=this.hasAttribute("rows")?this.getAttribute("rows"):this.defaultRows;let bg=this.style["background-color"]?this.style["background-color"]:this.defaultBackgroundColor;let c=this.style["color"]?this.style["color"]:this.defaultTextColor;this.innerHTML=``;this.textarea=this.children[0];let src=false;if(this.hasAttribute("src")&&this.getAttribute("src")&&this.getAttribute("src")!==pyprezScript.src){src=this.getAttribute("src");console.debug("fetching script for pyprez-editor src",src);if(src.endsWith(".js")){this.language="javascript"}else if(src.endsWith(".py")){this.language="python"}else if(src.endsWith(".html")){this.language="html"}}this.startup();if(src){this.text+="\n"+this.consolePrompt+"# importing code from "+src;fetch(src).then(r=>r.text()).then((code=>{this.text+="\n"+this.consolePrompt+"# running code from "+src;this.eval(code)}).bind(this))}this.textarea.addEventListener("keydown",this.keydown.bind(this));pyprez.register(this)}defaultCols=120;defaultRows=20;defaultBackgroundColor="#000";defaultTextColor="#fff";consolePrompt=consolePrompt;consoleOut=consoleOut;consoleEscape=consoleEscape;startup(){if(this.language==="python"){pyprez.loadAndRunAsync(`
                           import sys
                           s = f"""Python{sys.version}
                           Type "help", "copyright", "credits" or "license" for more information."""
      @@ -130,4 +149,4 @@ ${this.header}
                           
                       
      - `;this.pyprezEditor=this.children[1].children[0].children[0];this.stackOverflow=this.children[1].children[1].children[0];this.sync();this.pyprezEditor.addEventListener("keydown",(()=>{setTimeout(this.sync.bind(this),10)}).bind(this));this.pyprezEditor.themeSelect.addEventListener("change",(()=>{setTimeout(this.sync.bind(this),10)}).bind(this));codemirrorImported.then((()=>{setTimeout(this.sync.bind(this),1e3)}).bind(this))}sync(){console.warn("syncing");this.stackOverflow.runnable=this.pyprezEditor.getRunnable()}}window.addEventListener("load",()=>{customElements.define("stack-converter",StackOverflowConverter)});class TOOLTIP{empty={id:"tooltip",innerHTML:"",style:{position:"absolute","z-index":1e5,top:100,left:100,display:"none",opacity:.999}};def={style:{position:"relative","background-color":"#fff","border-color":"#606060","border-style":"solid","border-width":"2px","border-radius":"5px",margin:"5px",padding:"5px",opacity:.95}};trigger="mouseover";attrNames=["tooltip","learningtooltip"];hold=false;constructor(){this.el=this.createElement(this.empty);domContentLoaded.then(()=>{document.body.append(this.el)});this.addEventListener();let mouseDown=0;window.addEventListener("mousedown",e=>{this.mousedown++;this.onEvent(e,true)});window.addEventListener("mouseup",()=>{this.mousedown--});let enabled=localStorage.getItem("learningModeEnabled")==="true";console.log("enabled=",enabled);if(enabled){this.enableLearningMode()}else{this.disableLearningMode()}this.keydownToggle=this.keydownToggle.bind(this)}mousedown=0;hideBlocked=false;createElement(ob,innerHTML=-1){if(innerHTML===-1){innerHTML=ob.innerHTML}let el=document.createElement("div");let toggleWarning=document.createElement("div");toggleWarning.innerHTML='
      spacebar to toggle tooltip
      ';let inner=document.createElement("div");el.id=ob.id;for(let[k,v]of Object.entries(ob.style)){el.style[k]=v}inner.innerHTML=innerHTML;el.appendChild(inner);return el}getDefHTML(t,innerHTML){let temp=this.createElement(this.def,t);t=temp.outerHTML;return t}setInnerHTML(s,def=false){if(s.endsWith("#def")|def){s=this.getDefHTML(s.split("#def")[0])}this.el.innerHTML=s}show(s,clientY,clientX){this.el.style.top=window.scrollY+clientY+1+"px";let maxLeft=window.innerWidth-this.el.getBoundingClientRect().width-200;this.el.style.left=Math.min(clientX+1,maxLeft)+"px";this.setInnerHTML(s);this.el.style.display="block";maxLeft=window.innerWidth-this.el.getBoundingClientRect().width;this.el.style.left=Math.min(clientX+1,maxLeft)+"px";this.hold=true;document.body.addEventListener("keydown",this.keydownToggle);setTimeout(()=>{this.hold=false},100)}hide(){if(!this.hold){if(!this.mousedown){this.hideBlocked=!this.hideBlocked}if(!this.hideBlocked){this.el.style.top=0;this.el.style.left=0;this.el.style.display="none";this.el.innerHTML="";this.hideTimer=false;document.body.removeEventListener("keydown",this.keydownToggle)}}}keydownToggle(e){console.log(e.key);if(e.key===" "){this.toggleLearningMode();e.preventDefault()}}get learningMode(){return this.attrNames.includes("learningtooltip")}enableLearningMode(){if(!this.learningMode){this.attrNames.push("learningtooltip")}localStorage.setItem("learningModeEnabled",true)}disableLearningMode(){if(this.learningMode){this.attrNames.splice(this.attrNames.indexOf("learningtooltip"),1)}this.hide();localStorage.setItem("learningModeEnabled",false)}toggleLearningMode(){console.log(this.attrNames);if(this.learningMode){this.disableLearningMode()}else{this.enableLearningMode()}}onEvent(event,click=false){let attrNames=this.attrNames;if(click){attrNames=attrNames.concat(["clicktooltip"])}let p=event.path;let i=0;let found=false;if(p.length>4){while(!found&&i{setTimeout(this.sync.bind(this),10)}).bind(this));this.pyprezEditor.themeSelect.addEventListener("change",(()=>{setTimeout(this.sync.bind(this),10)}).bind(this));codemirrorImported.then((()=>{setTimeout(this.sync.bind(this),1e3)}).bind(this))}sync(){this.stackOverflow.runnable=this.pyprezEditor.getRunnable()}}window.addEventListener("load",()=>{customElements.define("stack-converter",StackOverflowConverter)});class TOOLTIP{empty={id:"tooltip",innerHTML:"",style:{position:"absolute","z-index":1e5,top:100,left:100,display:"none",opacity:.999}};def={style:{position:"relative","background-color":"#fff","border-color":"#606060","border-style":"solid","border-width":"2px","border-radius":"5px",margin:"5px",padding:"5px",opacity:.95}};trigger="mouseover";attrNames=["tooltip","learningtooltip"];hold=false;constructor(){this.el=this.createElement(this.empty);domContentLoaded.then(()=>{document.body.append(this.el)});this.addEventListener();let mouseDown=0;window.addEventListener("mousedown",e=>{this.mousedown++;this.onEvent(e,true)});window.addEventListener("mouseup",()=>{this.mousedown--});let enabled=localStorage.getItem("learningModeEnabled")==="true";if(enabled){this.enableLearningMode()}else{this.disableLearningMode()}this.keydownToggle=this.keydownToggle.bind(this)}mousedown=0;hideBlocked=false;createElement(ob,innerHTML=-1){if(innerHTML===-1){innerHTML=ob.innerHTML}let el=document.createElement("div");let toggleWarning=document.createElement("div");toggleWarning.innerHTML='
      spacebar to toggle tooltip
      ';let inner=document.createElement("div");el.id=ob.id;for(let[k,v]of Object.entries(ob.style)){el.style[k]=v}inner.innerHTML=innerHTML;el.appendChild(inner);return el}getDefHTML(t,innerHTML){let temp=this.createElement(this.def,t);t=temp.outerHTML;return t}setInnerHTML(s,def=false){if(s.endsWith("#def")|def){s=this.getDefHTML(s.split("#def")[0])}this.el.innerHTML=s}offsetX=5;offsetY=5;show(s,clientY,clientX){this.el.style.top=window.scrollY+clientY+this.offsetY+"px";let maxLeft=window.innerWidth-this.el.getBoundingClientRect().width-200;this.el.style.left=Math.min(clientX+this.offsetX,maxLeft)+"px";this.setInnerHTML(s);this.el.style.display="block";maxLeft=window.innerWidth-this.el.getBoundingClientRect().width;this.el.style.left=Math.min(clientX+this.offsetX,maxLeft)+"px";this.hold=true;document.body.addEventListener("keydown",this.keydownToggle);setTimeout(()=>{this.hold=false},100)}hide(){if(!this.hold){if(!this.mousedown){this.hideBlocked=!this.hideBlocked}if(!this.hideBlocked){this.el.style.top=0;this.el.style.left=0;this.el.style.display="none";this.el.innerHTML="";this.hideTimer=false;document.body.removeEventListener("keydown",this.keydownToggle)}}}keydownToggle(e){if(e.key===" "){this.toggleLearningMode();e.preventDefault()}}get learningMode(){return this.attrNames.includes("learningtooltip")}enableLearningMode(){if(!this.learningMode){this.attrNames.push("learningtooltip")}localStorage.setItem("learningModeEnabled",true)}disableLearningMode(){if(this.learningMode){this.attrNames.splice(this.attrNames.indexOf("learningtooltip"),1)}this.hide();localStorage.setItem("learningModeEnabled",false)}toggleLearningMode(){if(this.learningMode){this.disableLearningMode()}else{this.enableLearningMode()}}onEvent(event,click=false){let attrNames=this.attrNames;if(click){attrNames=attrNames.concat(["clicktooltip"])}let p=event.path;let i=0;let found=false;if(p.length>4){while(!found&&i + + + PyPrez Min + + + + + + + + + + \ No newline at end of file diff --git a/samples/iframe.html b/samples/iframe.html new file mode 100644 index 0000000..153ee51 --- /dev/null +++ b/samples/iframe.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/site/doc.html b/site/doc.html new file mode 100644 index 0000000..9c8c96b --- /dev/null +++ b/site/doc.html @@ -0,0 +1,101 @@ + + + + + + + Scribbler Documentation - a code documentation page template for codrops + + + + + + + +
      + +
      + +
      +
      +

      Get Started

      +

      Learn how to configure settings for Scribbler, such as your syntax highlighting preference and the default saving folder location.

      +

      Installation

      +
      +
      +              
      +                $ scribbler  ——config
      +                  {
      +                    “encryption”: true, 
      +                    “highlighting“: true,
      +                    “prettyTable”: false,
      +                    “font”: [“Helvetica”, “sans-serif”],
      +                    “folder”: “~/Desktop“
      +                  }
      +              
      +            
      +
      +
      +
      +

      Configuration

      +

      Learn how to configure settings for Scribbler, such as your syntax highlighting preference and the default saving folder location.

      + + + + + + + + + + + + + + + + + + + + + +
      OptionsValueDefault
      encryptionencrypt all notes before saving. If turned on, it requires password to open the file.false
      highlightingShow syntax highlight on markdown text.true
      prettyTableRender table with Scribbler’s pretty table style.true
      +

      Malis percipitur an pro. Pro aperiam persequeris at, at sonet sensibus mei, id mea postulant definiebas concludaturque. Id qui malis abhorreant, mazim melius quo et. At eam altera dolorum, case dicant lobortis ius te, ad vel affert oportere reprehendunt. Quo no verterem deseruisse, mea brute postea te, ne per tacimates suavitate vituperatoribus.

      +
      +
      +
      +

      Keybindings

      +

      Lorem ipsum dolor sit amet, scripta tibique indoctum sit ei, mel inani aeterno ad. Facer oratio ex per. At eam movet verear, in sea brute patrioque disputando, usu nonumes torquatos an. Ex his quaerendum intellegebat, ut vel homero accusam. Eum at debet tibique, in vocibus temporibus adversarium sed. Porro verear eu vix, ne usu tation vituperata.

      +

      Malis percipitur an pro. Pro aperiam persequeris at, at sonet sensibus mei, id mea postulant definiebas concludaturque. Id qui malis abhorreant, mazim melius quo et. At eam altera dolorum, case dicant lobortis ius te, ad vel affert oportere reprehendunt. Quo no verterem deseruisse, mea brute postea te, ne per tacimates suavitate vituperatoribus.

      +

      Malis percipitur an pro. Pro aperiam persequeris at, at sonet sensibus mei, id mea postulant definiebas concludaturque. Id qui malis abhorreant, mazim melius quo et. At eam altera dolorum, case dicant lobortis ius te, ad vel affert oportere reprehendunt. Quo no verterem deseruisse, mea brute postea te, ne per tacimates suavitate vituperatoribus.

      +
      +
      +
      +

      Issues

      +

      Lorem ipsum dolor sit amet, scripta tibique indoctum sit ei, mel inani aeterno ad. Facer oratio ex per. At eam movet verear, in sea brute patrioque disputando, usu nonumes torquatos an. Ex his quaerendum intellegebat, ut vel homero accusam. Eum at debet tibique, in vocibus temporibus adversarium sed. Porro verear eu vix, ne usu tation vituperata.

      +

      Malis percipitur an pro. Pro aperiam persequeris at, at sonet sensibus mei, id mea postulant definiebas concludaturque. Id qui malis abhorreant, mazim melius quo et. At eam altera dolorum, case dicant lobortis ius te, ad vel affert oportere reprehendunt. Quo no verterem deseruisse, mea brute postea te, ne per tacimates suavitate vituperatoribus.

      +
      +
      +
      + +
      Scribbler is a free HTML template created exclusively for Codrops.
      + + + + + \ No newline at end of file diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..51941f2 --- /dev/null +++ b/site/index.html @@ -0,0 +1,150 @@ + + + + + + + PyPrez | Run Python codesnippets in the the browser. Embed runnable answers in Stack Overflow or your webpage. + + + + + + + + + + +
      +

      PyPrez

      +

      Present your Python with runnable and editable code snippets

      + + +#!/usr/bin/env python +import numpy as np +x = np.random.rand(4) + +if __name__ == "__main__": + print(f"{x=}") + +
      +
      +
      +

      Import

      +
      +
        +
      • HTML
      • +
      • Install Locally
      • +
      +
      +            <script src="https://github.com/modularizer/pyprez/pyprez.min.js"></script>
      +            git clone https://github.com/modularizer/pyprez.git
      +          
      +
      +
      +
      +
      +

      No Installation

      +

      PyPrez is a javascript library which gets loaded when the webpage loads, meaning no installation + is necessary to developers or users

      +
      +
      +

      No Server Needed

      +

      Can be used in any HTML document! No server needed (aside from the file server to serve HTML/JS file)

      +
      +
      +

      Edit & Try Again

      +

      Tinker with your code until you get it to work. Editor doubles a Python Console after it is run.

      +
      +
      +

      Use On Stack Overflow

      +

      Embed runnable code snippets into your answers. Instructions, Samples

      +
      +
      +

      Customize

      +

      Choose from darkmode, light mode, or more!

      +
      +
      +

      Keybindings

      +

      You can expect common keybindings in the editor, plus a few extra!

      +
      +
      +
      +
        +

        Default Keybindings

        +
      • Select All Ctrl+A
      • +
      • Copy Selected Cmd+C
      • +
      • Paste Ctrl+V
      • +
      • Undo Cmd+Z
      • +
      +
        +

        Special Keybindings

        +
      • Shift+Enter Run Code
      • +
      • Shift + Backspace Reset Code
      • +
      +
      +
      +

      Read our documentation for advanced keybindings and customization

      + Documentation +
      +
      +
      +
      +

      Changelog

      +
      +
      +

      v0.7

      + 10/12/2017 +
      +
      +
        +
      • Improving the writing workflow with better key bindings
      • +
      • Design updates
      • +
      • SSL Verification for web hooks
      • +
      • Render Emoji
      • +
      +
      +
      +
      +
      +

      v0.6

      + 7/30/2017 +
      +
      +
        +
      • Adding Unicode support
      • +
      • Basic text highlighting
      • +
      • Fresh Design
      • +
      +
      +
      +
      +
      +

      v0.5

      + 5/10/2017 +
      +
      +
        +
      • Save default md file in new folders
      • +
      • Ability to quick search on existing notes
      • +
      +
      +
      + +
      +
      +
      Scribbler is a free HTML template created exclusively for Codrops.
      + + + + + \ No newline at end of file diff --git a/site/logo.svg b/site/logo.svg new file mode 100644 index 0000000..2cb5cef --- /dev/null +++ b/site/logo.svg @@ -0,0 +1,14 @@ + + + + logo + Created with Sketch. + + + + + \ No newline at end of file diff --git a/site/screenshot.jpg b/site/screenshot.jpg new file mode 100644 index 0000000..d08e7f0 Binary files /dev/null and b/site/screenshot.jpg differ diff --git a/site/scribbler-doc.css b/site/scribbler-doc.css new file mode 100644 index 0000000..a3f8c96 --- /dev/null +++ b/site/scribbler-doc.css @@ -0,0 +1,115 @@ +html, body { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +/* layout */ +.header { + border-bottom: 1px solid var(--code-bg-color); + grid-template-columns: 1fr 150px 60% 1fr; +} + +.wrapper { + display: flex; + flex-grow: 1; +} + +/* logo */ +.logo { + font-weight: 900; + color: var(--primary-color); + font-size: 1.4em; + grid-column: 2; +} + +.logo__thin { + font-weight: 300; +} + +/* menu */ +.menu { + grid-template-columns: 1fr 180px 60% 1fr; +} + +.menu__item { + padding: 1.5rem 1rem; +} + +/* doc */ +.doc__bg { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 28%; + background-color: var(--bg-color); + z-index: -1; +} + +.doc__nav { + flex-basis: 20%; + font-weight: 200; +} + +.doc__nav ul { + list-style: none; + padding-left: 0; + line-height: 1.8; +} + +.doc__nav ul.fixed { + position: fixed; + top: 2rem; +} + +.doc__nav li:hover { + color: var(--primary-color-light); + cursor: pointer; + transition: color .3s ease-in-out; +} + +.doc__nav .selected { + color: var(--accent-color); + position: relative; +} + +.doc__nav .selected:after { + position: absolute; + content: ""; + width: 1rem; + height: 1rem; + background-color: var(--accent-color); + left: -1.5rem; + top: 0.3rem; +} + +.doc__content { + flex-basis: 80%; + padding: 0 0 5rem 1rem; +} + +@media (max-width: 750px) { + .wrapper { + flex-direction: column; + } + .doc__content { + padding-left: 0; + } + .doc__nav ul { + border-bottom: 1px solid var(--code-bg-color); + padding-bottom: 0.5rem; + } + .doc__nav ul.fixed { + /* nutralized the fixed menu for mobile*/ + position: relative; + top: 0; + } + .doc__nav li { + display: inline-block; + padding-right: 1rem; + } + .doc__nav .selected:after { + display: none; + } +} \ No newline at end of file diff --git a/site/scribbler-global.css b/site/scribbler-global.css new file mode 100644 index 0000000..f7dca94 --- /dev/null +++ b/site/scribbler-global.css @@ -0,0 +1,313 @@ +/* css variables*/ +:root { + --primary-color: #432E30; + --primary-color-light: #8E7474; + --accent-color: #FE6A6B; + --accent-color-light: #FFE4E4; + --accent-color-dark: #B94B4C; + --white-color: #FAFBFC; + --light-gray-color: #C6CBD1; + --medium-gray-color: #959DA5; + --dark-gray-color: #444D56; + --bg-color: #F8F8FA; + --code-bg-color: #F0E8E8; +} + +/* normalized */ +html, body { + padding: 0; + margin: 0; + font-family: 'Nunito Sans', sans-serif; + background-color: white; +} + +p { + font-weight: 300; + color: #4A4A4A; +} + +a, a:hover { + text-decoration: none; + color: var(--primary-color); +} + +hr { + padding: 1rem 0; + border: 0; + border-bottom: 1px solid var(--bg-color); +} + +* { + box-sizing: border-box; +} + +/* global components */ + +/* typography */ +.section__title { + color: var(--primary-color); +} + +/* tabs */ +.tab__container { + position: relative; +} + +.tab__container > ul { + position: absolute; + list-style: none; + margin: 0; + right: 1rem; + top: -2rem; + padding-left: 0; +} + +.tab__container .code { + white-space: normal; + padding: 1rem 1.5rem; +} + +.tab { + display: inline-block; + padding: 0.3rem 0.5rem; + font-weight: 200; + cursor: pointer; +} + +.tab.active { + border-bottom: 1px solid var(--primary-color); + font-weight: 700; + display: inline-block; +} + +.tab__pane { + display: none; +} + +.tab__pane.active { + display: block; +} + +/* code */ +.code { + border-radius: 3px; + font-family: Space Mono, SFMono-Regular, Menlo,Monaco, Consolas, Liberation Mono, Courier New, monospace; + background: var(--bg-color); + border: 1px solid var(--code-bg-color); + color: var(--primary-color-light); +} + +.code--block { + white-space: pre-line; + padding: 0 1.5rem; +} + +.code--inline { + padding: 3px 6px; + font-size: 80%; +} + +/* buttons */ +.button--primary { + padding: 10px 22px; + background-color: var(--accent-color); + color: white; + position: relative; + text-decoration: none; + border: 0; + transition: all .3s ease-out; +} + +.button--primary:after { + position: absolute; + content: ""; + width: 1rem; + height: 1rem; + background-color: var(--accent-color-light); + right: -0.4rem; + top: -0.4rem; + transition: all 0.3s ease-out; +} + +.button--primary:hover { + text-shadow: 0px 1px 1px var(--accent-color-dark); + color: white; + transform: translate3D(0, -3px, 0); +} + +.button--primary:hover::after { + transform: rotate(90deg); +} + +.button--secondary { + padding: 10px 22px; + border: 2px solid var(--primary-color); + transition: all 0.5s ease-out; +} + +.button--secondary:hover { + border-color: var(--accent-color); + color: var(--accent-color); +} + +/* links */ +.link { + text-decoration: none; + transition: all 0.3s ease-out; +} + +.link:hover { + color: var(--accent-color); +} + +.link--dark { + color: var(--primary-color); +} + +.link--light { + color: var(--accent-color); +} + +/* menu */ +nav { + display: grid; + grid-template-columns: 70px auto; +} + +.menu { + margin: 0; + text-align: right; + overflow: hidden; + list-style: none; +} + +.toggle { + display: none; + position: relative; +} + +.toggle span, +.toggle span:before, +.toggle span:after { + content: ''; + position: absolute; + height: 2px; + width: 18px; + border-radius: 2px; + background: var(--primary-color); + display: block; + cursor: pointer; + transition: all 0.3s ease-in-out; + right: 0; +} + +.toggle span:before { + top: -6px; +} + +.toggle span:after { + bottom: -6px; +} + +.toggle.open span{ + background-color: transparent; +} + +.toggle.open span:before, +.toggle.open span:after { + top: 0; +} + +.toggle.open span:before { + transform: rotate(45deg); +} + +.toggle.open span:after { + transform: rotate(-45deg); +} + +.menu__item { + padding: 1rem; + display: inline-block; +} + +.menu__item.toggle { + display: none; +} + +/* table */ +table { + border-collapse: collapse; + width: 100%; + transition: color .3s ease-out; + margin-bottom: 2rem; +} + +table td, table th { + border: 1px solid var(--code-bg-color); + padding: 0.8rem; + font-weight: 300; +} + +table th { + text-align: left; + background-color: white; + border-color: white; + border-bottom-color: var(--code-bg-color); +} + +table td:first-child { + background-color: var(--bg-color); + font-weight: 600; +} + +@media screen and (max-width: 600px) { + nav { + grid-template-columns: 70px auto; + } + + .menu__item{ + display: none; + } + + .menu__item.toggle { + display: inline-block; + } + + .menu { + text-align: right; + padding: 0.5rem 1rem; + } + + .toggle { + display: block; + } + + .menu.responsive .menu__item:not(:first-child) { + display: block; + padding: 0 0 0.5rem 0; + } +} + +/* layout */ +.wrapper { + margin: 0 auto; + width: 70%; +} + +.footer { + text-align: center; + background-color: var(--primary-color); + padding: 2rem; + color: white; +} + +@keyframes fadeUp { + 0% { + opacity: 0; + transform: translate3d(0,30px,0); + } + 100% { + transform: translate3d(0,0,0); + } +} \ No newline at end of file diff --git a/site/scribbler-landing.css b/site/scribbler-landing.css new file mode 100644 index 0000000..0ff9b61 --- /dev/null +++ b/site/scribbler-landing.css @@ -0,0 +1,180 @@ +/* nav specialized to landing page */ +.logo { + background: url('logo.svg') no-repeat; + background-size: contain; + margin: 1rem 0 0 1rem; +} + +nav { + background-color: var(--bg-color); +} + +/* hero section */ +.hero { + text-align: center; + background-color: var(--bg-color); + padding: 2rem 0 2rem 0; +} + +.hero__title { + font-weight: 900; + color: var(--primary-color); +} + +.hero__description { + margin: -1rem auto 2rem auto; +} + +.hero__terminal { + width: 60%; + margin: -11rem auto 3rem auto; + text-align: left; + color: white; + padding: 0 1rem; + border-radius: 4px; + background-color: #232323; + min-height: 285px; + animation: fadeUp 2s; + box-shadow: 0px 12px 36.8px 9.2px rgba(0, 0, 0, 0.1); +} + +.hero__terminal pre { + white-space: pre-line; + padding-top: 1rem; +} + +/* feature section */ +.feature { + display: flex; + flex-wrap: wrap; +} + +.feature__item { + max-width: calc(33% - 20px); + margin: 0 20px 0 0; +} + +.feature__item .section__title { + margin-bottom: 0; +} + +.feature__item p { + margin-top: 0.5rem; +} + +/* keybinding section */ +.keybinding { + margin-top: 3rem; + display: flex; +} + +.keybinding__detail { + position: relative; + border: 1px solid var(--code-bg-color); + flex-basis: 50%; + padding: 2rem 1rem 1rem 1rem; + list-style: none; + line-height: 2rem; +} + +.keybinding__detail:first-child { + text-align: right; + padding-right: 1rem; +} + +.keybinding__detail:last-child { + padding-left: 1rem; + margin-left: -1px; +} + +.keybinding__detail:first-child .keybinding__title { + position: absolute; + right: 0.5rem; + top: -2rem; + background-color: white; + padding: 0 0.5rem; +} + +.keybinding__detail:last-child .keybinding__title { + position: absolute; + left: 0.5rem; + top: -2rem; + background-color: white; + padding: 0 0.5rem; +} + +.keybinding__label { + background: var(--white-color); + border: 1px solid var(--light-gray-color); + box-shadow: 0 1px 0 0 var(--medium-gray-color); + border-radius: 3px; + font-family: Courier; + font-size: 0.7rem; + color: var(--dark-gray-color); + padding: 3px 3px 1px 3px; + vertical-align: middle; +} + +/* callout section */ +.callout { + text-align: center; + padding: 1rem 0 3rem 0; +} + +.callout .button--primary { + display: inline-block; + margin-top: 0.5rem; +} + +/* changelog section */ +.changelog { + background-color: var(--bg-color); + padding: 2rem 0; +} + +.changelog__item { + display: flex; +} + +.changelog__meta { + flex-basis: 25%; +} + +.changelog__meta small { + color: var(--primary-color-light); + font-weight: 200; + letter-spacing: 1px; +} + +.changelog__title { + margin-bottom: 0; +} + +.changelog__callout { + margin: 3rem auto 2rem auto; + text-align: center; +} + +@media (max-width: 750px) { + .hero__terminal { + width: 70%; + } + .tab__container > ul { + right: auto; + left: 0; + padding-left: 0; + } + .tab__container .code { + margin-top: 2rem; + } + .feature, .keybinding, .changelog__item { + flex-direction: column; + } + .feature__item { + max-width: 100%; + margin: 0; + } + .keybinding { + font-size: 0.8rem; + } +} \ No newline at end of file diff --git a/site/scribbler.js b/site/scribbler.js new file mode 100644 index 0000000..ce9c32b --- /dev/null +++ b/site/scribbler.js @@ -0,0 +1,130 @@ +// utilities +var get = function (selector, scope) { + scope = scope ? scope : document; + return scope.querySelector(selector); +}; + +var getAll = function (selector, scope) { + scope = scope ? scope : document; + return scope.querySelectorAll(selector); +}; + +// setup typewriter effect in the terminal demo +if (document.getElementsByClassName('demo').length > 0) { + var i = 0; + var txt = `scribbler + [Entry mode; press Ctrl+D to save and quit; press Ctrl+C to quit without saving] + + ###todo for new year dinner party + + - milk + - butter + - green onion + - lots and lots of kiwis 🥝`; + var speed = 60; + + function typeItOut () { + if (i < txt.length) { + document.getElementsByClassName('demo')[0].innerHTML += txt.charAt(i); + i++; + setTimeout(typeItOut, speed); + } + } + + setTimeout(typeItOut, 1800); +} + +// toggle tabs on codeblock +window.addEventListener("load", function() { + // get all tab_containers in the document + var tabContainers = getAll(".tab__container"); + + // bind click event to each tab container + for (var i = 0; i < tabContainers.length; i++) { + get('.tab__menu', tabContainers[i]).addEventListener("click", tabClick); + } + + // each click event is scoped to the tab_container + function tabClick (event) { + var scope = event.currentTarget.parentNode; + var clickedTab = event.target; + var tabs = getAll('.tab', scope); + var panes = getAll('.tab__pane', scope); + var activePane = get(`.${clickedTab.getAttribute('data-tab')}`, scope); + + // remove all active tab classes + for (var i = 0; i < tabs.length; i++) { + tabs[i].classList.remove('active'); + } + + // remove all active pane classes + for (var i = 0; i < panes.length; i++) { + panes[i].classList.remove('active'); + } + + // apply active classes on desired tab and pane + clickedTab.classList.add('active'); + activePane.classList.add('active'); + } +}); + +//in page scrolling for documentaiton page +var btns = getAll('.js-btn'); +var sections = getAll('.js-section'); + +function setActiveLink(event) { + // remove all active tab classes + for (var i = 0; i < btns.length; i++) { + btns[i].classList.remove('selected'); + } + + event.target.classList.add('selected'); +} + +function smoothScrollTo(i, event) { + var element = sections[i]; + setActiveLink(event); + + window.scrollTo({ + 'behavior': 'smooth', + 'top': element.offsetTop - 20, + 'left': 0 + }); +} + +if (btns.length && sections.length > 0) { + for (var i = 0; i ul'); + + if( docNav) { + if (window.pageYOffset > 63) { + docNav.classList.add('fixed'); + } else { + docNav.classList.remove('fixed'); + } + } +}); + +// responsive navigation +var topNav = get('.menu'); +var icon = get('.toggle'); + +window.addEventListener('load', function(){ + function showNav() { + if (topNav.className === 'menu') { + topNav.className += ' responsive'; + icon.className += ' open'; + } else { + topNav.className = 'menu'; + icon.classList.remove('open'); + } + } + icon.addEventListener('click', showNav); +}); + diff --git a/site/sketch/scribbler.sketch b/site/sketch/scribbler.sketch new file mode 100644 index 0000000..8a20af5 Binary files /dev/null and b/site/sketch/scribbler.sketch differ