Skip to content

Commit

Permalink
split twinkle.js into two files
Browse files Browse the repository at this point in the history
One file for functions, one file for everything else.

For unit test reasons. The non-function code will get in the way if we want to write unit tests for the functions.

Refactoring. No-op.
  • Loading branch information
NovemLinguae committed Nov 25, 2024
1 parent 93d287e commit cb88962
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 260 deletions.
2 changes: 1 addition & 1 deletion gadget.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
* Twinkle [ResourceLoader |dependencies=ext.gadget.morebits, ext.gadget.select2, mediawiki.api, mediawiki.language |rights=autoconfirmed |type=general |peers=Twinkle-pagestyles |requiresES6] |Twinkle.js |Twinkle.css |twinklearv.js |twinklewarn.js |twinkleblock.js |twinklewelcome.js |twinkleshared.js |twinkletalkback.js |twinklespeedy.js |twinkleprod.js |twinklexfd.js |twinkleimage.js |twinkleprotect.js |twinkletag.js |twinklediff.js |twinkleunlink.js |twinklerollback.js |twinkledeprod.js |twinklebatchdelete.js |twinklebatchprotect.js |twinklebatchundelete.js |twinkleconfig.js
* Twinkle [ResourceLoader |dependencies=ext.gadget.morebits, ext.gadget.select2, mediawiki.api, mediawiki.language |rights=autoconfirmed |type=general |peers=Twinkle-pagestyles |requiresES6] |Twinkle.js |twinkleutil.js |Twinkle.css |twinklearv.js |twinklewarn.js |twinkleblock.js |twinklewelcome.js |twinkleshared.js |twinkletalkback.js |twinklespeedy.js |twinkleprod.js |twinklexfd.js |twinkleimage.js |twinkleprotect.js |twinkletag.js |twinklediff.js |twinkleunlink.js |twinklerollback.js |twinkledeprod.js |twinklebatchdelete.js |twinklebatchprotect.js |twinklebatchundelete.js |twinkleconfig.js
* morebits [ResourceLoader |dependencies=mediawiki.user, mediawiki.util, mediawiki.Title, jquery.ui |hidden |requiresES6] |morebits.js |morebits.css
* Twinkle-pagestyles [hidden |skins=vector, vector-2022] |Twinkle-pagestyles.css
* select2 [ResourceLoader |hidden] |select2.min.js |select2.min.css
2 changes: 1 addition & 1 deletion scripts/dev-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async function readFiles(filePaths) {
}
const server = http.createServer(async (request, response) => {
const moduleFiles = (await fs.readdir('./modules')).filter(f => f.endsWith('.js'));
const jsFiles = ['morebits.js', 'twinkle.js'].concat(moduleFiles.map(f => 'modules/' + f));
const jsFiles = ['morebits.js', 'twinkle.js', 'twinkleutil.js'].concat(moduleFiles.map(f => 'modules/' + f));
const cssFiles = ['morebits.css', 'twinkle.css'];

let jsCode = `mw.loader.using(['jquery.ui', 'ext.gadget.select2']).then(function () {\n`;
Expand Down
258 changes: 0 additions & 258 deletions twinkle.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@ const Twinkle = {};
window.Twinkle = Twinkle; // allow global access

Twinkle.initCallbacks = [];
/**
* Adds a callback to execute when Twinkle has loaded.
* @param {function} func
* @param {string} [name] - name of module used to check if is disabled.
* If name is not given, module is loaded unconditionally.
*/
Twinkle.addInitCallback = function twinkleAddInitCallback(func, name) {
Twinkle.initCallbacks.push({ func: func, name: name });
};

Twinkle.defaultConfig = {};
/**
Expand Down Expand Up @@ -175,139 +166,6 @@ Twinkle.defaultConfig = {
markSharedIPAsMinor: true
};

Twinkle.getPref = function twinkleGetPref(name) {
if (typeof Twinkle.prefs === 'object' && Twinkle.prefs[name] !== undefined) {
return Twinkle.prefs[name];
}

// Old preferences format, used before twinkleoptions.js was a thing
if (typeof window.TwinkleConfig === 'object' && window.TwinkleConfig[name] !== undefined) {
return window.TwinkleConfig[name];
}
if (typeof window.FriendlyConfig === 'object' && window.FriendlyConfig[name] !== undefined) {
return window.FriendlyConfig[name];
}

// Backwards compatibility code because we renamed confirmOnFluff to confirmOnRollback, and confirmOnMobileFluff to confirmOnMobileRollback
if (name === 'confirmOnRollback' && typeof Twinkle.prefs === 'object' && Twinkle.prefs.confirmOnFluff !== undefined) {
return Twinkle.prefs.confirmOnFluff;
} else if (name === 'confirmOnMobileRollback' && typeof Twinkle.prefs === 'object' && Twinkle.prefs.confirmOnMobileFluff !== undefined) {
return Twinkle.prefs.confirmOnMobileFluff;
}

return Twinkle.defaultConfig[name];
};


/**
* Adds a portlet menu to one of the navigation areas on the page.
*
* @return {String} portletId
*/
Twinkle.addPortlet = function() {
/** @type {String} id of the target navigation area (skin dependent, on vector either of "#left-navigation", "#right-navigation", or "#mw-panel") */
let navigation;

/** @type {String} id of the portlet menu to create, preferably start with "p-". */
let id;

/** @type {String} name of the portlet menu to create. Visibility depends on the class used. */
let text;

/** @type {Node} the id of the node before which the new item should be added, should be another item in the same list, or undefined to place it at the end. */
let nextnodeid;

switch (mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
navigation = '#right-navigation';
id = 'p-twinkle';
text = 'TW';
// In order to get mw.util.addPortlet to generate a dropdown menu in vector and vector-2022, the nextnodeid must be p-cactions. Any other nextnodeid will generate a non-dropdown portlet instead.
nextnodeid = 'p-cactions';
break;
case 'timeless':
navigation = '#page-tools .sidebar-inner';
id = 'p-twinkle';
text = 'Twinkle';
nextnodeid = 'p-userpagetools';
break;
default:
navigation = null;
id = 'p-cactions';
}

if (navigation === null) {
return id;
}

// make sure navigation is a valid CSS selector
const root = document.querySelector(navigation);
if (!root) {
return id;
}

// if we already created the portlet, return early. we don't want to create it again.
const item = document.getElementById(id);
if (item) {
return id;
}

mw.util.addPortlet(id, text, '#' + nextnodeid);

// The Twinkle dropdown menu has been added to the left of p-cactions, since that is the only spot that will create a dropdown menu. But we want it on the right. Move it to the right.
if (mw.config.get('skin') === 'vector') {
$('#p-twinkle').insertAfter('#p-cactions');
} else if (mw.config.get('skin') === 'vector-2022') {
const $landmark = $('#right-navigation > .vector-page-tools-landmark');
$('#p-twinkle-dropdown').insertAfter($landmark);

// .vector-page-tools-landmark is unstable and could change. If so, log it to console, to hopefully get someone's attention.
if (!$landmark) {
mw.log.warn('Unexpected change in DOM');
}
}

return id;
};

/**
* Builds a portlet menu if it doesn't exist yet, and adds a portlet link. This function runs at the top of every Twinkle module, ensuring that the first module to be loaded adds the portlet, and that every module can add a link to itself to the portlet.
*
* @param task Either a URL for the portlet link or a function to execute.
*/
Twinkle.addPortletLink = function(task, text, id, tooltip) {
// Create a portlet to hold all the portlet links (if not created already). And get the portletId.
const portletId = Twinkle.addPortlet();

// Create a portlet link and add it to the portlet.
const link = mw.util.addPortletLink(portletId, typeof task === 'string' ? task : '#', text, id, tooltip);

// Related to the hidden peer gadget that prevents jumpiness when the page first loads
$('.client-js .skin-vector #p-cactions').css('margin-right', 'initial');

// Add a click listener for the portlet link
if (typeof task === 'function') {
$(link).click((ev) => {
task();
ev.preventDefault();
});
}

// $.collapsibleTabs is a feature of Vector 2010
if ($.collapsibleTabs) {
// Manually trigger a recalculation of what tabs to put where. This is to account for the space that the TW menu we just added is taking up.
$.collapsibleTabs.handleResize();
}

return link;
};


/**
* **************** General initialization code ****************
*/

const scriptpathbefore = mw.util.wikiScript('index') + '?title=',
scriptpathafter = '&action=raw&ctype=text/javascript&happy=yes';

Expand Down Expand Up @@ -356,60 +214,6 @@ $.ajax({
// Developers: you can import custom Twinkle modules here
// For example, mw.loader.load(scriptpathbefore + "User:UncleDouggie/morebits-test.js" + scriptpathafter);

Twinkle.load = function () {
// Don't activate on special pages other than those listed here, so
// that others load faster, especially the watchlist.
let activeSpecialPageList = [ 'Block', 'Contributions', 'Recentchanges', 'Recentchangeslinked' ]; // wgRelevantUserName defined for non-sysops on Special:Block
if (Morebits.userIsSysop) {
activeSpecialPageList = activeSpecialPageList.concat([ 'DeletedContributions', 'Prefixindex' ]);
}
if (mw.config.get('wgNamespaceNumber') === -1 &&
activeSpecialPageList.indexOf(mw.config.get('wgCanonicalSpecialPageName')) === -1) {
return;
}

// Prevent clickjacking
if (window.top !== window.self) {
return;
}

// Set custom Api-User-Agent header, for server-side logging purposes
Morebits.wiki.api.setApiUserAgent('Twinkle (' + mw.config.get('wgWikiID') + ')');

Twinkle.disabledModules = Twinkle.getPref('disabledModules').concat(Twinkle.getPref('disabledSysopModules'));

// Redefine addInitCallback so that any modules being loaded now on are directly
// initialised rather than added to initCallbacks array
Twinkle.addInitCallback = function(func, name) {
if (!name || Twinkle.disabledModules.indexOf(name) === -1) {
func();
}
};
// Initialise modules that were saved in initCallbacks array
Twinkle.initCallbacks.forEach((module) => {
Twinkle.addInitCallback(module.func, module.name);
});

// Increases text size in Twinkle dialogs, if so configured
if (Twinkle.getPref('dialogLargeFont')) {
mw.util.addCSS('.morebits-dialog-content, .morebits-dialog-footerlinks { font-size: 100% !important; } ' +
'.morebits-dialog input, .morebits-dialog select, .morebits-dialog-content button { font-size: inherit !important; }');
}

// Hide the lingering space if the TW menu is empty
const isVector = mw.config.get('skin') === 'vector' || mw.config.get('skin') === 'vector-2022';
if (isVector && Twinkle.getPref('portletType') === 'menu' && $('#p-twinkle').length === 0) {
$('#p-cactions').css('margin-right', 'initial');
}

// If using a skin with space for lots of modules, display a link to Twinkle Preferences
const usingSkinWithDropDownMenu = mw.config.get('skin') === 'vector' || mw.config.get('skin') === 'vector-2022' || mw.config.get('skin') === 'timeless';
if (usingSkinWithDropDownMenu) {
Twinkle.addPortletLink(mw.util.getUrl('Wikipedia:Twinkle/Preferences'), 'Config', 'tw-config', 'Open Twinkle preferences page');
}
};


/**
* Twinkle-specific data shared by multiple modules
* Likely customized per installation
Expand All @@ -425,68 +229,6 @@ Twinkle.summaryAd = ' ([[WP:TW|TW]])';
// ensure MOS:ORDER
Twinkle.hatnoteRegex = 'short description|hatnote|main|correct title|dablink|distinguish|for|further|selfref|year dab|similar names|highway detail hatnote|broader|about(?:-distinguish| other people)?|other\\s?(?:hurricane(?: use)?s|people|persons|places|ships|uses(?: of)?)|redirect(?:-(?:distinguish|synonym|multi))?|see\\s?(?:wiktionary|also(?: if exists)?)';

/**
* When performing rollbacks with [rollback] links, then visiting a user talk page, some data such as page name can be prefilled into Wel/AIV/Warn. Twinkle calls this a "prefill". This method gets a prefill, either from URL parameters (e.g. &vanarticle=Test) or from data previously stored using Twinkle.setPrefill()
*/
Twinkle.getPrefill = function (key) {
Twinkle.prefill = Twinkle.prefill || {};
if (!Object.prototype.hasOwnProperty.call(Twinkle.prefill, key)) {
Twinkle.prefill[key] = mw.util.getParamValue(key);
}
return Twinkle.prefill[key];
};

/**
* When performing rollbacks with [rollback] links, then visiting a user talk page, some data such as page name can be prefilled into Wel/AIV/Warn. Twinkle calls this a "prefill". This method sets a prefill. This data will be lost if the page is refreshed, unless it is added to the URL as a parameter.
*/
Twinkle.setPrefill = function (key, value) {
Twinkle.prefill = Twinkle.prefill || {};
Twinkle.prefill[key] = value;
};

// Used in XFD and PROD
Twinkle.makeFindSourcesDiv = function makeSourcesDiv(divID) {
if (!$(divID).length) {
return;
}
if (!Twinkle.findSources) {
const parser = new Morebits.wiki.preview($(divID)[0]);
parser.beginRender('({{Find sources|' + Morebits.pageNameNorm + '}})', 'WP:AFD').then(() => {
// Save for second-time around
Twinkle.findSources = parser.previewbox.innerHTML;
$(divID).removeClass('morebits-previewbox');
});
} else {
$(divID).html(Twinkle.findSources);
}
};

/** Twinkle-specific utility functions shared by multiple modules */
// Used in batch, unlink, and deprod to sort pages by namespace, as
// json formatversion=2 sorts by pageid instead (#1251)
Twinkle.sortByNamespace = function(first, second) {
return first.ns - second.ns || (first.title > second.title ? 1 : -1);
};

// Used in batch listings to link to the page in question with >
Twinkle.generateArrowLinks = function (checkbox) {
const link = Morebits.htmlNode('a', ' >');
link.setAttribute('class', 'tw-arrowpage-link');
link.setAttribute('href', mw.util.getUrl(checkbox.value));
link.setAttribute('target', '_blank');
checkbox.nextElementSibling.append(link);
};

// Used in deprod and unlink listings to link the page title
Twinkle.generateBatchPageLinks = function (checkbox) {
const $checkbox = $(checkbox);
const link = Morebits.htmlNode('a', $checkbox.val());
link.setAttribute('class', 'tw-batchpage-link');
link.setAttribute('href', mw.util.getUrl($checkbox.val()));
link.setAttribute('target', '_blank');
$checkbox.next().prepend([link, ' ']);
};

}(window, document, jQuery)); // End wrap with anonymous function

// </nowiki>
Loading

0 comments on commit cb88962

Please sign in to comment.