Instantly share code, notes, and snippets.

Embed
What would you like to do?
Offline Text Editor in < 140 bytes (115 bytes). Powered by localStorage & contentEditable
with({
l: localStorage // Alias for localStorage, where we'll store text content
}) with(document.body) // With the document body..
contentEditable = !0, // Turn on contentEditable
innerHTML = [l.c], // Set innerHTML of our element to the value stored in localStorage
oninput = function () { // On input..
l.c = innerHTML // store our content in localStorage
}
!function(l){with(document.body)contentEditable=!0,innerHTML=[l.c],oninput=function(){l.c=innerHTML}}(localStorage)

Edit: Thanks to help from @mathiasbynens, @subzey and @beaufortfrancois we are now down to 115 bytes from my original 136.

I use this as a JavaScript snippet or bookmarklet in Chrome to turn the current blank tab into an offline text-editor which stores what I write in localStorage. The < 140 character version can be run via console.

The original version that you can add as a bookmarklet:

(function(d){l=localStorage,k='c',q=d.body;q.contentEditable=true;q.innerHTML=l[k]||'';q.oninput=function(){l[k]=q.innerHTML;}})(document);

A demo version of this is available on JSBin.

The optimized version (115 bytes):

!function(l){with(document.body)contentEditable=!0,innerHTML=[l.c],oninput=function(){l.c=innerHTML}}(localStorage)

Pretty version

Prettier version of the implementation using document.write (hah) to write over any existing tab content, gives you some styling too:

javascript:(function(d){d.write('<body contenteditable style="font: 2rem/1.5 monospace;max-width:60rem;margin:0 auto;padding:4rem;">');var k = 'c'; var q = d.querySelector('body');q.innerHTML=localStorage[k];q.oninput=function(){localStorage[k]=q.innerHTML;}})(document);

Preview:

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Copyright (C) 2014 ADDY OSMANI <addyosmani.com>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "offlineTextEditor",
"description": "ContentEditable-powered Offline text editor",
"keywords": [
"text",
"edit",
"editor",
"localStorage",
"offline"
]
}
<!DOCTYPE html>
<title>Offline Text Editor</title>
<style>
body { font: 2rem/1.5 monospace;max-width:60rem;margin:0 auto;padding:4rem;}
</style>
<body>
<script>
!function(l){with(document.body)contentEditable=!0,innerHTML=[l.c],oninput=function(){l.c=innerHTML}}(localStorage);
</script>
</body>
@mathiasbynens

This comment has been minimized.

mathiasbynens commented May 23, 2014

Let’s save some bytes there!

k = 'c'
q.contentEditable = true

q.contentEditable = k = 'c'

Update: Too bad, this actually throws an error (at least in Opera/Chrome). We can still change true into !0.

Let’s use input instead of keyup — it’s the same amount of bytes, but it feels much more responsive.

@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 23, 2014

Good tip on saving bytes! I'll update.

There was an input example in there, but per your suggestion I've updated the defaults across all examples to this. Leaving in one keyup called out for pure reference.

@beaufortfrancois

This comment has been minimized.

beaufortfrancois commented May 23, 2014

Shorter: javascript: (function(b){l=localStorage,k='c';b.contentEditable=true;b.innerHTML=l[k]||'';b.oninput=function(){l[k]=b.innerHTML;}})(document.body);

@mathiasbynens

This comment has been minimized.

mathiasbynens commented May 23, 2014

Fixed version that doesn’t leak to the global scope as required by the @140bytes rules:

(function(b, l) {
  b.contentEditable = !0;
  b.innerHTML = l._ || '';
  b.oninput = function() {
    l._ = b.innerHTML;
  };
}(document.body, localStorage));

122 bytes:

!function(a,b){a.contentEditable=!0,a.innerHTML=b._||"",a.oninput=function(){b._=a.innerHTML}}(document.body,localStorage)

Note that storing 'innerHTML' in a variable doesn’t save bytes in this case:

!function(a,b,c){a.contentEditable=!0,a[c]=b._||'',a.oninput=function(){b._=a[c]}}(document.body,localStorage,'innerHTML')
@subzey

