Skip to content

Instantly share code, notes, and snippets.

@myfonj
Last active October 8, 2024 04:51
Show Gist options
  • Save myfonj/c8ce74bf549e19600026ce9022388df8 to your computer and use it in GitHub Desktop.
Save myfonj/c8ce74bf549e19600026ce9022388df8 to your computer and use it in GitHub Desktop.
HTML sandbox - editor in data URI 1176 b, with live preview and persistence.
data:text/html;charset=utf-8;verbatim,<!doctype html><html style="color-scheme:dark light"><title>HTML sandbox 2.0.6</title><meta name=viewport content=width=device-width,initial-scale=1><body style=margin:0;display:flex;height:100vh onload="OT=(DC=document).title,H=(L=location).hash.slice(1)||'',RX=/(^data:.+?(;verbatim)?,)?([^]*)/,A.value=H.match(RX)[2]?H:decodeURIComponent(H)||A.value;T=W=0;E=RegExp('^'+(D='data:text/html;charset=utf-8,'));F=()=>{if(W!=(V=A.value))W=V,M=V.match(RX),I.src=M[2]?V:(M[1]||D)+encodeURIComponent(M[3]),DC.title=NT=((TM=V.match(/<title\b[^]*?\x3E([^]*?)<\/title\b/m))&&(NT=TM[1])&&(NT=NT.trim())&&(DC.title=NT+' @ '+OT))||OT};F()"><textarea autocapitalize=off style=resize:horizontal;width:50vw autofocus id=A onkeyup=clearTimeout(T);T=setTimeout(F,400) onblur=try{history.pushState({},NT,'\u0023'+(S=I.src.replace(E,'')))}catch(e){L.hash=S}><!doctype html><html lang="en" style="color-scheme: dark light;">%0A<meta name="viewport" content="width=device-width, initial-scale=1">%0A<title>%0A%0A</title>%0A<style>%0A%0A</style>%0A<body>%0A%s%0A<script>%0A%0A</script>%0A</textarea><iframe style=border:0;flex-grow:1;width:0 id=I>
data:text/html;charset=utf-8,<title>HTML sandbox 1.1.4</title><meta name=viewport content=width=device-width,initial-scale=1><body style=margin:0;display:flex;height:100vh;background:black onload="OT=(DC=document).title,A.value=decodeURIComponent((L=location).hash.slice(1)||'');T=W=0;E=RegExp('^'+(D='data:text/html;charset=utf-8,'));F=()=>{if(W!=(V=A.value))W=V,M=V.match(/(^data:.+?,)?([^]*)/),I.src=M[1]?V:D+encodeURIComponent(M[2]),DC.title=NT=((TM=V.match(/<title\b[^]*?\x3E([^]*?)<\/title\b/m))&&(NT=TM[1])&&(NT=NT.trim())&&(DC.title=NT+' @ '+OT))||OT};F()"><textarea style=resize:horizontal;width:50vw;border:inset;opacity:.7;color:snow;background:transparent autofocus id=A onkeyup=clearTimeout(T);T=setTimeout(F,400) onblur=try{history.pushState({},NT,'\u0023'+(S=I.src.replace(E,'')))}catch(e){L.hash=S}></textarea><iframe style=flex-grow:1;width:0;border:0;background:grey id=I>#%3C!doctype html%3E%3Chtml lang%3D""%3E%3Ctitle%3E%0A%0A%3C%2Ftitle%3E%0A%3Cmeta name%3D"viewport" content%3D"width%3Ddevice-width%2C initial-scale%3D1"%3E%0A%3Cstyle%3E%0A%3Aroot %7B background%3A dimgray%3B color%3A snow%3B %7D%0A%3Alink %7B color%3A aqua%3B %7D %3Avisited %7B color%3A lime%3B %7D%0A%0A%3C%2Fstyle%3E%0A%3Cbody%3E%0A%0A%3Cscript%3E%0A%0A%3C%2Fscript%3E%0A
@myfonj
Copy link
Author

myfonj commented Sep 17, 2019

