Skip to content
WombatFromHell edited this page Sep 22, 2019 · 9 revisions

Writing new scripts

Create a new javascript file in the scripts directory and add to the manifest.json under "content_scripts": [...]. The script below assumes a common use-case that this is a script with an option and a sub-option (in Options panel).

const ExampleScript = {
    install(postElement, root_id, is_root_post) {
        // ...do things with the processPostEvent arguments here...
        getEnabledSuboption("example_suboption").then(response => {
            if (response) {
                // do things if the suboption is enabled...
            }
        });
    }
};

// load only if the Option 'example_script_option' is enabled...
addDeferredHandler(getEnabled("example_script_option"), (response) => {
    if (response) processPostEvent.addHandler(ExampleScript.install);
});

To write a script that only runs once when the page loads replace the processPostEvent.addHandler() call with: fullPostsCompletedEvent.addHandler(ExampleScript.install); and don't forget to target DOM nodes with document.querySelector() or document.querySelectorAll().

Special considerations when building scripts

When you would typically use fetch() or XMLHttpRequest() you -must- use Chrome Shack's wrapper fetchSafe() due to Mozilla's constraints on data sanitization when parsing or injecting external data sources into the DOM. Mozilla's AMO policies can be found here.

Extension Storage (Options and Sub-Options)

Chrome Shack currently uses the WebExtensions storage facility which allows for pushing/retrieving javascript primitives into sandboxed browser storage. There are methods in settings.js that provide shortcuts for managing these settings primitives, for example:

getEnabled - takes an optional key argument. Looks up key values in the enabled_scripts array and returns the key name if found. This function is asynchronous and so it returns a resolved Promise once the call completes.

setEnabled - takes a required key argument. Pushes a key into the enabled_scripts array (avoiding exact duplicates). This function is asynchronous and so it returns a resolved Promise once the call completes.

removeEnabled - takes a required key argument. Removes a key from the enabled_scripts array if found. This function is asynchronous and so it returns a resolved Promise once the call completes.

getEnabledSuboption - takes an optional key argument. Looks up key values in the enabled_scripts array and returns the key name if found. This function is asynchronous and so it returns a resolved Promise once the call completes.

setEnabledSuboption - takes a required key argument. Pushes a key into the enabled_scripts array (avoiding exact duplicates). This function is asynchronous and so it returns a resolved Promise once the call completes.

removeEnabledSuboption - takes a required key argument. Removes a key from the enabled_scripts array if found. This function is asynchronous and so it returns a resolved Promise once the call completes.

getSetting - takes an optional key argument. Looks up key values in the root of the storage facility and returns the result. This function is asynchronous and so it returns a resolved Promise once the call completes.

setSetting - takes required key and value arguments. Sets a key with the value provided, in the root of the storage facility, overwriting any other exactly named key, and resolves when the call completes. This function is asynchronous and so it returns a Promise.

removeSetting - takes a required key argument. Removes a key from the root of the storage facility and resolves when the call completes. This function is asynchronous and so it returns a Promise.

Defining an Option or Sub-Option in the Options panel

You can define an Option or Sub-Option inside options.html. An Option is defined like so:

<h2>
    <input type="checkbox" class="script_check" id="new_option" />
    <label for="new_option">Example Option</label>
</h2>
<p class="info">Description for Example Option goes here.</p>

A Sub-Option can be defined like so (extending the above Option example):

<div id="new_option_settings">
    <div class="suboption indent">
        <input type="checkbox" class="suboption" id="new_suboption" />
        <label for="new_suboption">Description for Example Sub-Option goes here.</label>
    </div>
</div>

You would then retrieve whether or not the Option is enabled with:

getEnabled("new_option").then(async (response) => {
    // response is truthy if the option is found/enabled or falsey otherwise
    let suboptionEnabled = await getEnabledSuboption("new_suboption");
    // subOptionEnabled is truthy if the option is found/enabled or falsey otherwise
});

Events

The currently exposed list of events and their arguments when raised are as follows (live DOM refs are used when possible):

processPostEvent - passes three arguments: postElement, root_id, and is_root_post. This event is raised once for each post that is displayed.

processPostBoxEvent - passes one argument: inlineReplyElement. This event is raised each time the post box is opened.

fullPostsCompletedEvent - passes no arguments, so make sure to target DOM elements with document.querySelector or other similar methods. This is raised each time the full Chatty page or a Chatty thread link is opened.

processRefreshIntentEvent - passes four arguments: lastPostId, lastRootId, is_root, and from_reply. This event is raised when a refresh button is clicked on a root thread or post.

processEmptyTagsLoadedEvent - passes four arguments: postElement, rootElement, postHasTags, and rootHasTags. This event is raised when a post is opened that contains empty nuLOL tag data.

processTagDataLoadedEvent - passes four arguments: postElement, rootElement, postHasTags, and rootHasTags. This event is raised when a post is opened that contains updated nuLOL tag data.

processPostRefreshEvent - passes four arguments: postElement, rootElement, postHasTags, and rootHasTags. This event is raised after a post has completely finished refreshing (including tag data, empty or not).

processReplyEvent - passes one argument: parentId. This event is raised when a reply has been submitted (NOTE: as of right now, nuLOL tag data is not updated automatically upon submitting a reply; Chrome Shack uses a workaround instead, see processPostRefreshEvent above).

Notes

If your javascript events aren't getting called, it may be because the shack is importing the nodes you are modifying which strips the javascript from them. This is why there is an event that fires when the post box is displayed: the post box that exists when the page is first loaded is copied into the page when the reply button is clicked.