This comment has been minimized.

subzey commented May 23, 2014

with doesn't leak into outer scope as well:

with({l:localStorage})with(document.body)contentEditable=!0,innerHTML=[l.c],oninput=function(){l.c=innerHTML}

By the way contentEditable = 'c' (sadly) raises error in Chrome:
SyntaxError: Failed to set the 'contentEditable' property on 'HTMLElement': The value provided ('c') is not one of 'true', 'false', 'plaintext-only', or 'inherit'.

UPD: Oops, I totally forgot about another 140bytes rule: submission must be an assigable expression. Here's the tradeoff:

!function(l){with(document.body)contentEditable=!0,innerHTML=[l.c],oninput=function(){l.c=innerHTML}}(localStorage)
@jnicholls

This comment has been minimized.

jnicholls commented May 23, 2014

Nice!

@mathiasbynens

This comment has been minimized.

mathiasbynens commented May 23, 2014

@subzey Ah, too bad. Was worth a shot.

Nice 109 bytes solution! Unfortunately it is not an assignable, valid expression (also required by the rules) without wrapping it in an IIFE anyway.

@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 23, 2014

@mathiasbynens Nice! I'm running into the same Failed to set the 'contentEditable' property on 'HTMLElement' exception for that one liner. Possible to fix/update? Otherwise it's really starting to shape up.

@beaufortfrancois

This comment has been minimized.

beaufortfrancois commented May 23, 2014

Setting a variable for the localStorage namespace is not mandatory.

(function (b) {               // Alias for current document
    l = localStorage,         // Alias for localStorage, used for storing text content
    b.contentEditable = !0;   // Switch on contentEditable
    b.innerHTML = l.c || '';  // Set content to localStorage value or empty string
    b.oninput = function () { // On input, update localStorage value to textContent of your contentEditable area
        l.c = b.innerHTML;
    }
})(document.body);
@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 23, 2014

@beaufortfrancois Slick. I totally forgot it should work without providing a namespace. I like it.

@mathiasbynens your updated version now works fine in Chrome + Opera:

!function(a,b,c){a.contentEditable=!0,a[c]=b[c]||"",a.oninput=function(){b[c]=a[c]}}(document.body,localStorage,"innerHTML")
@xem

This comment has been minimized.

xem commented May 23, 2014

Here's my little contribution, the same thing as @subzey, but in HTML and in 94b

<body id=b onload=b.contentEditable=!0;b.innerHTML=[(l=localStorage).c] oninput=l.c=innerHTML>

Complete DHTML apps allow more golfing than standalone JS functions ;)

@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 23, 2014

What do we think of this version, which factors in updates from Mathias and Francois?

(function(b, l, i) {
  b.contentEditable = !0,
  b[i] = l.c || '';
  b.oninput = function() {
    l.c = b[i];
  };
}(document.body, localStorage, 'innerHTML'));

122 Bytes:

!function(a,b,c){a.contentEditable=!0,a[c]=b.c||"",a.oninput=function(){b.c=a[c]}}(document.body,localStorage,"innerHTML")
@mathiasbynens

This comment has been minimized.

mathiasbynens commented May 23, 2014

@subzey’s is shorter (115 bytes)!

@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 23, 2014

@xem Sweet. I think data:text/html,<body id=b onload=b.contentEditable=!0;b.innerHTML=[(l=localStorage).c] oninput=l.c=innerHTML> could be awesome for a one-line paster if Chrome/Blink allowed you to access Storage in this manner. Unfortunately they don't, but 94b is still impressive.

@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 23, 2014

@mathiasbynens you're right! Totally missed it saved even more bytes.

@subzey

This comment has been minimized.

subzey commented May 23, 2014

@addyosmani, why need to set up contentEditable attribute onload when you can set an attubute in the markup? ;) data:text/html,<body contentEditable id=b…

@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 23, 2014

Hah! Indeed :)

@xem

This comment has been minimized.

xem commented May 23, 2014

Thanks, so 89b:
<body id=b contentEditable onload=b[i="innerHTML"]=[(l=localStorage).c] oninput=l.c=b[i]>

@vasilionjea

This comment has been minimized.

vasilionjea commented May 23, 2014

I'm not sure why the following is wrapped in brackets: innerHTML=[l.c] but if you remove them it brings us down to 113 chars. So then it would be this:
!function(l){with(document.body)contentEditable=!0,innerHTML=l.c,oninput=function(){l.c=innerHTML}}(localStorage)

Maybe I'm missing something but this seems to work fine.

@aemkei

This comment has been minimized.

aemkei commented May 23, 2014

@vasilionjea: You need the brackets if you open the page for the first time. Otherwise you would see undefined.

@aemkei

This comment has been minimized.

aemkei commented May 23, 2014

How about getting rid of the second function and save another byte?

!function x(a,l){with(l=localStorage,oninput=x,document.body)contentEditable=!0,a?l.c=innerHTML:innerHTML=[l.c]}()

#114 bytes

BTW: I love the simplicity of the 89b version by @xem!

@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 23, 2014

@aemkei Really like the adjustment to get it down by another byte.

@xem That 89b version is amazing.

@pocketpc

This comment has been minimized.

pocketpc commented May 24, 2014

This is awesome. Is there any way to use this with Firefox? I want to try it myself.

@xem

This comment has been minimized.

xem commented May 24, 2014

@pocketpc, just type the JS version in your Firefox console:
!function x(a,l){with(l=localStorage,oninput=x,document.body)contentEditable=!0,a?l.c=innerHTML:innerHTML=[l.c]}()

Or click here for the online version:
http://xem.github.io/postit

(https://github.com/xem/postit)

@subzey

This comment has been minimized.

subzey commented May 24, 2014

98 bytes:

document.body.outerHTML='<body contentEditable oninput=localStorage.c=innerHTML>'+[localStorage.c]
@xem

This comment has been minimized.

xem commented May 24, 2014

just, whoa.

@subzey

This comment has been minimized.

subzey commented May 24, 2014

...or maybe old dirty document.write?

document.write('<body contentEditable oninput=localStorage.c=innerHTML>'+[localStorage.c])

(90 bytes)

@francoisrigal

This comment has been minimized.

francoisrigal commented May 24, 2014

Now with linkification (perfect web notepad)?

@xem

This comment has been minimized.

xem commented May 24, 2014

What do you mean by linkification?
an online version? => http://xem.github.io/postit
or a version that can be used to share the content with a link => http://xem.github.io/paste
I'm wondering if the two features (localstorage + share via URL) can be mixed...

@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 24, 2014

@subzey just..wow. That's pretty damn impressive on both counts. Well done!

@tsaniel

This comment has been minimized.

tsaniel commented May 24, 2014

@subzey If you use it as a bookmarklet then you don't even need document.write.

javascript:'<body contentEditable oninput=localStorage.c=innerHTML>'+[localStorage.c]

(85 bytes)

@xem

This comment has been minimized.

xem commented May 24, 2014

neat! I didn't know we could output html just after a "javascript:".
Do you know how to add a custom bookmarklet on firefox 29? There's no field to edit the url of a bookmark :s

@xem

This comment has been minimized.

xem commented May 24, 2014

FYI, guoj has hosted a fork of this "post it" app here:
post.guoj.org

@tsaniel

This comment has been minimized.

tsaniel commented May 24, 2014

@xem enable Bookmarks Toolbar and there you go

@fghhfg

This comment has been minimized.

fghhfg commented May 24, 2014

How about this one?
What's the difference?

@xem

This comment has been minimized.

xem commented May 24, 2014

It's the same thing, expect this one has an autosave feature. (localstorage persistance)

@addyosmani

This comment has been minimized.

Owner

addyosmani commented May 25, 2014

@xem it would be nice if guoj included a link back :)

@tsaniel Whoa. I had no idea that could work. 85 bytes is the current golf record for this one. Nice job!

@tsaniel

This comment has been minimized.

