Hacker News new | past | comments | ask | show | jobs | submit login
Hn.js (news.ycombinator.com)
69 points by aragonite on Dec 20, 2023 | hide | past | favorite | 44 comments



This is great. I love how unapologetically "good enough" this code is—it does the absolute minimum needed to get the job done, with zero extra ceremony. A great example is the `vote` function:

    function vote (id, how, auth, _goto) {
      vis($('up_' + id), how == 'un');
      vis($('down_' + id), how == 'un');
      var unv = '';
      if (how != 'un') {
        unv = " | <a id='un_" + id + "' class='clicky' " +
          "href='" + vurl(id, 'un', auth, _goto) + "'>" +
          (how == 'up' ? 'unvote' : 'undown') + "</a>"
      }
      $('unv_' + id).innerHTML = unv;
      new Image().src = vurl(id, how, auth, _goto);
    }
Hide the up_ and down_ vote buttons, then either erase the innerHTML of the un_ vote div or construct new HTML for it from scratch. To top it all off, let's just inform the server of the vote by creating a new throwaway Image element, because it works everywhere and is less verbose than fetch.


> To top it all off, let's just inform the server of the vote by creating a new throwaway Image element, because it works everywhere and is less verbose than fetch.

I don't think it's less verbose; fetch(vurl(...)) would work perfectly fine, and I think it's clearer what the intent is. fetch is also very widely supported by now. I don't blame anyone for writing it the way it was, back when fetch probably wasn't available, but I'd definitely just use that today.


At first I thought "bleh, HN is powered with jQuery?" but actually no: $ and vis are one-liner functions defined above vote.


This approach is also ripe for an XSS exploit.


Do you mean CSRF? I don't see upvote links for some reason so I can't debug - but does auth contains a CSRF token?

If not you could craft a page which upvote posts for the current visitor (if they're logged in HN)

You could mitigate it with server side checks (or maybe some new browser tech I don't know about?) but I think the synchroniser token pattern is still the current solution.


> does auth contains a CSRF token?

Yes, it looks like it does. Also, the vote auth param is different for every link.


I think there's server-side checking to avoid that. Let us know if you have a PoC though.


Needs to be completely redone using Next.js, TypeScript, and React Router for optimal efficiency. Use a React Reducer to reduce the anchor tags into discrete actions and run the code in a webworker off the main thread which maintains the vdom and does real time dom diffs based on semantic type decorators. For communication, I prefer post message and shared array buffers.


I think it also needs analytics (including cursor tracking) and a bit of A/B testing.


I you're gonna make this point, maybe don't use an example where the browser history is frequently busted after commenting. HN would benefit from a bit of SPA logic.


Busted is a stretch. You can long click on the previous button and skip the problematic page, if there's one.

In any case, an SPA is not the solution for this problem.


Don’t give them ideas


I would have used longer and more descriptive name for the functions to make the code more readable and understandable. Function names like `vis` or `upclass` don't tell much and makes the code much less understandable.


This. I'm fine with everything else, but I see no reason for making a 150 LoC file obscure just because of lazy naming.


It discourages any modifications or insertions


The Paul Graham school of Lisp, and of Arc, is well on display with these small pure functions with cryptic names. He used to say functions that are used often should be shortened as much as possible. Life is too short to write `getElementById` 30 times a day. In Arc (or was it Bel), even `defmacro` got shortened to `mac`.

I might not agree completely, but I see the wisdom in it.


I also agree 90%. However, when you shorten defmacro to mac, you break tooling.

Here is an example. Let's put this into a file called file.lisp:

  (defmacro foo ())

  (defwidget widg ())  ;; I just made up defwidget: it doesn't exist!

  (mac bar ())
Now, let's run Exuberant Tags (ctags):

  $ ctags file.lisp
  $ cat tags
  !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
  !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
  !_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/
  !_TAG_PROGRAM_NAME Exuberant Ctags //
  !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
  !_TAG_PROGRAM_VERSION 5.9~svn20110310 //
  foo file.lisp /^(defmacro foo ())$/;" f
  widg file.lisp /^(defwidget widg ())$/;" f
Hey look, ctags recognized my defwidget invention and index widg as a tag.

Where is Graham's mac?

Always call your custom definers defsomething, and put them in the first column as (defsomething name* ...*. Then ctags will index you custom defined constructs.

Also, saving a couple of character in defining a macro is false economy, because a macro should be used at least several times and considerably condense your program and make it more readable.

defmacro is already abbreviated; it could be define-macro which would be bad. defmac is a nice possibility.


That corroborates our suspicion that lisps (and lispers) are "write once, read never" languages.

For most of us less insane folks, we like to be able to read our code.

Interestingly, Paul Graham seems to not have taken this approach towards writing. I mean, if u can omit bnch o chars in wrds & stll gt the meanin acrx, why bthr wrtin ful wrds or sntnces?


Groan.


This might have made sense 20 years ago, but it kind of assumes you're writing code in Notepad. Literally, I type `g` + `Tab` to get an element. I would spend multitudes more time guessing what functions mean than I would typing.


Since this is here - was anything related to voting changed in the past year or so? Every now and then I pull out an old Windows phone with old-Edge or IE11, and upvoting worked as it should a while ago, but now it does a full page reload (the vote still counts). At a glance, the code looks quite tame and not full of fancy new features.


I see this line with an arrow function:

      fetch(url + next).then(r => r.json()).then(newstory);
It's not supported before Edge 12 [1], it probably causes a parse error on Edge 11 and IE 11, neutralizing the whole Javascript file.

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


This line is only triggered for /hide action.


Yep, but the whole file is parsed. This is not an execution issue, this is a parsing issue. IE and Edge < 12 don't know how to read this JS file because of this syntax. The whole thing.


A good example of progressive enhancement, without useless resource hogging. HN works well without JS, but uses some lightweight JS for more convenience.


Please, rewrite it in Rust. :D

For my personal single-html projects, I prefer a straightforward coding style that avoids external dependencies. I implement what I need in a simple, quick, and efficient manner using only native browser APIs, supplemented with a few helpers like those in 'Hn.js' to make the rest of the code shorter.


They have an API now actually too. I’ve been working on a Hacker News cross platform app on the side as an excuse to try out compose multi platform and kotlin on iOS.


Exactly same here. But the site is actually so simple that I just did web scraping with BeautifulSoup for my Kotlin Compose HN client.


    function $ (id) { return document.getElementById(id); }
I love this, it’s the ultimate lightweight jquery alternative.


    function $ (s) { return document.querySelectorAll(s); }

Would be more jquery like.


You’re right, $('#id') would fail with Paul’s version, haha.


You can also:

if ( ! Nodelist.prototype.map ) { Nodelist.prototype.map = Array.prototype.map }

So map() works. forEach() already does.


Anyone with the time would care to explain how voting is handled on the server? I image that the image which is generated on the client is somehow tied to an endpoint that runs the necessary SQL or something.



When is the Next.js/Vercel rewrite?


when is this going to use React already


Could be more Svelte


Solidjs the new hotness


If it doesn’t use signals it was developed by the cognitive and moral equivalent of a cane toad


Old garbage, it's more than 2 years old /s/


The <script> tag should just point to a JS file that ChatGPT generates from scratch with a new custom framework every day.


YC would probably fund that!


Trends come and go. Plain old JavaScript is forever.


The inconsistent naming conventions are surprising




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: