The lightest notes app implementation in 111 loc

66 points by antonmedv a day ago on lobsters | 35 comments

strickinato | a day ago

[OP] antonmedv | a day ago

I thought debounce was kinda normal)

strickinato | a day ago

I thought debounce was kinda normal)

LOL -- 🤷. The whole thing is a neat trick!

I guess it so happens with my background -- it was the debounce that stood out. I've seen tricks with the url bar and the compression is not too much more than a call to the browser API.

Most of the frontend I've written (that was complicated enough to warrant a timeout) was in a framework or in elm -- so I guess I hadn't really seen a debouncer written like that before. And now that you've said this and I've poked around - i see it's a conventional implementation.

But as I read through the (delightfully simple and short) implementation for this project - that's what popped out to me. So much - that I even had to comment!

carlana | a day ago

That debounce is pretty standard JS. See for example in my code here from eight years ago: https://github.com/golang/tools/blame/5ca1b5722305658ddc54bb061ac34eb3c0c29cf4/cmd/present/static/slides.js#L462-L470

[OP] antonmedv | a day ago

I think a neat trick is compression)

xnacly | a day ago

What even is that

function debounce(ms, fn) {
    let timer
    return (...args) => {
      clearTimeout(timer)
      timer = setTimeout(() => fn(...args), ms)
    }
  }

k749gtnc9l3w | a day ago

Every time that you want to create a new debouncer, a new local variable (named timer) gets created, and a function using that timer via capturing from the lexical scope is returned. Whenever the returned function is called, it schedules the real work to be done ms milliseconds later, cancelling the currently scheduled execution if it has not yet happened. Thus if the same thing is triggered multiple times with intervals smaller than ms milliseconds between the attempts, it only gets executed once, approximately ms milliseconds after the last attempt to trigger it.

carlana | a day ago

You also sometimes need the opposite of a debounce, which is called a throttle, where things happen immediately but the same event can't happen again until at least X milliseconds later. That is typically something like

function throttle(ms, fn) {
    let shouldRun = true
    return (...args) => {
      if (shouldRun) {
          shouldRun = false
          setTimeout(() => { shouldRun = true }, ms)
          fn()
    }
}

k749gtnc9l3w | a day ago

There are two dimensions of opposite here: whether to do work at the beginning or at the end, and whether to do work periodically or once per long dense series! (Of course any of the combinations can be the best fit, and it is always good to choose consciously between them)

[OP] antonmedv | a day ago

In case you missed it: it is possible to style textarea via CSS and share it.

jrgtt | 12 hours ago

Color me impressed -- this is neat!

simonw | a day ago

I love this. I picked up several new tricks from reading the source code.

louwers | a day ago

Which ones?

simonw | 19 hours ago

The debounce thing, the browser encryption APIs, patterns using ? and ??, the fact that contenteditable can be plain text only. The null byte delimiter trick is neat too.

[OP] antonmedv | a day ago

Thanks!)

metahost | a day ago

Just a note for anyone thinking of storing massive amounts of data in the URL for anything substantial: browsers typically support only a couple of thousand characters in the URL. See: https://stackoverflow.com/a/417184.

[OP] antonmedv | a day ago

dz4k | a day ago

wow that makes my browser hang for a solid two seconds when I click the URL bar.

[OP] antonmedv | a day ago

Works surprisingly good on iphone

strongoose | 12 hours ago

Sorry, Fennec had a problem and crashed

😅

simonw | a day ago

That classic StackOverflow answer is talking about the bit that comes before the # - the bit that's communicated to servers and through HTTP proxies and suchlike.

This notes app is using the #fragment portion.

Chrome documents that limit as 2MB: https://chromium.googlesource.com/chromium/src/%2B/HEAD/docs/security/url_display_guidelines/url_display_guidelines.md?utm_source=chatgpt.com#url-length - it looks like antonmedv's Chrime and Punishment demo is 534KB thanks to compression.

I couldn't find documented limits for Firefox and Safari.

Update: I had Claude Code for web checkout Firefox and WebKit and Chromium and go hunting for URL length limits, here's the result from that: https://github.com/simonw/research/blob/main/url-limits-investigation/README.md

Chrome limit is 2MB, Firefox is 1MB, WebKit is apparently String::MaxLength (INT32_MAX) aka 2GB.

jaredkrinke | a day ago

Comment removed by author

similarly: https://github.com/topaz/paste, in about 200 LOC. try here: https://topaz.github.io/paste/.

dz4k | a day ago

[OP] antonmedv | a day ago

For me the long word is correctly wrapped into pieces. What browser do you use?

Please try with overflow-wrap: break-word;

dz4k | a day ago

overflow-wrap: break-word; works. I'm on Firefox.

cgc373 | 14 hours ago

A while ago I set up this JavaScript bookmarklet:

data:text/html,<body contenteditable style="line-height:1.5;font-size:20px;">

It's zserge's idea., discussed on lobste.rs some years ago.

But where do I store the notes URLs? Hmm.

[OP] antonmedv | 26 minutes ago

Bookmarks!

JamieTanna | 21 hours ago

Is it possible to out a license on it? Right now it'll default to All-Rights-Reserved without an explicit license ☺️

adam_d_ruppe | 7 hours ago

Before clicking, I was gonna be like "i can do it in one line, har har, <textarea> gg" but curious, why didn't you use the textarea here? Content editable is legit cool too, fun to show that off to people who have no idea you can do it, but curious what your specific thought was in choosing it here here since the code and functionality would be pretty much the same (maybe like a one line diff to change the border? lol).

[OP] antonmedv | 6 hours ago

textarea can’t grow with content automatically. Contenteditable plaintext-only basically is the same as textarea but grows with content.

adam_d_ruppe | an hour ago

Oh ok, yeah, that makes sense. I was thinking just making the textarea be full screen (like height: 100vh; width: 100vw;) but the contenteditable does work better, the textarea never quite seems to fit perfectly even with my css stuff so that's a nice benefit.

reezer | 4 hours ago

CompressionStream is nice.. Didn't know about that. Back when I used to do more JS that would have been so nice compared to adding additional dependencies. But seems like browsers only added that API a couple of years ago.

alper | a day ago

It's fun how increasingly more things just don't work anymore on Safari.

I don't keep up with web development. How bad is this browser?

masklinn | a day ago

An odd comment when the thing seems to work fine in safari, and the only other mention of safari in the comments is the fragment size limit being unspecified?