2
\$\begingroup\$

I use Reddit a lot (just browsing, not posting), and one of the things I don't like about it is that posts always display "x years ago" instead of absolute timestamp.

So today I decided to change that. So I did a little research and wrote a simple JavaScript file to fix this problem. And I have written a working JavaScript solution (I am not very familiar with JavaScript).

I used TamperMonkey to inject the code, and I have tested that the code is absolutely working. Here is the code:

// ==UserScript== // @name Reddit absolute timestamp // @namespace https://www.reddit.com // @version 2024-12-27 // @description shows absolute timestamp instead of elapsed time. // @author Ξένη Γήινος // @match *://www.reddit.com/* // @match *//reddit.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=mozilla.org // @grant none // ==/UserScript== const arr = document.evaluate( "//time", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); for (let i = 0; i < arr.snapshotLength; i++) { const node = arr.snapshotItem(i); node.innerText = node.attributes.datetime.textContent; } function callback(changes, observer) { for (let change of changes) { if( change.type == "childList") { for( let node of change.addedNodes) { if (node.tagName == "TIME") { node.innerText = node.attributes.datetime.textContent; } } } } } const body = document.getElementsByTagName("body")[0] const config = { childList: true, subtree: true }; const observer = new MutationObserver(callback); observer.observe(body, config); 

I am wondering if I can make it more concise. This question isn't really about the code, it is about best coding practices.

\$\endgroup\$
0

    1 Answer 1

    3
    \$\begingroup\$

    Kudos for writing a descriptive userscript doc comment, with a version (I don't bother doing this with my userscripts, but probably should).

    For small userscripts, particularly small ones written for yourself such as is the case here, code quality doesn't matter. The important thing is that the script works as intended and can be written easily.

    Using a GPT is a pragmatic way to write such a script, but I'm guessing you didn't use a GPT here to make it more educational, which is fine.

    So I'll review onward, artificially assuming that quality matters.


    Here's an initial rewrite (comment is kept the same), followed by analysis:

    for (const node of document.querySelectorAll("time")) { node.textContent = node.attributes.datetime.textContent; } const observer = new MutationObserver(changes => { for (const change of changes) { for (const node of change.addedNodes) { if (node.tagName === "TIME") { node.textContent = node.attributes.datetime.textContent; } } } }); observer.observe(document.body, { childList: true, subtree: true, }); 

    Avoid XPath and document.getElementsByTagName in favor of document.querySelector and document.querySelectorAll. XPath in particular is extremely cumbersome to work with. The virtue of XPath is that it's more powerful than CSS selectors, and can be used for rare cases when you need the power. But 99% of the time it's overkill for a simple tag selection like the one you're doing, which CSS selectors are perfectly suited for.

    If document.querySelector is used repeatedly, it's fine to alias it in small scripts where including jQuery would be excessive:

    const $ = s => document.querySelector(s); const $$ = s => document.querySelectorAll(s); 

    Variable names should be clear--callback and arr are too generic in this context. Only use names like this when writing a function that can accept literally any callback or array. Here, handleMutation and timeNodes would be more appropriate.

    It's OK to break out variables for conditions and arguments when doing so adds clarity. But more variables means more state to have to reason about. Here, you can inline single-use variables into the .observe() call parameter list. This saves having to make the mental connection between callback, config and body and the mutation observer that uses them. This is trivial here, but in more complex functions, reducing single-use state can lighten the cognitive load.

    Notice that I did keep the observer variable, which helps reduce the visual clutter of chaining .observe() from the new MutationObserver call. It's also common to want to disconnect observers, so the extra variable helps facilitate more operations on it in the future. New objects make sense to add to state, but parameters don't (usually).

    Here are some further nitpicks, with the caveat that none of these matter much in userscripts:

    • Use for .. of, .forEach, .map and .filter instead of C-style counter-based for loops.
    • Use const instead of let except when you need to reassign a variable.
    • Always use === for comparisons, never ==, avoiding type coercion footguns.
    • Prefer .textContent to .innerText.
    • If you're exclusively setting childList: true in the mutation observer config, there's no need to explicitly check if( change.type == "childList") {, which is the only possibility.
    • Use Prettier to format your code consistently.
    • Remove all unused variables, like the observer callback parameter.

    You mention:

    I am wondering if I can make it more concise

    As food for thought, I often write these sort of userscripts like this:

    // ... left as is ... // @run-at document-start // ==/UserScript== (function poll() { requestAnimationFrame(poll); document.querySelectorAll("time") .forEach(e => e.textContent = e.attributes.datetime.textContent); })(); 

    Advantages:

    • Extremely easy to write and read, just one line once you're familiar with the IIFE/RAF loop boilerplate.
    • Using @run-at document-start and a tight animation frame poll removes the "blink" effect that the mutation observer suffers from, where the wrong time format is shown for a split second before correction.

    The only downside is that the script has to do more work. But computers are fast and the RAF is throttled way back when the tab is inactive, so I doubt there'll be any noticeable performance/battery impact.

    Nowadays, GPTs can trivially generate a mutation observer, which demotivates the sloppier approach somewhat. Even so, I can still hammer out the above code into a console as fast as a GPT can, and the succinctness is aesthetically pleasing, so I still use it.

    Furthermore, the CPU work can be avoided or mitigated by disconnecting the poll loop once it has modified the elements you want to modify, stopping it after a few seconds, or using a less frequent setInterval. This gives you the best of all worlds, but is use-case specific.

    \$\endgroup\$
    1
    • \$\begingroup\$I didn't try to write a comment. I used TamperMonkey's "create a new script" button to add the script, there is a comment template. I have determined that @match parameter is necessary to tell where the script should run. And I didn't use GPT to write it, I was born behind the GFW therefore I don't have access to any GPT, even though I use VPNs I need USA phone number to get free access to any GPT, and I wouldn't trust any code GPT spew out to be performant anyways.\$\endgroup\$CommentedDec 29, 2024 at 8:45

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.