tsaniel commented May 25, 2014

I don't think there is any room to save bytes.

@aemkei

This comment has been minimized.

aemkei commented May 26, 2014

If we don't care about global leaks, it could be 83 bytes:

document.write('<body contentEditable oninput=l.c=innerHTML>'+[(l=localStorage).c])
@yogendra

This comment has been minimized.

yogendra commented May 26, 2014

Here's a extended version (slightly longer) but works offline and preserves your text, no matter which site you are on. Just make a bookmarklet of this.

javascript:window.open("javascript:'<body contentEditable oninput=localStorage.c=innerHTML>'+[localStorage.c]", "_mynotes")

UPDATE: Yikes wrote that too soon. This still is page dependent. It will open a new window but will use localStorage on a per page basis. Sad :(

@liorama

This comment has been minimized.

liorama commented May 26, 2014

Change 'body' to a simple 'a':
javascript:'<a contentEditable oninput=localStorage.c=innerHTML>'+[localStorage.c]

@tsaniel

This comment has been minimized.

tsaniel commented May 26, 2014

@yogendra Interesting. However it does not work for me?

@tsaniel

This comment has been minimized.

tsaniel commented May 26, 2014

@liorama There are some problems not using body:

  1. The editing area's height and width are not 100%.
  2. It may not work with paragraphs (more than one line).
@tsaniel

This comment has been minimized.

tsaniel commented May 26, 2014

@aemkei Actually If we count javascript: it should be +11bytes, or if we don't count javascript: mine will be -11bytes. Not sure which one to go.

@liorama

This comment has been minimized.

liorama commented May 26, 2014

@tsaniel I thought about the 100% height issue, but the element takes the height of it's contents so does it really matter? (apart from the visible border around the text)
As for the paragraph, it works well for me in Chrome (as well as when using 'body'). If it is a problem we can try a div instead.

@aemkei

This comment has been minimized.

aemkei commented May 26, 2014

@tsaniel: You are right: It depends on the rules. And if we follow the strict rules from 140byt.es it is still 114 bytes.

@tsaniel

This comment has been minimized.

tsaniel commented May 26, 2014

@aemkei Indeed.
By the way, it's been a long time since 140bytes was created, a lot of things have changed.
Back then, Jed said the community should stick to ES3, now even ES6 is coming out. Maybe 140bytes really needs an update. For instance, arrow function.

@tsaniel

This comment has been minimized.

tsaniel commented May 26, 2014

@liorama Maybe it depends on personal preference. I just find full height element handy, as I can click to focus everywhere.

@liorama

This comment has been minimized.

liorama commented May 26, 2014

@tsaniel My personal preference is formatting and syntax highlighting but those might be a problem with the 140bytes restriction 😄

@xem

This comment has been minimized.

xem commented May 27, 2014

Maybe 140bytes really needs an update. For instance, arrow function.
My #1 request would be to allow 140 characters instead of 140 bytes, to allow things like that:
http://xem.github.io/miniCodeEditor/tweet.html
But then the name of the site would have to change...

@tsaniel

This comment has been minimized.

tsaniel commented May 28, 2014

@xem I'm afraid if 140 characters are allowed then every gist will use the same encoding trick?

@xem

This comment has been minimized.

xem commented May 28, 2014

yeah... but it's not only for the encoding trick.
Twitter counts Unicode characters, 140byt.es counts bytes...
So a 140-char script containing some multibyte characters would fit in twitter but not here.

@xcislav

This comment has been minimized.

xcislav commented Jul 16, 2014

89b

I don't understand:
square brackets [ ]
localStorage (the cookies of a browser)
innerHTML in quotes
.c

My understanding is:
and equals sign (=)
onload event
contentEditable
.innerHTML as a property

(for example, I understand the .innerHTML property of

when using it with contentEditable attribute - div then becomes like <input/textarea>)

Summary: Could Someone give a brief description of the editor's work?

@orschiro

This comment has been minimized.

orschiro commented May 10, 2017

Can someone please explain to me how to use this code as a bookmarklet?

Thanks!

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