Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/**
* CodeQuickLinks/code.js
* @file Adds modules containing quick links to personal and wiki code files
* @author Eizen <dev.fandom.com/wiki/User_talk:Eizen>
* @license CC-BY-SA 3.0
* @external "mediawiki.util"
* @external "I18n-js"
*/
/**
* <pre>
* <em>Table of contents</em> <em>Summary</em>
* - Pseudo-enums Storage for CodeQuickLinks utility consts
* - Utility methods Helper methods for validation, etc.
* - Assembly methods Builder functions assembling on-page HTML
* - Main methods Main functionality/app logic methods
* - Setup methods Initialization methods for loading, setup
* </pre>
*/
/* jshint -W030, undef: true, unused: true, eqnull: true, laxbreak: true */
;(function (module, window, $, mw) {
"use strict";
// Prevent double loads and respect prior double load check formatting
if (
!window || !$ || !mw || module.isLoaded || window.isCodeQuickLinksLoaded ||
$("body").hasClass("mainpage")
) {
return;
}
module.isLoaded = true;
/****************************************************************************/
/* Pseudo-enums */
/****************************************************************************/
// Namespace protected properties
Object.defineProperties(this, {
/**
* @description This pseudo-enum used to initialize the script stores data
* related to the external dependencies and core modules required by the
* script. It consists of two properties. The former, a constant
* <code>object</code> called "ARTICLES," originally contained key/value
* pairs wherein the key was the specific name of the <code>mw.hook</code>
* and the value was the script's location for use by
* <code>importArticles.articles</code>. However, this system was eventually
* replaced in favor of an array of <code>object</code>s containing
* properties for hook, <code>window.dev</code> alias, and script for more
* efficient, readable loading of dependencies. The latter array, a constant
* array named <code>MODULES</code>, contains a listing of the core modules
* required for use by <code>mw.loader.using</code>.
* <br />
* <br />
* The key for the <code>ARTICLES</code> array entries is as follows:
* <pre>
* - DEV/WINDOW: The name and location of the <code>window</code> property
* - HOOK: The name of the <code>mw.hook</code> event
* - ARTICLE: The location of the script or stylesheet on the Dev wiki
* - TYPE: Either "script" for JS scripts or "style" for CSS stylesheets
* - MODULE: Name of the temporary ResourceLoader module used to async load
* </pre>
*
* @readonly
* @enum {object}
*/
Dependencies: {
enumerable: true,
writable: false,
configurable: false,
value: Object.freeze({
ARTICLES: Object.freeze([
Object.freeze({
DEV: "i18n",
HOOK: "dev.i18n",
ARTICLE: "u:dev:MediaWiki:I18n-js/code.js",
TYPE: "script",
}),
Object.freeze({
DEV: null,
HOOK: null,
ARTICLE: "u:dev:MediaWiki:CodeQuickLinks.css",
TYPE: "style",
}),
]),
MODULES: Object.freeze([
"mediawiki.util",
]),
}),
},
/**
* @description This pseudo-enum of the <code>main</code> namespace object
* is used to store all CSS selectors in a single place in the event that
* one or more need to be changed. The formatting of the object literal key
* naming is type (id or class), location (placement, modal, content,
* preview), and either the name for ids or the type of element (div, span,
* etc.). This system was adopted, like many aspects of this script, from
* the author's MassEdit script.
*
* @readonly
* @enum {object}
*/
Selectors: {
enumerable: true,
writable: false,
configurable: false,
value: Object.freeze({
// ID selectors
ID_MODULE_WRAPPER: "cql-module",
ID_LISTING_USERFILES: "cql-listing-user",
ID_LISTING_SITEFILES: "cql-listing-site",
// General purpose id/class selectors
ID_WIKIA_RAIL: "WikiaRail",
CLASS_RAIL_MODULE: "rail-module",
// Module selectors
CLASS_MODULE_HEADER: "cql-module-header",
CLASS_MODULE_CONTENT: "cql-module-content",
// Column selectors
CLASS_LISTING_CONTAINER: "cql-listing",
CLASS_LISTING_HEADER: "cql-listing-header",
CLASS_LISTING_CONTENT: "cql-listing-content",
CLASS_LISTING_LIST: "cql-listing-list",
CLASS_LISTING_ENTRY: "cql-listing-entry",
CLASS_LISTING_LINK: "cql-listing-link",
}),
},
/**
* @description This pseudo-enum is used to store various data related to
* the construction of the default link objects displayed in the
* CodeQuickLinks rail module in most cases. It contains an array-populated
* <code>object</code>, <code>NAMES</code>, and a pair of arrays, namely
* <code>SUFFIXES</code> and <code>DIVISIONS</code>. <code>NAMES</code>
* holds the names of common MediaWiki/MyPage files commonly encountered on
* most wikis, while <code>SUFFIXES</code> holds the accepted file suffixes
* for CSS and JavaScript files. <code>DIVISIONS</code> houses the names of
* properties that are used in <code>this.buildDefaultFiles</code> to
* organize the assembled files object.
*
* @readonly
* @enum {object}
*/
Files: {
enumerable: true,
writable: false,
configurable: false,
value: Object.freeze({
NAMES: Object.freeze({
STANDARD: Object.freeze([
"Chat",
"Common",
"Wikia"
]),
CUSTOM: Object.freeze([
"Global",
"ImportJS",
"JSPages"
]),
}),
SUFFIXES: Object.freeze([
".css",
".js"
]),
DIVISIONS: Object.freeze([
"siteFiles",
"userFiles",
])
})
},
/**
* @description This pseudo-enum is used to store the <code>string</code>
* names of the various <code>WikipediaGlobal</code> (wg) variables required
* for use in the script. These are fetched within the body of the
* <code>this.preload</code> function via a <code>mw.config.get</code>
* invocation and stored in a namespace property named <code>globals</code>
* for subsequent usage. This approach replaces the deprecated approach
* previously used in the script of assuming the relevant wg variables exist
* as properties of the <code>window</code> object, an assumption that is
* discouraged in more recent versions of MediaWiki.
*
* @readonly
* @enum {object}
*/
Globals: {
enumerable: true,
writable: false,
configurable: false,
value: Object.freeze([
"wgArticlePath",
"wgFormattedNamespaces",
"wgLoadScript",
"wgVersion",
]),
},
/**
* @description The <code>Config</code> pseudo-enum is used primarily by
* <code>this.validateConfig</code> to ensure that the user's input config
* (if applicable) is well-formed and properly defined prior to its usage
* by the script. If the user has chosen not to include certain properties
* in the config object, the default values established in this enum are
* applied instead as default values. The enum contains a pair of data
* <code>object</code> establishing both the formal name of the property as
* it exists in the config object and its associated default value.
*
* @readonly
* @enum {object}
*/
Config: {
enumerable: true,
writable: false,
configurable: false,
value: Object.freeze({
REPLACE: Object.freeze({
NAME: "replaceAllDefaultLinks",
DEFAULT: false,
}),
LINKS: Object.freeze({
NAME: "linkSet",
DEFAULT: {},
}),
}),
},
/**
* @description This catchall pseudo-enum of the <code>init</code< namespace
* object is used to house assorted values of various data types that don't
* fit well into other pseudo-enums. It contains the I18n-js language cache
* version <code>number</code>, a <code>string</code> constant denoting the
* name of the script, and another <code>string</code> for the name of the
* <code>mw.hook</code> event.
*
* @readonly
* @enum {string|boolean|number}
*/
Utility: {
enumerable: true,
writable: false,
configurable: false,
value: Object.freeze({
SCRIPT: "CodeQuickLinks",
HOOK_NAME: "dev.cql",
CACHE_VERSION: 1,
}),
},
});
/****************************************************************************/
/* Utility methods */
/****************************************************************************/
/**
* @description As the name implies, this helper function capitalizes the
* first character of the input string and returns the altered, adjusted
* string. it is generally used in the dynamic construction of i18n messages
* in various assembly methods.
*
* @param {string} paramTarget - <code>string</code> to be capitalized
* @returns {string} - Capitalized <code>string</code>
*/
this.capitalize = function (paramTarget) {
return paramTarget.charAt(0).toUpperCase() + paramTarget.slice(1);
};
/**
* @description This helper method is used to check whether the target object
* is one of several types of <code>object</code>. It is most often used to
* determine if the target is an <code>array</code> or a straight-up
* <code>object</code>.
*
* @param {string} paramType - Either "Object" or "Array"
* @param {string} paramTarget - Target to check
* @returns {boolean} - Flag denoting the nature of the target
*/
this.isThisAn = function (paramType, paramTarget) {
return Object.prototype.toString.call(paramTarget) === "[object " +
this.capitalize.call(this, paramType.toLowerCase()) + "]";
};
/**
* @description This helper function is used to automatically generate an
* appropriate contrived ResourceLoader module name for use in loading scripts
* via <code>mw.loader.implement</code> on UCP wikis. The use of this function
* replaces the previous approach that saw the inclusion of hardcoded module
* names as properties of the relevant dependency <code>object</code>s stored
* in <code>this.Dependencies.ARTICLES</code>. When passed an argument
* formatted as <code>u:dev:MediaWiki:Test/code.js</code>, the function will
* extract the subdomain name ("dev") and join it to the name of the script
* ("Test") with the article type ("script") as <code>script.dev.Test</code>.
*
* @param {string} paramType - Either "script" or "style"
* @param {string} paramPage - Article formatted as "u:dev:MediaWiki:Test.js"
* @returns {string} - A ResourceLoader module name formatted as "dev.Test"
*/
this.generateModuleName = function (paramType, paramPage) {
return $.merge([paramType], paramPage.split(/[\/.]+/)[0].split(":").filter(
function (paramItem) {
return !paramItem.match(/^u$|^mediawiki$/gi);
}
)).join(".");
};
/**
* @description This helper function, based on MassEdit's assorted validation
* methods, is used to ensure that the user's inputted config has properties
* of the proper data type, i.e. <code>boolean</code> for the
* <code>replaceAllDefaultLinks</code> flag and <code>object</code> for
* <code>linkSet</code>. If no property exists or if the wrong data type is
* detected, the default value specified in <code>this.Config</code> is
* applied instead.
*
* @param {object} paramConfig - User config <code>object</code> to validate
* @returns {object} config - Frozen well-formed config <code>object</code>
*/
this.validateConfig = function (paramConfig) {
// Declarations
var element, entry, fields, config;
// Definitions
config = {};
fields = this.Config;
// Set to default if user input doesn't exist or if wrong data type
for (element in fields) {
if (!fields.hasOwnProperty(element)) {
continue;
}
entry = fields[element];
// Define with default if no property or if input is of wrong data type
config[entry.NAME] = (!paramConfig.hasOwnProperty(entry.NAME) ||
typeof paramConfig[entry.NAME] !== typeof entry.DEFAULT)
? entry.DEFAULT
: paramConfig[entry.NAME];
}
return Object.freeze(config);
};
/****************************************************************************/
/* Assembly methods */
/****************************************************************************/
/**
* @description This function is a simple recursive <code>string</code> HTML
* generator that makes use of <code>mw.html</code>'s assembly methods to
* construct wellformed HTML strings from a set of nested input arrays. This
* allows for a more readable means of producing proper HTML than the default
* <code>jQuery</code> approach or the hardcoded HTML <code>string</code>
* approach employed in earlier iterations of this script. Through the use of
* nested arrays, this function permits the laying out of parent/child DOM
* nodes in array form in a fashion similar to actual HTML, enhancing both
* readability and usability.
* <br />
* <br />
* Furthermore, as the <code>assembleElement</code> function returns a
* <code>string</code>, nested invocations of the method within parameter
* arrays is permitted, as evidenced in certain, more specialized assembly
* methods elsewhere in the script.
* <br />
* <br />
* An example of wellformed input is shown below:
* <br />
* <pre>
* this.assembleElement(
* ["div", {id: "foo-id", class: "foo-class"},
* ["button", {id: "bar-id", class: "bar-class"},
* "Button text",
* ],
* ["li", {class: "overflow"},
* ["a", {href: "#"},
* "Link text",
* ],
* ],
* ],
* );
* </pre>
*
* @param {Array<string>} paramArray - Wellformed array representing DOM nodes
* @returns {string} - Assembled <code>string</code> HTML
*/
this.assembleElement = function (paramArray) {
// Declarations
var type, attributes, counter, content;
// Make sure input argument is a well-formatted array
if (!this.isThisAn("Array", paramArray)) {
return this.assembleElement.call(this,
Array.prototype.slice.call(arguments));
}
// Definitions
counter = 0;
content = "";
type = paramArray[counter++];
// mw.html.element requires an object for the second param
attributes = (this.isThisAn("Object", paramArray[counter]))
? paramArray[counter++]
: {};
while (counter < paramArray.length) {
// Check if recursive assembly is required for another inner DOM element
content += (this.isThisAn("Array", paramArray[counter]))
? this.assembleElement(paramArray[counter++])
: paramArray[counter++];
}
return mw.html.element(type, attributes, new mw.html.Raw(content));
};
/**
* @description This self-contained assembly function is used to assemble the
* rail module from the links <code>object</code> passed as a parameter. This
* object should possess a pair of arrays, namely <code>userFiles</code>
* and <code>siteFiles</code>, that contain objects with properties
* <code>name</code> and <code>href</code> representing each link to be added
* to the module in one of the two columns. Originally, this method made use
* of three helper functions for the construction of links, columns, and the
* rail module itself, though these were eventually merged into a single
* method for readability's sake. This approach, using MassEdit's recursive
* <code>assembleElement</code> method, replaces the previous approach, which
* saw the script fix affix the rail module to the rail before populating its
* contents with columns and links.
*
* @param {object} paramLinks - <code>object</code> containing link href/names
* @returns {string} - Assembled <code>string</code> HTML
*/
this.buildRailModule = function (paramLinks) {
return this.assembleElement(
["section", {
"class": this.Selectors.CLASS_RAIL_MODULE,
"id": this.Selectors.ID_MODULE_WRAPPER,
},
["h2", {"class": this.Selectors.CLASS_MODULE_HEADER,},
this.i18n.msg("title").plain()
],
["div", {"class": this.Selectors.CLASS_MODULE_CONTENT,},
Object.keys(paramLinks).map(function (paramColumn) {
return this.assembleElement(
["div", {
"class": this.Selectors.CLASS_LISTING_CONTAINER,
"id": this.Selectors["ID_LISTING_" + paramColumn.toUpperCase()],
},
["h4", {"class": this.Selectors.CLASS_LISTING_HEADER,},
this.i18n.msg(
(paramColumn === "userFiles") ? "personal" : "local"
).plain()
],
["div", {"class": this.Selectors.CLASS_LISTING_CONTENT,},
["ul", {"class": this.Selectors.CLASS_LISTING_LIST,},
paramLinks[paramColumn].map(function (paramLink) {
return this.assembleElement(
["li", {"class": this.Selectors.CLASS_LISTING_ENTRY,},
["a", {
"class": this.Selectors.CLASS_LISTING_LINK,
"href": paramLink.href,
"title": paramLink.name,
},
paramLink.name,
],
]
);
}.bind(this)).join(""),
],
],
]
);
}.bind(this)).join(""),
],
]
);
};
/****************************************************************************/
/* Main methods */
/****************************************************************************/
/**
* @description A method more or less just copied and reformatted from the
* previous incarnation of CodeQuickLinks, this function serves as the primary
* means by which default link <code>object</code>s possessing the properties
* <code>name</code> and <code>href</code> are assembled from the
* <code>string</code> names listed in <code>this.Files.NAMES</code>. The
* method is not very efficient or optimized and will require a refactor and
* simplification in the future once a better way is developed by the author.
* <br />
* <br />
* The method takes the names specified in <code>this.Files.NAMES</code> and
* constructs link objects that are subsequently converted to actual links for
* display in the rail module by <code>this.buildRailModule</code>. For non-
* rule-breaking links, personal "Special:MyPage" and MediaWiki namespace
* versions of each link are constructed for both CSS and JS pages, while
* rulebreakers like <code>MediaWiki:ImportJS</code> that lack personal
* equivalents or suffixes are handled separately. All entries are sorted
* prior to their return from the method.
*
* @returns {object} assembledFiles - Built from <code>this.Files.NAMES</code>
*/
this.buildDefaultFiles = function () {
// Declarations
var assembledFiles, fileNames, prefix, suffixes, prefixes, communityServer,
divisions;
// Definitions
assembledFiles = {};
communityServer = "https://community.fandom.com";
// Define prefixes using wgFormattedNamespaces
$.each(prefixes = {mw: 8, sp: -1}, function (paramName, paramId) {
prefixes[paramName] = this.globals.wgFormattedNamespaces[paramId] + ":";
}.bind(this));
prefixes.my = prefixes.sp + "MyPage/";
// Aliases
fileNames = this.Files.NAMES;
suffixes = this.Files.SUFFIXES;
divisions = this.Files.DIVISIONS;
// Populate files object with container arrays
divisions.forEach(function (paramColumn) {
assembledFiles[paramColumn] = [];
});
// Assemble non-rulebreaking file names
fileNames.STANDARD.forEach(function (paramFile) {
divisions.forEach(function (paramColumn) {
prefix = prefixes[(paramColumn === "siteFiles") ? "mw" : "my"];
suffixes.forEach(function (paramSuffix) {
assembledFiles[paramColumn].push({
name: paramFile + paramSuffix,
href: mw.util.getUrl(prefix + paramFile.toLowerCase() + paramSuffix)
});
});
});
}.bind(this));
// Handle rule-breakers
fileNames.CUSTOM.forEach(function (paramFile) {
if (paramFile === "Global") {
suffixes.forEach(function (paramSuffix) {
assembledFiles.userFiles.push({
name: paramFile + paramSuffix,
href: communityServer + this.globals.wgArticlePath.replace("$1",
prefixes.my + paramFile.toLowerCase() + paramSuffix)
});
}.bind(this));
} else {
prefix = prefixes[(paramFile === "ImportJS") ? "mw" : "sp"];
assembledFiles.siteFiles.push({
name: paramFile,
href: mw.util.getUrl(prefix + paramFile)
});
}
}.bind(this));
// Sort entries alphabetically
$.each(assembledFiles, function (paramColumn, paramFiles) {
paramFiles.sort(function (paramA, paramB) {
return paramA.name.localeCompare(paramB.name);
});
});
return assembledFiles;
};
/**
* @description The <code>main</code> method is called by
* <code>this.init</code> once the initialization process is complete and the
* <code>mw.hook</code> event triggered. The main method is used to assemble
* all the link objects denoting list elements existing in the rail module's
* columns, using the user's input if it exists. Once the link config objects
* have been assembled, the method assembles a rail module via
* <code>this.buildRailModule</code> and prepends it to the top of the Wikia
* rail.
*
* @returns {void}
*/
this.main = function () {
// Declarations
var userLinks, assembledFiles, railModule;
// Definitions/aliases
userLinks = this.config.linkSet;
if (this.config.replaceAllDefaultLinks && userLinks) {
assembledFiles = userLinks;
} else {
// Assemble default page names
assembledFiles = this.buildDefaultFiles();
if (Object.keys(userLinks).length) {
$.each(userLinks, function (paramColumn, paramLinks) {
$.merge(assembledFiles[paramColumn], paramLinks);
});
}
}
railModule = this.buildRailModule(assembledFiles);
$("#" + this.Selectors.ID_WIKIA_RAIL).prepend(railModule);
};
/****************************************************************************/
/* Setup methods */
/****************************************************************************/
/**
* @description The <code>this.init</code> initialization method is called
* once all external dependencies and ResourceLoader modules have been loaded.
* The method is responsible for validating the user's config (if applicable),
* setting the i18n language for the script, and defining a new protected
* <code>module</code> property <code>exports</code> containing exposed public
* methods for post-load invocation. At present, the only method publicly
* accessible is <code>observeScript</code>, which allows the user to view the
* layout of the script and the namespace object's various properties via a
* <code>console.dir</code> invocation. The method returns after firing the
* CodeQuickLinks <code>mw.hook</code> event and attaching
* <code>this.main</code> as a listener callback.
*
* @param {undefined|function} paramRequire - Either function or undefined
* @param {object} paramLang - I18n-js data content
* @returns {void}
*/
this.init = function (paramRequire, paramLang) {
// Validate user-input config elements
this.config = this.validateConfig(window.customCodeQuickLinks || {});
// Add i18n data as local property
(this.i18n = paramLang).useContentLang();
// Expose public methods for external debugging
Object.defineProperty(module, "exports", {
enumerable: true,
writable: false,
configurable: false,
value: Object.freeze({
observeScript: window.console.dir.bind(this, this),
})
});
// Dispatch hook with window.dev.cql once initialization is complete
mw.hook(this.Utility.HOOK_NAME).fire(module).add(this.main.bind(this));
};
/**
* @description Originally a pair of functions called <code>init.load</code>
* and <code>init.preload</code>, this function is used to load all required
* external dependencies from Dev and attach <code>mw.hook</code> listeners.
* Once all scripts have been loaded and their events fired, the I18n-js
* method <code>loadMessages</code> is invoked, the <code>$.Deferred</code>
* promise resolved, and the resultant i18n data passed for subsequent usage
* in <code>init.main</code>.
* <br />
* <br />
* As an improvement to the previous manner of loading scripts, this function
* first checks to see if the relevant <code>window.dev</code> property of
* each script already exists, thus signaling that the script has already been
* loaded elsewhere. In such cases, this function will skip that import and
* move on to the next rather than blindly reimport the script again as it
* did in the previous version.
* <br />
* <br />
* As of the 1st of July update, an extendable framework for the loading of
* ResourceLoader modules and Dev external dependencies (scripts and
* stylesheets alike) on both UCP wikis and legacy 1.19 wikis has been put
* into place, pending UCPification of the aforementioned Dev scripts or the
* importation of legacy features to the UCP codebase. To handle the lack of
* async callbacks in <code>mw.loader.load</code>, this framework invokes
* <code>mw.loader.implement</code> to create temporary, local RL modules that
* can then be asynchronously loaded via <code>mw.loader.using</code> and
* handled by a dedicated callback.
*
* @param {object} paramDeferred - <code>$.Deferred</code> instance
* @returns {void}
*/
this.load = function (paramDeferred) {
// Declarations
var debug, articles, counter, numArticles, $loadNext, current, isLoaded,
article, server, params, resource, moduleName;
// Definitions
debug = false;
counter = 0;
articles = this.Dependencies.ARTICLES;
numArticles = articles.length;
$loadNext = new $.Deferred();
/**
* @description The passed <code>$.Deferred</code> argument instance called
* <code>paramDeferred</code> is variously notified during the loading of
* dependencies by the <code>$loadNext</code> promise whenever a dependency
* has been successfully imported by <code>window.importArticles</code> or
* <code>mw.loader.using</code>. The <code>progress</code> handler checks if
* all dependencies have been successfully loaded for use before loading the
* latest version of cached <code>i18n</code> messages and resolving itself
* to pass program execution on to <code>init.main</code>.
*/
paramDeferred.notify().progress(function () {
if (counter === numArticles) {
// Resolve helper $.Deferred instance
$loadNext.resolve();
if (debug) {
window.console.log("$loadNext", $loadNext.state());
}
// Load latest version of cached i18n messages
window.dev.i18n.loadMessages(this.Utility.SCRIPT, {
cacheVersion: this.Utility.CACHE_VERSION,
}).then(paramDeferred.resolve).fail(paramDeferred.reject);
} else {
if (debug) {
window.console.log((counter + 1) + "/" + numArticles);
}
// Load next
$loadNext.notify(counter++);
}
}.bind(this));
/**
* @description The <code>$loadNext</code> helper <code>$.Deferred</code>
* instance is used to load each dependency using methods appropriate to the
* version of MediaWiki detected on the wiki. While the standard
* <code>importArticle</code> method is used for legacy 1.19 wikis, a local
* ResourceLoader module is defined via <code>mw.loader.implement</code> and
* loaded via <code>mw.loader.using</code> to sidestep the fact that the
* <code>mw.loader.load</code> method traditionally used to load dependencies
* has no callback or promise. Once all imports are loaded, the handler
* applies a callback to any extant <code>mw.hook</code> events and notifies
* the main <code>paramDeferred.progress</code> handler to check if all
* dependencies have been loaded.
*/
$loadNext.progress(function (paramCounter) {
// Selected dependency to load next
current = articles[paramCounter];
// If window has property related to dependency indicating load status
isLoaded =
(current.DEV && window.dev.hasOwnProperty(current.DEV)) ||
(current.WINDOW && window.hasOwnProperty(current.WINDOW));
// Add hook if loaded; dependencies w/o hooks must always be loaded
if (isLoaded && current.HOOK) {
if (debug) {
window.console.log("isLoaded", current.ARTICLE);
}
return mw.hook(current.HOOK).add(paramDeferred.notify);
}
// Use standard importArticle approach if legacy wiki
if (!this.flags.isUCP) {
article = window.importArticle({
type: current.TYPE,
article: current.ARTICLE,
});
// Log for local debugging (problem spot)
if (debug) {
window.console.log("importArticle", article);
}
// Styles won't have hooks; notify status with load event if styles
return (current.HOOK)
? mw.hook(current.HOOK).add(paramDeferred.notify)
: $(article).on("load", paramDeferred.notify);
}
// Build url with REST params
server = "https://dev.fandom.com";
params = "?" + $.param({
mode: "articles",
only: current.TYPE + "s",
articles: current.ARTICLE,
});
resource = server + this.globals.wgLoadScript + params;
moduleName = this.generateModuleName(current.TYPE, current.ARTICLE);
// Ensure wellformed module name
if (debug) {
window.console.log(moduleName);
}
// Define temp local modules to sidestep mw.loader.load's lack of callback
try {
mw.loader.implement.apply(null, $.merge([moduleName],
(current.TYPE === "script")
? [[resource]]
: [null, {"url": {"all": [resource]}}]
));
} catch (paramError) {
if (debug) {
window.console.error(paramError);
}
}
// Load script/stylesheet once temporary module has been defined
mw.loader.using(moduleName)
.then((current.HOOK)
? mw.hook(current.HOOK).add(paramDeferred.notify)
: paramDeferred.notify)
.fail(paramDeferred.reject);
}.bind(this));
};
/**
* @description This particular loading function is used simply to calculate
* and inject some pre-load <code>init</code> object properties prior to the
* loading of required external dependencies or ResourceLoader modules. As the
* loading process depends on this function's set informational properies, the
* function is called prior to the initial <code>init.load</code> invocation
* at the start of the script's execution and returns a reference to the
* <code>init</code> object (presumably) for use in subsequent method chaining
* purposes.
*
* @returns {object} init - Reference to <code>init</code> object for chaining
*/
this.preload = function () {
// Fetch, define, and cache globals for use in init and MassEdit instance
this.globals = Object.freeze(mw.config.get(this.Globals));
// Object for informational booleans (extended in MassEdit init method)
this.flags = {
isUCP: window.parseFloat(this.globals.wgVersion) > 1.19,
};
// Return reference for method chaining purposes
return this;
};
$.when(
mw.loader.using((this.preload.call(this)).Dependencies.MODULES),
new $.Deferred(this.load.bind(this)).promise())
.then(this.init.bind(this))
.fail(window.console.error);
}.call(Object.create(null), (this.dev = this.dev || {}).cql =
this.dev.cql || {}, this, this.jQuery, this.mediaWiki));