-
Notifications
You must be signed in to change notification settings - Fork 6
How to add a feature
As of v4, the modular nature of the userscript's code means adding new features has been somewhat standardized. Unless the new feature runs before the page has even loaded there will only be a few key building blocks that all features require:
- The appropriate page module (
src/modules/
) to organize based on the feature's scope - The feature class (inside the module) which contains all of the feature's logic
- The feature initialization inside
src/features.ts
The page modules store all the typescript files relating to specific scopes: usually, single pages (or types of pages) that the feature will run on. These are purely for organizational purposes, as the feature's scope is determined by the feature's Class, not its directory.
There are three special modules for atypical scopes (Global, Core, and Shared) plus the typical page scopes.
Nearly all features will use page scopes instead of the aforementioned scopes. Simply choose the page your feature will run on, and add a new Class.
If you wish to add a new page scope, additional changes are required:
- Add the new
/modules/page.ts
file - In
types.ts
, add the page toValidPage
andSettingGroup
types - In
check.ts
, find thepage()
function and add a new entry to thecases
Object. The property should be the string returned bywindow.location.pathname
and the value should be the name of the page (as added toValidPage
) - Add a new comment block in
feature.ts
- Add a reference path in
app.ts
This module is for features that run on every page. Special care should be taken to ensure that the element the feature requires is present on all pages; otherwise, include a race condition when starting it.
This module is for special features that meet either of the following criteria:
- They are important components of the userscript.
- They are intended to be consumed by other features.
Finally, they must meet the additional requirement that they will generate user-changeable settings on the Preferences page. If these requirements aren't all met consider using /modules/shared.ts
or utils.ts
instead.
Shared features are, essentially, utility functions that are too specific to be in the utils.ts
file. They mainly exist to promote Don't Repeat Yourself (DRY) code, and are often used when two or three features do something nearly identical.
Shared features must be public
and can be accessed by creating a new Shared()
in the feature that requires it.
The feature class, placed inside the appropriate Page Module, contains all the logic of your feature. It requires a few key components in order to properly implement the Feature
interface.
If you are using VS Code, you will have access to some custom snippets to generate a new feature. Just start typing mp
to trigger suggestions for mp-check
, mp-text
, and mp-drop
. Each one will fill out the required code for a feature of its type and will guide you through editing it.
class YourFeatureName implements Feature {
// Choose a setting type based on how the user will interact with the feature's settings
private _settings: [CheckboxSetting | DropdownSetting | TextboxSetting] = { /* Settings */ }
// An element that must exist in order for the feature to run
private _tar: string = '#';
// The code that runs when the feature is created on `features.ts`.
// It's typically the same on all features, but can be changed as long as `Util.startFeature(...)` runs.
// If `startFeature` returns true, init the feature
constructor() {
Util.startFeature(/* settings, elem, page */)
.then( t => { if (t) { this._init() } } );
}
// This is where your feature starts
private async _init() {
/* code */
}
// This must match the type selected for `this._settings`
get settings(): [CheckboxSetting | DropdownSetting | TextboxSetting] {
return this._settings;
}
}
The above code is a suitable template for most features. The only key addition missing is the value of this._settings
, which depends on the required setting type. These are the properties and value types they require.
scope: SettingGroup;
title: string;
type: 'checkbox';
desc: string;
scope: SettingGroup;
title: string;
type: 'dropdown';
desc: string;
tag: string; /* Short title/info appearing before the dropdown */
options: StringObject;
/* StringObject: {
default: "Default text",
settingID: "Setting text",
} */
scope: SettingGroup;
title: string;
type: 'textbox';
desc: string;
tag: string; /* Short title/info appearing before the dropdown */
placeholder: string; /* Example shown in empty textbox */
Additionally, the Util.startFeature(settings,elem,page?)
values are almost always identical, but the page the feature will run on needs to be specified.
-
settings:FeatureSettings
- alwaysthis._settings
-
elem:string
- alwaysthis._tar
-
page?:ValidPage[]
- an array of valid pages, ex.['shoutbox','home']
Features in a page module tend to always use the same singlepage
value. Certain features (ex. shoutbox) require more than one page.
In order for your feature to be run, it must be added to features.ts
. This page also determines the order in which settings will be generated on the Settings page. Settings will be grouped by type (not controlled by this page), and Features of one type that are called before other Features of the same type will appear first.
Simply add new YourFeatureName();
in the appropriate location inside the constructor.