Recently I had been thinking about using localstorage to elaborate a settings menu that could be used to toggle various scripts that persist browser sessions and would make the process of script management less fiddly and prone to collisions for end-users.

Then @Perry made a subscriptions panel using precisely this sort of logic (a modal leveraging localstorage), so I had a turnkey logic ready to go, and gratefully made use of his modal implementation. Thank you!

kbin-megamod exposes a modal that covers the screen and could be used to fit a large amount of checkboxes (toggles) for different scripts, or any fancy menu-like styling. The idea is that by integrating scripts into a suite, users do not have to manage numerous disparate scripts, updates, and the potential for collisions between them, because they would be tested and integrated a priori into the suite and served from a unified endpoint.

An icon at the top right of the navbar to trigger the modal

The “Megamod Settings” panel exposes a series of toggles that trigger different scripts. This can cover the screen and could fit a large amount of scripts. Tabs or columns could be added as well.

The masthead of the script defines a series of includes (@require) that call a list of scripts sourced from the /mods directory of the same repository. For maintanability purposes, each “feature” integrated into the megamod should be an atomic script sourced here. The calling script itself only manages the menu and storage of settings. For security reasons, scripts should be peer reviewed and audited by contributors and served from a single location, reducing the likelihood of a remote script being changed to execute malicious code, or simply to introduce breaking changes. When the megamod is loaded, @require scripts are fetched and saved in local cache. (Note: when testing, if you wish to update the local cache, bump the version number of the megamod script to force a redownload.)

The business logic follows:

Three arrays and one object are declared and initialized. The first array contains a list of human-readable “labels” for each option in the menu:

/*human readable mod label*/
     const mmLabels = [
            "Add mail icon",
            "Add subs to navbar",
            "Label OP",
            "Profile dropdown",
        ];

The second array contains a human-readable list of descriptions for what each feature does.

    /*human readable mod desc*/
    const mmDescs = [
            "Add mail link to usernames if on kbin.social",
            "Add magazine subscriptions to navbar",
            "Add 'OP' label to thread author",
            "Convert profile page links to dropdown",
            ];

The third array contains the identifiers for the functions, and is used when populating DOM elements with a unique classname. These must be unique.

    /*function identifier, can be same as function name*/
    const mmFuncs = [
        "addMail",
        "initMags",
        "labelOp",
        "dropdownEntry"
        ];

Finally, an object maps these classnames to the actual function entrypoint in the cached script. This is merely used as a workaround for interpolating the classname variables returned from the event listener in order to call the cached function, as explained in the section after this. The entry point name need not be the same as the function identifier (classname) above, but must be unique to prevent collisions. For simplicity, I used entry points with the same name, but they could be anything, such as myEntryPoint.

The entry point logic in the target script must parse an incoming boolean and either enable or disable the logic accordingly. In the case of disabling, this means the script must tear down or hide any DOM elements previously created.

     /*object used for interpolation of function names*/
    /*key MUST be same as mmFuncs array*/
    /*value MUST be literal entry point in the target script, will be passed boolean*/
    /*literal func name need not be identical to key*/
      const funcObj = {
       addMail: addMail,
       initMags: initMags,
       labelOp: labelOp,
       dropdownEntry: dropdownEntry
       };

The toggle logic. The method argument is the name from the mmFuncs array that is returned when the event listener triggers on a checkbox. This is then used as a key in the funcObj and interpolated to the value (the actual script entry point) and a boolean argument is passed.

function applySettings(method) {
        const settings = getSettings();
        if (settings[method] == true) {
            funcObj[method](true);
        } else {
             funcObj[method](false);
        }
    }

The toggle logic in the destination script’s entry point may look something like this:

function initMags(toggle){
    if (toggle === false) {
        $('.subs-nav').remove();
    } else {
        createMags();
    }
}

I believe this could be a way forward for curating a collection of scripts, or to use the language from the repository, a “megamod” collection of “mods.” It should give end-users a more straightforward means of configuring their experience without having to collect and install disparate scripts, which may be difficult for some. (Of course, power users can continue to pick and choose from standalone scripts if they wish.)

Integrating a new feature is as simple as adding four lines to megamod.user.js

  • The feature label in the first array (mmLabels)
  • The feature description in the second array (mmDescs)
  • The classname in the third array (mmFuncs)
  • The entry point mapping in the final object (funcObj)

That means you only need to write two human-readable sentence fragments and two names, add the script to the /mods directory, and the script will be integrated in the modal. All of the business logic of the called feature is then handled by the cached script. Ensure that the script handles setup and teardown correctly when it is toggled and that the entry points are defined correctly. Also, don’t forget to add jQuery to the @requires if necessary and any @grants or other remote content to the masthead of the megamod.user.js so that they are passed through.

Going forward, we should look into externalizing this into a manifest and preparing the arrays/objects programmatically, rather than inline.

I would like to ask for your contributions and suggestions. Below I am including callouts to users I saw had posted scripts here recently (from the first few pages). If you wish to integrate your script, please make a PR. Sorry if I missed anyone. The main area that will require some testing will be ensuring that the scripts “play nicely” with each other and that there is no reduplication of efforts or collisions. I believe most scripts are being MIT licensed, so they could be integrated anyway with proper attribution, but obviously having the script authors integrate their own code would be preferable.

The main thing you would have to refactor is add some teardown logic to your script so that when toggled to the off state, it reverts all of its changes and restores the elements to their prior state.

@raltsm4k, @minnieo, @blobcat, @artillect, @0rito, @SirPsychoMantis, @CodingAndCoffee

Postscript: as for the megamod script itself, I have not thoroughly cleaned everything yet. I have not fully integrated the “add dropdown” functionality from my other script, but merely added it as the last checkbox for testing purposes, so the cosmetic teardown is not working correctly when disabling it. Otherwise, the other toggles are working correctly and act as a minimum viable prototype. You can test enabling/disabling these features and should see the effects live.

Lastly, in case you are wondering, “won’t these features be added to kbin upstream eventually?” If you are already a script author, I think you know the answer to this, but to clarify for other interested parties: there will always be a need for third-party modifications on any site, no matter how mature, as users may have wildly different needs. Changing things on the client-side is non-invasive and can be prototyped easily, as well as takes the strain on upstream developers off of UI concerns and lets them focus on more important issues. Finally, client-side scripts could also serve as a reference for designs used in production code, so these efforts are not wasted.