Changelog

  • 2.0.6 (2024-09-03) • reverted embed back to iframe: embed is OK on Firefox, but behaves strangely in Chrome: sometimes not rendered until inspected with devtools. Heisenbug, maybe worth investigating.
  • 2.0.5 (2024-08-22) • experimentally switched iframe for embed, since it seems there is no difference for this purpose. And its shorter (no style to alter) and even "valid" (void, no closing tag needed). Also removed the inset styling of the textarea.
  • 2.0.4 (2024-07-04) • fixed bug with persistence of ;verbatim (erroneous decode when restored); bloated few bytes back
  • 2.0.3 (2024-07-04) • changed meta theme to style attribute (both in "app" and boilerplate), saving few bytes
  • 2.0.2 (2024-05-07) • baked simple HTML boilerplate in (so the size increased by 235 bytes) • allow both color-scheme (no need to restrict)
  • 2.0.0 (2023-07-06) Early stage. • Ditched colours in styles for color-scheme="dark" (must be present in document as well); • made "verbatim" mode derived from explicit ;verbatim, in datauri prologue instead of sole prologue presence; • possibility to have default content even when used as physical file.
  • 1.1.4 (2021-12-17) Better title matching, slightly smaller
  • 1.1.3 (2021-12-16) editor title prefixed with contents of <title>...</title> from content, if any. New default HTML content template.
  • 1.1.2 (2021-09-16) better "explicit mime-type" mode: when data prefix is explicit, no further encoding is done
  • 1.1.1 (2020-11-30) fix persistence in Chrome

Features

live preview

  • happens in iframe
  • previewed content is loaded as datauri (each preview is fresh load) after 400 ms of keyboard inactivity
  • warning: no infinite loop prevention
  • default (implied-if-omitted) mimetype is text/html (text/html;charset=utf-8,)
  • use explicit mime-type datauri prefix for testing others
    • e.g. data:application/xhtml+xml, / data:image/svg+xml, / data:text/plain,
  • all URI encoding (auto-escaping) happens automatically, no need to write %XX escapes.

"Verbatim" mode

  • (since v2) use ;verbatim, in prefix to have plain non-encoded readable value in the hash (or more precisely, leave it on the browser)
  • turns off the auto-escaping
    • then you have to URI-encode special chars yourself. # = %23; % = %25; line break = %0A.
    • (!) real line breaks in the source are effectively discarded, so they will not survive in persisted version (!)

Persistence (bookmark-ability and keyword-searchability)

  • persistence happens in #hash after editor blur event
    • so enter favourite boilerplate and bookmark with keyword for really quick offline access
    • so you can force refresh page and content of the editor should remain
    • you can copy & paste URL to other browser to test support
  • use %s in bookmark URL for query substitution
    • so if your bookmark keyword is A, URL ends with #<b>%s</b> entering A bar in URLbar will produce <b>bar</b> in editor
    • current version of the app includes minimalistic HTML boilerplate as the default value of the textarea, with %s sequence inside <body>, so when bookmarked this way you'll get the content in there; moving %s from the middle to the end after added # would make it work so the boilerplate would be used without keyword search argument and when argument supplied, it will become the sole content without surroundings.
  • to get just the "content document" you can
    • either use context menu of preview iframe This frame > Show only this frame (or Open frame in new tab/window) (Firefox)
    • or manually strip the editor "core" (everything before #) from URL

Technical restrictions

  • Because preview happens in an iframe that always loads newly constructed dataURI, (and the entire editor can be also served this way), it is in always anonymous (insecure) origin mode, what prevents for example access to locaStorage. The only mean to save some state across iframe reloads is to (ab)use window.name.

Notes

  • Scripting inside top-level dataURI document has peculiarities across browsers:
    • Firefox allows location.hash assignment but it throws on history.pushState;
    • Chrome throws on location.hash assignment (as it was top level navigation) but allows history.pushState. If my memory serves, old versions of Chrome treated hash as content continuation and wrote it into document, so data:text/html,content?search#hash had content content?search#hash and location.hash empty, now both browsers agree that content is content?search and location.hash is #hash. DataURI docs say nothing about #hash part.

Online variation of this, served over HTTP: https://myfonj.github.io/sandbox.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment